// Parse-time state shared by the bytecode interpreter and generated parsers.
// This is the MoonBit counterpart of the `peg$*` variables and helper
// functions that PEG.js emits inside every generated `peg$parse`.
///|
/// A slot of the parsing machine's stack: a value, a saved input position or
/// the `peg$FAILED` marker. Actions never observe `Failed` or `Pos`.
pub(all) enum Slot[T] {
Failed
Pos(Int)
Val(Value[T])
} derive(Eq, Debug)
///|
/// Which helpers the generated parser provides (the PEG.js `features`
/// option). Disabled action helpers raise `FeatureDisabled`.
pub(all) struct Features {
text : Bool
offset : Bool
range : Bool
location : Bool
expected : Bool
error : Bool
filename : Bool
default_tracer : Bool
} derive(Eq, Debug)
///|
pub fn Features::all() -> Features {
{
text: true,
offset: true,
range: true,
location: true,
expected: true,
error: true,
filename: true,
default_tracer: true,
}
}
///|
pub(all) enum TraceType {
RuleEnter
RuleMatch
RuleFail
} derive(Eq, Debug)
///|
pub fn TraceType::name(self : TraceType) -> String {
match self {
RuleEnter => "rule.enter"
RuleMatch => "rule.match"
RuleFail => "rule.fail"
}
}
///|
pub(all) struct TraceEvent[T] {
type_ : TraceType
rule : String
result : Value[T]?
location : Location
} derive(Eq, Debug)
///|
/// Receives rule enter/match/fail events when tracing is enabled.
pub(all) struct Tracer[T] {
trace : (TraceEvent[T]) -> Unit raise
}
///|
/// The PEG.js `DefaultTracer`: logs indented events through `log`
/// (`println` by default).
pub fn[T] Tracer::default(log? : (String) -> Unit = println) -> Tracer[T] {
let indent = Ref(0)
fn emit(event : TraceEvent[T]) {
let loc = event.location
let sb = StringBuilder()
sb.write_string(
"\{loc.start.line}:\{loc.start.column}-\{loc.end.line}:\{loc.end.column} ",
)
let name = event.type_.name()
sb.write_string(name)
for _ in name.length()..<10 {
sb.write_char(' ')
}
sb.write_char(' ')
for _ in 0.. {
match event.type_ {
RuleEnter => {
emit(event)
indent.val += 1
}
RuleMatch | RuleFail => {
indent.val -= 1
emit(event)
}
}
},
}
}
///|
priv struct ExpectFrame {
mut pos : Int
mut variants : Array[Expectation]
}
///|
/// A memoized rule result (the `cache` option).
pub(all) struct CacheEntry[T] {
next_pos : Int
result : Slot[T]
expectations : Array[Expectation]
}
///|
pub(all) struct State[T] {
input : String
mut curr_pos : Int
mut saved_pos : Int
mut silent_fails : Int
priv expected : Array[ExpectFrame]
priv pos_cache : PosCache
priv results_cache : Map[(Int, Int), CacheEntry[T]]
tracer : Tracer[T]?
options : Map[String, Value[T]]
features : Features
}
///|
pub fn[T] State::new(
input : String,
options~ : Map[String, Value[T]],
features~ : Features,
tracer? : Tracer[T],
) -> State[T] {
let filename = match options.get("filename") {
Some(Str(f)) if features.filename && f != "" => Some(f)
_ => None
}
{
input,
curr_pos: 0,
saved_pos: 0,
silent_fails: 0,
expected: [],
pos_cache: PosCache::new(input, filename?),
results_cache: {},
tracer,
options,
features,
}
}
///|
/// JavaScript `String.prototype.substring`: clamps and swaps its bounds.
pub fn js_substring(s : String, start : Int, end : Int) -> String {
let len = s.length()
let a = if start < 0 { 0 } else if start > len { len } else { start }
let b = if end < 0 { 0 } else if end > len { len } else { end }
if a <= b {
s.unsafe_substring(start=a, end=b)
} else {
s.unsafe_substring(start=b, end=a)
}
}
///|
pub fn[T] State::compute_location(
self : State[T],
start : Int,
end : Int,
) -> Location {
self.pos_cache.location(start, end)
}
///|
/// `peg$begin`: opens a new expectation namespace at the current position.
pub fn[T] State::begin(self : State[T]) -> Unit {
self.expected.push({ pos: self.curr_pos, variants: [], })
}
///|
/// `peg$expect`: records an expectation at the current position.
pub fn[T] State::expect(self : State[T], expected : Expectation) -> Unit {
let top = self.expected[self.expected.length() - 1]
if self.curr_pos < top.pos {
return
}
if self.curr_pos > top.pos {
top.pos = self.curr_pos
top.variants = []
}
top.variants.push(expected)
}
///|
/// `peg$end`: closes the innermost namespace, merging its expectations into
/// the enclosing one (negated when `invert`) if both are at the same position.
pub fn[T] State::end(self : State[T], invert : Bool) -> Unit {
let expected = self.expected.pop().unwrap()
let top = self.expected[self.expected.length() - 1]
if top.pos != expected.pos {
return
}
for e in expected.variants {
top.variants.push(
if invert {
match e {
Not(inner) => inner
_ => Not(e)
}
} else {
e
},
)
}
}
///|
/// `rule$expects` without caching: records unless failures are silenced.
pub fn[T] State::rule_expects(self : State[T], expected : Expectation) -> Unit {
if self.silent_fails == 0 {
self.expect(expected)
}
}
///|
/// Looks up a memoized result for `rule` at the current position. On a hit,
/// restores the position and replays the recorded expectations.
pub fn[T] State::cache_lookup(self : State[T], rule : Int) -> CacheEntry[T]? {
match self.results_cache.get((self.curr_pos, rule)) {
Some(entry) => {
self.curr_pos = entry.next_pos
if self.silent_fails == 0 {
for e in entry.expectations {
self.expect(e)
}
}
Some(entry)
}
None => None
}
}
///|
pub fn[T] State::cache_store(
self : State[T],
rule : Int,
start_pos : Int,
result : Slot[T],
expectations : Array[Expectation],
) -> Unit {
self.results_cache[(start_pos, rule)] = {
next_pos: self.curr_pos,
result,
expectations,
}
}
///|
/// Emits a trace event if tracing is enabled.
pub fn[T] State::trace(
self : State[T],
type_ : TraceType,
rule : String,
start_pos : Int,
result? : Slot[T],
) -> Unit raise {
guard self.tracer is Some(tracer) else { return }
let (location, value) = match (type_, result) {
(RuleMatch, Some(Val(v))) =>
(self.compute_location(start_pos, self.curr_pos), Some(v))
(RuleMatch, _) => (self.compute_location(start_pos, self.curr_pos), None)
_ => (self.compute_location(start_pos, start_pos), None)
}
(tracer.trace)({ type_, rule, result: value, location, })
}
///|
/// `peg$buildError` + the final end-of-input check of `peg$parse`.
pub fn[T] State::finish(self : State[T], result : Slot[T]) -> Value[T] raise {
if result is Val(v) && self.curr_pos == self.input.length() {
return v
}
if !(result is Failed) && self.curr_pos < self.input.length() {
self.expect(End)
}
let expected = self.expected[0]
let fail_pos = expected.pos
let (found, location) = if fail_pos < self.input.length() {
(
Some(self.input.unsafe_substring(start=fail_pos, end=fail_pos + 1)),
self.compute_location(fail_pos, fail_pos + 1),
)
} else {
(None, self.compute_location(fail_pos, fail_pos))
}
raise SyntaxError(
message=build_message(expected.variants, found),
expected=Some(expected.variants),
found~,
location~,
)
}
///|
/// The helpers available to actions, predicates and initializers: labeled
/// values plus the PEG.js `text()`, `location()`, `error()`... functions.
pub struct ActionContext[T] {
state : State[T]
params : ArrayView[String]
args : ArrayView[Value[T]]
}
///|
pub fn[T] ActionContext::new(
state : State[T],
params : ArrayView[String],
args : ArrayView[Value[T]],
) -> ActionContext[T] {
{ state, params, args, }
}
///|
/// The value bound to `label`. Raises `ReferenceError` if the label is not
/// in scope, like the generated JavaScript would.
pub fn[T] ActionContext::label(
self : ActionContext[T],
label : String,
) -> Value[T] raise ReferenceError {
for i, p in self.params {
if p == label {
return self.args[i]
}
}
raise ReferenceError(label)
}
///|
/// Like `label`, but `None` when the label is not in scope.
pub fn[T] ActionContext::label_opt(
self : ActionContext[T],
label : String,
) -> Value[T]? {
for i, p in self.params {
if p == label {
return Some(self.args[i])
}
}
None
}
///|
pub fn[T] ActionContext::input(self : ActionContext[T]) -> String {
self.state.input
}
///|
pub fn[T] ActionContext::options(
self : ActionContext[T],
) -> Map[String, Value[T]] {
self.state.options
}
///|
pub fn[T] ActionContext::text(self : ActionContext[T]) -> String raise {
guard self.state.features.text else { raise FeatureDisabled("text") }
js_substring(self.state.input, self.state.saved_pos, self.state.curr_pos)
}
///|
pub fn[T] ActionContext::offset(self : ActionContext[T]) -> Int raise {
guard self.state.features.offset else { raise FeatureDisabled("offset") }
self.state.saved_pos
}
///|
pub fn[T] ActionContext::range(self : ActionContext[T]) -> (Int, Int) raise {
guard self.state.features.range else { raise FeatureDisabled("range") }
(self.state.saved_pos, self.state.curr_pos)
}
///|
pub fn[T] ActionContext::location(self : ActionContext[T]) -> Location raise {
guard self.state.features.location else { raise FeatureDisabled("location") }
self.state.compute_location(self.state.saved_pos, self.state.curr_pos)
}
///|
/// Aborts the parse with a "expected " syntax error.
pub fn[T, R] ActionContext::expected(
self : ActionContext[T],
description : String,
location? : Location,
) -> R raise {
guard self.state.features.expected else { raise FeatureDisabled("expected") }
let state = self.state
let location = match location {
Some(l) => l
None => state.compute_location(state.saved_pos, state.curr_pos)
}
let expected = [Expectation::Other(description~)]
let found = js_substring(state.input, state.saved_pos, state.curr_pos)
raise SyntaxError(
message=build_message(expected, Some(found)),
expected=Some(expected),
found=Some(found),
location~,
)
}
///|
/// Aborts the parse with a syntax error carrying `message`.
pub fn[T, R] ActionContext::error(
self : ActionContext[T],
message : String,
location? : Location,
) -> R raise {
guard self.state.features.error else { raise FeatureDisabled("error") }
let state = self.state
let location = match location {
Some(l) => l
None => state.compute_location(state.saved_pos, state.curr_pos)
}
raise SyntaxError(message~, expected=None, found=None, location~)
}