Parser
Parser defines the signature of a relation between an input tokens, a result value, and the remaining tokens after parsing that value. Those relations are not meant to implement parsing, just to declare the behavior of parsers to then prove the correctness of any implementation.
abbrev Parser (α: Type) := List String → α → List String → Proptk illustrates the above introduction: given a token, specifies the behavior of a parser that recognizes that token at the head of input, or fails.
@[reducible]
def tk (s: String): Parser String :=
fun input result rest =>
input = s :: rest ∧ result = sany specifies the behavior of a parser that always succeeds on non-empty sequences of tokens, consuming the head token.
@[reducible]
def any : Parser String :=
fun input result rest => input = result :: restpure consumes no input and returns its parameter as successfully parsed result
@[reducible]
def pure (x: α) : Parser α :=
fun input result rest => result = x ∧ rest = inputfail always fails to parse.
@[reducible]
def fail : Parser α := fun _input _result _rest => Falsechoice succeeds if any of the parsers passed as argument succeeds, in no particular order, i.e. non-deterministically.
@[reducible]
def choice (x y : Parser α) : Parser α :=
fun input result rest =>
x input result rest ∨ y input result restfirst_choice is a more convenient specification for the choice like behavior described above, since implementations tend to deterministically try the first parser, and if it fails then they try the second. To rely on the above choice definition would make proofs to cumbersome for some typical implementations.
@[reducible]
def first_choice (x y : Parser α) : Parser α :=
fun input result rest =>
x input result rest ∨
((¬ ∃ r s, x input r s) ∧ y input result rest)seq applies two parsers sequentially. The first on the original input, the second on the remaining tokens after the first succeeds. Both results are compound the resulting tuple value.
@[reducible]
def seq (p: Parser α) (q: Parser β): Parser (α × β) :=
fun input result rest =>
∃ a b mid, result = (a, b) ∧ p input a mid ∧ q mid b restinfixl:60 " <*> " => seqmany applies a parser zero or more times, resulting on a list of values.
@[reducible]
def many (p: Parser α): Parser (List α)
| input, [], rest => input = rest
| input, a :: results, rest =>
∃ mid, p input a mid ∧ many p mid results restmap applies a function to the result value of a successful Parser
@[reducible]
def map (f: α → β) (p: Parser α): Parser β :=
fun input result rest =>
∃ a, p input a rest ∧ f a = resultinfixr:100 " <$> " => mapguardp constraints the success of a parser using a predicate f on its result value
@[reducible]
def guardp(f: α → Bool) (p: Parser α): Parser α :=
fun input result rest =>
(f result) ∧ p input result restdef all_digits (xs: String) := xs.all Char.isDigitdef all_alpha (xs: String) := xs.all Char.isAlphadigits and alpha are convenient specifications for parsers that succeed only on digit tokens and alphabetic characters respectively.
def digits := guardp all_digits anyleft applies two parsers but only returns the result of the first one
@[reducible]
def left (p: Parser α) (q: Parser β): Parser α :=
fun input result rest =>
∃ mid ignored, p input result mid ∧ q mid ignored restinfixl:60 " <* " => leftright applies two parsers but only returns the result of the second one
@[reducible]
def right (p: Parser α) (q: Parser β): Parser β :=
fun input result rest =>
∃ mid ignored, p input ignored mid ∧ q mid result restinfixl:60 " *> " => right