/- Copyright (c) 2018 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Sebastian Ullrich Module-Level parsers -/ prelude import init.lean.parser.command namespace Lean namespace Parser open Combinators MonadParsec open Parser.HasTokens Parser.HasView local postfix `?`:10000 := optional local postfix *:10000 := Combinators.many local postfix +:10000 := Combinators.many1 structure ModuleParserConfig extends CommandParserConfig := (commandParsers : TokenMap commandParser) instance moduleParserConfigCoe : HasCoe ModuleParserConfig CommandParserConfig := ⟨ModuleParserConfig.toCommandParserConfig⟩ section @[derive Monad Alternative MonadReader MonadState MonadParsec MonadExcept] def ModuleParserM := StateT ParserState $ ParserT ModuleParserConfig id abbrev moduleParser := ModuleParserM Syntax end instance ModuleParserM.liftParserT (ρ : Type) [HasLiftT ModuleParserConfig ρ] : HasMonadLift (ParserT ρ id) ModuleParserM := { monadLift := λ α x st cfg, (λ a, (a, st)) <$> x.run ↑cfg } section local attribute [reducible] BasicParserM instance ModuleParserM.BasicParserM (ρ : Type) [HasLiftT ModuleParserConfig ρ] : HasMonadLift BasicParserM ModuleParserM := inferInstance end namespace Module @[derive Parser.HasView Parser.HasTokens] def prelude.Parser : basicParser := node! «prelude» ["prelude"] @[derive Parser.HasView Parser.HasTokens] def importPath.Parser : basicParser := -- use `raw` to ignore registered tokens like ".." node! importPath [ dirups: (rawStr ".")*, Module: ident.Parser] @[derive Parser.HasView Parser.HasTokens] def import.Parser : basicParser := node! «import» ["import", imports: importPath.Parser+] @[derive Parser.HasView Parser.HasTokens] def header.Parser : basicParser := node! «header» [«prelude»: prelude.Parser?, imports: import.Parser*] @[pattern] def eoi : SyntaxNodeKind := ⟨`Lean.Parser.Module.eoi⟩ def eoi.Parser : moduleParser := do MonadParsec.eoi, it ← leftOver, -- add `eoi` Node for left-over input let stop := it.toEnd, pure $ Syntax.mkNode eoi [Syntax.atom ⟨some ⟨⟨stop, stop⟩, stop.offset, ⟨stop, stop⟩⟩, ""⟩] /-- Read command, recovering from errors inside commands (attach partial Syntax tree) as well as unknown commands (skip input). -/ private def commandWrecAux : Bool → Nat → ModuleParserM (Bool × Syntax) | recovering 0 := error "unreachable" | recovering (Nat.succ n) := do -- terminate at EOF Nat.succ _ ← remaining | (Prod.mk false) <$> eoi.Parser, (recovering, c) ← catch (do { cfg ← read, c ← monadLift $ command.Parser.run cfg.commandParsers, pure (false, some c) } <|> do { -- unknown command: try to skip token, or else single character when (¬ recovering) $ do { it ← leftOver, logMessage {expected := DList.singleton "command", it := it, custom := some ()} }, try (monadLift token *> pure ()) <|> (any *> pure ()), pure (true, none) }) $ λ msg, do { -- error inside command: log error, return partial Syntax tree logMessage msg, pure (true, some msg.custom.get) }, /- NOTE: We need to make very sure that these recursive calls are happening in tail positions. Otherwise, resuming the coroutine is linear in the number of previous commands. -/ match c with | some c := pure (recovering, c) | none := commandWrecAux recovering n def parseCommandWithRecovery (recovering : Bool) := do { rem ← remaining, commandWrecAux recovering rem.succ } end Module open Module structure ModuleParserSnapshot := -- it there was a parse error in the previous command, we shouldn't complain if parsing immediately after it -- fails as well (recovering : Bool) (it : String.OldIterator) -- return (partial) Syntax tree and single fatal or multiple non-fatal messages def resumeModuleParser {α : Type} (cfg : ModuleParserConfig) (snap : ModuleParserSnapshot) (mkRes : α → Syntax × ModuleParserSnapshot) (p : ModuleParserM α) : Syntax × Except Message (ModuleParserSnapshot × MessageLog) := let (r, _) := ((((Prod.mk <$> p <*> leftOver).run {messages:=MessageLog.empty}).run cfg).runFrom snap.it).run {} in match r with | Except.ok ((a, it), st) := let (stx, snap) := mkRes a in (stx, Except.ok ({snap with it := it}, st.messages)) | Except.error msg := (msg.custom.get, Except.error $ messageOfParsecMessage cfg msg) def parseHeader (cfg : ModuleParserConfig) := let snap := {ModuleParserSnapshot . recovering := false, it := cfg.input.mkOldIterator} in resumeModuleParser cfg snap (λ stx, (stx, snap)) $ do -- `token` assumes that there is no leading whitespace monadLift whitespace, monadLift header.Parser def parseCommand (cfg) (snap) := resumeModuleParser cfg snap (λ p, (Prod.snd p, {snap with recovering := Prod.fst p})) (parseCommandWithRecovery snap.recovering) end Parser end Lean