syntax

package
v0.0.0-...-4916209 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package syntax da sentido al texto: qué es palabra clave, qué es cadena, qué es comentario, y qué paréntesis cierra a cuál.

Qué hay y qué falta

El plan preveía Tree-sitter compilado a WebAssembly y ejecutado con wazero. Esa integración **no está hecha**, y la razón está medida, no supuesta: los grammars que se distribuyen compilados —los de web-tree-sitter— son módulos laterales de emscripten. Llevan una sección `dylink`, importan `env.memory` y exportan `__wasm_apply_data_relocs`, `__wasm_call_ctors` y `tree_sitter_<lenguaje>`; intentar instanciarlos sueltos con wazero falla con «module[env] not instantiated». Usarlos exige implementar en Go el enlazador dinámico de emscripten —bases de memoria y de tabla, reubicaciones, trampolines `invoke_*`— y además cargar el runtime de Tree-sitter, que es otro módulo del mismo tipo. La alternativa, compilar Tree-sitter y un grammar en un único módulo WASI autónomo, necesita clang con wasi-sdk o emscripten.

Mientras tanto, esta capa hace con un lexer lo que se puede hacer sin árbol sintáctico, que resulta ser casi todo lo que se ve: resaltado, emparejado de paréntesis y expansión de la selección. La frontera está en la interfaz: lo que se publica son tramos con un tipo, y de dónde salgan esos tramos —de un lexer por tabla hoy, de un árbol incremental mañana— no lo sabe nadie más.

Por qué un lexer por tabla y no uno por lenguaje

Escribir un lexer a mano por lenguaje es la forma segura de tener ocho implementaciones con ocho errores distintos. Lo que de verdad cambia entre Go, Rust, Python y JSON es una lista de palabras clave y cómo se delimitan comentarios y cadenas; el recorrido es el mismo. Aquí eso es una tabla, y añadir un lenguaje son quince líneas de datos.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LanguageName

func LanguageName(path string) string

LanguageName devuelve el nombre del lenguaje de una ruta, o cadena vacía. Lo usa la barra de estado.

Types

type Highlighter

type Highlighter struct {
	// contains filtered or unexported fields
}

Highlighter mantiene el resaltado de un documento de forma incremental.

Por qué se guarda el estado y no los tramos

Guardar los tramos de cada línea costaría memoria proporcional al documento y habría que invalidarlos en cada edición. Lo que se guarda es el estado con el que *empieza* cada línea —un byte: si viene un comentario de bloque abierto, una cadena sin cerrar o nada—, y los tramos se calculan al pintar, solo para las líneas visibles. Tokenizar sesenta líneas cuesta microsegundos, así que recalcularlas en cada fotograma sale más barato que mantener una caché.

No es seguro para uso concurrente: pertenece al hilo que dibuja.

func NewHighlighter

func NewHighlighter(src Source, path string) *Highlighter

NewHighlighter crea el resaltador de un documento. Con una ruta de extensión desconocida devuelve un resaltador que no colorea nada, que es lo correcto: inventarle una sintaxis a un archivo desconocido es peor que dejarlo en paz.

func (*Highlighter) BracketAt

func (h *Highlighter) BracketAt(p Pos) (Pos, bool)

BracketAt devuelve el delimitador que hay junto a una posición, mirando primero debajo del cursor y después justo detrás.

Mirar los dos lados es lo que hace que el emparejado se vea tanto al escribir —el cursor queda detrás del paréntesis recién puesto— como al navegar.

func (*Highlighter) Edited

func (h *Highlighter) Edited(line int)

Edited avisa de que una línea ha cambiado.

Solo hace falta la primera línea tocada: el estado de arranque de las anteriores no depende de lo que venga después. De ahí hacia abajo se recalcula cuando alguien pregunte, y solo hasta donde pregunte.

func (*Highlighter) Enabled

func (h *Highlighter) Enabled() bool

Enabled indica si el documento se está resaltando.

func (*Highlighter) Enclosing

func (h *Highlighter) Enclosing(from, to Pos) (open, close Pos, ok bool)

Enclosing busca el par de delimitadores más cercano que contiene a un rango.

Es la operación que sostiene la expansión de la selección: sin árbol sintáctico, el siguiente nivel de una expresión es el paréntesis, el corchete o la llave que la envuelve.

func (*Highlighter) Language

func (h *Highlighter) Language() *Language

Language devuelve el lenguaje detectado, o nil.

func (*Highlighter) Line

func (h *Highlighter) Line(i int) []Span

Line devuelve los tramos de una línea, en índices de byte dentro de ella.

El resultado es válido hasta la siguiente llamada: se reutiliza el mismo buffer para no reservar memoria por línea y por fotograma.

func (*Highlighter) MatchBracket

func (h *Highlighter) MatchBracket(at Pos) (Pos, bool)

MatchBracket busca la pareja del delimitador que hay en una posición.

Devuelve dónde está la pareja y si se encontró. Se ignora todo lo que caiga dentro de una cadena o de un comentario: un paréntesis en un mensaje de error no cierra nada.

func (*Highlighter) Name

func (h *Highlighter) Name() string

Name devuelve el nombre del lenguaje, o cadena vacía.

func (*Highlighter) SetSource

func (h *Highlighter) SetSource(src Source, path string)

SetSource cambia el documento, por ejemplo al abrir otro archivo.

type Kind

type Kind uint8

Kind es lo que un tramo de texto significa a efectos de color.

La lista es corta a propósito. Un tema con cuarenta colores distintos no se lee mejor: se lee peor, porque el ojo deja de distinguir qué es importante. Estos son los tipos que cualquier tema de cualquier editor colorea de forma distinta, y ni uno más.

const (
	// Plain es texto sin significado propio: identificadores corrientes.
	Plain Kind = iota

	Keyword  // if, func, return
	Type     // int, string, los tipos primitivos del lenguaje
	Constant // true, false, nil, los números
	String   // literales de cadena y de carácter
	Comment  // de línea o de bloque
	Function // un identificador seguido de un paréntesis de apertura
	Operator // + - = < > &&
	Punct    // , ; : y los delimitadores

)

Tipos de tramo reconocidos.

func (Kind) String

func (k Kind) String() string

String devuelve el nombre del tipo, para los volcados de las pruebas.

type Language

type Language struct {
	Name string

	// Extensions son las extensiones de archivo que lo identifican, con punto.
	Extensions []string

	// Keywords, Types y Constants se buscan por identificador exacto.
	Keywords  map[string]bool
	Types     map[string]bool
	Constants map[string]bool

	// LineComment abre un comentario hasta el final de la línea.
	LineComment string

	// BlockOpen y BlockClose delimitan los comentarios de bloque. Nested marca
	// los lenguajes en los que anidan, como Rust.
	BlockOpen, BlockClose string
	Nested                bool

	// Quotes son los delimitadores de cadena de una línea, con escapes.
	Quotes string

	// RawQuote es el delimitador de cadena que puede ocupar varias líneas, como
	// el acento grave de Go. Vacío si el lenguaje no tiene.
	RawQuote byte
	// contains filtered or unexported fields
}

Language describe cómo se tokeniza un lenguaje.

Es una tabla de datos y no código: lo que cambia entre Go, Rust, Python y JSON es la lista de palabras clave y cómo se delimitan comentarios y cadenas, no el recorrido. Añadir un lenguaje son quince líneas aquí abajo.

func Detect

func Detect(path string) *Language

Detect devuelve el lenguaje de un archivo por su ruta.

func (*Language) Lex

func (l *Language) Lex(line string, in State, dst []Span) ([]Span, State)

Lex tokeniza una línea y devuelve sus tramos y el estado con el que empieza la siguiente.

dst se reutiliza para no reservar memoria por línea y por fotograma: se tokenizan las líneas visibles en cada repintado, y a varios miles de fotogramas por segundo eso sería basura suficiente para provocar pausas del recolector.

type Pos

type Pos struct {
	Line, Col int
}

Pos es una posición en el documento: línea y columna en bytes.

Los paréntesis se buscan por líneas y no por desplazamiento absoluto porque el recorrido es línea a línea de todas formas —hay que tokenizar cada una para saber qué está dentro de una cadena—, y así no hace falta que esta capa sepa convertir entre las dos formas.

func (Pos) Before

func (p Pos) Before(q Pos) bool

Before indica si una posición va antes que otra.

type Source

type Source interface {
	LineCount() int
	Line(i int) string
}

Source es lo que el resaltador necesita saber del documento.

Son las mismas dos operaciones que necesita el visor, y a propósito: así el resaltado funciona igual sobre un buffer editable que sobre un archivo proyectado en memoria.

type Span

type Span struct {
	Start, End int
	Kind       Kind
}

Span es un tramo de una línea con un tipo.

Las posiciones son índices de byte dentro de su línea, no del documento. Es lo que necesita quien pinta —que trabaja línea a línea— y evita tener que recalcularlo todo cuando el texto de encima cambia de longitud.

type State

type State uint8

State es lo que hay abierto al empezar una línea.

Es lo único que una línea necesita saber de las anteriores, y cabe en un byte. Esa es la propiedad que hace incremental todo lo demás: al editar una línea basta volver a tokenizar hacia abajo hasta que el estado de arranque vuelva a coincidir con el que ya estaba guardado, que en el caso normal es la línea siguiente.

const (
	StateNormal State = iota
	StateBlock        // dentro de un comentario de bloque
	StateRaw          // dentro de una cadena de varias líneas
	StateFence        // dentro de un bloque de código de Markdown

)

Estados posibles. El nivel de anidamiento de los comentarios de bloque va en los bits altos, para los lenguajes en los que anidan.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL