defmodule ExPression.Parser.Grammar do
@moduledoc """
Expressions formal language grammar definition
"""
import Xpeg
# credo:disable-for-this-file
def peg do
peg Expr do
# Space characters
S <- {' ', '\t', '\r', '\n'}
# Basic atoms
True <- ("True" | "true") * fn cs -> [true | cs] end
False <- ("False" | "false") * fn cs -> [false | cs] end
Null <- ("None" | "null") * fn cs -> [nil | cs] end
# Strings
Xdigit <- {'0'..'9', 'a'..'f', 'A'..'F'}
Unicode_escape <- 'u' * Xdigit[4]
Escape <- '\\' * ({'"', '\\', '/', 'b', 'f', 'n', 'r', 't'} | Unicode_escape)
String_body <- star(Escape) * star(+({'\x20'..'\x7f', 'а'..'я', 'А'..'Я', '\xC2\xA0'..'\xC2\xBF', '\xC3\x80'..'\xC3\xBF'} - {'"'} - {'\\'}) * star(Escape))
String <- '"' * str(String_body) * '"'
# Numbers
Integer <- int(opt('-') * ('0' | {'1'..'9'}) * star({'0'..'9'}))
Float <- float(opt('-') * ('0' | {'1'..'9'}) * star({'0'..'9'}) * (("." * +{'0'..'9'}) | ({'e', 'E'} * opt({'+', '-'}) * +{'0'..'9'})))
KeyWord <- ("None" | "False" | "True" | "false" | "true" | "null" | "or" | "and" | "not")
IdentifierRest <- {'a'..'z', 'A'..'Z', '_', '0'..'9'}
Identifier1 <- {'a'..'z', 'A'..'Z', '_'} * star(IdentifierRest)
Identifier <- str((KeyWord * Identifier1) | (!KeyWord * Identifier1))
# Expressions
Expr <- star(S) * L2 * star(L1BinOp)
L2 <- L3 * star(L2BinOp)
L3 <- L3UnOp | L4
L4 <- L5 * star(L4BinOp)
L5 <- L6 * star(L5BinOp)
L6 <- L7 * star(L6BinOp)
L7 <- (Special | FCall | Object | Array | Var | Const | "(" * star(S) * Expr * star(S) * ")") * star(AccessOp)
Const <- String | Float | Integer | Null | True | False
Var <- Identifier * fn [name | cs] -> [{:var, [name]} | cs] end
Special <- str("$") * (String | str(+IdentifierRest)) * fn [value, special | cs] -> [{:special, [special, value]} | cs] end
# Function call
FCall <- Identifier * "(" * fn cs -> [[] | cs] end * star(S) * (FArg * star("," * star(S) * FArg) | star(S)) * ")" * fn [args, name | cs] ->
[{:fun_call, [name | Enum.reverse(args)]} | cs]
end
FArg <- Expr * fn [arg, args | cs] -> [[arg | args] | cs] end
# Arrays
ArrayItem <- Expr * fn [v, a | cs] -> [[v | a] | cs] end
Array <- "[" * fn cs -> [[] | cs] end * (ArrayItem * star("," * ArrayItem) | star(S)) * star(S) * "]" *
fn [a | cs] -> [{:array, Enum.reverse(a)} | cs] end
# Objects
ObjPair <- star(S) * String * star(S) * ":" * Expr * fn [v, k, obj | cs] -> [[{k, v} | obj] | cs] end
Object <- "{" * fn cs -> [[] | cs] end * (ObjPair * star("," * ObjPair) | star(S)) * star(S) * "}" * fn [obj | cs] -> [{:obj, obj} | cs] end
# Access
AccessOp <- Access | FieldAccess
FieldAccess <- star(L7) * "." * Identifier * fn [field, obj | cs] -> [{:field_access, [obj, field]} | cs] end
Access <- star((FCall | Var | Const) * star(AccessOp)) * "[" * Expr * star(S) * "]" * fn [arg, obj | cs] -> [{:access, [obj, arg]} | cs] end
# Operators
# Op With lowest priority
L1BinOp <- +S * "or" * +S * Expr * fn [b, a | cs] ->
[{:or, [a, b]} | cs]
end
L2BinOp <- +S * "and" * +S * L2 * fn [b, a | cs] ->
[{:and, [a, b]} | cs]
end
L3UnOp <- "not" * +S * L3 * fn [x | cs ] -> [{:not, [x]} | cs] end
L4BinOp <- star(S) * str("==" | "!=" | ">=" | ">" | "<=" | "<") * star(S) * L4 * fn [b, op, a | cs] ->
case op do
"==" -> [{:==, [a, b]} | cs]
"!=" -> [{:!=, [a, b]} | cs]
">" -> [{:>, [a, b]} | cs]
">=" -> [{:>=, [a, b]} | cs]
"<" -> [{:<, [a, b]} | cs]
"<=" -> [{:<=, [a, b]} | cs]
end
end
L5BinOp <- star(S) * str({'+', '-'}) * star(S) * L5 * fn [b, op, a | cs] ->
case op do
"+" -> [{:+, [a, b]} | cs]
"-" -> [{:-, [a, b]} | cs]
end
end
L6BinOp <- star(S) * str({'*', '/'}) * star(S) * L6 * fn [b, op, a | cs] ->
case op do
"*" -> [{:*, [a, b]} | cs]
"/" -> [{:/, [a, b]} | cs]
end
end
end
end
end