Подразделы

Другие разделы

Дата и время

22/12/2024 09:11:47

Авторизация

Имя:
Пароль:
Зарегистрироваться
Восстановить пароль
 

printРазработка компиляторов и интерпретаторов

printГенератор трансляторов 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)ожидаемое количество конфликтов свёртка-сдвиг в грамматике
loading