Commandes intégrées :
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 );
Le script Tcl est composé de commandes séparées par des points-virgules ou des symboles de nouvelle ligne. Les commandes, à leur tour, sont constituées de mots séparés par des espaces. Pour intégrer les espaces au mot, on peut utiliser des guillemets doubles ou des accolades.
Une partie importante du langage est la substitution de commande , lorsque le résultat d'une commande entre accolades est renvoyé dans le cadre de la commande externe, par exemple puts [+ 1 2]
.
Le seul type de données du langage est une chaîne. Bien que cela puisse compliquer les opérations mathématiques, cela ouvre la voie à la création de vos propres DSL afin d’améliorer le langage.
N'importe quel symbole peut faire partie du mot, à l'exception des symboles spéciaux suivants :
r
, n
, point-virgule ou EOF - utilisé pour délimiter les commandesPartcl a des fonctions d'assistance spéciales pour ces 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 comporte différemment selon le mode de cotation (paramètre q
). À l'intérieur d'une chaîne entre guillemets, les symboles de point-virgule et de fin de ligne perdent leur signification particulière et deviennent des caractères imprimables normaux.
Partcl lexer est implémenté dans une seule fonction :
int tcl_next(const char *s, size_t n, const char **from, const char **to, int *q);
La fonction tcl_next
trouve le jeton suivant dans la chaîne s
. from
et to
sont définis pour pointer vers le début/la fin du jeton, q
désigne le mode de cotation et est modifié si "
est respecté.
Une macro spéciale tcl_each(s, len, skip_error)
peut être utilisée pour parcourir tous les jetons de la chaîne. Si skip_error
est faux, la boucle se termine à la fin de la chaîne, sinon la boucle peut se terminer plus tôt si une erreur de syntaxe est trouvée. Il permet de "valider" la chaîne d'entrée sans l'évaluer et de détecter quand une commande complète a été lue.
Tcl utilise des chaînes comme type de données principal. Lorsque le script Tcl est évalué, de nombreuses chaînes sont créées, supprimées ou modifiées. Dans les systèmes embarqués, la gestion de la mémoire peut être complexe, de sorte que toutes les opérations avec des valeurs Tcl sont déplacées dans des fonctions isolées qui peuvent être facilement réécrites pour optimiser certaines parties (par exemple pour utiliser un pool de chaînes, un allocateur de mémoire personnalisé, mettre en cache des valeurs numériques ou une liste pour augmenter performances, 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);
Gardez à l’esprit que les fonctions ..._append()
doivent libérer l’argument tail. De plus, la chaîne renvoyée par tcl_string()
n'est pas destinée à être mutée ou mise en cache.
Dans l'implémentation par défaut, les listes sont implémentées sous forme de chaînes brutes qui ajoutent des échappements (accolades) autour de chaque élément. C'est une solution simple qui réduit également le code, mais dans certains cas exotiques, l'échappement peut devenir erroné et des résultats invalides seront renvoyés.
Un type spécial, struct tcl_env
est utilisé pour conserver l'environnement d'évaluation (un ensemble de fonctions). L'interpréteur crée un nouvel environnement pour chaque procédure définie par l'utilisateur. Il existe également un environnement global par interprète.
Il n'y a que 3 fonctions liées à l'environnement. L'un crée un nouvel environnement, un autre cherche une variable (ou en crée une nouvelle), le dernier détruit l'environnement et toutes ses variables.
Ces fonctions utilisent malloc/free, mais peuvent facilement être réécrites pour utiliser des pools de mémoire à la place.
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);
Les variables sont implémentées sous forme de liste à lien unique, chaque variable est une paire de valeurs (nom + valeur) et un pointeur vers la variable suivante.
L'interpréteur Partcl est une structure simple struct tcl
qui conserve l'environnement actuel, le tableau des commandes disponibles et une dernière valeur de résultat.
La logique de l'interpréteur s'articule autour de deux fonctions : l'évaluation et la substitution.
Substitution:
$
, créez une commande temporaire [set name]
et évaluez-la. En Tcl, $foo
n'est qu'un raccourci vers [set foo]
, qui renvoie la valeur de la variable "foo" dans l'environnement actuel.[
- évaluez ce qu'il y a entre crochets et renvoyez le résultat.{foo bar}
) - renvoyez-le tel quel, juste sans accolades.Évaluation:
TCMD
pour eux) - alors trouvez une commande appropriée (le premier mot de la liste) et appelez-la. D'où proviennent les commandes ? Initialement, un interpète Partcl démarre sans commandes, mais on peut ajouter les commandes en appelant tcl_register()
.
Chaque commande a un nom, une arité (combien d'arguments doit prendre - l'interpréteur le vérifie avant d'appeler la commande, utilisez une arité nulle pour les varargs) et un pointeur de fonction C qui implémente réellement la commande.
"set" - tcl_cmd_set
, attribue une valeur à la variable (le cas échéant) et renvoie la valeur actuelle de la variable.
"subst" - tcl_cmd_subst
, effectue la substitution de commande dans la chaîne d'argument.
"puts" - tcl_cmd_puts
, imprime l'argument sur la sortie standard, suivi d'une nouvelle ligne. Cette commande peut être désactivée en utilisant #define TCL_DISABLE_PUTS
, ce qui est pratique pour les systèmes embarqués qui n'ont pas de "stdout".
"proc" - tcl_cmd_proc
, crée une nouvelle commande en l'ajoutant à la liste des commandes actuelles de l'interpréteur. C'est ainsi que les commandes définies par l'utilisateur sont créées.
"if" - tcl_cmd_if
, fait un simple if {cond} {then} {cond2} {then2} {else}
.
"while" - tcl_cmd_while
, exécute une boucle while while {cond} {body}
. On peut utiliser "break", "continue" ou "return" à l'intérieur de la boucle pour contrôler le flux.
Diverses opérations mathématiques sont implémentées en tant que tcl_cmd_math
, mais peuvent également être désactivées si votre script n'en a pas besoin (si vous souhaitez utiliser Partcl comme shell de commande, pas comme langage de programmation).
Toutes les sources sont dans un seul fichier, tcl.c
. Il peut être utilisé comme interpréteur autonome ou inclus comme bibliothèque à fichier unique (vous souhaiterez peut-être alors le renommer en tcl.h).
Les tests sont exécutés avec clang et la couverture est calculée. Exécutez simplement "make test" et vous avez terminé.
Le code est formaté au format clang pour conserver le style de codage propre et lisible. Veuillez également l'exécuter pour les demandes d'extraction.
Le code est distribué sous licence MIT, n'hésitez pas à l'utiliser également dans vos projets propriétaires.