UNPKG

functionalscript

Version:

FunctionalScript is a purely functional subset of JavaScript

133 lines (132 loc) 3.35 kB
/** * Types for defining language grammar using Backus-Naur Form (BNF). * * Utilities for serializing and deserializing BNF grammar * and creating a simple LL(1) parser. * * See [Backus-Naur form](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form). * * @module * * @example * * ```ts * import type { Rule } from './module.f.ts' * * // Matches 'A-Z', 'a-z', and '0-9' * const grammar: Rule = () => [ * [[65, 90]], * [[97, 122]], * [[48, 57]], * ] * ``` */ import { stringToCodePointList } from "../../text/utf16/module.f.js"; import { map, toArray } from "../../types/list/module.f.js"; import { one } from "../../types/range/module.f.js"; const toTerminalRangeMap = map(one); /** * Converts a string to an array of terminal ranges where each character is a separate range. * * @param s - The input string. * @returns An array of terminal ranges representing each character in the string. * * @example * * ```ts * const ranges = str('abc') // [[97, 97], [98, 98], [99, 99]] * ``` */ export const str = (s) => toArray(toTerminalRangeMap(stringToCodePointList(s))); /** * Converts a single character string to a terminal range. * * @param a - The input character string. * @returns A terminal range representing the character. * * @example * ```ts * const range = cp('A'); // [65, 65] * ``` */ export const cp = (a) => one(a.codePointAt(0)); /** * Converts a two-character string into a terminal range. * * @param ab - The input string of two characters. * @returns A terminal range representing the two characters. * * @throws {number} Throws an error if the input string does not have exactly two code points. * * @example * ```ts * const result = range('AZ'); // [65, 90] * ``` */ export const range = (ab) => { const a = toArray(stringToCodePointList(ab)); if (a.length !== 2) { throw a.length; } // deno-lint-ignore no-explicit-any return a; }; const toOr = (r) => r.map(v => [v]); /** * Convert a sequence of character into `OrRangeSet` * * @param s a set of code points * @returns A set compatible with `Or` */ export const set = (s) => toOr(str(s)); const removeOne = (set, [a, b]) => { let result = []; for (const [a0, b0] of set) { if (a0 < a) { // [a0 // ]a result = [...result, [a0, Math.min(b0, a - 1)]]; } if (b < b0) { // b0] // b[ result = [...result, [Math.max(b + 1, a0), b0]]; } } return result; }; /** * Removes a terminal range from a set of ranges. * * @param range the original range. * @param removeSet the set of ranges to be removed. * @returns The resulting set of ranges after removal. * * @example * * ```ts * const result = remove([65, 90], [cp('C'), cp('W')]) // [A..Z] w/o C and W * ``` */ export const remove = (range, removeSet) => { let result = [range]; for (const r of removeSet) { result = removeOne(result, r); } return toOr(result); }; export const repeat0 = (rule) => { const result = () => [ [], [rule, result], ]; return result; }; export const join0 = (rule, separator) => { const tail = repeat0(() => [[separator, rule]]); const result = () => [ [], [rule, tail], ]; return result; };