|
1 | 1 | # This file provides an adaptor to match the API expected by the Julia runtime
|
2 | 2 | # code in the binding Core._parse
|
3 | 3 |
|
| 4 | +# Find the first error in a SyntaxNode tree, returning the index of the error |
| 5 | +# within its parent and the node itself. |
| 6 | +function _first_error(t::SyntaxNode) |
| 7 | + if is_error(t) |
| 8 | + return 0,t |
| 9 | + end |
| 10 | + if haschildren(t) |
| 11 | + for (i,c) in enumerate(children(t)) |
| 12 | + if is_error(c) |
| 13 | + return i,c |
| 14 | + else |
| 15 | + x = _first_error(c) |
| 16 | + if x != (0,nothing) |
| 17 | + return x |
| 18 | + end |
| 19 | + end |
| 20 | + end |
| 21 | + end |
| 22 | + return 0,nothing |
| 23 | +end |
| 24 | + |
| 25 | +# Classify an incomplete expression, returning a Symbol compatible with |
| 26 | +# Base.incomplete_tag(). |
| 27 | +# |
| 28 | +# Roughly, the intention here is to classify which expression head is expected |
| 29 | +# next if the incomplete stream was to continue. (Though this is just rough. In |
| 30 | +# practice several categories are combined for the purposes of the REPL - |
| 31 | +# perhaps we can/should do something more precise in the future.) |
| 32 | +function _incomplete_tag(n::SyntaxNode) |
| 33 | + i,c = _first_error(n) |
| 34 | + if isnothing(c) |
| 35 | + return :none |
| 36 | + end |
| 37 | + # TODO: Check error hits last character |
| 38 | + if kind(c) == K"error" && begin |
| 39 | + cs = children(c) |
| 40 | + length(cs) > 0 |
| 41 | + end |
| 42 | + k1 = kind(cs[1]) |
| 43 | + if k1 == K"ErrorEofMultiComment" |
| 44 | + return :comment |
| 45 | + elseif k1 == K"ErrorEofChar" |
| 46 | + # TODO: Make this case into an internal node |
| 47 | + return :char |
| 48 | + end |
| 49 | + for cc in cs |
| 50 | + if kind(cc) == K"error" |
| 51 | + return :other |
| 52 | + end |
| 53 | + end |
| 54 | + end |
| 55 | + kp = kind(c.parent) |
| 56 | + if kp == K"string" |
| 57 | + return :string |
| 58 | + elseif kp == K"cmdstring" |
| 59 | + return :cmd |
| 60 | + elseif kp in KSet"block quote let try" |
| 61 | + return :block |
| 62 | + elseif kp in KSet"for while function if" |
| 63 | + return i == 1 ? :other : :block |
| 64 | + elseif kp in KSet"module struct" |
| 65 | + return i == 2 ? :other : :block |
| 66 | + elseif kp == K"do" |
| 67 | + return i < 3 ? :other : :block |
| 68 | + else |
| 69 | + return :other |
| 70 | + end |
| 71 | +end |
| 72 | + |
| 73 | +#------------------------------------------------------------------------------- |
4 | 74 | @static if isdefined(Core, :_setparser!)
|
5 | 75 | const _set_core_parse_hook = Core._setparser!
|
6 | 76 | elseif isdefined(Core, :set_parser)
|
@@ -93,11 +163,29 @@ function _core_parser_hook(code, filename, lineno, offset, options)
|
93 | 163 | end
|
94 | 164 |
|
95 | 165 | if any_error(stream)
|
96 |
| - e = Expr(:error, ParseError(SourceFile(code, filename=filename), stream.diagnostics)) |
97 |
| - ex = options === :all ? Expr(:toplevel, e) : e |
| 166 | + tree = build_tree(SyntaxNode, stream, wrap_toplevel_as_kind=K"None") |
| 167 | + _,err = _first_error(tree) |
| 168 | + # In the flisp parser errors are normally `Expr(:error, msg)` where |
| 169 | + # `msg` is a String. By using a ParseError for msg we can do fancy |
| 170 | + # error reporting instead. |
| 171 | + if last_byte(err) == lastindex(code) |
| 172 | + tag = _incomplete_tag(tree) |
| 173 | + # Here we replicate the particular messages |
| 174 | + msg = |
| 175 | + tag === :string ? "incomplete: invalid string syntax" : |
| 176 | + tag === :comment ? "incomplete: unterminated multi-line comment #= ... =#" : |
| 177 | + tag === :block ? "incomplete: construct requires end" : |
| 178 | + tag === :cmd ? "incomplete: invalid \"`\" syntax" : |
| 179 | + tag === :char ? "incomplete: invalid character literal" : |
| 180 | + "incomplete: premature end of input" |
| 181 | + error_ex = Expr(:incomplete, msg) |
| 182 | + else |
| 183 | + error_ex = Expr(:error, ParseError(stream, filename=filename)) |
| 184 | + end |
| 185 | + ex = options === :all ? Expr(:toplevel, error_ex) : error_ex |
98 | 186 | else
|
99 | 187 | # FIXME: Add support to lineno to this tree build (via SourceFile?)
|
100 |
| - ex = build_tree(Expr, stream, filename=filename, wrap_toplevel_as_kind=K"None") |
| 188 | + ex = build_tree(Expr, stream; filename=filename, wrap_toplevel_as_kind=K"None") |
101 | 189 | if Meta.isexpr(ex, :None)
|
102 | 190 | # The None wrapping is only to give somewhere for trivia to be
|
103 | 191 | # attached; unwrap!
|
|
0 commit comments