gluon 0.18.2

A static, type inferred programming language for application embedding
Documentation
//! A simple parser combinator library.

let prelude = import! std.prelude
let { Functor, Applicative, Alternative, Monad } = prelude
let { id, flip } = import! std.function
let { Bool } = import! std.bool
let char @ { ? } = import! std.char
let int = import! std.int
let { Result } = import! std.result
let string = import! std.string
let { (<>) } = import! std.prelude
let list @ { List } = import! std.list
let { Option } = import! std.option

type OffsetString = { start : Int, end : Int, buffer : String }
type Position = Int
type Error = { position : Position, message : String }
type ParseResult a = Result Error { value : a, buffer : OffsetString }

/// `Parser` is a monad which parses a `String` into structured values
type Parser a =
    OffsetString
        -> ParseResult a

let parser : Parser a -> Parser a = id

let functor : Functor Parser = {
    map = \f m ->
        parser
            (\buffer ->
                let result = parser m buffer
                match result with
                | Ok a -> Ok { value = f a.value, buffer = a.buffer }
                | Err err -> Err err),
}

let { map } = functor

let applicative : Applicative Parser = {
    functor,

    apply = \f m ->
        parser
            (\buffer ->
                let result1 = parser f buffer
                match result1 with
                | Ok g ->
                    let result2 = parser m g.buffer
                    match result2 with
                    | Ok a -> Ok { value = g.value a.value, buffer = a.buffer }
                    | Err err -> Err err
                | Err err -> Err err),

    wrap = \value -> parser (\buffer -> Ok { value, buffer }),
}

let { (*>), (<*), wrap } = import! std.applicative

let alternative : Alternative Parser = {
    applicative,

    empty = parser (\stream -> Err { position = stream.start, message = "empty" }),

    or = \l r ->
        parser
            (\stream ->
                match parser l stream with
                | Ok a -> Ok a
                | Err _ -> parser r stream),
}

let { (<|>) } = import! std.alternative

let monad : Monad Parser = {
    applicative,

    flat_map = \f m ->
        parser
            (\buffer ->
                match parser m buffer with
                | Ok a -> parser (f a.value) a.buffer
                | Err err -> Err err),
}

let { flat_map } = import! std.monad

let uncons stream : OffsetString -> Option { char : Char, rest : OffsetString } =
    if stream.start == stream.end then None
    else
        let c = string.char_at stream.buffer stream.start
        let char_len = char.len_utf8 c
        Some
            {
                char = c,
                rest = {
                    start = stream.start + char_len,
                    end = stream.end,
                    buffer = stream.buffer,
                },
            }

let update_position c position : Char -> Position -> Position = position + char.len_utf8 c

/// Returns `message` as what was expected by `p`
#[infix(left, 0)]
let (<?>) p message : Parser a -> String -> Parser a =
    parser
        (\stream ->
            match p stream with
            | Ok x -> Ok x
            | Err _ -> Err { position = stream.start, message })

/// Parses any character. Only errors if the stream is out of input
let any : Parser Char =
    parser
        (\stream ->
            match uncons stream with
            | Some record ->
                let { char, rest } = record
                Ok { value = char, buffer = rest }
            | None -> Err { position = stream.start, message = "End of stream" })

/// Fails the parser with `message` as the cause
let fail message : String -> Parser a = parser (\stream -> Err { position = stream.start, message })

/// Succeeds if `predicate` returns `Some`, fails if `None` is returned
let satisfy_map predicate : (Char -> Option a) -> Parser a =
    let f c =
        match predicate c with
        | Some x -> wrap x
        | None -> fail ("Unexpected character " <> char.show.show c)
    flat_map f any

/// Succeeds if `predicate` returns True, fails if `False` is returned
let satisfy predicate : (Char -> Bool) -> Parser Char =
    satisfy_map (\c -> if predicate c then Some c else None)

/// Succeeds if the next token is `expected`
let token expected : Char -> Parser Char = satisfy (\c -> expected == c)

/// Succeds if the next token is a letter
let letter : Parser Char = satisfy char.is_alphabetic <?> "letter"

/// Succeds if the next token is a digit
let digit : Parser Char = satisfy (flip char.is_digit 10) <?> "digit"

/// Succeds if the next token is alphanumeric
let alpha_num : Parser Char = satisfy char.is_alphanumeric <?> "letter or digit"

/// Succeds if the next token is a space
let space : Parser Char = token ' '

/// Succeds if the next token is a tab
let tab : Parser Char = token '\t'

/// Parses one or more tokens passing `predicate` and returns the `String` between the start and
/// end of those tokens
let take1 predicate : (Char -> Bool) -> Parser String =
    parser
        (\stream ->
            let take_ stream2 =
                match uncons stream2 with
                | Some record ->
                    if predicate record.char then take_ record.rest
                    else if stream.start == stream2.start then
                        Err { position = stream.start, message = "Unexpected token" }
                    else
                        Ok
                            {
                                value = string.slice stream.buffer stream.start stream2.start,
                                buffer = stream2,
                            }
                | None ->
                    Ok
                        {
                            value = string.slice stream.buffer stream.start stream.end,
                            buffer = stream2,
                        }
            take_ stream)

/// Parses zero or more tokens passing `predicate` and returns the `String` between the start and
/// end of those tokens
let take predicate : (Char -> Bool) -> Parser String = take1 predicate <|> wrap ""

/// Parses using `p` and returns the `String` between the start and of what `p` parsed
let recognize p : Parser a -> Parser String =
    parser
        (\stream ->
            match parser p stream with
            | Ok a ->
                Ok
                    {
                        value = string.slice stream.buffer stream.start a.buffer.start,
                        buffer = a.buffer,
                    }
            | Err err -> Err err)

/// Skips over whitespace characters
let spaces = take char.is_whitespace

/// Creates a parser from a factory function. Useful to prevent mutually recursive parser from looping forever
let lazy_parser f : (() -> Parser a) -> Parser a = parser (\stream -> f () stream)

/// Parses `x` between `l` and `r`, returning the result of `x`
let between l r x : Parser a -> Parser b -> Parser c -> Parser c = l *> x <* r

rec
/// Parses with `p` zero or more times
let many p : Parser a -> Parser (List a) = many1 p <|> wrap Nil

/// Parses with `p` one or more times
let many1 p : Parser a -> Parser (List a) =
    do h = p
    map (\t -> Cons h t) (many p)
in
rec
/// Parses with `p` zero or more times, ignoring the result of the parser
let skip_many p : Parser a -> Parser () = skip_many1 p <|> wrap ()
/// Parses with `p` one or more times, ignoring the result of the parser
let skip_many1 p : Parser a -> Parser () =
    p
    skip_many p
in
/// Parses one of the characters of `s`
let one_of s : String -> Parser Char =
    satisfy
        (\first ->
            let len = string.len s
            let one_of_ i =
                if i == len then False
                else
                    let c = string.char_at s i
                    if first == c then True
                    else one_of_ (i + char.len_utf8 c)
            one_of_ 0)
        <|> fail ("Expected one of `" <> s <> "`")


/// Parses at least one element of `parser` separated by `sep`
let sep_by1 parser sep : Parser a -> Parser b -> Parser (List a) =
    do x = parser
    do xs = many (sep *> parser)
    wrap (Cons x xs)

/// Parses `parser` separated by `sep`
let sep_by parser sep : Parser a -> Parser b -> Parser (List a) = sep_by1 parser sep <|> wrap Nil

/// Like `sep_by1` but applies the function returned by `op` on the left fold of successive parses
let chainl1 p op : Parser a -> Parser (a -> a -> a) -> Parser a =
    do l = p
    let rest x =
        (
            do f = op
            do r = p
            rest (f x r))
            <|> wrap x
    rest l

/// Like `sep_by` but applies the function returned by `op` on the left fold of successive parses
let chainl p op v : Parser a -> Parser (a -> a -> a) -> a -> Parser a = chainl1 p op <|> wrap v


/// Parses `input` using `p`
let parse p input : Parser a -> String -> Result String a =
    match p { start = 0, end = string.len input, buffer = input } with
    | Ok ok -> Ok ok.value
    | Err err -> Err (int.show.show err.position <> ":" <> err.message)

{
    Position,
    Error,
    ParseResult,
    Parser,
    OffsetString,

    functor,
    applicative,
    alternative,
    monad,

    parser,

    any,
    between,
    token,
    many,
    many1,
    satisfy,
    satisfy_map,
    spaces,
    take1,
    take,
    lazy_parser,
    fail,
    recognize,
    skip_many,
    skip_many1,
    one_of,
    sep_by,
    sep_by1,
    chainl1,
    chainl,
    (<?>),

    alpha_num,
    letter,
    digit,
    space,
    tab,

    parse,
}