2024-05-24 10:49:56 +00:00
|
|
|
import { INVALID, Parser, UNRECOGNIZED, VALUE } from '../parser.js';
|
2024-04-13 00:53:44 +00:00
|
|
|
|
2024-05-24 10:49:56 +00:00
|
|
|
/**
|
|
|
|
* Parses a literal value.
|
|
|
|
* @param value The value to parse
|
|
|
|
*/
|
2024-04-13 00:53:44 +00:00
|
|
|
export class Literal extends Parser {
|
|
|
|
_create (value) {
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
_parse (stream) {
|
|
|
|
const subStream = stream.fork();
|
|
|
|
for ( let i=0 ; i < this.value.length ; i++ ) {
|
|
|
|
let { done, value } = subStream.next();
|
|
|
|
if ( done ) return UNRECOGNIZED;
|
|
|
|
if ( this.value[i] !== value ) return UNRECOGNIZED;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream.join(subStream);
|
|
|
|
return { status: VALUE, $: 'literal', value: this.value };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-24 10:49:56 +00:00
|
|
|
/**
|
2024-05-24 14:57:59 +00:00
|
|
|
* Parses matching characters as a string.
|
|
|
|
* @param test Function that takes a character, and returns whether to include it.
|
2024-05-24 10:49:56 +00:00
|
|
|
*/
|
2024-04-13 00:53:44 +00:00
|
|
|
export class StringOf extends Parser {
|
2024-05-24 14:57:59 +00:00
|
|
|
_create (test) {
|
|
|
|
this.test = test;
|
2024-04-13 00:53:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_parse (stream) {
|
|
|
|
const subStream = stream.fork();
|
|
|
|
let text = '';
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
let { done, value } = subStream.look();
|
|
|
|
if ( done ) break;
|
2024-05-24 14:57:59 +00:00
|
|
|
if ( ! this.test(value) ) break;
|
2024-04-13 00:53:44 +00:00
|
|
|
|
|
|
|
subStream.next();
|
|
|
|
text += value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (text.length === 0) {
|
|
|
|
return UNRECOGNIZED;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream.join(subStream);
|
|
|
|
return { status: VALUE, $: 'stringOf', value: text };
|
|
|
|
}
|
2024-05-24 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
2024-05-31 14:47:02 +00:00
|
|
|
/**
|
|
|
|
* Parses characters into a string, until it encounters the given character, unescaped.
|
|
|
|
* @param testOrCharacter End of the string. Either a character, or a function that takes a character,
|
|
|
|
* and returns whether it ends the string.
|
|
|
|
* @param escapeCharacter Character to use as the escape character. By default, is '\'.
|
|
|
|
*/
|
|
|
|
export class StringUntil extends Parser {
|
|
|
|
_create(testOrCharacter, { escapeCharacter = '\\' } = {}) {
|
|
|
|
if (typeof testOrCharacter === 'string') {
|
|
|
|
this.test = (c => c === testOrCharacter);
|
|
|
|
} else {
|
|
|
|
this.test = testOrCharacter;
|
|
|
|
}
|
|
|
|
this.escapeCharacter = escapeCharacter;
|
|
|
|
}
|
|
|
|
|
|
|
|
_parse(stream) {
|
|
|
|
const subStream = stream.fork();
|
|
|
|
let text = '';
|
|
|
|
let lastWasEscape = false;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
let { done, value } = subStream.look();
|
|
|
|
if ( done ) break;
|
|
|
|
if ( !lastWasEscape && this.test(value) )
|
|
|
|
break;
|
|
|
|
|
|
|
|
subStream.next();
|
|
|
|
if (value === this.escapeCharacter) {
|
|
|
|
lastWasEscape = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
lastWasEscape = false;
|
|
|
|
text += value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastWasEscape)
|
|
|
|
return INVALID;
|
|
|
|
|
|
|
|
if (text.length === 0)
|
|
|
|
return UNRECOGNIZED;
|
|
|
|
|
|
|
|
stream.join(subStream);
|
|
|
|
return { status: VALUE, $: 'stringUntil', value: text };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-24 10:49:56 +00:00
|
|
|
/**
|
|
|
|
* Parses an object defined by the symbol registry.
|
|
|
|
* @param symbolName The name of the symbol to parse.
|
|
|
|
*/
|
|
|
|
export class Symbol extends Parser {
|
|
|
|
_create(symbolName) {
|
|
|
|
this.symbolName = symbolName;
|
|
|
|
}
|
|
|
|
|
|
|
|
_parse (stream) {
|
|
|
|
const parser = this.symbol_registry[this.symbolName];
|
|
|
|
if ( ! parser ) {
|
|
|
|
throw new Error(`No symbol defined named '${this.symbolName}'`);
|
|
|
|
}
|
|
|
|
const subStream = stream.fork();
|
|
|
|
const result = parser.parse(subStream);
|
|
|
|
if ( result.status === UNRECOGNIZED ) {
|
|
|
|
return UNRECOGNIZED;
|
|
|
|
}
|
|
|
|
if ( result.status === INVALID ) {
|
|
|
|
return { status: INVALID, value: result };
|
|
|
|
}
|
|
|
|
stream.join(subStream);
|
|
|
|
result.$ = this.symbolName;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Does no parsing and returns a discarded result.
|
|
|
|
*/
|
|
|
|
export class None extends Parser {
|
|
|
|
_create () {}
|
|
|
|
|
|
|
|
_parse (stream) {
|
|
|
|
return { status: VALUE, $: 'none', $discard: true };
|
|
|
|
}
|
|
|
|
}
|
2024-05-31 09:00:09 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Always fails parsing.
|
|
|
|
*/
|
|
|
|
export class Fail extends Parser {
|
|
|
|
_create () {}
|
|
|
|
|
|
|
|
_parse (stream) {
|
|
|
|
return UNRECOGNIZED;
|
|
|
|
}
|
|
|
|
}
|