the next generation of the in-browser educational proof assistant
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve parsing error messages with expects clause

+52 -26
+46 -19
src/Parser.res
··· 4 4 idx: int, 5 5 } 6 6 7 - type err<'info> = { 8 - message: 'info, 7 + type info = { 8 + msg?: string, 9 + expects: array<string>, 10 + } 11 + 12 + type err = { 13 + info: info, 9 14 pos: sourceloc, 10 15 } 11 16 12 17 type state = {pos: sourceloc} 13 18 14 - type t<'a> = (string, state) => result<('a, state), err<string>> 19 + type t<'a> = (string, state) => result<('a, state), err> 15 20 21 + let infoPretty = (info: info) => { 22 + let expectedMsg = switch info.expects { 23 + | [] => None 24 + | [expect] => Some(`expected ${expect}`) 25 + | _ => { 26 + let n = Array.length(info.expects) 27 + let first = info.expects->Array.slice(~start=0, ~end=n - 1)->Array.join(", ") 28 + Some(`expected ${first}, or ${info.expects[n - 1]->Option.getUnsafe}`) 29 + } 30 + } 31 + switch (info.msg, expectedMsg) { 32 + | (Some(infoMsg), Some(expectedMsg)) => `parse failed: ${infoMsg}\n ${expectedMsg}` 33 + | (None, Some(msg)) | (Some(msg), None) => `parse failed: ${msg}` 34 + | (None, None) => `parse failed` 35 + } 36 + } 16 37 let initialState = {pos: {line: 1, col: 1, idx: 0}} 17 - let runParser = (p: t<'a>, str: string): result<('a, string), err<string>> => { 18 - p(str, initialState)->Result.map(((res, state)) => ( 19 - res, 20 - str->String.sliceToEnd(~start=state.pos.idx), 21 - )) 38 + let runParser = (p: t<'a>, str: string): result<('a, string), string> => { 39 + p(str, initialState) 40 + ->Result.map(((res, state)) => (res, str->String.sliceToEnd(~start=state.pos.idx))) 41 + ->Result.mapError(err => { 42 + `around ${str->String.slice(~start=err.pos.idx, ~end=err.pos.idx + 5)}:\n${infoPretty( 43 + err.info, 44 + )}` 45 + }) 22 46 } 23 47 24 48 let map = (p: t<'a>, f: 'a => 'b): t<'b> => 25 49 (str, state) => p(str, state)->Result.map(((res, state)) => (f(res), state)) 50 + let mapError = (p: t<'a>, f: err => err) => (str, state) => p(str, state)->Result.mapError(f) 51 + let label = (p: t<'a>, label: string) => 52 + p->mapError(err => {...err, info: {...err.info, expects: [label]}}) 26 53 let pure = (a): t<'a> => (_, state) => Ok((a, state)) 27 54 let bind = (p1: t<'a>, p2: 'a => t<'b>): t<'b> => 28 55 (str, state) => p1(str, state)->Result.flatMap(((res, state)) => p2(res)(str, state)) ··· 30 57 let then = (p1: t<'a>, p2: t<'b>): t<'b> => p1->bind(_ => p2) 31 58 let thenIgnore = (p1: t<'a>, p2: t<'b>): t<'a> => p1->bind(res => p2->map(_ => res)) 32 59 33 - let fail = (info): t<'a> => (_, state) => Error({message: info, pos: state.pos}) 60 + let fail = (info): t<'a> => (_, state) => Error({info: {msg: info, expects: []}, pos: state.pos}) 61 + let expected = (expects: array<string>, ~msg=?): t<'a> => 62 + (_, state) => Error({info: {?msg, expects}, pos: state.pos}) 34 63 let void = (p: t<'a>): t<unit> => p->map(_ => ()) 35 64 // backtracks by default 36 - let optional = (p: t<'a>): t<option<'a>> => 37 - (str, state) => { 38 - switch p(str, state) { 39 - | Ok((res, state)) => Ok((Some(res), state)) 40 - | Error(_) => Ok((None, state)) 41 - } 42 - } 43 65 let or = (p1: t<'a>, p2: t<'a>): t<'a> => 44 66 (str, state) => 45 67 switch p1(str, state) { 46 68 | Ok(r) => Ok(r) 47 - | Error(_) => p2(str, state) 69 + | Error(e1) => 70 + p2(str, state)->Result.mapError(e2 => { 71 + ...e2, 72 + info: {expects: Array.concat(e1.info.expects, e2.info.expects)}, 73 + }) 48 74 } 75 + let optional = (p: t<'a>): t<option<'a>> => p->map(a => Some(a))->or(pure(None)) 49 76 let choice = (ps: array<t<'a>>): t<'a> => { 50 77 ps->Array.reduce(fail("no matches"), or) 51 78 } ··· 115 142 if str->String.startsWith(s) { 116 143 consume(String.length(s))->then(pure(s)) 117 144 } else { 118 - fail(`${str->String.slice(~start=0, ~end=10)} doesn't start with string ${s}`) 145 + expected([`literal ${s}`]) 119 146 } 120 147 ) 121 148 ··· 131 158 getCurrentStr->bind(str => 132 159 switch execRe(wrapped, str) { 133 160 | Some((matches, l)) => consume(l)->then(pure(matches)) 134 - | None => fail("regex failed") 161 + | None => expected([`regex pattern ${re->RegExp.source}`]) 135 162 } 136 163 ) 137 164 }
+6 -7
src/SExp.res
··· 295 295 ) 296 296 let inner = fix(f => 297 297 choice([ 298 - schemaLit, 299 - var, 300 - liftParse(Atom.parse, ~scope, ~gen?)->map(a => Atom(a)), 298 + schemaLit->label("schematic"), 299 + var->label("variable"), 300 + liftParse(Atom.parse, ~scope, ~gen?)->map(a => Atom(a))->label("atom"), 301 301 many(f) 302 - ->between(token("("), token(")")) 302 + ->between(token("(")->label("open expression"), token(")")->label("close expression")) 303 303 ->map(subexps => Compound({subexps: subexps})), 304 304 ])->lexeme 305 305 ) 306 306 whitespace->then(inner) 307 307 } 308 308 309 - let parse = (str: string, ~scope: array<string>, ~gen=?) => { 310 - Parser.runParser(mkParser(~scope, ~gen?), str)->Result.mapError(e => e.message) 311 - } 309 + let parse = (str: string, ~scope: array<string>, ~gen=?) => 310 + Parser.runParser(mkParser(~scope, ~gen?), str) 312 311 313 312 let rec concrete = t => 314 313 switch t {