Comandos incorporados:
subst arg
set var ?val?
while cond loop
if cond branch ?cond? ?branch? ?other?
proc name args body
return
break
continue
+, -, *, /, <, >, <=, >=, ==, !=
struct tcl tcl ;
const char * s = "set x 4; puts [+ [* $x 10] 2]" ;
tcl_init ( & tcl );
if ( tcl_eval ( & tcl , s , strlen ( s )) != FERROR ) {
printf ( "%.*sn" , tcl_length ( tcl . result ), tcl_string ( tcl . result ));
}
tcl_destroy ( & tcl );
El script Tcl se compone de comandos separados por punto y coma o símbolos de nueva línea. Los comandos, a su vez, se componen de palabras separadas por espacios en blanco. Para hacer que los espacios en blanco formen parte de la palabra, se pueden utilizar comillas dobles o llaves.
Una parte importante del lenguaje es la sustitución de comandos , cuando el resultado de un comando entre llaves se devuelve como parte del comando externo, por ejemplo, puts [+ 1 2]
.
El único tipo de datos del idioma es una cadena. Aunque puede complicar las operaciones matemáticas, abre un amplio camino para crear sus propios DSL para mejorar el lenguaje.
Cualquier símbolo puede formar parte de la palabra, excepto los siguientes símbolos especiales:
r
, n
, punto y coma o EOF: se utilizan para delimitar comandosPartcl tiene funciones auxiliares especiales para estas clases de caracteres:
static int tcl_is_space(char c);
static int tcl_is_end(char c);
static int tcl_is_special(char c, int q);
tcl_is_special
se comporta de manera diferente según el modo de cotización (parámetro q
). Dentro de una cadena entrecomillada, las llaves, el punto y coma y los símbolos de fin de línea pierden su significado especial y se convierten en caracteres imprimibles normales.
Partcl lexer se implementa en una función:
int tcl_next(const char *s, size_t n, const char **from, const char **to, int *q);
La función tcl_next
encuentra el siguiente token en la cadena s
. from
y to
se configuran para apuntar al inicio/final del token, q
denota el modo de cotización y se cambia si se cumple "
.
Se puede utilizar una macro especial tcl_each(s, len, skip_error)
para iterar sobre todos los tokens de la cadena. Si skip_error
es falso, el bucle finaliza cuando finaliza la cadena; de lo contrario, el bucle puede finalizar antes si se encuentra un error de sintaxis. Permite "validar" la cadena de entrada sin evaluarla y detectar cuando se ha leído un comando completo.
Tcl utiliza cadenas como tipo de datos principal. Cuando se evalúa el script Tcl, muchas de las cadenas se crean, eliminan o modifican. En los sistemas integrados, la gestión de la memoria puede ser compleja, por lo que todas las operaciones con valores Tcl se trasladan a funciones aisladas que se pueden reescribir fácilmente para optimizar ciertas partes (por ejemplo, para usar un grupo de cadenas, un asignador de memoria personalizado, caché numérico o valores de lista para aumentar rendimiento, etc.).
/* Raw string values */
tcl_value_t *tcl_alloc(const char *s, size_t len);
tcl_value_t *tcl_dup(tcl_value_t *v);
tcl_value_t *tcl_append(tcl_value_t *v, tcl_value_t *tail);
int tcl_length(tcl_value_t *v);
void tcl_free(tcl_value_t *v);
/* Helpers to access raw string or numeric value */
int tcl_int(tcl_value_t *v);
const char *tcl_string(tcl_value_t *v);
/* List values */
tcl_value_t *tcl_list_alloc();
tcl_value_t *tcl_list_append(tcl_value_t *v, tcl_value_t *tail);
tcl_value_t *tcl_list_at(tcl_value_t *v, int index);
int tcl_list_length(tcl_value_t *v);
void tcl_list_free(tcl_value_t *v);
Tenga en cuenta que las funciones ..._append()
deben liberar el argumento final. Además, la cadena devuelta por tcl_string()
no debe mutarse ni almacenarse en caché.
En la implementación predeterminada, las listas se implementan como cadenas sin formato que agregan algunos escapes (llaves) alrededor de cada iterm. Es una solución simple que también reduce el código, pero en algunos casos exóticos el escape puede resultar incorrecto y se devolverán resultados no válidos.
Se utiliza un tipo especial, struct tcl_env
para mantener el entorno de evaluación (un conjunto de funciones). El intérprete crea un nuevo entorno para cada procedimiento definido por el usuario, además hay un entorno global por intérprete.
Sólo hay 3 funciones relacionadas con el medio ambiente. Uno crea un nuevo entorno, otro busca una variable (o crea una nueva), el último destruye el entorno y todas sus variables.
Estas funciones usan malloc/free, pero se pueden reescribir fácilmente para usar grupos de memoria.
static struct tcl_env *tcl_env_alloc(struct tcl_env *parent);
static struct tcl_var *tcl_env_var(struct tcl_env *env, tcl_value_t *name);
static struct tcl_env *tcl_env_free(struct tcl_env *env);
Las variables se implementan como una lista de enlace simple, cada variable es un par de valores (nombre + valor) y un puntero a la siguiente variable.
El intérprete Partcl es una estructura simple struct tcl
que mantiene el entorno actual, la variedad de comandos disponibles y el último valor del resultado.
La lógica del intérprete gira en torno a dos funciones: evaluación y sustitución.
Sustitución:
$
, cree un comando temporal [set name]
y evalúelo. En Tcl $foo
es solo un atajo para [set foo]
, que devuelve el valor de la variable "foo" en el entorno actual.[
- evalúa lo que hay dentro de los corchetes y devuelve el resultado.{foo bar}
), devuélvalo tal como está, pero sin llaves.Evaluación:
TCMD
para ellos), busque un comando adecuado (la primera palabra de la lista) y llámelo. ¿De dónde se toman los comandos? Inicialmente, un intérprete Partcl comienza sin comandos, pero se pueden agregar los comandos llamando tcl_register()
.
Cada comando tiene un nombre, aridad (cuántos argumentos debe tomar; el intérprete lo verifica antes de llamar al comando, use aridad cero para varargs) y un puntero de función C que realmente implementa el comando.
"set" - tcl_cmd_set
, asigna valor a la variable (si corresponde) y devuelve el valor de la variable actual.
"subst" - tcl_cmd_subst
, realiza la sustitución de comandos en la cadena de argumento.
"puts" - tcl_cmd_puts
, imprime el argumento en la salida estándar, seguido de una nueva línea. Este comando se puede desactivar usando #define TCL_DISABLE_PUTS
, lo cual es útil para sistemas integrados que no tienen "stdout".
"proc" - tcl_cmd_proc
, crea un nuevo comando y lo agrega a la lista de comandos del intérprete actual. Así es como se crean los comandos definidos por el usuario.
"if" - tcl_cmd_if
, hace un simple if {cond} {then} {cond2} {then2} {else}
.
" while " - tcl_cmd_while
, ejecuta un bucle while while {cond} {body}
. Se puede utilizar "romper", "continuar" o "regresar" dentro del bucle para controlar el flujo.
Se implementan varias operaciones matemáticas como tcl_cmd_math
, pero también se pueden deshabilitar si su secuencia de comandos no las necesita (si desea usar Partcl como un shell de comandos, no como un lenguaje de programación).
Todas las fuentes están en un archivo, tcl.c
Puede usarse como un intérprete independiente o incluirse como una biblioteca de un solo archivo (es posible que desee cambiarle el nombre a tcl.h en ese momento).
Las pruebas se ejecutan con sonido metálico y se calcula la cobertura. Simplemente ejecute "hacer prueba" y listo.
El código está formateado usando formato clang para mantener el estilo de codificación limpio y legible. Ejecútelo también para solicitudes de extracción.
El código se distribuye bajo licencia MIT, siéntase libre de usarlo también en sus proyectos propietarios.