Встроенные команды:
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 );
Сценарий Tcl состоит из команд , разделенных точкой с запятой или символом новой строки. Комнады, в свою очередь, состоят из слов, разделенных пробелами. Чтобы сделать пробелы частью слова, можно использовать двойные кавычки или фигурные скобки.
Важной частью языка является подстановка команд , когда результат команды внутри квадратных скобок возвращается как часть внешней команды, например puts [+ 1 2]
.
Единственный тип данных языка — строка. Хотя это может усложнить математические операции, оно открывает широкие возможности для создания собственных DSL для улучшения языка.
Частью слова может быть любой символ, кроме следующих специальных символов:
r
, n
, точка с запятой или EOF — используются для разделения команд.Partcl имеет специальные вспомогательные функции для этих классов символов:
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
ведет себя по-разному в зависимости от режима цитирования (параметр q
). Внутри строковых скобок в кавычках точка с запятой и символы конца строки теряют свое особое значение и становятся обычными печатными символами.
Лексер Partcl реализован в одной функции:
int tcl_next(const char *s, size_t n, const char **from, const char **to, int *q);
Функция tcl_next
находит следующий токен в строке s
. from
и to
указывают на начало/конец токена, q
обозначает режим цитирования и изменяется, если выполняется "
.
Специальный макрос tcl_each(s, len, skip_error)
можно использовать для перебора всех токенов в строке. Если skip_error
имеет значение false, цикл завершается, когда заканчивается строка, в противном случае цикл может завершиться раньше, если обнаружена синтаксическая ошибка. Это позволяет «проверять» входную строку, не оценивая ее, и определять, когда была прочитана полная команда.
Tcl использует строки в качестве основного типа данных. При оценке сценария Tcl многие строки создаются, удаляются или изменяются. Во встроенных системах управление памятью может быть сложным, поэтому все операции со значениями Tcl переносятся в изолированные функции, которые можно легко переписать для оптимизации определенных частей (например, для использования пула строк, специального распределителя памяти, числовых значений кэша или списков для увеличения производительность и т. д.).
/* 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);
Имейте в виду, что функции ..._append()
должны освобождать хвостовой аргумент. Кроме того, строка, возвращаемая tcl_string()
не предназначена для изменения или кэширования.
В реализации по умолчанию списки реализованы в виде необработанных строк, в которые вокруг каждого итерма добавляются экранирующие скобки (скобки). Это простое решение, которое также сокращает объем кода, но в некоторых экзотических случаях экранирование может оказаться неправильным и будут возвращены недопустимые результаты.
Специальный тип struct tcl_env
используется для хранения среды оценки (набора функций). Интерпретатор создает новую среду для каждой определяемой пользователем процедуры, а также для каждого интерпретатора имеется одна глобальная среда.
Есть только 3 функции, связанные с окружающей средой. Один создает новую среду, другой ищет переменную (или создает новую), последний уничтожает среду и все ее переменные.
Эти функции используют malloc/free, но их можно легко переписать для использования вместо них пулов памяти.
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);
Переменные реализованы в виде односвязного списка, каждая переменная представляет собой пару значений (имя+значение) и указатель на следующую переменную.
Интерпретатор Partcl — это простая структура struct tcl
, которая хранит текущую среду, массив доступных команд и последнее значение результата.
Логика интерпретатора основана на двух функциях — оценке и замене.
Замена:
$
— создайте временную команду [set name]
и оцените ее. В Tcl $foo
— это просто ярлык для [set foo]
, который возвращает значение переменной «foo» в текущей среде.[
— оцените, что находится внутри квадратных скобок, и верните результат.{foo bar}
) — верните его как есть, только без фигурных скобок.Оценка:
TCMD
) — то находим подходящую команду (первое слово в списке) и вызываем ее. Откуда берутся команды? Изначально интерпретатор Partcl запускается без команд, но можно добавить команды, вызвав tcl_register()
.
Каждая команда имеет имя, арность (сколько аргументов она должна принимать — интерпретатор проверяет это перед вызовом команды, используйте нулевую арность для переменных аргументов) и указатель на функцию C, которая фактически реализует команду.
«set» — tcl_cmd_set
, присваивает значение переменной (если есть) и возвращает текущее значение переменной.
«subst» — tcl_cmd_subst
, выполняет подстановку команд в строке аргумента.
«puts» — tcl_cmd_puts
, выводит аргумент на стандартный вывод, за которым следует новая строка. Эту команду можно отключить с помощью #define TCL_DISABLE_PUTS
, что удобно для встроенных систем, у которых нет «стандартного вывода».
«proc» — tcl_cmd_proc
, создает новую команду, добавляя ее в список текущих команд интерпретатора. Именно так создаются определяемые пользователем команды.
«if» — tcl_cmd_if
, выполняет простое if {cond} {then} {cond2} {then2} {else}
.
« while» — tcl_cmd_while
, запускает цикл while while {cond} {body}
. Внутри цикла можно использовать «разрыв», «продолжение» или «возврат» для управления потоком.
Различные математические операции реализованы как tcl_cmd_math
, но их также можно отключить, если они не нужны вашему сценарию (если вы хотите использовать Partcl как командную оболочку, а не как язык программирования).
Все исходники находятся в одном файле tcl.c
Его можно использовать как автономный интерпретатор или включить в качестве однофайловой библиотеки (в этом случае вы можете переименовать ее в tcl.h).
Тесты запускаются с помощью clang и рассчитывается покрытие. Просто запустите «make test», и все готово.
Код отформатирован с использованием формата clang, чтобы сохранить чистый и читаемый стиль кодирования. Пожалуйста, запустите его и для запросов на включение.
Код распространяется по лицензии MIT, поэтому вы также можете свободно использовать его в своих собственных проектах.