import { CutoutTextLines, Text, TextChar, TextLines, cutoffAfterIndentation, cutoutTextLines, textOf } from "./textlines.js"; import { ParseResult, Parser, Result, joinResults } from "./parser.js"; import { nat } from "things"; import { Lexer } from "./lexer.js"; export function emptyP() : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); const result : Result = { type : null, startLine : line, startColumnInclusive : column, endLine : line, endColumnExclusive : column, children : [] }; return { state : state, result : result }; } return parse; } export function failP() : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); return undefined; } return parse; } export function seqP(...parsers : Parser[]) : Parser { if (parsers.length === 0) return emptyP(); if (parsers.length === 1) return parsers[0]; function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); let children : Result[] = []; const startLine = line; const startColumn = column; for (const parser of parsers) { const parsed = parser(state, lines, line, column); if (parsed === undefined) { return undefined; } const result = parsed.result; if (result.type !== undefined) { children.push(result); } state = parsed.state; line = result.endLine; column = result.endColumnExclusive; } const result : Result = { type : null, startLine : startLine, startColumnInclusive : startColumn, endLine : line, endColumnExclusive : column, children : children }; return { state : state, result : result }; } return parse; } export function charP(predicate : (c : TextChar) => boolean) : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); if (line >= lines.lineCount) return undefined; const text = lines.lineAt(line); if (column >= text.count) return undefined; if (!predicate(text.charAt(column))) return undefined; const result : Result = { type : undefined, startLine : line, startColumnInclusive : column, endLine : line, endColumnExclusive : column + 1, children : [] }; return { state : state, result : result }; } return parse; } export function anyCharP() : Parser { return charP(c => true); } export function newlineP() : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); if (line + 1 < lines.lineCount && column === lines.lineAt(line).count) { const result : Result = { type : undefined, startLine : line, startColumnInclusive : column, endLine : line + 1, endColumnExclusive : 0, children : [] }; return { state : state, result : result }; } else return undefined; } return parse; } export function modifyParseResultP(parser : Parser, modify : (lines : TextLines, parse_result : ParseResult) => ParseResult) : Parser { function parse(state : State, lines : TextLines, line : number, column : nat) : ParseResult { return modify(lines, parser(state, lines, line, column)); } return parse; } export function modifyResultP(parser : Parser, modify : (lines : TextLines, state : State, result : Result) => Result | undefined) : Parser { return modifyParseResultP(parser, (lines, parse_result) => { if (parse_result === undefined) return undefined; const result = modify(lines, parse_result.state, parse_result.result) if (result === undefined) return undefined; parse_result.result = result; return parse_result; }); } export function modifyTypeP(parser : Parser, type : (lines : TextLines, state : State, result : Result) => T | null | undefined) : Parser { return modifyResultP(parser, (lines, state, result) => { result.type = type(lines, state, result); return result; }); } export function setTypeP(type : T | null | undefined, parser : Parser) : Parser { return modifyTypeP(parser, (lines, state, result) => type); } export function literalP(text : string | Text, type? : T | null) : Parser { const literal = textOf(text); const parsers = [...literal].map(c => charP(d => c === d)); const parser = seqP(...parsers); return setTypeP(type, parser) } export function literalsP(...texts : (string | Text)[]) : Parser { const parsers : Parser [] = texts.map(t => literalP(t)); return orP(...parsers); } export function orP(...parsers : Parser[]) : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); for (const parser of parsers) { const result = parser(state, lines, line, column); if (result !== undefined) return result; } return undefined; } return parse; } export function optP(...parsers : Parser[]) : Parser { return orP(seqP(...parsers), emptyP()); } export function repP(...parsers : Parser[]) : Parser { const parser = seqP(...parsers); function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); let children : Result[] = []; const startLine = line; const startColumn = column; while (true) { const parsed = parser(state, lines, line, column); if (parsed === undefined) { const result: Result = { type : null, startLine : startLine, startColumnInclusive : startColumn, endLine : line, endColumnExclusive : column, children : children }; return { state : state, result : result }; } state = parsed.state; const result = parsed.result; if (result.type !== undefined) children.push(result); line = result.endLine; column = result.endColumnExclusive; } } return parse; } export function rep1P(...parsers : Parser[]) : Parser { const parser = seqP(...parsers); return seqP(parser, repP(parser)); } export function joinP(elemP : Parser, jointP : Parser) : Parser { return seqP(elemP, repP(jointP, elemP)); } export function lazyP(parser : () => Parser) : Parser { let p : Parser | null = null; function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { if (p === null) p = parser(); return p(state, lines, line, column); } return parse; } export function eofP() : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); if (line === lines.lineCount || (line + 1 === lines.lineCount && column === lines.lineAt(line).count)) { const result : Result = { type : undefined, startLine : line, startColumnInclusive : column, endLine : line, endColumnExclusive : column, children : [] }; return { state : state, result : result}; } else return undefined; } return parse; } export function bolP() : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); if (line < lines.lineCount && column === 0) { const result : Result = { type : undefined, startLine : line, startColumnInclusive : column, endLine : line, endColumnExclusive : column, children : [] }; return { state : state, result : result}; } else return undefined; } return parse; } export function eolP() : Parser { return orP(eofP(), newlineP()); } export function shiftResult(lines : CutoutTextLines, result : Result) { const [startLine, startColumn] = lines.shift(result.startLine, result.startColumnInclusive); const [endLine, endColumn] = lines.shift(result.endLine, result.endColumnExclusive); result.startLine = startLine; result.startColumnInclusive = startColumn; result.endLine = endLine; result.endColumnExclusive = endColumn; for (const child of result.children) { shiftResult(lines, child); } } export function notP(parser : Parser) : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { const parse_result = parser(state, lines, line, column); if (parse_result === undefined) { const result = joinResults([], undefined, [line, column], [line, column]); return { state : state, result : result }; } else { return undefined; } } return parse; } export function lookaheadP(parser : Parser) : Parser { function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { const parse_result = parser(state, lines, line, column); if (parse_result !== undefined) { const result = joinResults([], undefined, [line, column], [line, column]); return { state : state, result : result }; } else { return undefined; } } return parse; } /*const readWhitespaceL : Lexer = parserL(rep1P(literalP(" "))); const readIndentationL : Lexer = parserL(orP(literalP(" "), seqP(literalsP(" ", " ", " "), eofP()))); */ export function sectionP( bulletP : Parser, bodyP : (lines : TextLines, state : State, bullet_result : Result) => Parser, spacesL : Lexer, indentationL : Lexer, afterP : Parser) : Parser { function isIndented(text : Text) : boolean { return spacesL(text, 0) > 0; } function parse(state : State, lines : TextLines, line : nat, column : nat) : ParseResult { lines.assert(line, column); if (column > 0) return undefined; const start : [nat, nat] = [line, column]; const bulletLines = cutoffAfterIndentation(lines, line, isIndented); const bulletParseResult = bulletP(state, bulletLines, line, column); if (bulletParseResult === undefined) return undefined; state = bulletParseResult.state; line = bulletParseResult.result.endLine; column = bulletParseResult.result.endColumnExclusive; const bodyLines = cutoutTextLines(lines, line, column, spacesL, indentationL); const bodyParser = bodyP(lines, state, bulletParseResult.result); const bodyParseResult = bodyParser(state, bodyLines, 0, 0); if (bodyParseResult === undefined) return undefined; const bodyResult = bodyParseResult.result; shiftResult(bodyLines, bodyParseResult.result); line = bodyResult.endLine; column = bodyResult.endColumnExclusive; state = bodyParseResult.state; const after = afterP(state, lines, line, column); const results : Result[] = [bulletParseResult.result, bodyResult]; if (after !== undefined) { results.push(after.result); state = after.state; line = after.result.endLine; column = after.result.endColumnExclusive; } const joinedResult = joinResults(results, null, start, [line, column]); return { state : bodyParseResult.state, result : joinedResult }; } return parse; }