diff --git a/Monkey/Ast/Ast.lean b/Monkey/Ast/Ast.lean index e3d6139..de8f708 100644 --- a/Monkey/Ast/Ast.lean +++ b/Monkey/Ast/Ast.lean @@ -42,6 +42,7 @@ inductive Statement where deriving Repr, DecidableEq +/-- Statement を文字列に変換する -/ def Statement.toString (stmt: Statement) : String := match stmt with | .letStmt name value => s!"let {name} = {value}" diff --git a/Monkey/Parser/Parser.lean b/Monkey/Parser/Parser.lean index fb2f894..af7612c 100644 --- a/Monkey/Parser/Parser.lean +++ b/Monkey/Parser/Parser.lean @@ -13,6 +13,9 @@ structure Parser where /-- 次のトークン -/ peekToken : Token + /-- 構文解析エラー -/ + errors : List String + /-- Parser を文字列に変換する -/ def Parser.toString (p : Parser) : String := s!"⟨curToken={p.curToken}, peekToken={p.peekToken}⟩ : Parser" @@ -24,7 +27,12 @@ instance : ToString Parser where def Parser.nextToken : StateM Parser PUnit := do let p ← get let ⟨newToken, newLexer⟩ := p.l.nextToken - let newParser := Parser.mk newLexer p.peekToken newToken + let newParser : Parser := { + l := newLexer, + curToken := p.peekToken, + peekToken := newToken, + errors := p.errors + } set newParser /-- 新しくパーサを作る -/ @@ -32,7 +40,7 @@ def Parser.new (l : Lexer) : Parser := -- Id モナドは無言で取り出せる let (curToken, l') := l.nextToken let (peekToken, l'') := l'.nextToken - { l := l'', curToken, peekToken } + { l := l'', curToken, peekToken, errors := []} /-- p の curToken が指定されたトークンと種類が一致するか -/ def Parser.curTokenIs (p : Parser) (t : Token) : Bool := @@ -42,14 +50,22 @@ def Parser.curTokenIs (p : Parser) (t : Token) : Bool := def Parser.peekTokenIs (p : Parser) (t : Token) : Bool := Token.sameType p.peekToken t +/-- `expectPeek` の過程でエラーが起きたときのために、 +エラーメッセージを蓄積する処理 -/ +def Parser.peekError (expectedToken : String) : StateM Parser Unit := do + let p ← get + set { p with errors := p.errors ++ [s!"expected next token to be {expectedToken}, got {p.peekToken} instead"] } + open Lean Parser Term in /-- p の peekToken が指定されたトークン `pat` と種類が一致するか判定。一致した場合は次に進める -/ macro "expectPeek " pat:term rest:doSeqItem* : doElem => do + let pat' : Lean.StrLit := pat.raw.getSubstring?.get! |> toString |> Lean.Syntax.mkStrLit `(doElem| do - let $pat := (← get).peekToken - | return none - nextToken - $rest* + let $pat := (← get).peekToken + | Parser.peekError $pat' + return none + nextToken + $rest* ) /-- let 文をパースする -/ @@ -60,7 +76,6 @@ def Parser.parseLetStatement : StateM Parser (Option Statement) := do while ! (← get).curTokenIs (Token.SEMICOLON) do nextToken - -- let ident := Token.IDENT name return Statement.letStmt name Expression.notImplemented /-- 一文をパースする -/ diff --git a/Monkey/Parser/ParserTest.lean b/Monkey/Parser/ParserTest.lean index a5acea7..a1477ff 100644 --- a/Monkey/Parser/ParserTest.lean +++ b/Monkey/Parser/ParserTest.lean @@ -22,6 +22,16 @@ def testLetStatement (stmt : Statement) (expectedId : String) : IO Bool := do return true +/-- Parser にエラーが一つでもあれば全部出力する -/ +def checkParserErrors (p : Parser) : IO Unit := do + if p.errors.isEmpty then + return + + for err in p.errors do + IO.eprintln err + + throw <| .userError "parser has errors" + /-- 具体的な `let` 文に対する parser のテスト -/ def testLetStatements : IO Unit := do let input := " @@ -32,10 +42,10 @@ def testLetStatements : IO Unit := do let l := Lexer.new input let p := Parser.new l - -- none だったときの処理を簡潔に書くことができる - let some program := p.parseProgram |>.fst - | throw <| .userError s!"ParseProgram returned none" + let ⟨result, parser⟩ := p.parseProgram + checkParserErrors parser + let some program := result | IO.eprintln s!"ParseProgram returned none" IO.println s!"given program={program}" -- 入力の文はちょうど3つのはず @@ -50,4 +60,13 @@ def testLetStatements : IO Unit := do IO.println "ok!" +/- ## TODO: +入力として以下のように複数のエラーが起こる文を与えたとき、 +``` + let input := " + let x 5; + let = 10; + let foobar = 838383;" +``` +エラーメッセージがなぜ一度しか表示されないのか?-/ #eval testLetStatements