const ABORT_SEQUENCE = Symbol('ABORT_SEQUENCE'); export class Abstract_Item_Condition { match(sequence, context={}) { return this.match_value(sequence[context.start_index ?? 0], context); } } export class Abstract_Sequence_Condition { } export class Match { constructor(rule, value, context) { Object.assign(this, { rule, value, ...context }); } } export class Predicate extends Abstract_Item_Condition { constructor(predicate) { super(); Object.assign(this, { predicate }); } match_value(item, context={}) { if (this.predicate(item, context)) { const si = context.start_index ?? 0; return new Match(this, item, { ...context, match_start: si, match_end: si }); }; } } export class Type_Is extends Abstract_Item_Condition { constructor(type) { super(); Object.assign(this, { type }); } match_value(item, context={}) { if (typeof item === this.type) { const si = context.start_index ?? 0; return new Match(this, item, { ...context, match_start: si, match_end: si }); }; } } export class Strict_Equality extends Abstract_Item_Condition { constructor(value) { super(); Object.assign(this, { value }); } match_value(item, context={}) { if (item === this.value) { const si = context.start_index ?? 0; return new Match(this, item, { ...context, match_start: si, match_end: si }); } } } export class Sequence_Condition extends Abstract_Sequence_Condition { constructor(sequence) { super(); Object.assign(this, { sequence }); } match(sequence, context={}) { //TODO: For anchors we need anchor_start_index and anchor_end_index (compare with regexp ^ and $) const start_index = context.start_index ?? 0; const end_index = context.end_index ?? sequence.length - 1; const match_from = (sequence_index, pattern_index=0) => { if (pattern_index === this.sequence.length) { return []; } //TODO - There are plenty of optimizations to be implemented here but we must be suer they are correct - we will start naively const sub_condition = this.sequence[pattern_index]; const sub_match = sub_condition.match(sequence, { ...context, start_index: sequence_index }); //console.log('match_from', {sequence_index, pattern_index, sub_condition, sub_match}) if (sub_match) { const remaining = match_from(sub_match.match_end + 1, pattern_index + 1); if (remaining) { return [sub_match, ...remaining]; } } } for (let i=start_index; i<=end_index; i++) { const m = match_from(i); if (m) { // NOTE: If result is empty array which can be a positive match, match_start and match_end will be undefined which is by design return new Match(this, m, { match_start: m.at(0)?.match_start, match_end: m.at(-1)?.match_end, ...context}); } } } }