ryan 0.2.3

Ryan: a configuration language for the practical programmer
Documentation
WHITESPACE = _{ " " | "\n" | "\t" }
COMMENT = _{ "//" ~ (!"\n" ~ ANY)* ~ "\n" }
root = _{ SOI ~ main ~ EOI }

main = _{ block? }

// Literals:
literal = { null | number | bool | text | identifier }
unsigned = @{
    '0'..'9' ~ ('0'..'9' | "_")* ~ ("." ~ ('0'..'9' | "_")*)?
    ~ ("e" ~ "_"* ~ ("+" | "-")? ~ ('0'..'9' | "_")+ )? 
}
    null = @{ "null" }
    sign = @{ "+" | "-" }
    number = @{ sign? ~ unsigned }
    bool = @{ "true" | "false" }
    escaped = @{ !"\"" ~ ("\\" ~ controlCode | ANY)}
    // see: https://stackoverflow.com/questions/19176024/
    controlCode = @{
        "\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" | "u" ~ ('0'..'9' | 'a'..'f' | 'A'..'F'){4}
    }
    text = @{ "\"" ~ escaped* ~ "\"" }
    identifier = @{
        !reserved ~ identifierStr
    }
    identifierStr = @{ (ASCII_ALPHA | "_") ~ ( ASCII_ALPHANUMERIC | "_")* }
    reserved = @{
        ("_" | "true" | "false" | "and" | "or" | "not" | "if" | "then" | "else" | "let"
        | "for" | "int" | "in" | "null" | "import" | "as" | "text" | "type" | "bool" 
        | "float" | "number" | "any") ~ !( ASCII_ALPHANUMERIC | "_")
    }


// Templates:
templateString = ${ "`" ~ templateEscaped* ~ "`" }
    templateEscaped = ${ !"`" ~ ("\\" ~ templateControlCode | interpolation | ANY) }
    templateControlCode = ${ "`" | "$" }
    interpolation = !{ "${" ~ expression ~ "}" }


// Expressions:
expression = { prefixOp* ~ term ~ postfixOp* ~ (binaryOp ~ prefixOp* ~ term ~ postfixOp*)* }
binaryOp = _{
    orOp | andOp | equalsOp | notEqualsOp | typeMatchesOp | greaterEqualOp | greaterOp 
    | lesserEqualOp | lesserOp | lesserEqualOp | isContainedOp | plusOp | minusOp | timesOp 
    | dividedOp | remainderOp | defaultOp | juxtapositionOp 
}
	orOp = { "or" }
    andOp = { "and" }
    equalsOp = { "==" }
    notEqualsOp = { "!=" }
    typeMatchesOp = { "#" }
    greaterOp = { ">" }
    greaterEqualOp = { ">=" }
    lesserOp = { "<" }
    lesserEqualOp = { "<=" }
    isContainedOp = { "in" }
    plusOp = { "+" }
    minusOp = { "-" }
    timesOp = { "*" }
    dividedOp = { "/" }
    remainderOp = { "%" }
    defaultOp = { "?" }
    juxtapositionOp = { "" }
prefixOp = _{ notOp }
    notOp = { "not" }
postfixOp = _{ accessOp | castInt | castFloat | castText }
    accessOp = { "." ~ identifier }
    pathOp = { "[" ~ (
        expression ~ ("," ~ expression )* ~ ","?
    ) ~ "]" }
    castInt = { "as" ~ "int" }
    castFloat = { "as" ~ "float" }
    castText = { "as" ~ "text" }

term = _{
    list
    | listComprehension
    | dict 
    | dictComprehension
    | conditional
    | literal
    | templateString
    | import
    | "(" ~ expression ~ ")"
}
    list = { "[" ~ (
        listItem ~ ("," ~ listItem )* ~ ","?
    )? ~ "]" }
        listItem = { flatExpression | expression }
        flatExpression = { "..." ~ expression }
    dict = { "{" ~ (
        dictItem ~ ("," ~ dictItem)* ~ ","?
    )? ~ "}" }
        dictItem = { flatExpression | keyValue }
        keyValue = { (text | identifier) ~ (":" ~ expression)? ~ ifGuard? }
    conditional = { "if" ~ expression ~ "then" ~ expression ~ "else" ~ expression }


// Comprehensions:
listComprehension = { "[" ~ expression ~ (forClause) ~ ifGuard? ~ "]" }
dictComprehension = { "{" ~ keyValueClause ~ (forClause) ~ ifGuard? ~ "}" }
    forClause = { "for" ~ pattern ~ "in" ~ expression }
    ifGuard = { "if" ~ expression }
    keyValueClause = { expression ~ ":" ~ expression }

// Patterns:
pattern = {
    wildcard
	| matchIdentifier
    | literal // any literal not an identifier...
    | matchList
    | matchHead
    | matchTail
    | matchDict
    | matchDictStrict
}
    wildcard = { "_" }
    matchIdentifier = { identifier ~ (":" ~ typeExpression)?}
    matchList = { "[" ~ (
        pattern ~ ("," ~ pattern )* ~ ","?
    )? ~ "]" }
    matchHead = { "[" ~ (pattern ~ ",")* ~ ".." ~ "]" }
    matchTail = { "[" ~ ".." ~ ("," ~ pattern)* ~ "]" }
    matchDict = { "{" ~ (
        matchDictItem ~ ("," ~ matchDictItem)* ~ "," ~ ".."
    )? ~ "}" }
    matchDictStrict = { "{" ~ (
        matchDictItem ~ ("," ~ matchDictItem)* ~ ","?
    )? ~ "}" }
    matchDictItem = {
        text ~ ":" ~ pattern
        | identifier ~ ":" ~ !matchIdentifier ~ pattern
        | matchIdentifier
    }


// Bindings:
binding = { destructuringBiding | patternMatchBinding | typeDefinition }
    patternMatchBinding = { "let" ~ identifier ~ pattern ~ "=" ~ block  }
    destructuringBiding = { "let" ~ pattern ~ "=" ~ block }
    typeDefinition = { "type" ~ identifier ~ "=" ~ typeExpression }
block = {
    (binding ~ ";")* ~ expression
    | binding ~ (";" ~ binding )* ~ ";"?
}


// Import statements:
import = { "import" ~ text ~ ("as" ~ importFormat)? ~ ("or" ~ expression)? }
importFormat = _{ importFormatText }
    importFormatText = { "text" }


// Types:
primitive = { "any" | "null" | "bool" | "int" | "float" | "number" | "text" }
typeExpression = { typeTerm ~ ("|" ~ typeTerm)*}
typeTerm = _{ 
    optionalType
    | listType
    | tupleType
    | recordType
    | strictRecordType
    | dictionaryType
    | primitive
    | identifier
}
    optionalType = { "?" ~ typeExpression }
    listType = { "[" ~ typeExpression ~  "]" }
    dictionaryType = { "{" ~ typeExpression ~  "}" }
    tupleType = {"(" ~ (
        typeExpression ~ ("," ~ typeExpression )* ~ ","?
    )? ~ ")"}
    recordType = { "{" ~ (
        typeItem ~ ("," ~ typeItem )* ~ ","?
    )? ~ ".." ~ "}" }
    strictRecordType = { "{" ~ (
        typeItem ~ ("," ~ typeItem )* ~ ","?
    )? ~ "}" }
        typeItem = { (identifier | text) ~ ":" ~ typeExpression }