tokay 0.6.13

Tokay is a programming language designed for ad-hoc parsing.
Documentation
# Tokay default prelude

# The `Not`-builtin runs its parser and returns its negated result,
# so that `P` occurence becomes rejected.
#
# Everything else becomes accepted (Empty).
Not : @<P> {
    P reject
    Empty
}

# The `Peek`-builtin runs `P` and returns its result, but resets the reading-context afterwards.
#
# It can be used to look ahead parsing constructs, but leaving the rest of the parser back to
# its original position, to decide.
#
# Due to Tokays memorizing features, the parsing will only be done once, and is remembered.
Peek : @<P> {
    P reset
}

# The `Expect`-builtin either expects `P`, and otherwise raises a syntax error,
# reporting a useful parse error message.
Expect : @<P> msg=void {
    accept P
    error(msg || "Expecting " + *P + ", but got " + repr(Peek<(Token | Char | "end-of-file")>))
}

# This is a simple programmatic sequential repetition of `P`.
#
# It allows to specify a minimum (`min`) and maximum (`max`) count of allowed repetitions
# Blurrying (`blur`) means, to turn empty list into void, and return single-item lists as the
# item, which was the default with the built-in repeat construct in Tokay < 0.7
#
# For several reasons, repetitions can also be expressed on a specialized token-level or by
# the grammar itself using left- and right-recursive structures, resulting in left- or
# right-leaning parse trees.
#
# Used by the `Pos<P>` and `Kle<P>` modifiers.
Repeat : @<
    P,          # Parselet
    min: 1,     # minimum occurence
    max: void,  # maximum occurence, void for unlimited
    blur: true  # result blurrying; empty list becomes void, one-item list becomes item
> {
    res = ,
    cnt = 0

    loop {
        P {
            res.push($1)  # explicit push!
            cnt += 1
            if max && cnt == max break
        }

        if cnt < min reject
        break
    }

    if cnt < min reject

    if blur {
        if res.len == 0 accept void
        if res.len == 1 accept res[0]
    }

    res
}

# Repeats `P` one or multiple times.
Pos : @<P, blur: true>{ Repeat<P, blur: blur> }

# Repeats `P` none or multiple times.
Kle : @<P, blur: true>{ Repeat<P, min: 0, blur: blur> || void }

# Optionally accepts `P` or nothing.
Opt : @<P>{ P | Empty }

# Implements a recursive, separated list.
#
# `P` is parsed as part of a list, which is separated by `Separator`.
# `Separator` defaults to `(',' _)`.
#
# The `empty` config allows for a trailing `Separator` with no `P`.
List : @<P, Separator: (',' _), empty: true> {
    Self Separator P   $1 + $3
    if empty (Self Separator)   # allows for trailing Separator
    P  $1,
}

# Parse keywords, which may not be the prefix of another identifier.
Keyword : @<P> {
    P Not<(Alphanumeric | '_')>
}

# Parses any number, either Float or Int.
Number : Float | Int

# Parse any token, which is just no whitespace in default terms.
Token : AsciiPunctuation | Word | Number

# Functions

# Calculates the maximum of a value.
#
# In case the provided value is not an iterator,
# it will be turned into an iterator with just
# its own value.
max : @value {
    iter(value).max
}

# Calculates the minimum of a value.
#
# In case the provided value is not an iterator,
# it will be turned into an iterator with just
# its own value.
min : @value {
    iter(value).min
}

# Calculates the sum of a value.
#
# In case the provided value is not an iterator,
# it will be turned into an iterator with just
# its own value.
sum : @value {
    res = void

    for i in value {
        res += i
    }

    res
}

# Calculates the average of a value.
#
# In case the provided value is not an iterator,
# it will be turned into an iterator with just
# its own value.
avg : @value {
    res = void
    cnt = 0

    for i in value {
        res += i
        cnt++
    }

    # TODO: Facilities for type checking divisable types must be improved!
    if !cnt || (type(res) != "int" && type(res) != "float" && type(res) != "bool" && type(res) != "null")
        return void

    res / cnt
}