Comandos integrados:
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 );
O script Tcl é composto de comandos separados por ponto e vírgula ou símbolos de nova linha. Os comandos, por sua vez, são compostos de palavras separadas por espaços em branco. Para tornar o espaço em branco parte da palavra, pode-se usar aspas duplas ou colchetes.
Uma parte importante da linguagem é a substituição de comandos , quando o resultado de um comando entre colchetes é retornado como parte do comando externo, por exemplo, puts [+ 1 2]
.
O único tipo de dados da linguagem é uma string. Embora possa complicar as operações matemáticas, abre um amplo caminho para a construção de suas próprias DSLs para aprimorar a linguagem.
Qualquer símbolo pode fazer parte da palavra, exceto os seguintes símbolos especiais:
r
, n
, ponto e vírgula ou EOF - usado para delimitar comandosPartcl possui funções auxiliares especiais para estas classes char:
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 maneira diferente dependendo do modo de cotação (parâmetro q
). Dentro de colchetes, os símbolos de ponto e vírgula e de fim de linha perdem seu significado especial e se tornam caracteres regulares imprimíveis.
O lexer Partcl é implementado em uma função:
int tcl_next(const char *s, size_t n, const char **from, const char **to, int *q);
A função tcl_next
encontra o próximo token na string s
. from
e to
são definidos para apontar para o início/fim do token, q
denota o modo de cotação e é alterado se "
for atendido.
Uma macro especial tcl_each(s, len, skip_error)
pode ser usada para iterar todos os tokens na string. Se skip_error
for falso - o loop termina quando a string termina, caso contrário, o loop pode terminar mais cedo se um erro de sintaxe for encontrado. Permite "validar" a string de entrada sem avaliá-la e detectar quando um comando completo foi lido.
Tcl usa strings como tipo de dados primário. Quando o script Tcl é avaliado, muitas das strings são criadas, descartadas ou modificadas. Em sistemas embarcados o gerenciamento de memória pode ser complexo, então todas as operações com valores Tcl são movidas para funções isoladas que podem ser facilmente reescritas para otimizar certas partes (por exemplo, usar um conjunto de strings, um alocador de memória personalizado, armazenar valores numéricos ou de lista de cache para aumentar desempenho, 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);
Tenha em mente que as funções ..._append()
devem liberar o argumento tail. Além disso, a string retornada por tcl_string()
não deve ser mutada ou armazenada em cache.
Na implementação padrão, as listas são implementadas como strings brutas que adicionam alguns escapes (chaves) em torno de cada item. É uma solução simples que também reduz o código, mas em alguns casos exóticos o escape pode ficar errado e resultados inválidos serão retornados.
Um tipo especial, struct tcl_env
é usado para manter o ambiente de avaliação (um conjunto de funções). O intérprete cria um novo ambiente para cada procedimento definido pelo usuário, além de haver um ambiente global por intérprete.
Existem apenas 3 funções relacionadas ao meio ambiente. Um cria um novo ambiente, outro busca uma variável (ou cria uma nova), o último destrói o ambiente e todas as suas variáveis.
Essas funções usam malloc/free, mas podem ser facilmente reescritas para usar conjuntos de memória.
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);
As variáveis são implementadas como uma lista vinculada única, cada variável é um par de valores (nome + valor) e um ponteiro para a próxima variável.
O interpretador Partcl é uma estrutura simples struct tcl
que mantém o ambiente atual, a matriz de comandos disponíveis e um último valor de resultado.
A lógica do interpretador envolve duas funções - avaliação e substituição.
Substituição:
$
- crie um comando temporário [set name]
e avalie-o. Em Tcl $foo
é apenas um atalho para [set foo]
, que retorna o valor da variável "foo" no ambiente atual.[
- avalie o que está entre colchetes e retorne o resultado.{foo bar}
) - retorne-o como está, apenas sem colchetes.Avaliação:
TCMD
para eles) - encontre um comando adequado (a primeira palavra da lista) e chame-o. De onde são retirados os comandos? Inicialmente, um interpretador Partcl começa sem comandos, mas pode-se adicionar os comandos chamando tcl_register()
.
Cada comando tem um nome, aridade (quantos argumentos devem ser necessários - o intérprete verifica antes de chamar o comando, usa aridade zero para varargs) e um ponteiro de função C que realmente implementa o comando.
"set" - tcl_cmd_set
, atribui valor à variável (se houver) e retorna o valor atual da variável.
"subst" - tcl_cmd_subst
, faz a substituição do comando na string do argumento.
"puts" - tcl_cmd_puts
, imprime o argumento no stdout, seguido por uma nova linha. Este comando pode ser desabilitado usando #define TCL_DISABLE_PUTS
, o que é útil para sistemas embarcados que não possuem "stdout".
"proc" - tcl_cmd_proc
, cria um novo comando anexando-o à lista de comandos atuais do interpretador. É assim que os comandos definidos pelo usuário são criados.
"if" - tcl_cmd_if
, faz um if {cond} {then} {cond2} {then2} {else}
.
"while" - tcl_cmd_while
, executa um loop while {cond} {body}
. Pode-se usar "break", "continue" ou "return" dentro do loop para controlar o fluxo.
Várias operações matemáticas são implementadas como tcl_cmd_math
, mas também podem ser desativadas se o seu script não precisar delas (se você quiser usar Partcl como um shell de comando, não como uma linguagem de programação).
Todas as fontes estão em um arquivo, tcl.c
. Ele pode ser usado como um intérprete independente ou incluído como uma biblioteca de arquivo único (você pode querer renomeá-lo para tcl.h).
Os testes são executados com clang e a cobertura é calculada. Basta executar "make test" e pronto.
O código é formatado usando o formato clang para manter o estilo de codificação limpo e legível. Execute-o também para solicitações pull.
O código é distribuído sob licença do MIT, sinta-se à vontade para usá-lo também em seus projetos proprietários.