diff --git a/toml/_parser.ts b/toml/_parser.ts index 33994f191617..26c42d2ac7a8 100644 --- a/toml/_parser.ts +++ b/toml/_parser.ts @@ -2,6 +2,7 @@ // This module is browser compatible. import { deepMerge } from "@std/collections/deep-merge"; +import { TOMLArray, TOMLTable, TOMLValue } from "./types"; // --------------------------- // Interfaces and base classes @@ -16,27 +17,27 @@ interface Failure { } type ParseResult = Success | Failure; -type ParserComponent = (scanner: Scanner) => ParseResult; +type ParserComponent = (scanner: Scanner) => ParseResult; type Block = { type: "Block"; - value: Record; + value: TOMLTable; }; type Table = { type: "Table"; keys: string[]; - value: Record; + value: TOMLTable; }; type TableArray = { type: "TableArray"; keys: string[]; - value: Record; + value: TOMLTable; }; export class Scanner { - #whitespace = /[ \t]/; + readonly #whitespace = /[ \t]/; + readonly #source: string; #position = 0; - #source: string; constructor(source: string) { this.#source = source; @@ -131,18 +132,21 @@ export class Scanner { // Utilities // ----------------------- -function success(body: T): Success { +function success(body: T): Success { return { ok: true, body }; } function failure(): Failure { return { ok: false }; } +type DeepStringRecord = {[Key: string]: DeepStringRecord|T}; /** * Creates a nested object from the keys and values. * * e.g. `unflat(["a", "b", "c"], 1)` returns `{ a: { b: { c: 1 } } }` */ +export function unflat(keys: string[], values: T): DeepStringRecord; +export function unflat(keys: string[]): Record; export function unflat( keys: string[], values: unknown = {}, @@ -157,7 +161,7 @@ function isObject(value: unknown): value is Record { return typeof value === "object" && value !== null; } -function getTargetValue(target: Record, keys: string[]) { +function getTargetValue(target: Record, keys: string[]) { const key = keys[0]; if (!key) { throw new Error( @@ -167,8 +171,8 @@ function getTargetValue(target: Record, keys: string[]) { return target[key]; } -function deepAssignTable( - target: Record, +function deepAssignTable>( + target: T, table: Table, ) { const { keys, type, value } = table; @@ -189,8 +193,8 @@ function deepAssignTable( throw new Error("Unexpected assign"); } -function deepAssignTableArray( - target: Record, +function deepAssignTableArray>( + target: T, table: TableArray, ) { const { type, keys, value } = table; @@ -229,25 +233,25 @@ export function deepAssign( // --------------------------------- // deno-lint-ignore no-explicit-any -function or[]>( +function or[]>( parsers: T, ): ParserComponent< ReturnType extends ParseResult ? R : Failure > { - return (scanner: Scanner) => { + return ((scanner: Scanner) => { for (const parse of parsers) { const result = parse(scanner); if (result.ok) return result; } return failure(); - }; + }) as unknown as any; } /** Join the parse results of the given parser into an array. * * If the parser fails at the first attempt, it will return an empty array. */ -function join( +function join( parser: ParserComponent, separator: string, ): ParserComponent { @@ -273,7 +277,7 @@ function join( * * This requires the parser to succeed at least once. */ -function join1( +function join1( parser: ParserComponent, separator: string, ): ParserComponent { @@ -294,13 +298,13 @@ function join1( }; } -function kv( +function kv( keyParser: ParserComponent, separator: string, valueParser: ParserComponent, -): ParserComponent<{ [key: string]: unknown }> { +): ParserComponent { const Separator = character(separator); - return (scanner: Scanner): ParseResult<{ [key: string]: unknown }> => { + return (scanner: Scanner): ParseResult => { const position = scanner.position; const key = keyParser(scanner); if (!key.ok) return failure(); @@ -322,12 +326,12 @@ function kv( } function merge( - parser: ParserComponent, -): ParserComponent> { - return (scanner: Scanner): ParseResult> => { + parser: ParserComponent, +): ParserComponent { + return (scanner: Scanner): ParseResult => { const result = parser(scanner); if (!result.ok) return failure(); - let body = {}; + let body: TOMLTable = {}; for (const record of result.body) { if (typeof record === "object" && record !== null) { body = deepMerge(body, record); @@ -337,7 +341,7 @@ function merge( }; } -function repeat( +function repeat( parser: ParserComponent, ): ParserComponent { return (scanner: Scanner) => { @@ -353,7 +357,7 @@ function repeat( }; } -function surround( +function surround( left: string, parser: ParserComponent, right: string, @@ -446,7 +450,7 @@ export function basicString(scanner: Scanner): ParseResult { scanner.skipWhitespaces(); if (scanner.char() !== '"') return failure(); scanner.next(); - const acc = []; + const acc: string[] = []; while (scanner.char() !== '"' && !scanner.eof()) { if (scanner.char() === "\n") { throw new SyntaxError("Single-line string cannot contain EOL"); @@ -691,13 +695,13 @@ export function localTime(scanner: Scanner): ParseResult { return success(match); } -export function arrayValue(scanner: Scanner): ParseResult { +export function arrayValue(scanner: Scanner): ParseResult { scanner.skipWhitespaces(); if (scanner.char() !== "[") return failure(); scanner.next(); - const array: unknown[] = []; + const array: TOMLValue[] = []; while (!scanner.eof()) { scanner.nextUntilChar(); const result = value(scanner); @@ -718,7 +722,7 @@ export function arrayValue(scanner: Scanner): ParseResult { export function inlineTable( scanner: Scanner, -): ParseResult> { +): ParseResult { scanner.nextUntilChar(); if (scanner.char(1) === "}") { scanner.next(2); @@ -759,7 +763,7 @@ export function block( ): ParseResult { scanner.nextUntilChar(); const result = merge(repeat(pair))(scanner); - if (result.ok) return success({ type: "Block", value: result.body }); + if (result.ok) return success({ type: "Block" as const, value: result.body }); return failure(); } @@ -772,7 +776,7 @@ export function table(scanner: Scanner): ParseResult { scanner.nextUntilChar(); const b = block(scanner); return success({ - type: "Table", + type: "Table" as const, keys: header.body, value: b.ok ? b.body.value : {}, }); @@ -789,7 +793,7 @@ export function tableArray( scanner.nextUntilChar(); const b = block(scanner); return success({ - type: "TableArray", + type: "TableArray" as const, keys: header.body, value: b.ok ? b.body.value : {}, }); @@ -797,10 +801,10 @@ export function tableArray( export function toml( scanner: Scanner, -): ParseResult> { +): ParseResult { const blocks = repeat(or([block, tableArray, table]))(scanner); if (!blocks.ok) return success({}); - const body = blocks.body.reduce(deepAssign, {}); + const body = blocks.body.reduce(deepAssign, {}) as TOMLTable; return success(body); } @@ -812,7 +816,7 @@ function createParseErrorMessage(scanner: Scanner, message: string) { return `Parse error on line ${row}, column ${column}: ${message}`; } -export function parserFactory(parser: ParserComponent) { +export function parserFactory(parser: ParserComponent) { return (tomlString: string): T => { const scanner = new Scanner(tomlString); try { diff --git a/toml/types.ts b/toml/types.ts new file mode 100644 index 000000000000..09363b1db56e --- /dev/null +++ b/toml/types.ts @@ -0,0 +1,4 @@ +export type TOMLTable = {[Key in string]: TOMLValue} & {[Key in string]: TOMLValue|undefined}; +export type TOMLArray = TOMLValue[] | readonly TOMLValue[]; +export type TOMLPrimitive = string | number | boolean | Date; +export type TOMLValue = TOMLPrimitive | TOMLArray | TOMLTable;