Appearance
| 1 | namespace Syntax.Parsers is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | trait Parser[T: class] is | |
| 5 | // Null means the parser could not produce a result — parsers | |
| 6 | // return null on unparseable input as part of error recovery, | |
| 7 | // so every caller sees the possibility in the type. | |
| 8 | parse(context: CONTEXT) -> T? | |
| 9 | si | |
| 10 | ||
| 11 | class Base[T: class]: Parser[T] abstract is | |
| 12 | _expected_tokens: Collections.MutableList[Lexical.TOKEN]? | |
| 13 | parsers: Collections.MAP[Lexical.TOKEN, (CONTEXT) -> T?] | |
| 14 | ||
| 15 | expected_tokens: Collections.Iterable[Lexical.TOKEN] is | |
| 16 | let result = _expected_tokens ?? _build_expected_tokens() | |
| 17 | ||
| 18 | _expected_tokens = result | |
| 19 | ||
| 20 | return result | |
| 21 | si | |
| 22 | ||
| 23 | description: string? => null | |
| 24 | ||
| 25 | syntax_error_message: string => | |
| 26 | let d = description in | |
| 27 | if d? then | |
| 28 | "in {d}" | |
| 29 | else | |
| 30 | "syntax error" | |
| 31 | fi | |
| 32 | ||
| 33 | init() is | |
| 34 | parsers = Collections.MAP[Lexical.TOKEN, (CONTEXT) -> T?]() | |
| 35 | si | |
| 36 | ||
| 37 | add_parser(p: (CONTEXT) -> T?, token: Lexical.TOKEN) is | |
| 38 | if parsers.contains_key(token) then | |
| 39 | throw Exception("{token} already has a parser ({p})") | |
| 40 | fi | |
| 41 | ||
| 42 | parsers[token] = p | |
| 43 | si | |
| 44 | ||
| 45 | add_parser(p: (CONTEXT) -> T?, tokens: Collections.Iterable[Lexical.TOKEN]) is | |
| 46 | for t in tokens do | |
| 47 | add_parser(p, t) | |
| 48 | od | |
| 49 | si | |
| 50 | ||
| 51 | parse(context: CONTEXT) -> T? is | |
| 52 | let token = context.current.token | |
| 53 | ||
| 54 | return | |
| 55 | if parsers.contains_key(token) then | |
| 56 | let p = parsers[token] | |
| 57 | ||
| 58 | p(context) | |
| 59 | else | |
| 60 | other_token(context) | |
| 61 | fi | |
| 62 | si | |
| 63 | ||
| 64 | other_token(context: CONTEXT) -> T? is | |
| 65 | if context.current.token != Lexical.TOKEN.CANCEL_STRING then | |
| 66 | context.error(context.location, | |
| 67 | "{syntax_error_message}: expected {Lexical.TOKEN_NAMES[expected_tokens]} but found {context.current_token_name}" | |
| 68 | ) | |
| 69 | fi | |
| 70 | return _ | |
| 71 | si | |
| 72 | ||
| 73 | _build_expected_tokens() -> Collections.MutableList[Lexical.TOKEN] is | |
| 74 | let et = Collections.LIST[Lexical.TOKEN]() | |
| 75 | ||
| 76 | for k in parsers.keys do | |
| 77 | et.add(k) | |
| 78 | od | |
| 79 | ||
| 80 | assert et.count > 0 else "should have at least one expected token" | |
| 81 | ||
| 82 | return et | |
| 83 | si | |
| 84 | si | |
| 85 | si |