// Parse-time state and node construction.
//
// The reference makes its grammar a Menhir FUNCTOR over the trivia context, so
// every semantic action can register the span of the node it builds:
//
// %parameter
//
// MoonBit has no functors and MoonYacc has no equivalent, so the context lives
// in a package-level Ref that `parse` sets on entry. The consequence is that
// PARSING IS NOT REENTRANT -- one parse at a time per process. That is fine for
// a CLI; if this ever backs a language server, the fix is to thread a state
// record through the actions instead.
///|
/// The trivia context the running parse is registering spans with.
///
/// Reset by `set_context` at the start of every parse, so a previous parse's
/// leftovers can never leak into the next one.
let context : Ref[@trivia.Context] = @ref.new(@trivia.Context::new())
///|
pub fn set_context(ctx : @trivia.Context) -> Unit {
context.val = ctx
parenthesized.clear()
semantic_error.val = None
}
///|
/// Byte spans of expressions that were written inside parentheses.
///
/// Needed only to enforce the non-associativity of the comparison operators.
/// The reference declares that precedence level `%nonassoc`, which makes
/// `1 < 2 < 3` a syntax error while `1 < (2 < 3)` is accepted -- but MoonYacc
/// panics when a %nonassoc conflict actually arises (see the note in
/// parser.mbty), so the level is `%left` and the rule is enforced here instead.
///
/// The distinction is one the GRAMMAR knows and the AST does not: the
/// parenthesized production returns its inner instruction unchanged, so the two
/// forms are indistinguishable afterwards. Rather than add a paren node to the
/// AST -- which the reference does not have, and which the printer would then
/// have to ignore -- the parenthesized spans are recorded here, outside the
/// tree, and consulted only by `reject_chained_comparison`.
let parenthesized : Set[(Int, Int)] = Set([], capacity=64)
///|
/// Record that `i` was written inside parentheses.
fn mark_parenthesized(i : Instr[Location]) -> Instr[Location] {
parenthesized.add((i.info.start.cnum, i.info.end.cnum))
i
}
///|
/// Whether this operator is one of the comparison operators, which together
/// form a single non-associative precedence level.
fn is_comparison(op : @ast.BinOp) -> Bool {
match op {
Eq | Ne | Lt(_) | Gt(_) | Le(_) | Ge(_) => true
_ => false
}
}
///|
/// Reject a chained comparison such as `1 < 2 < 3`.
///
/// Raised at the span of the SECOND operator, which is where the reference's
/// parser stops: verified against the pinned binary, which reports
/// startOffset/endOffset covering exactly that token.
///
/// A parenthesized operand is fine -- `1 < (2 < 3)` and `(1 < 2) < 3` are both
/// accepted by the reference -- which is what the `parenthesized` set is for.
fn reject_chained_comparison(
oploc : (Position, Position),
op : @ast.BinOp,
i : Instr[Location],
) -> Unit {
if !is_comparison(op) {
return
}
if i.desc is BinOpI(inner, _, _) &&
is_comparison(inner.desc) &&
!parenthesized.contains((i.info.start.cnum, i.info.end.cnum)) {
record_error(
loc_of(oploc),
"Comparison operators are non-associative; parenthesize to say which comparison was meant.",
)
}
}
///|
/// A syntax error raised from a semantic action.
///
/// Distinct from MoonYacc's own `ParseError`, which reports a token the
/// automaton could not shift. These are the errors the reference raises from
/// inside its actions -- "this identifier is not a value type", "a parameter
/// list is required" -- where the token stream is fine but what it says is not.
/// Their message text is hand-written in the reference, so unlike the automaton
/// messages it can be reproduced exactly.
suberror SyntaxError {
SyntaxError(
loc~ : Location,
message~ : String,
hint~ : String?,
/// A machine-applicable repair, when the message names an exact one.
fix~ : @basic.Edit?
)
} derive(Debug)
///|
/// The first semantic error of the running parse, if any.
///
/// MoonYacc generates its semantic actions as
/// `(Position, ArrayView[...]) -> YYObj` -- no `raise` in the signature -- so an
/// action CANNOT propagate an error. The reference simply raises from inside
/// its actions; here the error is recorded instead, the action returns a
/// harmless placeholder, and the driver reports it and discards the tree.
///
/// First error wins, which matches the reference: it raises on the first
/// semantic error, so nothing after that is reported there either.
let semantic_error : Ref[SyntaxError?] = @ref.new(None)
///|
/// Record a syntax error and return `default` so the action can complete.
///
/// The returned value is never used for anything: the driver discards the whole
/// tree whenever an error was recorded. It exists only because the action has
/// to return something of the right type.
fn[T] fail_at(
loc : Location,
message : String,
default~ : T,
hint? : String? = None,
fix? : @basic.Edit? = None,
) -> T {
if semantic_error.val is None {
semantic_error.val = Some(SyntaxError(loc~, message~, hint~, fix~))
}
default
}
///|
/// Record a syntax error where the action has nothing to return.
fn record_error(
loc : Location,
message : String,
hint? : String? = None,
fix? : @basic.Edit? = None,
) -> Unit {
if semantic_error.val is None {
semantic_error.val = Some(SyntaxError(loc~, message~, hint~, fix~))
}
}
///|
/// The semantic error the last parse recorded, if it hit one.
fn taken_error() -> SyntaxError? {
semantic_error.val
}
///|
/// Build a location from a MoonYacc `$loc`-style position pair.
fn loc_of(p : (Position, Position)) -> Location {
{ start: p.0, end: p.1 }
}
///|
/// Build an instruction node and register its span with the trivia context.
///
/// Registration is what lets a comment be attached to this node later, so every
/// node the printer might emit has to go through here or through `annot`.
///
/// A freshly parsed instruction carries no hints (a hint attribute fills them
/// in afterwards) and no `expected` type (that is a decompiler-only channel).
fn with_loc(
p : (Position, Position),
desc : InstrDesc[Location],
) -> Instr[Location] {
let info = loc_of(p)
context.val.record_pos(info)
{ desc, info, hints: @ast.no_hints, expected: None }
}
///|
/// Build any other located node -- a module field, an identifier, a body -- and
/// register its span.
fn[T] annot(p : (Position, Position), desc : T) -> Annotated[T, Location] {
let info = loc_of(p)
context.val.record_pos(info)
{ desc, info }
}
///|
/// Re-span an already-built annotated node.
///
/// Used where a branch of a conditional group must keep its own
/// `#[if] { ... }` span, marker included, rather than only the combined span of
/// the pair -- the editor's dead-branch dimming locates a single branch by it.
fn[T] respan(
a : Annotated[T, Location],
p : (Position, Position),
) -> Annotated[T, Location] {
{ desc: a.desc, info: loc_of(p) }
}
///|
/// The signature of a block that did not state one.
fn blocktype(bt : FuncType?) -> FuncType {
match bt {
Some(t) => t
None => { params: [], results: [] }
}
}
///|
/// Build a binary-operator node.
///
/// The operator gets a span of its own (`oploc`, its token) rather than only
/// the whole expression's, so a comment written between an operand and the
/// operator attaches where it was written.
fn binop(
sloc : (Position, Position),
oploc : (Position, Position),
op : @ast.BinOp,
i : Instr[Location],
j : Instr[Location],
) -> Instr[Location] {
reject_chained_comparison(oploc, op, i)
with_loc(sloc, BinOpI(annot(oploc, op), i, j))
}
///|
fn unop(
sloc : (Position, Position),
oploc : (Position, Position),
op : @ast.UnOp,
i : Instr[Location],
) -> Instr[Location] {
with_loc(sloc, UnOpI(annot(oploc, op), i))
}