Интерпретируем и компилируем с OMeta

предыдущая серия

Чтобы вместо интерпретатора, который вычисляет значение правила на лету, спроектировать компилятор, достаточно всего лишь изменить результат парсинга правила, благо OMeta позволяет сделать это единообразным способом. В частности, достаточно всего лишь возвращать не значение, а список с указанием операции или типа данных.

Воспользуемся для этого готовым примером:

ometa OMetaSharp.Examples.CalculatorParser<char> {
Digit   ^= Super(Digit):d<int>        -> { d },
Number  ^= Number:n<int> Digit:d<int> -> { n * 10 + d }
| Digit,
AddExpr  = AddExpr:x '+' MulExpr:y    -> { "add", x, y }
| AddExpr:x '-' MulExpr:y    -> { "sub", x, y }
| MulExpr,
MulExpr  = MulExpr:x '*' PrimExpr:y   -> { "mul", x, y }
| MulExpr:x '/' PrimExpr:y   -> { "div", x, y }
| PrimExpr,
PrimExpr = '(' Expr:x ')'             -> { x }
| Number:n                   -> { "num", n },
Expr     = AddExpr
}

Только в нашем коде надо изменить

protected T Parse<T>(string text, Func<Calculator, Rule<char>> ruleFetcher)

на

protected T Parse<T>(string text, Func<CalculatorParser, Rule<char>> ruleFetcher)

или наоборот, поменять название класса в самом файле .ometa.

Подадим теперь на вход тестовый код:

var result = Parse<string>("(1+(3*(2-(4)))-2)", x => x.Expr);

и получим такой результат:

"[sub, [add, [num, 1], [mul, [num, 3], [sub, [num, 2], [num, 4]]]], [num, 2]]"

По сути, мы выполнили компиляцию исходного выражения в AST (или, точнее, в обратную польскую запись), которое теперь удобно использовать, например, в качестве условного кода для внешнего интерпретатора конкатенативного языка. Однако нам это и не требуется, так как OMeta ведь уже и так готовый интерпретатор (генератор интерпретаторов)!

Вот такая простая грамматика будет выполнять интерпретацию нашего нового кода:

ometa OMetaSharp.Examples.Calculator<char, int> : Parser<char> {

dig = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9',
num = dig+:n -> {n},

eval = "[" Expr:x "]" -> {x},
Expr = "num, " num:n -> {n}
| "add, " eval:x ", " eval:y -> {x+y}
| "sub, " eval:x ", " eval:y -> {x-y}
| "mul, " eval:x ", " eval:y -> {x*y}
| "div, " eval:x ", " eval:y -> {x/y}
}

Вызвав

var result = Parse<string>("[sub, [add, [num, 1], [mul, [num, 3], [sub, [num, 2], [num, 4]]]], [num, 2]]", x => x.eval);

мы получим результат -7.

В заключение этого примера, выполним обратную трансляцию в исходный арифметический вид:

ometa OMetaSharp.Examples.Calculator<char, string> : Parser<char> {

dig = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9',
num = dig+:n -> {n},

eval = "[" Expr:x "]" -> {x},
Expr = "num, " num:n -> {n}
| "add, " eval:x ", " eval:y -> {'('+x+'+'+y+')'}
| "sub, " eval:x ", " eval:y -> {'('+x+'-'+y+')'}
| "mul, " eval:x ", " eval:y -> {'('+x+'*'+y+')'}
| "div, " eval:x ", " eval:y -> {'('+x+'/'+y+')'}
}

Обратите внимание, что теперь тип возвращаемого значения string, а не int, и в ходе парсинга мы выполняем формирование одной строки ‘(‘+x+’+’+y+’)’, где x и y — строки, а не выдачу списка.

Теперь результат будет таким:

"((1+(3*(2-4)))-2)"

Как видим, совершенно минималистичными усилиями мы можем создавать работающие трансляторы и интерпретаторы произвольных языков программирования, и встраивать их в свои программы на C#.

Далее рассмотрим версии OMeta для JavaScript/веба и Python/Linux.

Поделиться статьей ...Share on Facebook0Share on Google+0Tweet about this on TwitterShare on LinkedIn0Share on VKPrint this page

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *