import { Token, TokenType, Items, Item, isGroup, isToken, isSpace, isTokenType } from './tokens.js'; import { Pos } from './position.js'; import { List, ArrayList, atEnd, notAtEnd } from './list.js'; //--------------------------------------------------------------------------- // Patterns over Item export type PatternResult = [T, List] | null; export type Pattern = (i: List) => PatternResult; export function match(p: Pattern, items: Items, failure: F): T | F { const r = p(new ArrayList(items)); if (r === null) return failure; if (notAtEnd(r[1])) return failure; return r[0]; } export const noItems = new ArrayList([]); export const fail: Pattern = _i => null; export function succeed(t: T): Pattern { return i => [t, i]; } export const discard: Pattern = _i => [void 0, noItems]; export const rest: Pattern = i => [i.toArray(), noItems]; export const end: Pattern = i => atEnd(skipSpace(i)) ? [void 0, noItems] : null; export const pos: Pattern = i => notAtEnd(i) ? [isGroup(i.item) ? i.item.start.start : i.item.start, i] : null; export const newline: Pattern = i => { while (notAtEnd(i) && isTokenType(i.item, TokenType.SPACE)) i = i.next; if (!notAtEnd(i) || !isTokenType(i.item, TokenType.NEWLINE)) return null; return [i.item, i.next]; }; export function skipSpace(i: List): List { while (notAtEnd(i) && isSpace(i.item)) i = i.next; return i; } export function collectSpace(i: List, acc: Array): List { while (notAtEnd(i) && isSpace(i.item)) { acc.push(i.item); i = i.next; } return i; } export function withoutSpace(p: Pattern): Pattern { return i => p(skipSpace(i)); } export function seq(... patterns: Pattern[]): Pattern { return i => { for (const p of patterns) { const r = p(i); if (r === null) return null; i = r[1]; } return [void 0, i]; }; } export function alt(... alts: Pattern[]): Pattern { return i => { for (const a of alts) { const r = a(i); if (r !== null) return r; } return null; }; } export function scope(pf: (scope: T) => Pattern): Pattern { return i => { const scope = Object.create(null); const r = pf(scope)(i); if (r === null) return null; return [scope, r[1]]; }; } export function value(pf: (scope: {value: T}) => Pattern): Pattern { return i => { const scope = Object.create(null); const r = pf(scope)(i); if (r === null) return null; return [scope.value, r[1]]; }; } export function bind(target: T, key: K, pattern: Pattern): Pattern { return i => { const r = pattern(i); if (r === null) return null; target[key] = r[0]; return r; }; } export function exec(thunk: (i: List) => void): Pattern { return i => { thunk(i); return [void 0, i]; }; } export function map(p: Pattern, f: (t: T) => R): Pattern { return i => { const r = p(i); if (r === null) return null; return [f(r[0]), r[1]]; }; } export function mapm(p: Pattern, f: (t: T) => Pattern): Pattern { return i => { const r = p(i); if (r === null) return null; return f(r[0])(r[1]); }; } export interface ItemOptions { skipSpace?: boolean, // default: true advance?: boolean, // default: true } export interface GroupOptions extends ItemOptions { } export interface TokenOptions extends ItemOptions { tokenType?: TokenType, // default: TokenType.ATOM } export function group(opener: string, items: Pattern, options: GroupOptions = {}): Pattern { return i => { if (options.skipSpace ?? true) i = skipSpace(i); if (!notAtEnd(i)) return null; if (!isGroup(i.item)) return null; if (i.item.start.text !== opener) return null; const r = items(new ArrayList(i.item.items)); if (r === null) return null; if (!atEnd(r[1])) return null; return [r[0], (options.advance ?? true) ? i.next : i]; }; } export function atomString(text: T, options: TokenOptions = {}): Pattern { return map(atom(text, options), t => text); } export function atom(text?: string, options: TokenOptions = {}): Pattern { return i => { if (options.skipSpace ?? true) i = skipSpace(i); if (!notAtEnd(i)) return null; if (!isToken(i.item)) return null; if (i.item.type !== (options.tokenType ?? TokenType.ATOM)) return null; if (text !== void 0 && i.item.text !== text) return null; return [i.item, (options.advance ?? true) ? i.next : i]; } } export function anything(options: ItemOptions = {}): Pattern { return i => { if (options.skipSpace ?? true) i = skipSpace(i); if (!notAtEnd(i)) return null; return [i.item, (options.advance ?? true) ? i.next : i]; }; } export function upTo(p: Pattern): Pattern { return i => { const acc = []; while (true) { const r = p(i); if (r !== null) return [acc, i]; if (!notAtEnd(i)) break; acc.push(i.item); i = i.next; } return null; }; } export function separatedBy(itemPattern: Pattern, separator: Pattern): Pattern { return i => { const acc: T[] = []; if (end(i) !== null) return [acc, noItems]; while (true) { { const r = itemPattern(i); if (r === null) return null; acc.push(r[0]); i = r[1]; } { const r = separator(i); if (r === null) { if (end(i) !== null) return [acc, noItems]; return null; } i = r[1]; } } }; } export interface RepeatOptions { min?: number; max?: number; separator?: Pattern; } export function repeat(p: Pattern, options: RepeatOptions = {}): Pattern { return i => { const acc: T[] = []; let needSeparator = false; const finish = (): PatternResult => (acc.length < (options.min ?? 0)) ? null : [acc, i]; while (true) { if (acc.length == (options.max ?? Infinity)) return [acc, i]; if (needSeparator) { if (options.separator) { const r = options.separator(i); if (r === null) return finish(); i = r[1]; } } else { needSeparator = true; } const r = p(i); if (r === null) return finish(); acc.push(r[0]); i = r[1]; } }; } export function option(p: Pattern): Pattern { return repeat(p, { max: 1 }); } //--------------------------------------------------------------------------- // Search-and-replace over Item export function replace(items: Items, p: Pattern, f: (t: T) => Items): Items { const walkItems = (items: Items): Items => { let i: List = new ArrayList(items); const acc: Items = []; while (notAtEnd(i = collectSpace(i, acc))) { const r = p(i); if (r !== null) { acc.push(... f(r[0])); i = r[1]; } else if (isToken(i.item)) { acc.push(i.item); i = i.next; } else { acc.push({ ... i.item, items: walkItems(i.item.items) }); i = i.next; } } return acc; }; return walkItems(items); }