31
parboiled2 генерируемые макросами генераторы PEG-парсеров Александр Мыльцев / / github linkedin.com/in/alexandermyltsev презентация

parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

Embed Size (px)

DESCRIPTION

parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+ Доклад (https://www.youtube.com/watch?v=kZto4nWVlmA) от 29 мая для Moscow Scala Group (http://www.meetup.com/Scala-Moscow/events/180007162/)

Citation preview

Page 1: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

parboiled2генерируемые макросами генераторы PEG-парсеров

Александр Мыльцев / / github linkedin.com/in/alexandermyltsevпрезентация

Page 2: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

задача парсингавход: строка, поток байт, ...выход: обладает структурой (да/нет), структурапример

1 + 2 * 3 — арифметическое выражение: да или нет? — да

1 + 2 * 3 — структура? — Plus(1, Mul(2, 3))

Page 3: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

регулярные выражениянельзя: рекурсивные структуры

1. калькулятор2. json3. ...

Page 4: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

рекурсивный нисходящий парсер:C/C++

static int lex_scan_number(lex_t *lex, int c, json_error_t *error){ const char *saved_text; char *end; double doubleval;

lex->token = TOKEN_INVALID;

if(c == '-') c = lex_get_save(lex, error);

if(c == '0') { c = lex_get_save(lex, error); if(l_isdigit(c)) { lex_unget_unsave(lex, c); goto out; } } else if(l_isdigit(c)) { c = lex_get_save(lex, error); while(l_isdigit(c)) c = lex_get_save(lex, error); } else { lex_unget_unsave(lex, c); goto out; }

if(c != '.' && c != 'E' && c != 'e') { json_int_t intval;

lex_unget_unsave(lex, c);

saved_text = strbuffer_value(&lex->saved_text);

https://github.com/akheron/jansson/blob/master/src/load.c

Page 5: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

рекурсивный нисходящий парсер:JavaScript

number = function () {

// Parse a number value.

var number, string = '';

if (ch === '-') { string = '-'; next('-'); } while (ch >= '0' && ch <= '9') { string += ch; next(); } if (ch === '.') { string += '.'; while (next() && ch >= '0' && ch <= '9') { string += ch; } } if (ch === 'e' || ch === 'E') { string += ch; next(); if (ch === '-' || ch === '+') { string += ch; next(); } while (ch >= '0' && ch <= '9') { string += ch; next(); } } number = +string;

https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js

Page 6: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

рекурсивный нисходящий парсерплюсы

1. код на одном языке2. одна среда разработки3. высокая производительность

минусы1. нет предметно-ориентиррованного языка2. низкоуровневое кодирование

Page 7: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

внутренний язык и среда разработки

генерация java-файлов

подключение файлов к проекту

ANTLR3

grammar SimpleCalc; add : NUMBER PLUS NUMBER; NUMBER : ('0'..'9')+ ; PLUS : '+';

java org.antlr.Tool SimpleCalc.g

Page 8: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

ANTLR3плюсы

1. предметно-ориентированный язык2. высокая производительность

минусы1. новый язык, другая среда2. генерация и подключение файлов

Page 9: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

комбинаторы парсеровHaskell ParsecScala Combinator ParsersFParsec

Page 10: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

комбинаторы парсеровплюсы

1. программа на исходном языке2. файлы в исходном проекте3. исходная среда разработки4. типизация парсеров5. близкая к БНФ форма записи

// Expr ::= Term { '+' Term | '-' Term }def expr = term ~ rep( "+" ~ term | "-" ~ term )

// Term ::= Factor { '*' Factor | '/' Factor }def term = factor ~ rep ( "*" ~ factor | "/" ~ factor )

// Factor ::= Number | '(' Expr ')'def factor = number | "(" ~ expr ~ ")"

минусы1. интерпретатор2. временные объекты3. низкая производительность

Page 11: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

генератор парсеров1. на одном языке в одной среде разработки2. типизированный DSL3. компиляция, а не интерпритация

Page 12: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

parboiled2PEG — грамматика, разбирающая выражениетипизированный DSLкомпиляция правил

Page 13: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

rule, ch, strреализация класса `Parser`правила грамматики внутри метода rule { }

class SampleParser(val input: ParserInput) extends Parser { def Abc = rule { "abc" } def Ch = rule { '7' } def Num = rule { 42 } // ошибка компиляции}

Page 14: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

a ~ b, a | bclass SampleParser(val input: ParserInput) extends Parser { def r1 = rule { "a" ~ "b" } // "ab" def r2 = rule { "a" | "b" } // { "a", "b" } def r3 = rule { "a" ~ ("b" | "c") } // { "ab", "ac" } def r4 = rule { "ab" | "a" }}

Page 15: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

zeroOrMore, oneOrMoreclass SampleParser(val input: ParserInput) extends Parser { def r1 = rule { zeroOrMore("a") } // { "", "a", "aa", "aaa", ... } def r2 = rule { oneOrMore("a") } // { "a", "aa", "aaa", ... }}

Page 16: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

парсер арифметическихвыражений

class CalculatorParser(val input: ParserInput) extends Parser { def InputLine = rule { Expression ~ EOI }

// Рекурсивный вызов в Parens, поэтому нужен тип def Expression: Rule0 = rule { Term ~ zeroOrMore('+' ~ Term | '-' ~ Term) }

def Term = rule { Factor ~ zeroOrMore('*' ~ Factor | '/' ~ Factor) }

def Factor = rule { Number | Parens }

def Parens = rule { '(' ~ Expression ~ ')' }

def Number = rule { oneOrMore(CharPredicate.Digit) }}

Page 17: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

арифметическое выражение:да или нет?

val line = readline()val calc = new CalculatorParser(line)

calc.InputLine().run() match { case Success(_) => "Yes" case Failure(_) => "No"}

Page 18: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

parser actions, value-stackvalue-stack — отличие от haskell parsec et al.

class ValueStack(initialSize: Int, maxSize: Int) extends Iterable[Any] { var buffer = new Array[Any](initialSize) var size = 0}

def Digits = rule { oneOrMore("0" | "1" | /*...*/ "9") } // oneOrMore("0"-"9")

def False = rule { "false" ~ push(false) } // "false" >>> VStk: [false]

def NumberStr = rule { capture(Digits) } // "42" >>> VStk: ["42"]

def Number = rule { capture(Digits) ~> (_.toInt) } "42" >>> // VStk: [42]

def Sum = rule { Number ~ " " ~ Number ~> ((_:Int) + _) } // "11 15" >>> VStk: [26]

Page 19: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

калькуляторclass CalculatorParser(val input: ParserInput) extends Parser { def Expression: Rule1[Int] = rule { Term ~ zeroOrMore( '+' ~ Term ~> ((_: Int) + _) | '-' ~ Term ~> ((_: Int) - _)) }

def Term = rule { Factor ~ zeroOrMore( '*' ~ Factor ~> ((_: Int) * _) | '/' ~ Factor ~> ((_: Int) / _)) }}

`~>` может передавать элемент AST в стек`~>`-обработчик сам может быть парсером

Page 20: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

типизация правил и value-stackdef InputRule: Rule[HNil, String :: HNil] = rule { capture("a") }

class Rule[-I <: HList, +O <: HList]I - тип значения со стекаO - тип значения в стек

type RuleN[L <: HList] = Rule[HNil, L]type Rule0 = RuleN[HNil]type Rule1[T] = RuleN[T :: HNil]type Rule2[A, B] = RuleN[A :: B :: HNil]

Page 21: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

типизация правил и value-stackdef a: Rule1[String] = rule { capture("a") }

def b: Rule1[String] = rule { capture("7") }

def bi: Rule1[Int] = rule { b ~> ((_:String).toInt) }

Page 22: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

типизация правил и value-stackdef c1: Rule2[String, Int] = rule { a ~ bi }

def c2: Rule2[Int, Int] = rule { bi ~ push(42) }

def c3: Rule[Int :: HNil, Int :: HNil] = rule { capture("7") ~> ((x: Int, y: String) => x + y.toInt) }

Page 23: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

компиляцияabstract class Parser { def rule(r: Rule): Rule = macro Parser.ruleImpl}

object Parser { def ruleImpl(ctx: ParserContext)(r: ctx.Expr[Rule]): ctx.Expr[Rule] = reify { OpTree(r.tree).render().splice }}

Page 24: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

компиляцияobject OpTree { def apply(tree: Tree): OpTree = r.tree match { // квазицитирования case q"$a.this.str($s)" => LiteralString(s) case q"$a.this.ch($ch)" => LiteralChar(ch) case q"$lhs.|($rhs)" => FirstOf(OpTree(lhs), OpTree(rhs)) case q"$lhs.~($rhs)" => Sequence(OpTree(lhs), OpTree(rhs)) }}

abstract class OpTree { def render(): Expr[Rule]}

case class Rule(val matched: Boolean)

Page 25: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

LiteralCharimplicit def ch(c: Char): Rule0rule { ch('a') }case q"$a.this.ch($ch)" => // $a = CalculatorParser, $ch = CharLiteral('a')

case class LiteralChar(charTree: Tree) extends OpTree { def render(): Expr[Rule] = reify { val char = c.Expr[Char](charTree).splice val p = c.prefix.splice // Current parser Rule(p.__nextChar() == char) }}

Page 26: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

LiteralStringimplicit def str(c: String): Rule0rule { str("abc") }case q"$a.this.str($s)" => // $a = CalculatorParser, $s = StringLiteral("abc")

case class LiteralString(s: Tree) extends OpTree { def render(): Expr[Rule] = c.Expr[Rule](q̀ val p = ${c.prefix} // Current parser var ix = 0 while (ix < $s.length && $s.charAt(ix) == p.__nextChar()) { ix += 1 } Rule(ix == $s.length))̀

Page 27: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

FirstOf: a | b// case q"$lhs.|($rhs)" =>

case class FirstOf(charTree: Tree) extends OpTree { def render(): Expr[Rule] = c.Expr[Rule](q̀ val mark = p.__cursor if (${OpTree(lhs).render()}.matched) { Rule(true) } else { p.__cursor = mark ${OpTree(rhs).render()} } ̀)}

Page 28: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

Sequence: a ~ b// case q"$lhs.~($rhs)" =>

case class FirstOf(charTree: Tree) extends OpTree { def render(): Expr[Rule] = c.Expr[Rule](q̀ Rule(${OpTree(lhs).render()}.matched && ${OpTree(rhs).render()}.matched) ̀)}

Page 29: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

parboiled2.orgoneOrMorezeroOrMore~>...

Page 30: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

использованиеjson-парсер в ~100 быстрее Scala Parsers, в ~10 parboiled1typesafe, spray.io, spray-json

Page 31: parboiled2 – A Macro-Based PEG Parser Generator for Scala 2.10.3+

parboiled2.orglinkedin.com/in/alexandermyltsev

вопросы