Генератор трансляторов CAIO, пример интерпретатора
Инструмент CAIO (Caio is All in One) позволяет создать описание языка в одном файле с более единообразным, простым и соответствующим стандартам синтаксисом и сгенерировать по нему файлы для BISON и RE/flex.
Структура описаний для CAIO выглядит следующим образом:
объявления
%%
правила для лексического анализатора
%%
правила для синтаксического анализатора
%%
код подпрограмм
В первом разделе можно написать следующие виды объявлений:
1. %option список — указание опций, которые будут применяться при генерации кода или передаваться соответствующим инструментам.
2. %operator <вид1> список1 <вид2> список2 … — указание приоритета, позиции и ассоциативности операторов. Каждая строка задает операторы с одинаковым приоритетом. Здесь вид`i` может принимать одно из следующих значений xfx, xfy, yfx, fx, fy, xf, yf, где f – положение оператора, x – выражение с приоритетом оператора строго выше определяемого, y – выше или равного по приоритету. Этот способ описания применяется в языке Prolog и имеет большую наглядность и гибкость, чем объявления BISON.
3. %type <тип> список — объявление типа символов грамматики и/или узлов дерева. Так как разные виды символов грамматики в РБНФ отличаются визуально, можно не делать специальный вид объявления для токенов языка, как в BISON. Также можно не определять структуру для хранения атрибутов символов грамматики, а создать её автоматически по объявлениям (хотя в последних версиях BISON это было сделано, но осталась проблема передачи определений во FLEX). Также по данным объявлениям и действиям, указанных в правилах, автоматически создается иерархия классов для хранения узлов AST и функции для их создания.
4. %{ } и %code назначение {} – код, который помещается в заголовочный файл или в генерируемый код для инструмента, указанного в назначении (lex или grm).
Правила для лексического и синтаксического анализатора рекомендуется записывать в виде двух столбцов, в левом столбце записывается регулярное выражение или правило грамматики, а в правом — действие. Для большей совместимости с BISON пробелы в именах нетерминальных символов запрещены, в правилах грамматики можно указывать разделитель двоеточие вместо = и не писать запятых. Для наиболее частого варианта действия — создания узла AST — используется конструкция <имя_узла(аргументы)>. В остальных случаях можно написать произвольный код в { } или %{ } (при использовании РБНФ), который может содержать обращение к атрибутам результирующего символа в форме $$ и к атрибутам символов правил грамматики в форме $`n`, как в BISON. В действиях лексического анализатора можно написать распознанный терминальный символ и указать его атрибуты в <> или произвольный код в { }, в котором можно писать $$ и конструкцию «return терминальный_символ;». Если действие не указано, то по умолчанию выполняется поиск распознанной правилом лексемы среди литералов с помощью вызова yyliteral(yytext).
CAIO может выводить тип нетерминального символа в большинстве случаев. Пусть A – тип узла AST, S – стандартный тип (int, string, …), T – любой тип (A или S), E – отсутствие типа.
Конструкция | Тип |
…E T E… | T |
[S] | S? |
[A] | A |
{T} | List<T> |
T {T} или {T} T | List<T> |
T [T] или [T] T | List<T> |
Для распечатки типа символов можно использовать ключ -d3. Тип S? (на С++ – Maybe<S>) – это опциональное значение, с которым можно выполнять все операции, для проверки на отсутствие значения используется операция ! (преобразование к bool). Так как узлы являются указателями, то при отсутствии значения указатель равен nullptr
Тип List<T> – это список значений указанного типа, отсутствующие значения в список не включаются. Для создания списка используется функция cons с 1-м (создание списка из одного значения) или 2-мя аргументами (соединение списков или добавление одного значения в список).
Код подпрограмм, содержащийся в последнем разделе, может включать конструкции для обработки узлов AST.
Рассмотрим пример использования CAIO для создания интерпретатора простого языка типа BASIC, в котором есть комментарии (смотри пример), операторы присваивания, ввода (INPUT), вывода (PRINT), ветвления (IF…THEN…ELSE…END IF) и цикла (WHILE…WEND).
Файл basic.caio:
%option locations case-insensitive
%operator <yfx> '+' '-'
%operator <yfx> '*' '/' "mod"
%operator <fy> '-'
%type <int> ?число? ?идент? ?строка?
%type <Statement> оператор
%type <Expr> выр
%{
#include <string>
#include <iostream>
#include <map>
#include <vector>
#include <cctype>
using namespace std;
int find_id(string name);
extern vector<string> tc;
}
%code lex {
%{
static string s;
%}
}
%% //
[ \t\r] ; //
R"("/'"(.|\n)*?"'/"|'.*)" ; //
\d+ ?число? <stoi(yytext)>
[a-zA-Z]\w* ?идент? <find_id(yytext)>
R"(\")" { BEGIN(S_STRING); s=""; }
<S_STRING>R"(\"\")" { s+='"'; }
<S_STRING>R"(\")" { BEGIN(INITIAL);
$$=tc.size();
tc.push_back(s);
return ?строка?;
}
<S_STRING>. { s+=yytext; }
<S_STRING>\n { yyerror(&@$,this,"Ошибка в строке"); }
%% //
программа = код <$1>
;
код = { оператор '\n' }
;
оператор =
| ?идент? '=' выр <assign($1,$3)>
| "input" ?идент? <input($2)>
| "print" выр <print($2)>
| "print" ?строка? <printstr($2)>
| "while" выр '\n' код "wend" <whilestmt($2,$4)>
| "if" выр ["then"] '\n' код
[ "else" '\n' код
] "end" "if" <ifstmt($2,$5,$6)>
;
выр = выр '+' выр <plus($1,$3)>
| выр '-' выр <minus($1,$3)>
| выр '*' выр <mult($1,$3)>
| выр '/' выр <divide($1,$3)>
| выр "mod" выр <modulo($1,$3)>
| '-' выр <neg($2)>
| '(' выр ')'
| ?число? <number($1)>
| ?идент? <ident($1)>
;
%%
int mem[1000]; //
visitor eval<Expr,int> { //
visit plus(x, y): return eval(x)+eval(y);
visit minus(x, y): return eval(x)-eval(y);
visit mult(x, y): return eval(x)*eval(y);
visit divide(x, y): return eval(x)/eval(y);
visit modulo(x, y): return eval(x)%eval(y);
visit neg(x): return -eval(x);
visit number(x): return x;
visit ident(x): return mem[x];
}
void yyinterpret(List<Statement> p) //
{ for(auto s:p)
match s {
rule assign(v, e): mem[v]=eval(e);
rule input(v): cin>>mem[v];
rule print(e): cout<<eval(e)<<endl;
rule printstr(e): cout<<tc[e]<<endl;
rule whilestmt(e,p1): while(eval(e)) yyinterpret(p1);
rule ifstmt(e,p1,p2): if(eval(e)) yyinterpret(p1);
else yyinterpret(p2);
}
}
map<string,int> ti; //
int find_id(string name) //
{ for(auto &ch:name) ch=toupper(ch);
int k=ti[name];
if(k==0) k=ti[name]=ti.size();
return k;
}
vector<string> tc; //
Пример программы test.bas:
/' Программа для проверки гипотезы Коллатца (гипотеза 3n+1):
какое бы начальное число n мы ни взяли,
рано или поздно мы получим единицу
'/
print "Введите n:"
input n
k=0
while n-1
if n mod 2 then ' если n нечетное
n=n*3+1
else
n=n/2
end if
print n
k=k+1
wend
print "Количество итераций"
print k
Скачать архив с примером
Опции CAIO
Опция | Значение |
ebnf/bnf/rebnf | РБНФ/БНФ/RE РБНФ |
case-sensitive/case-insensitive | чувствительность к регистру букв |
interactive/nointeractive | интерактивный/буферизация ввода |
main/nomain | генерировать main() |
yyparse/noyyparse | генерировать yyparse(), должны быть указаны правила грамматики, при отсутствии – выполняется вызов yylex до обнаружения конца файла / правила грамматики не задаются, yyparse() нужно определять самостоятельно (например, через рекурсивный спуск) |
yylex/noyylex | генерировать yylex(), должны быть указаны лексические правила, также литералы могут быть определены по описанию типов, резервированному слову token в подпрограммах и правилам грамматики / лексические правила не задаются, yylex() нужно определять самостоятельно |
ast | включить режим работы с AST, также включаются опции nolocations nomain noyylex noyyparse |
locations/yylineno/nolocations | отслеживание позиций для лексем и AST/только строки для сообщений об ошибках/без остлеживания позиций |
literal-rules/noliteral-rules | добавлять правила для литералов |
default-echo/default-skip/ default-literal/nodefault | действия по умолчанию для неизвестных символов, по умолчанию: если существуют правила грамматики – попытка преобразовать символ в литерал, иначе выводить на печать |
yywrap/noyywrap | вызывать функцию yywrap по завершению ввода |
matcherror/nomatcherror | сообщать об ошибке, если match не содержит правила для некоторого вида узла |
visitor/novisitor | генерировать классы для посетителей, по умолчанию – генерировать, если операторы visitor есть в подпрограммах |
yyinterpret/noyyinterpret | вызывать yyinterpret после разбора, конкретная функция и параметры могут быть заданы в макросе YYINTERPRET(ast,scanner) |
astprint/noastprint | генерируется семейство функций astprint() для печати дерева, также для транслятора доступна опция –d2 для отладочной печати AST |
lexprint/nolexprint | генерируется функция lexprint() для печати токенов, также для транслятора доступна опция –d1 для отладочной печати результата лексического анализа |
yyerror/noyyerror | генерируется функция yyerror() для печати сообщений об ошибках |
line/noline | включать в результирующие файлы #line для корректных сообщений об ошибках компиляции |
expect(n) | ожидаемое количество конфликтов свёртка-сдвиг в грамматике |