parjs
Version:
A parser-combinator library for JavaScript.
139 lines (119 loc) • 3.99 kB
text/typescript
/**
* @module parjs/internal
*/
/** */
import {
ParjsFailure,
ParjsResult,
ResultKind,
ParjsSuccess,
Trace,
ErrorLocation
} from "./result";
import {BasicParsingState, FAIL_RESULT, ParsingState, UNINITIALIZED_RESULT} from "./state";
import defaults from "lodash/defaults";
import {ParserDefinitionError} from "../errors";
import {Parjser} from "./parjser";
import {pipe} from "./combinators/combinator";
function getErrorLocation(ps: ParsingState) {
let endln = /\r\n|\n|\r/g;
let {input, position} = ps;
let lastPos = 0;
let result: RegExpMatchArray | null;
let line = 0;
while ((result = endln.exec(ps.input))) {
if (result.index! > position) break;
lastPos = result.index! + result[0].length;
line++;
}
return {
line,
column: line === 0 ? position : position - lastPos
} as ErrorLocation;
}
/**
* A marker class used for storing the parser's user state.
*/
export class ParserUserState {
}
/**
* The base Parjs parser class, which supports only basic parsing operations. Should not be used in user code.
*/
export abstract class ParjserBase implements Parjser<any>{
abstract type: string;
abstract expecting: string | object;
/**
* Apply the parser to the given state.
* @param ps The parsing state.
*/
apply(ps: ParsingState): void {
let {position, userState} = ps;
// we do this to verify that the ParsingState's fields have been correctly set by the parser.
ps.kind = ResultKind.Unknown;
ps.reason = undefined as any;
ps.value = UNINITIALIZED_RESULT;
this._apply(ps);
if (ps.kind === ResultKind.Unknown) {
throw new ParserDefinitionError(this.type, "the parser's result kind field has not been set.");
}
if (!ps.isOk) {
ps.value = FAIL_RESULT;
ps.reason = ps.reason || this.expecting;
} else if (ps.value === UNINITIALIZED_RESULT) {
throw new ParserDefinitionError(this.type, "a parser must set the result's value field if it succeeds.");
}
if (!ps.isOk) {
if (ps.reason == null) {
throw new ParserDefinitionError(this.type, "a failure must have a reason");
}
ps.stack.push(this);
} else {
ps.stack = [];
}
}
/**
* The internal operation performed by the PARSER. This will be overriden by derived classes.
* @param ps
* @private
*/
abstract _apply(ps: ParsingState): void | void;
parse(input: string, initialState ?: any): ParjsResult<any> {
if (typeof input !== "string") {
// catches input === undefined, null
throw new Error("input must be a valid string");
}
let userState = defaults(new ParserUserState(), initialState);
let ps = new BasicParsingState(input, userState);
ps.initialUserState = initialState;
this.apply(ps);
if (ps.isOk) {
if (ps.position !== input.length) {
ps.kind = ResultKind.SoftFail;
ps.reason = "parsers did not consume all input";
}
}
if (ps.kind === ResultKind.Unknown) {
throw new Error("should not happen.");
}
let ret: ParjsResult<any>;
if (ps.kind === ResultKind.Ok) {
return new ParjsSuccess(ps.value);
} else {
let trace: Trace = {
userState: ps.userState,
position: ps.position,
reason: ps.reason,
input,
get location() {
return getErrorLocation(ps);
},
stackTrace: ps.stack,
kind: ps.kind
};
return new ParjsFailure(trace);
}
}
pipe(...funcs: ((x: any) => any)[]) {
return (pipe as any)(this, ...funcs);
}
}