functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
133 lines (132 loc) • 3.35 kB
JavaScript
/**
* 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;
};