La extraña estructura de la sintaxis léxica
(justine.lol)- Al abordar 42 lenguajes para crear un nuevo resaltador de sintaxis para llamafile, quedó claro que incluso el lexing simple está lleno de excepciones por lenguaje y gramáticas heredadas
- La implementación usa C++ y GNU gperf para acelerar la búsqueda de palabras clave, y resuelve el manejo centrado en cadenas, comentarios y palabras clave con un
forloop y una máquina de estados finitos basada enswitch - Los trigraphs de C, los terminadores de línea
u2028yu2029de JavaScript, los heredoc de Shell·Perl·Ruby y la interpolación de cadenas de Kotlin·Scala·TypeScript·Swift reaparecen como casos difíciles de manejar solo con lexing - Medido por líneas de código, FORTH queda en 125 líneas y Ruby llega hasta 1042; en Ruby, el operador
<<, los heredoc y los backquotes chocan entre sí, lo que vuelve especialmente difícil el resaltado sin parsing - El nuevo resaltador de llamafile se demostró en Windows 10 con Meta LLaMA 3.2 3B Instruct y también puede ejecutarse en macOS·Linux·FreeBSD·NetBSD, diferenciándose de ollama, que no tiene resaltado de sintaxis
Por qué se creó el resaltador de llamafile
- Para crear un nuevo resaltador de sintaxis para llamafile, se estudiaron durante un mes 42 lenguajes de programación
- Los lenguajes compatibles son Ada, Assembly, BASIC, C, C#, C++, COBOL, CSS, D, FORTH, FORTRAN, Go, Haskell, HTML, Java, JavaScript, Julia, JSON, Kotlin, ld, LISP, Lua, m4, Make, Markdown, MATLAB, Pascal, Perl, PHP, Python, R, Ruby, Rust, Scala, Shell, SQL, Swift, Tcl, TeX, TXT, TypeScript y Zig
- Esta lista incluye la mayor parte del TIOBE Index, pero Scratch queda fuera del alcance del resaltado porque usa bloques en lugar de texto
Método de implementación: gperf y máquinas de estados finitos
- En un resaltador de sintaxis básico, el mayor cuello de botella aparece al determinar si un token es una palabra clave, cuando crecen las comparaciones repetitivas de cadenas
- Se usó C++ y GNU gperf para generar una tabla hash perfecta
- Un ejemplo de entrada para
gperfdefine como palabras clave constantes de Java comotrue,falseynull - El archivo C generado por
gperfpuede crear una función hash que considera solo un único carácter para lograr búsquedas sin colisiones
- Un ejemplo de entrada para
- El resaltado de C puede procesar a 35 MB/s incluso definiendo unas 4,000 palabras clave gracias a gperf
- El resto del procesamiento se resuelve en gran parte con una máquina de estados finitos
- Se puede crear un resaltador básico solo con
forloops yswitch, sinflex,bisonniragel - Si se enfoca en cadenas, comentarios y palabras clave, en general se resuelve a nivel de lexing
- Para resaltar elementos como nombres de funciones en C, podría hacer falta parsing real
- Se puede crear un resaltador básico solo con
- highlight_ada.cpp se usa como implementación de ejemplo
Demo de llamafile y entorno de uso
- El nuevo resaltador y la interfaz de chatbot mejoraron la usabilidad de llamafile, y el resaltado de sintaxis se vuelve un diferenciador frente a ollama, que no lo ofrece
- La demo se ejecutó en Windows 10 con el modelo Meta LLaMA 3.2 3B Instruct
- Ese llamafile también puede ejecutarse en macOS, Linux, FreeBSD y NetBSD
- A medida que mejora la calidad de modelos de pesos abiertos como gemma 27b it, disminuye el incentivo para usar Claude
C: excepciones léxicas distintas de su imagen de lenguaje simple
- A diferencia de su reputación de simplicidad, C tiene elementos léxicos bastante peculiares
- Los trigraphs permiten sustituir caracteres como
#,[,\\,^,{,|,},~mediante??=,??(,??/,??),??',??<,??!,??>,??-- Fueron eliminados del estándar C23, pero parece probable que los compiladores sigan soportándolos por software legado
- Un buen resaltador de sintaxis también debería manejar esta gramática
- Los caracteres universales de C permiten expresar identificadores Unicode dentro de código fuente ASCII, como en
int \\uFEB2 = 1;- GCC arroja error si no pertenecen a cierto plano Unicode permitido por el comité del estándar
- Los comentarios de una sola línea en C pueden extenderse por varias líneas usando una barra invertida al final de la línea
- Lenguajes como Perl, Ruby o Shell permiten escapes con barra invertida en el código fuente, pero no soportan este comportamiento al estilo C
- Se entiende que Tcl y GNU Make sí soportan este comportamiento
- Emacs y Pygments a veces lo manejan mal, mientras que Vim parece tratar siempre correctamente la barra invertida
- C también tiene una directiva nula del preprocesador
- Los archivos
.cdel código fuente original de v6 a menudo empiezan con una línea que solo contiene# - Sigue siendo código válido hoy y puede usarse para hacer que el preprocesador elimine ciertos comentarios incluso si se pide preservar comentarios con
cc -C -E
- Los archivos
Sintaxis de comentarios: Haskell y D
- En C no se pueden anidar comentarios multilínea dentro de otros comentarios multilínea
- Haskell sí soporta comentarios anidados con la forma
{- ... {- ... -} ... -} - D adopta los comentarios
//y/* ... */de C, y además introduce una sintaxis de comentarios recursivos con/+ ... +/ - La documentación de la gramática léxica de D es formal y detallada, y da buena información para implementar
- El documento D lexical syntax cubre detalles como cadenas hexadecimales y cadenas heredoc
- D tiene múltiples formas de cadena como
"...", cadenas con backticks,r"...",q"...",x"..."y otras
Tcl y JavaScript: comillas y terminadores de línea invisibles
- En Tcl se pueden usar comillas dentro de un identificador
puts a"bimprimea"b- También se pueden poner comillas en nombres de variables, pero al referenciarlas hay que usar
${a"b}en lugar de$a"b
- JavaScript tiene una gramática léxica integrada para expresiones regulares
- Dentro de corchetes de un conjunto de caracteres, como en
/[/]/g, no hace falta escapar/ - Un enfoque que solo escanee hasta la siguiente barra de cierre puede fallar con código minificado
- Dentro de corchetes de un conjunto de caracteres, como en
- ECMAScript define
u2028LINE SEPARATOR yu2029PARAGRAPH SEPARATOR como terminadores de línea- En la práctica, estos caracteres se comportan como
\\n - Como son caracteres Trojan Source, en Emacs se configuran para mostrarse como
↵y¶, respectivamente - Mucho software no los reconoce y termina renderizándolos como signos de interrogación
- Se dice que, fuera de D, no se conoce otro lenguaje que haga este tratamiento
- En la práctica, estos caracteres se comportan como
- Gracias a esta característica fue posible crear un polyglot entre C y JavaScript en SectorLISP
- lisp.js se ejecuta en el navegador y también puede compilarse con GCC para ejecutarse localmente
- llamafile resalta correctamente este tipo de código, pero no se encontró otro resaltador que hiciera lo mismo
Shell y los casos límite de heredoc
- El heredoc de Shell permite escribir cadenas multilínea con la forma
cat <<EOF ... EOF - La sintaxis de heredoc entrecomillado, como
cat <<'END', desactiva la sustitución de variables - Si el marcador del heredoc se deja como cadena vacía, el heredoc termina en la siguiente línea vacía
- El programa de ejemplo imprime
helloyworlden dos líneas
- El programa de ejemplo imprime
- En lenguajes que soportan heredoc, como Shell, Ruby y Perl, puede haber varios heredoc en la misma línea
- En Shell, como en Tcl,
#no siempre inicia un comentario- En
${x#hi-},#se usa para eliminar un prefijo, y el ejemplo imprimethere
- En
Interpolación de cadenas: lenguajes que requieren una pila de estados
- En Kotlin, aunque una cadena empiece con
", la interpolación de cadenas puede hacer que al llegar a{se entre en otro estado - TypeScript, Swift, Kotlin y Scala soportan fuertemente la interpolación de cadenas en la dirección de incluir código real dentro de las cadenas
- Para resaltar Kotlin, Scala y TypeScript hace falta contar llaves y mantener una pila de estados del parser
- TypeScript es relativamente simple y basta con agregar unos cuantos estados a la máquina de estados finitos
- Kotlin y Scala soportan interpolación tanto en comillas dobles como en comillas triples, así que solo el lexing de cadenas requirió unas 13 estados independientes
- Swift soporta la interpolación
"\\(var)"y comillas triples, pero la implementación necesitó 10 estados
Swift, C#, FORTH: diferencias en cómo se delimitan las cadenas
- Swift soporta sintaxis para envolver cadenas
"...","""..."""y/regex/con cualquier cantidad de#- La cantidad de
#debe coincidir en ambos lados - Esto resuelve el problema de incluir comillas o delimitadores de regex dentro de la cadena
- La cantidad de
- C# se parece a las cadenas multilínea con comillas triples de Python, pero permite usar una cantidad mayor de comillas que debe coincidir al inicio y al final
- La cantidad de comillas del lado izquierdo determina la condición de cierre del lado derecho
- Se considera que permitir una cantidad arbitraria de comillas reduce las reglas de validez frente a las comillas triples clásicas de Python, y por eso puede decodificarse más simple con una máquina de estados finitos
- FORTH tokeniza todo por límites de espacio
- La sintaxis de inicio de cadena
c"también es un solo token c" hello world"significa lo mismo que"hello world"en otros lenguajes
- La sintaxis de inicio de cadena
FORTRAN y COBOL: reglas de columnas fijas
- llamafile se presenta como un caso de uso que puede ayudar a mantener sistemas bancarios incluso después del retiro de programadores de FORTRAN y COBOL
- Se menciona que en entornos con air gap se puede pedir código COBOL y FORTRAN a una IA controlable como Gemma 27b
- Las reglas de columnas fijas de FORTRAN son las siguientes
- Si en la columna 1 hay
*,coC, la línea es un comentario - Si en la columna 6 hay un carácter no en blanco, se puede continuar una línea que excede 80 caracteres
- Si en las columnas 1 a 5 hay números, se trata de una etiqueta
- Si en la columna 1 hay
- Las reglas de COBOL son las siguientes
- Si en la columna 7 hay
*, la línea es un comentario - Si en la columna 7 hay
-, se puede continuar una línea que excede 80 caracteres - En las columnas 1 a 6 va el número de línea
- Si en la columna 7 hay
Zig y Lua: soluciones distintas para cadenas multilínea
- Zig tiene una sintaxis de cadenas multilínea que empieza con dos barras invertidas
- Esta sintaxis elimina la necesidad de llamar a
textwrap.dedent()como suele pasar con cadenas de comillas triples en Python - Como desventaja, se considera que el punto y coma no se ve bien
- Se propone como una sintaxis de cadenas que podrían considerar lenguajes sin punto y coma obligatorio, como Go, Scala o Python
- Esta sintaxis elimina la necesidad de llamar a
- Las cadenas multilínea de Lua se basan en
[[...]]y pueden llevar una cantidad arbitraria de=entre los corchetes- Como en
[==[ ... ]==], la cantidad de=del inicio y del final debe coincidir - El mismo método puede usarse también para comentarios
- Son posibles tanto
--[[ ... ]]como--[==[ ... ]==]
- Como en
Assembly: combinación de dialectos y preprocesadores
- Assembly es uno de los lenguajes más difíciles de resaltar porque está fragmentado en muchos dialectos
- llamafile apunta a manejar razonablemente varias sintaxis, como AT&T y nasm
- Si las palabras clave se tratan como el primer identificador de la línea que no aparece después de dos puntos, la mayor parte del código assembly se ve aceptablemente bien
- La sintaxis de comentarios tampoco es simple
- El comentario original de UNIX solo requería un único
/ - GNU as todavía lo soporta, pero solo al inicio de la línea
- Se dice que Clang no soporta fixed comment, por lo que no resulta práctico en código abierto
- El comentario original de UNIX solo requería un único
- El assembler original de UNIX no usaba comilla de cierre en los literales de caracteres
'xrepresenta el valor0x78del carácterx- GNU as lo sigue soportando, pero LLVM no
- Como esta sintaxis existe en código heredado, un buen resaltador debería soportarla
- GNU assembler permite identificadores entrecomillados, así que en un símbolo se puede poner casi cualquier carácter
- Assembly suele usarse junto con el preprocesador de C o con m4
- También hay que tratar como comentarios líneas que empiecen con
dnl,m4_dnloC
- También hay que tratar como comentarios líneas que empiecen con
Ada y BASIC: casos pequeños que sacuden el lexing
- Ada es bastante simple de hacer lexing, pero tiene un uso peculiar de la comilla simple
- Puede tener literales de carácter como
'x', igual que C - También usa comilla simple para referenciar atributos, como en
Foo'Size Character'(')')'Imagedeclara un carácter y lo convierte en representación de cadena mediante la funciónImage
- Puede tener literales de carácter como
- El ejemplo de Commodore BASIC rompe varias suposiciones del resaltado de sintaxis
- En las cadenas se puede omitir la comilla de cierre al final de la línea
- Los nombres de variable llevan sigils como
$ - Palabras clave como
gotose reconocen activamente incluso dentro de identificadores
- Visual BASIC tiene sintaxis de literales de fecha como
#1/1/2024# - Visual BASIC también tiene directivas del preprocesador como
#If DEBUG Then,#Elsey#End If, lo que complica el lexing
Perl: complejidad entre Shell y lenguaje de programación
- Perl se ubica entre Shell y los lenguajes de programación, y hereda la complejidad de ambos lados
- Convirtió a las expresiones regulares en un elemento de primera clase del lenguaje, y esa influencia se extendió a otros lenguajes como Python
- La sintaxis de sustitución de Perl se parece a sed, como en
s/hello/Perl/i- En lugar de
/, puede usarse cualquier signo de puntuación como delimitador s!hello!Perl!ies práctico cuando la regex contiene barras- Si se usan caracteres pareados, como en
s{hello}{Perl}i, hacen falta caracteres adicionales
- En lugar de
- En Perl hay muchos prefijos mágicos que deben resaltarse como si fueran cadenas
m,s,y,qr,qw,qq,qxy otros se usan con distintos delimitadores
- Para no interpretar
y/x/y/como si fuera una división, hay que mirar el contexto- Como las variables de Perl llevan sigils como escalar
$, arreglo@y hash%, eso ayuda a distinguir sin parsear toda la gramática
- Como las variables de Perl llevan sigils como escalar
- En Perl es común insertar documentación POD para páginas man dentro del código fuente
- Una línea que empieza con
=wordinicia documentación POD y=cutla termina
- Una línea que empieza con
Ruby: el objetivo de lexing más difícil
- Ruby parece una unión de todos los lenguajes anteriores, y se evalúa que su gramática no está documentada de forma suficientemente formal
- Se considera que el documento de syntax del manual de Ruby es ligero en detalles
- Ruby soporta la sintaxis de backquotes y al mismo tiempo permite usar backquotes como nombre de método, lo que dificulta que el resaltador decida si algo es una cadena o no
- Ruby tiene tanto el operador
<<como heredoc- En código real aparecen formas como
options[:includes] <<arg; true - Esto puede parecer un heredoc, y Emacs también lo interpreta mal
- En código real aparecen formas como
- También es válido código como
puts "This is #{<<HERE.strip} evil", donde un heredoc aparece dentro de una interpolación de cadena - De los 42 lenguajes, Ruby fue el más impactante; se sugiere que quizá no puede hacerse lexing de Ruby sin parsing, y que incluso con parsing no se puede asegurar que sea interpretable
Complejidad vista por líneas de código de implementación
- Si se toma como referencia la cantidad de líneas de código de cada implementación del resaltador por lenguaje, FORTH es el más simple y Ruby el más complejo
- La implementación más corta es highlight_forth.cpp, con 125 líneas
- Entre las implementaciones relativamente cortas están m4 con 132 líneas, Ada con 149, LISP con 160, MATLAB con 166, COBOL con 186, BASIC con 199 y FORTRAN con 200
- Entre las implementaciones de tamaño medio están JavaScript con 337 líneas, TypeScript con 371, Kotlin con 387, Scala con 387, Assembly con 447, C con 449, Swift con 455 y D con 521
- Las implementaciones más largas son Shell con 570 líneas, Perl con 583 y highlight_ruby.cpp, con 1042 líneas
1 comentarios
Comentarios en Hacker News
Mi favorito de los trigraphs de C es algo como
do_action() ??!??! handle_error()Parece una sintaxis especial para manejo de errores, pero en realidad
??!??!se convierte en el OR lógico||, y por las reglas de evaluación de corto circuitohandle_error()se ejecuta cuandodo_action()devuelve un valor distinto de 0, lo cual resulta satisfactorio??!se transforma en|, así que??!??!se vuelve||, es decir, “or”||, usando un trigraph antiguo de C?Fue divertido de leer, pero más bien terminé simpatizando más con la perspectiva de Lisp
Según entendí, la postura es que la sintaxis no es una parte tan importante del lenguaje, y como tiende a ser más un obstáculo que una ayuda, debería ser lo más simple y uniforme posible para poder concentrarse en otras cosas
Aun así, aprender edición estructural en Lisp hasta ahora me ha resultado más un obstáculo que una ayuda, aunque supongo que algún día dará frutos
Si la haces simple al estilo Lisp, la discusión de sintaxis solo se desplaza a un problema semántico; en el fondo solo cambias de capa
Me parece que una sintaxis compleja es mucho más fácil de leer y escribir que una sintaxis simple con semántica compleja. Los errores de sintaxis dan retroalimentación rápida, pero los errores semánticos pueden permanecer ocultos hasta el momento de ejecución
Por ejemplo, existe algo como https://pyret.org/. En la práctica, no creo que Lisp sea realmente simple ni necesariamente uniforme
parinfery conocer los atajos deslurp,barfyraiseSolo con esas cuatro cosas puedes obtener alrededor del 95% de las ventajas de paredit sin la complejidad, y el resto de los trucos los puedes aprender cuando ya te hayas acostumbrado
Había una parte que decía “no sé quién querría resaltar sintaxis de C a 35 MB por segundo, pero ahora se puede”, y sí, es rápido, pero
tcccompila C a 29 MB por segundo en código binario incluso en computadoras muy antiguas: https://bellard.org/tcc/#speedSeguramente se podría ir más rápido, pero probablemente no haga falta
Hay varias cosas que el autor pasó por alto
No solo TypeScript, Swift, Kotlin y Scala llegaron al extremo de meter código real dentro de la interpolación de cadenas; C#, Python, JavaScript, Ruby, Shell y Make también soportan algo parecido
En Tcl, la distinción entre código y datos es difícil, así que
{ }son en la práctica delimitadores de cadena peculiares, y enxyzzy {#hello world}no se puede saber si#hello worldes un comentario o una cadena antes del momento de evaluaciónPostgreSQL tiene unas prácticas cadenas con comillas de dólar, así que https://www.postgresql.org/docs/current/sql-syntax-lexical.h... hace que
'Dianne''s horse',$$Dianne's horse$$, y$SomeTag$Dianne's horse$SomeTag$signifiquen lo mismoUna cadena como
\"I have $foo $bar's: @{[$bar x $foo]}\"puede imprimirI have 5 x's: xxxxxLa sintaxis
@{[...]}aprovecha la característica de Perl de permitir no solo interpolación de arreglos, sino también de escalares;[...]por dentro crea una referencia a un arreglo y@{...}por fuera la desreferencia. El intérprete de Perl permite código arbitrario dentro de esa expresión internaEntonces se puede anidar, como en
\"foo { toUpper(\"bar { x + y } bar\") } foo\", y si+fuera concatenación de cadenas, en esencia sería lo mismo que\"foo \" + toUpper(\"bar \" + (x + y) + \" bar\") + \" foo\"No sé si exista algún lenguaje que realmente funcione así
Me gusta Ruby, pero cuesta defender Ruby válido como
puts \"This is #{<que mezcla interpolación de cadenas con heredocAdemás, en Ruby incluso un espacio puede ser un carácter de cita. En un contexto sin operando izquierdo,
%inicia una cadena entrecomillada y el siguiente carácter indica el tipo de cita, así que% hellose convierte en una cadena conhello.%(this is a string)y%{this is a string}están bien, pero nunca he visto a nadie usar un espacio en el mundo real, y ni siquierairblo maneja bien, así que no molestaría si lo eliminaranSi escribes
val s"${a} + ${b}" = "1 + 2", entoncesaqueda como1ybcomo2Antes, en loops intensivos, código como
log.trace($\"Entering iteration {i} for customer {c.ID} [{c.ShortName}]\");terminaba llamando astring.Concaten cada iteración, incluso antes de que el logger decidiera si iba a registrar algo, por eso muchos loggers implementaron su propia interpolaciónEn C# puedes declarar overloads que acepten
DefaultInterpolatedStringHandlero el patrón de handlers personalizados, y esos overloads tienen prioridad, lo que permite comprobar primero si de verdad se necesita el log y posponer la creación de la cadenaHay otra rareza gramatical no mencionada aquí que rompe a la mayoría de los resaltadores de sintaxis.
En Java, los escapes Unicode pueden aparecer no solo dentro de cadenas, sino en cualquier parte.
Por ejemplo,
class Foo\u007b}es una clase válida, y\u000Adentro de un comentario//puede tratarse como un salto de línea real, así que el ejemplo deasserttambién puede comportarse de forma distinta a la esperada.Como se puede cerrar un comentario de bloque con escapes Unicode, para esconder código malicioso dentro de comentarios en un archivo fuente Java basta con inventar alguna excusa para que haya un bloque de escapes Unicode dentro del comentario.
Un desarrollador que no conozca esta característica probablemente lo verá como código comentado y lo pasará por alto.
Está esa parte de que “Ruby es un lenguaje que esquiva todos los intentos de entender su sintaxis”, pero desde la perspectiva de TeX, con su léxer reprogramable arbitrariamente, eso hasta se ve tierno.
“Todo programador de C sabe que no se pueden poner comentarios multilínea dentro de comentarios multilínea”, pero para un programador de Standard ML eso puede ser una limitación sorprendente.
Los comentarios anidados como
(* (* Nested (**) *) comment *)son válidos, y luegoval _ = print "hello, world\n"también se ejecuta normalmente.Considerando que cuando apareció C se lo veía como un lenguaje expresivo, resulta interesante que no se hayan incluido comentarios anidados en el lenguaje.
ML no tenía comentarios de una sola línea, así que esa también es una limitación sorprendente parecida; además, es la primera vez que oigo llamar a C “expresivo”, aunque comparado con ensamblador en 1972 quizá sí lo era.
Y tampoco sé qué relación tendría la sintaxis de comentarios con la expresividad del lenguaje; diría que, por definición, ninguna.
Tradicionalmente, el análisis léxico se consideraba un ámbito donde solo se usaban autómatas finitos, como las expresiones regulares, así que esa clase de función parecía quedar fuera del alcance del léxing.
#if 0.Se puede envolver como
#if 0 ... #if 0 ... #endif ... #endif.Me gustaría ver la implementación de resaltado de sintaxis de joe en una forma reutilizable.
El formato de https://joe-editor.sf.net/ es lo bastante potente como para resaltar correctamente los f-strings de Python.
Hay documentación y ejemplos relacionados en https://github.com/cmur2/joe-syntax/blob/joe-4.4/misc/HowItW... y https://gist.github.com/irdc/6188f11b1e699d615ce2520f03f1d0d....
La gramática de estados y transiciones me pareció mucho más fácil de entender que las herramientas estándar.
La desventaja es que el conjunto de reglas se vuelve verboso y es un poco difícil de estructurar de forma ideal, pero también lo veo como una ventaja porque facilita inferir todas las reglas de generación desde el código.
Parece que el autor no ha intentado resaltar sintaxis de TeX.
Puede que eso sea bueno para la salud mental, porque en TeX normalmente es imposible hacer un resaltado de sintaxis completo sin interpretarlo.
Parsearlo tampoco basta, porque se puede redefinir lo que hace cada carácter, hasta el punto de hacer cosas como “ahora K es
{y C es}”.De hecho, incluso hay artículos en arXiv que usan esta función maldita.
.texque encontré en mi disco duro resalta bastante bien sin romperse.El objetivo es cubrir el 99.9% de los casos de uso del mundo real. Con eso, probablemente también cubra la mayoría de lo que un LLM podría generar.
Incluso una sintaxis perversa normalmente no causa grandes problemas, siempre que una cadena o comentario no se extienda hasta el final del archivo y tape el resto del código fuente.
\makeatletterno significa “convertir algo en arroba”, sino que al parsear trata@como un carácter ordinario.Me parece que escribir un buen motor de coloreado de sintaxis como el de Vim no es fácil
El coloreado de sintaxis requiere manejo de contexto para aplicar reglas distintas a contenido anidado de cierta manera
El resaltador de sintaxis de Vim permite declarar dos tipos de elementos,
matchyregion;matches una regla léxica simple, yregiontiene expresiones que hacen coincidir por separado el inicio, el final y el medioLos elementos pueden declararse para que solo se activen cuando están dentro de cierta región, y también pueden declarar relaciones de inclusión entre sí
Sobre esta estructura semántica básica se agregan varias funciones para situaciones especiales. Incluso para Justine sería difícil hacerlo al instante en una entrevista; parece más bien una tarea para dejar de un día para otro
Aquí está el script
genman, que toma el resultado de convertir la manpage a HTML y lo ajusta al HTML del manual de TXR: https://www.kylheku.com/cgit/txr/tree/genman.txrLas partes que se ven en blanco son plantillas literales, y el código Lisp va dentro de directivas como
@(do ...). Las palabras clave de TXR se ven en morado y las palabras clave de TXR Lisp en verde; incluso la misma palabra cambia según el contextoLas cuasicadenas también pueden contener gramáticas anidadas, y dentro de ellas puede volver a haber código embebido o cuasicadenas. Los archivos de definición de sintaxis
txr.vimytl.vimse generan ambos desde https://www.kylheku.com/cgit/txr/tree/genvim.txrSi no se hace así, probablemente sea por un tema de eficiencia. Por ejemplo, en vez de resaltar solo la parte visible en pantalla, habría que parsear el archivo completo y el costo podría ser alto