UNPKG

@thi.ng/parse

Version:

Purely functional parser combinators & AST generation for generic inputs

160 lines (159 loc) 4.13 kB
import { isArrayLike } from "@thi.ng/checks/is-arraylike"; import { isString } from "@thi.ng/checks/is-string"; import { parseError } from "./error.js"; import { defArrayReader } from "./readers/array-reader.js"; import { defStringReader } from "./readers/string-reader.js"; import { __indent } from "./utils.js"; class ParseState { constructor(p, l, c, done) { this.p = p; this.l = l; this.c = c; this.done = done; } copy() { return new ParseState(this.p, this.l, this.c, this.done); } } class ParseScope { constructor(id, state, children, result) { this.id = id; this.state = state; this.children = children; this.result = result; } copy() { return new ParseScope( this.id, this.state, this.children, this.result ); } } class ParseContext { constructor(reader, opts) { this.reader = reader; this.opts = { maxDepth: 64, debug: false, retain: false, ...opts }; this._maxDepth = this.opts.maxDepth; this._debug = this.opts.debug; this._retain = this.opts.retain; this.reset(); } opts; _scopes; _curr; _maxDepth; _peakDepth; _debug; _retain; reset() { this._curr = new ParseScope("root", new ParseState(0, 1, 1)); this._scopes = [this._curr]; this._peakDepth = 1; this.reader.isDone(this._curr.state); return this; } start(id) { const { _scopes: scopes, _maxDepth } = this; const num = scopes.length; if (num >= _maxDepth) { parseError(this, `recursion limit reached ${_maxDepth}`); } const scope = new ParseScope(id, scopes[num - 1].state.copy()); this._peakDepth = Math.max(this._peakDepth, scopes.push(scope)); this._debug && console.log( `${__indent(scopes.length)}start: ${id} (${scope.state.p})` ); return this._curr = scope; } discard() { const scopes = this._scopes; const child = scopes.pop(); this._curr = scopes[scopes.length - 1]; this._debug && console.log(`${__indent(scopes.length + 1)}discard: ${child.id}`); return false; } end() { const scopes = this._scopes; const child = scopes.pop(); const parent = scopes[scopes.length - 1]; const cstate = child.state; this._debug && console.log( `${__indent(scopes.length + 1)}end: ${child.id} (${cstate.p})` ); child.state = this._retain ? parent.state.copy() : null; parent.state = cstate; parent.children?.push(child) ?? (parent.children = [child]); this._curr = parent; return true; } addChild(id, result = null, newState = false) { const curr = this._curr; const child = new ParseScope( id, this._retain ? curr.state.copy() : null, null, result ); this._debug && console.log( `${__indent(this._scopes.length + 1)}addChild: ${id} (${curr.state.p})` ); curr.children?.push(child) ?? (curr.children = [child]); if (newState !== false) { newState === true ? this.reader.next(curr.state) : this._curr.state = newState; } return true; } get scope() { return this._curr; } get state() { return this._curr.state; } set state(state) { this._curr.state = state; } get done() { return this._curr.state.done; } /** * Returns root node. */ get root() { return this._scopes[0]; } /** * Returns root node's `result` or `undefined`. */ get result() { const children = this.root.children; return children ? children[0].result : void 0; } /** * Returns root node's children or `undefined`. */ get children() { const children = this.root.children; return children ? children[0].children : void 0; } /** * Returns max. recursion depth which was actually reached. Will always be * less or equal configured {@link ContextOpts.maxDepth}. */ get peakDepth() { return this._peakDepth; } } function defContext(input, opts) { return new ParseContext( isString(input) ? defStringReader(input) : isArrayLike(input) ? defArrayReader(input) : input, opts ); } export { ParseContext, ParseScope, ParseState, defContext };