Integrierte Befehle:
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 );
Das Tcl-Skript besteht aus Befehlen, die durch Semikolons oder Zeilenumbrüche getrennt sind. Kommnads wiederum bestehen aus Wörtern, die durch Leerzeichen getrennt sind. Um Leerzeichen zu einem Teil des Wortes zu machen, können doppelte Anführungszeichen oder geschweifte Klammern verwendet werden.
Ein wichtiger Teil der Sprache ist die Befehlsersetzung , bei der das Ergebnis eines Befehls in eckigen Klammern als Teil des äußeren Befehls zurückgegeben wird, z. B. puts [+ 1 2]
.
Der einzige Datentyp der Sprache ist ein String. Obwohl es mathematische Operationen komplizierter machen kann, eröffnet es einen breiten Weg zum Aufbau eigener DSLs, um die Sprache zu verbessern.
Jedes Symbol kann Teil des Wortes sein, mit Ausnahme der folgenden Sonderzeichen:
r
, n
, Semikolon oder EOF – werden zum Trennen von Befehlen verwendetPartcl verfügt über spezielle Hilfsfunktionen für diese Zeichenklassen:
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
verhält sich je nach Quotierungsmodus ( q
-Parameter) unterschiedlich. Innerhalb einer in Anführungszeichen gesetzten Zeichenfolge verlieren Klammern, Semikolon und Zeilenendesymbole ihre besondere Bedeutung und werden zu regulären druckbaren Zeichen.
Partcl lexer ist in einer Funktion implementiert:
int tcl_next(const char *s, size_t n, const char **from, const char **to, int *q);
Die Funktion tcl_next
findet das nächste Token in der Zeichenfolge s
. from
und to
werden so eingestellt, dass sie auf den Start/Ende des Tokens zeigen, q
bezeichnet den Anführungsmodus und wird geändert, wenn "
erfüllt ist.
Ein spezielles Makro tcl_each(s, len, skip_error)
kann verwendet werden, um alle Token in der Zeichenfolge zu durchlaufen. Wenn skip_error
“ „false“ ist, endet die Schleife, wenn die Zeichenfolge endet. Andernfalls kann die Schleife früher enden, wenn ein Syntaxfehler gefunden wird. Es ermöglicht die „Validierung“ der Eingabezeichenfolge, ohne sie auszuwerten, und die Erkennung, wann ein vollständiger Befehl gelesen wurde.
Tcl verwendet Strings als primären Datentyp. Bei der Auswertung des Tcl-Skripts werden viele der Zeichenfolgen erstellt, entsorgt oder geändert. In eingebetteten Systemen kann die Speicherverwaltung komplex sein, daher werden alle Vorgänge mit Tcl-Werten in isolierte Funktionen verschoben, die leicht umgeschrieben werden können, um bestimmte Teile zu optimieren (z. B. um einen String-Pool, eine benutzerdefinierte Speicherzuweisung, numerische Caches oder Listenwerte zu verwenden, um sie zu erhöhen). Leistung usw.).
/* 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);
Beachten Sie, dass ..._append()
Funktionen das Tail-Argument freigeben müssen. Außerdem sollte die von tcl_string()
zurückgegebene Zeichenfolge nicht mutiert oder zwischengespeichert werden.
In der Standardimplementierung werden Listen als Rohzeichenfolgen implementiert, die um jeden Iterm herum einige Escapezeichen (geschweifte Klammern) hinzufügen. Es ist eine einfache Lösung, die auch den Code reduziert, aber in einigen exotischen Fällen kann das Escape-Zeichen falsch werden und ungültige Ergebnisse zurückgegeben werden.
Ein spezieller Typ, struct tcl_env
wird verwendet, um die Evaluierungsumgebung (eine Reihe von Funktionen) beizubehalten. Der Interpreter erstellt für jede benutzerdefinierte Prozedur eine neue Umgebung. Außerdem gibt es pro Interpreter eine globale Umgebung.
Es gibt nur drei Funktionen, die sich auf die Umgebung beziehen. Einer erstellt eine neue Umgebung, ein anderer sucht nach einer Variablen (oder erstellt eine neue), der letzte zerstört die Umgebung und alle ihre Variablen.
Diese Funktionen verwenden malloc/free, können aber leicht umgeschrieben werden, um stattdessen Speicherpools zu verwenden.
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);
Variablen werden als einfach verknüpfte Liste implementiert. Jede Variable ist ein Wertepaar (Name + Wert) und ein Zeiger auf die nächste Variable.
Der Partcl-Interpreter ist eine einfache Strukturstruktur struct tcl
die die aktuelle Umgebung, ein Array verfügbarer Befehle und einen letzten Ergebniswert speichert.
Die Dolmetscherlogik besteht aus zwei Funktionen: Bewertung und Substitution.
Auswechslung:
$
beginnt, erstellen Sie einen temporären Befehl [set name]
und werten Sie ihn aus. In Tcl ist $foo
nur eine Verknüpfung zu [set foo]
, die den Wert der Variablen „foo“ in der aktuellen Umgebung zurückgibt.[
beginnt, bewerten Sie den Inhalt der eckigen Klammern und geben Sie das Ergebnis zurück.{foo bar}
), geben Sie es unverändert zurück, nur ohne geschweifte Klammern.Auswertung:
TCMD
) – dann suchen Sie einen geeigneten Befehl (das erste Wort in der Liste) und rufen Sie ihn auf. Woher kommen die Befehle? Anfänglich startet ein Partcl-Interpeter ohne Befehle, aber man kann die Befehle hinzufügen, indem man tcl_register()
aufruft.
Jeder Befehl hat einen Namen, eine Arität (wie viele Argumente er annehmen soll – der Interpreter prüft dies, bevor er den Befehl aufruft, verwenden Sie eine Arität von Null für Varargs) und einen C-Funktionszeiger, der den Befehl tatsächlich implementiert.
„set“ – tcl_cmd_set
, weist der Variablen (falls vorhanden) einen Wert zu und gibt den aktuellen Variablenwert zurück.
„subst“ – tcl_cmd_subst
, führt Befehlsersetzung in der Argumentzeichenfolge durch.
„puts“ – tcl_cmd_puts
, gibt das Argument an die Standardausgabe aus, gefolgt von einer neuen Zeile. Dieser Befehl kann mit #define TCL_DISABLE_PUTS
deaktiviert werden, was für eingebettete Systeme praktisch ist, die nicht über „stdout“ verfügen.
„proc“ – tcl_cmd_proc
, erstellt einen neuen Befehl und hängt ihn an die Liste der aktuellen Interpreterbefehle an. So werden benutzerdefinierte Befehle erstellt.
„if“ – tcl_cmd_if
, führt ein einfaches if {cond} {then} {cond2} {then2} {else}
aus.
„while“ – tcl_cmd_while
, führt eine While-Schleife aus while {cond} {body}
. Man kann innerhalb der Schleife „break“, „continue“ oder „return“ verwenden, um den Fluss zu steuern.
Verschiedene mathematische Operationen werden als tcl_cmd_math
implementiert, können aber auch deaktiviert werden, wenn Ihr Skript sie nicht benötigt (wenn Sie Partcl als Befehlsshell und nicht als Programmiersprache verwenden möchten).
Alle Quellen befinden sich in einer Datei, tcl.c
. Es kann als eigenständiger Interpreter verwendet oder als Einzeldateibibliothek eingebunden werden (Sie möchten es dann möglicherweise in tcl.h umbenennen).
Tests werden mit Clang ausgeführt und die Abdeckung berechnet. Führen Sie einfach „make test“ aus und schon sind Sie fertig.
Der Code wird im Clang-Format formatiert, um den sauberen und lesbaren Codierungsstil beizubehalten. Bitte führen Sie es auch für Pull-Anfragen aus.
Der Code wird unter MIT-Lizenz verteilt. Sie können ihn gerne auch in Ihren proprietären Projekten verwenden.