diff --git a/src/Parser.res b/src/Parser.res index 86d2bba..a82b167 100644 --- a/src/Parser.res +++ b/src/Parser.res @@ -4,17 +4,17 @@ type sourceloc = { idx: int, } -type parserError<'info> = { +type err<'info> = { message: 'info, pos: sourceloc, } -type parserState = {pos: sourceloc} +type state = {pos: sourceloc} -type t<'a> = (string, parserState) => result<('a, parserState), parserError> +type t<'a> = (string, state) => result<('a, state), err> let initialState = {pos: {line: 1, col: 1, idx: 0}} -let runParser = (p: t<'a>, str: string): result<('a, string), parserError> => { +let runParser = (p: t<'a>, str: string): result<('a, string), err> => { p(str, initialState)->Result.map(((res, state)) => ( res, str->String.sliceToEnd(~start=state.pos.idx), @@ -27,20 +27,30 @@ let pure = (a): t<'a> => (_, state) => Ok((a, state)) let bind = (p1: t<'a>, p2: 'a => t<'b>): t<'b> => (str, state) => p1(str, state)->Result.flatMap(((res, state)) => p2(res)(str, state)) let apply = (p: t<'a>, pf: t<'a => 'b>): t<'b> => p->bind(a => pf->map(f => f(a))) - let then = (p1: t<'a>, p2: t<'b>): t<'b> => p1->bind(_ => p2) let thenIgnore = (p1: t<'a>, p2: t<'b>): t<'a> => p1->bind(res => p2->map(_ => res)) + +let fail = (info): t<'a> => (_, state) => Error({message: info, pos: state.pos}) +let void = (p: t<'a>): t => p->map(_ => ()) +// backtracks by default +let optional = (p: t<'a>): t> => (str, state) => { + switch p(str, state) { + | Ok((res, state)) => Ok((Some(res), state)) + | Error(_) => Ok((None, state)) + } +} let or = (p1: t<'a>, p2: t<'a>): t<'a> => (str, state) => switch p1(str, state) { | Ok(r) => Ok(r) | Error(_) => p2(str, state) } -let fail = (info): t<'a> => (_, state) => Error({message: info, pos: state.pos}) -let void = (p: t<'a>): t => p->map(_ => ()) +let choice = (ps: array>): t<'a> => { + ps->Array.reduce(fail("no matches"), or) +} -let getState: t = (_, state) => Ok((state, state)) -let setState = (newState: parserState): t => (_, _) => Ok(((), newState)) -let modifyState = (f: parserState => parserState) => getState->bind(state => setState(f(state))) +let getState: t = (_, state) => Ok((state, state)) +let setState = (newState: state): t => (_, _) => Ok(((), newState)) +let modifyState = (f: state => state) => getState->bind(state => setState(f(state))) let readStr: t = (str, state) => Ok((str, state)) let getCurrentStr: t = (str, state) => Ok(( str->String.sliceToEnd(~start=state.pos.idx), @@ -49,14 +59,25 @@ let getCurrentStr: t = (str, state) => Ok(( let eof: t = getState->bind(state => readStr->map(str => state.pos.idx >= str->String.length)) -// fixpoints are necessary because rescript compiler complains very ambiguously -// about not knowing the size of recursive combinators +// fixpoints using references as a layer of indirection are necessary because +// the compiler complains very ambiguously about not knowing the size of +// (directly) recursive combinators let fix = (f: t<'a> => t<'a>): t<'a> => { let pRef = ref(fail("umm")) pRef := f((str, state) => pRef.contents(str, state)) pRef.contents } +let many = p => + fix(f => + optional(p)->bind(o => + switch o { + | Some(res) => f->map(l => l->List.add(res)) + | None => pure(list{}) + } + ) + )->map(List.toArray) +let between = (inner: t<'a>, o1: t, o2: t): t<'a> => o1->then(inner)->thenIgnore(o2) let consume = (l: int): t => getCurrentStr->bind(str => { if str->String.length < l { @@ -92,7 +113,7 @@ let string = s => if str->String.startsWith(s) { consume(String.length(s))->then(pure(s)) } else { - fail("doesn't start with string") + fail(`${str->String.slice(~start=0, ~end=10)} doesn't start with string ${s}`) } ) @@ -146,5 +167,25 @@ let takeWhile = (f: string => bool) => } ) +let dbg = (p: t<'a>, ~label): t<'a> => { + let dbgInfo = title => + getCurrentStr->bind(str => + getState->map(state => { + let currSurr = `${str->String.slice(~start=0, ~end=10)}...` + let stateStr = `${state.pos.line->Int.toString}:${state.pos.col->Int.toString}` + Console.log(`${title} ${label} at:\n${currSurr}\nstate: ${stateStr}`) + }) + ) + dbgInfo("enter") + ->then(p) + ->bind(res => { + Console.log(("successfully parsed", res)) + dbgInfo("exit")->map(_ => res) + }) +} + let lexeme = p => p->thenIgnore(regex(%re(`/^\s*/`))->void) let token = s => string(s)->lexeme + +let decimal = regex1(%re(`/(\d+)/`))->map(xStr => xStr->Int.fromString->Option.getExn) +let whitespace = regex(%re(`/\s*/`))->void diff --git a/src/Rule.res b/src/Rule.res index 4378692..e9dbeeb 100644 --- a/src/Rule.res +++ b/src/Rule.res @@ -2,7 +2,7 @@ open Signatures // the tree sitter plugin hates backslashes in string literals unless they're on the top // level.. let ruleNamePattern = "^[^|()\\s\\-—][^()\\s]*" -let vinculumRES = "^\s*\\n\\s*[-—][-—][\\-—]+[ \t]*([^()|\\s\\-—][^()\\s]*)?" +let vinculumRES = "^\\n?\\s*[-—][-—][\\-—]+[ \t]*([^()|\\s\\-—][^()\\s]*)?" module Make = (Term: TERM, Judgment: JUDGMENT with module Term := Term) => { type rec t = {vars: array, premises: array, conclusion: Judgment.t} let rec substitute = (rule: t, subst: Term.subst) => { diff --git a/tests/ParserTests.res b/tests/ParserTests.res index 06b1efb..4d7c397 100644 --- a/tests/ParserTests.res +++ b/tests/ParserTests.res @@ -10,10 +10,14 @@ let testParse = (t: Zora.t, p, str, ~expect=?) => { } zoraBlock("parse", t => { + open Parser t->testParse(P.string("a"), "a", ~expect="a") t->testParse(P.token("a"), "a \n\r", ~expect="a") t->testParse(P.string("a")->P.or(P.string("b")), "b", ~expect="b") let abs = P.takeWhile(s => s->String.startsWith("a"))->P.bind(res => P.string("b")->P.map(b => (res, b))) t->testParse(abs, "aaaab", ~expect=("aaaa", "b")) + let brackets = many(lexeme(decimal))->between(token("("), token(")")) + t->testParse(brackets, "(1)", ~expect=[1]) + t->testParse(brackets, "(1 2 3)", ~expect=[1, 2, 3]) })