atto
Parser combinators for parsing arbitrary stream types.
Types
An expected or found component of an error.
pub type ErrorPart(t) {
Token(t)
Msg(String)
}
Constructors
-
Token(t)
-
Msg(String)
An error that occurred during parsing.
pub type ParseError(e, t) {
ParseError(
span: Span,
got: ErrorPart(t),
expected: Set(ErrorPart(t)),
)
Custom(span: Span, value: e)
}
Constructors
-
ParseError( span: Span, got: ErrorPart(t), expected: Set(ErrorPart(t)), )
-
Custom(span: Span, value: e)
Parser monad.
This type is parameterised by the result a
, the token t
, the stream s
,
a custom context c
, and the error type e
.
pub type Parser(a, t, s, c, e) {
Parser(
run: fn(ParserInput(t, s), Pos, c) ->
Result(#(a, ParserInput(t, s), Pos, c), ParseError(e, t)),
)
}
Constructors
-
Parser( run: fn(ParserInput(t, s), Pos, c) -> Result(#(a, ParserInput(t, s), Pos, c), ParseError(e, t)), )
An input to the parser.
This type is parameterised by the token type t
and the token stream s
.
pub type ParserInput(t, s) {
ParserInput(
src: s,
get: fn(s, #(Int, Int)) -> Result(#(t, s, #(Int, Int)), Nil),
render_token: fn(t) -> String,
render_span: fn(s, Span) -> #(String, String, String),
)
}
Constructors
-
ParserInput( src: s, get: fn(s, #(Int, Int)) -> Result(#(t, s, #(Int, Int)), Nil), render_token: fn(t) -> String, render_span: fn(s, Span) -> #(String, String, String), )
Arguments
-
src
The token stream being consumed.
-
get
Get the next token from the input stream, returning the token, the new stream and the new line/column, or an error on EOF.
-
render_token
Produce a string representation of a token.
-
render_span
Given the original stream and a span, render the span section of the stream and the before and after context. This is used for error messages. Usually, this will return the line that an error occurred on, split around the span.
Examples
let in = text.new("foo bar baz") let sp = Span(Pos(1, 5), Pos(1, 8)) in.render(in.src, sp) // -> #("foo ", "bar", " baz")
-
A position in the input stream, with an index, line, and column.
pub type Pos {
Pos(idx: Int, line: Int, col: Int)
}
Constructors
-
Pos(idx: Int, line: Int, col: Int)
Functions
pub fn ctx_put(
f: fn(a) -> a,
p: fn() -> Parser(b, c, d, a, e),
) -> Parser(b, c, d, a, e)
Modify the current context within a parser.
Examples
{
use <- ctx_put(fn(x) { x + 1 })
usw x <- ctx()
pure(x)
}
|> run(text.new(""), 5)
// -> Ok(6)
pub fn do(
p: Parser(a, b, c, d, e),
f: fn(a) -> Parser(f, b, c, d, e),
) -> Parser(f, b, c, d, e)
Compose two parsers. This can be used with use
syntax.
Examples
{
use a <- do(token("a"))
use b <- do(token("b"))
pure(#(a, b))
}
|> run(text.new("ab"), Nil)
// -> Ok(("a", "b"))
pub fn drop(
p: Parser(a, b, c, d, e),
q: fn() -> Parser(f, b, c, d, e),
) -> Parser(f, b, c, d, e)
Compose two parsers, discarding the result of the first.
This is just a wrapper for do
for convenient syntax with use
.
Examples
use <- drop(token("a"))
pub fn eof() -> Parser(Nil, a, b, c, d)
Matches the end of input. This should be placed at the end of the parser chain to ensure that the entire input is consumed.
pub fn get_token(
in: ParserInput(a, b),
pos: Pos,
) -> Result(#(a, ParserInput(a, b), Pos), ParseError(c, a))
Get the next token from the input stream.
Examples
get(text.new("foo"), Pos(1, 1))
// -> Ok(#("f", text.new("oo"), Pos(1, 2))
pub fn label(
name: String,
f: fn() -> Parser(a, b, c, d, e),
) -> Parser(a, b, c, d, e)
Label a parser. When this parser fails without consuming input, the ‘expected’ message is set to this message.
Examples
{
use <- label("foo")
satisfy(fn(c) { c == "5" })
}
|> run(text.new("6"), Nil)
// -> Error(ParseError(Pos(1, 1), Token("6"), set.insert(set.new(), Msg("foo")))
pub fn map(
p: Parser(a, b, c, d, e),
f: fn(a) -> f,
) -> Parser(f, b, c, d, e)
Map a function over the result of a parser.
Examples
pure(5) |> map(fn(x) { x + 1 })
|> run(text.new(""), Nil)
// -> Ok(6)
fail("oops!") |> map(fn(x) { x + 1 })
|> run(text.new(""), 5)
// -> Error(Custom(Pos(1, 1), "oops!"))
pub fn pos() -> Parser(Pos, a, b, c, d)
Get the current position in the input stream.
Examples
{
use <- drop(token("a"))
use p1 <- do(pos())
use <- drop(token("b"))
use p2 <- do(pos())
pure(#(p1, p2))
}
|> run(text.new("ab"), Nil)
// -> Ok((Pos(1, 2), Pos(1, 3)))
pub fn recover(
p: Parser(a, b, c, d, e),
in: ParserInput(b, c),
pos: Pos,
ctx: d,
f: fn(ParseError(e, b)) ->
Result(#(a, ParserInput(b, c), Pos, d), ParseError(e, b)),
) -> Result(#(a, ParserInput(b, c), Pos, d), ParseError(e, b))
Try to run a parser. When it fails without consuming input, run the provided function.
pub fn run(
p: Parser(a, b, c, d, e),
in: ParserInput(b, c),
ctx: d,
) -> Result(a, ParseError(e, b))
Run a parser against an input stream, returning a result or an error.
Examples
ops.many(ops.choice([token("a"), token("b")]))
|> run(text.new("aaba"), Nil)
// -> Ok(["a", "a", "b", "a"])
pub fn satisfy(f: fn(a) -> Bool) -> Parser(a, a, b, c, d)
Parse a token if it matches a predicate.
This should be labelled with label
to provide a useful error message.
Examples
satisfy(fn(c) { c == "5" })
|> run(text.new("5"), Nil)
// -> Ok("5")
{
use <- label("digit")
satisfy(fn(c) { "0" <= c && c <= "9" })
}
|> run(text.new("a"), Nil)
// -> Error(ParseError(Pos(1, 1), Token("a"), set.insert(set.new(), Msg("digit")))