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 | if _expected_tokens == null then | |
| 17 | populate_expected_tokens(); | |
| 18 | fi | |
| 19 | return _expected_tokens; | |
| 20 | si | |
| 21 | ||
| 22 | description: string? => null; | |
| 23 | ||
| 24 | syntax_error_message: string => | |
| 25 | let d = description in | |
| 26 | if d? then | |
| 27 | "in {d}" | |
| 28 | else | |
| 29 | "syntax error" | |
| 30 | fi; | |
| 31 | ||
| 32 | init() is | |
| 33 | parsers = Collections.MAP[Lexical.TOKEN, (CONTEXT) -> T](); | |
| 34 | si | |
| 35 | ||
| 36 | add_parser(p: (CONTEXT) -> T, token: Lexical.TOKEN) is | |
| 37 | if parsers.contains_key(token) then | |
| 38 | throw Exception("{token} already has a parser ({p})"); | |
| 39 | fi | |
| 40 | ||
| 41 | parsers[token] = p; | |
| 42 | si | |
| 43 | ||
| 44 | add_parser(p: (CONTEXT) -> T, tokens: Collections.Iterable[Lexical.TOKEN]) is | |
| 45 | for t in tokens do | |
| 46 | add_parser(p, t); | |
| 47 | od | |
| 48 | si | |
| 49 | ||
| 50 | parse(context: CONTEXT) -> T? is | |
| 51 | let token = context.current.token; | |
| 52 | ||
| 53 | return | |
| 54 | if parsers.contains_key(token) then | |
| 55 | let p = parsers[token]; | |
| 56 | ||
| 57 | p(context); | |
| 58 | else | |
| 59 | other_token(context); | |
| 60 | fi | |
| 61 | si | |
| 62 | ||
| 63 | other_token(context: CONTEXT) -> T? is | |
| 64 | if context.current.token != Lexical.TOKEN.CANCEL_STRING then | |
| 65 | context.error(context.location, | |
| 66 | "{syntax_error_message}: expected {Lexical.TOKEN_NAMES[expected_tokens]} but found {context.current_token_name}" | |
| 67 | ); | |
| 68 | fi | |
| 69 | return _; | |
| 70 | si | |
| 71 | ||
| 72 | populate_expected_tokens() private is | |
| 73 | let et = Collections.LIST[Lexical.TOKEN](); | |
| 74 | ||
| 75 | for k in parsers.keys do | |
| 76 | et.add(k); | |
| 77 | od | |
| 78 | ||
| 79 | assert et.count > 0 else "should have at least one expected token"; | |
| 80 | ||
| 81 | _expected_tokens = et; | |
| 82 | si | |
| 83 | si | |
| 84 | si |