1WHITESPACE = _{" " | NEWLINE }
2NON_WHITESPACE_CONTROL_HYPHEN = _{ !"-}}" ~ !"-%}" ~ "-" }
3// Lax liquid file won't raise errors. This allows blocks to override
4// liquid rules and parse their content on their own.
5LaxLiquidFile = ${ SOI ~ (Element | InvalidLiquid)* ~ EOI }
6LiquidFile = ${ SOI ~ Element* ~ EOI }
7
8// A token that could not be parsed as valid liquid
9InvalidLiquid = { !Expression ~ ANY }
10
11// Element-level parsing
12Element = _{ Expression | Tag | Raw }
13
14TagStart = _{ (WHITESPACE* ~ "{%-") | "{%" }
15TagEnd =   _{ ("-%}" ~ WHITESPACE*) | "%}" }
16TagInner = !{Identifier ~ TagToken*}
17ExpressionStart = _{ (WHITESPACE* ~ "{{-") | "{{" }
18ExpressionEnd =   _{ ("-}}" ~ WHITESPACE*) | "}}" }
19ExpressionInner = !{FilterChain}
20
21Tag = { TagStart ~ WHITESPACE* ~ TagInner ~ WHITESPACE* ~ TagEnd }
22Expression = { ExpressionStart ~ WHITESPACE* ~ ExpressionInner ~ WHITESPACE* ~ ExpressionEnd }
23// Not allowing Tag/Expression Start/End might become a problem
24// for {% raw %}, {% comment %} and other blocks that don't parse
25// the elements inside with liquid: unclosed delimiters won't be accepted
26Raw = @{ (!(TagStart | ExpressionStart) ~ ANY)+ }
27
28
29// Inner parsing
30Identifier = @{ (ASCII_ALPHA | "_" | NON_WHITESPACE_CONTROL_HYPHEN) ~ (ASCII_ALPHANUMERIC | "_" | NON_WHITESPACE_CONTROL_HYPHEN)* }
31
32Variable = ${ Identifier
33            ~ ( ("." ~ Identifier)
34              | ("[" ~ WHITESPACE* ~ Value ~ WHITESPACE* ~ "]")
35              )*
36            }
37Value = { Literal | Variable }
38Filter = { Identifier ~ (":" ~ FilterArgument ~ ("," ~ FilterArgument)*)? }
39FilterChain = { Value ~ ("|" ~ Filter)* }
40PositionalFilterArgument = {Value}
41KeywordFilterArgument = {Identifier ~ ":" ~ Value}
42FilterArgument = _{KeywordFilterArgument | PositionalFilterArgument }
43
44// Literals
45NilLiteral = @{ "nil" | "null" }
46EmptyLiteral = @{ "empty" }
47BlankLiteral = @{ "blank" }
48StringLiteral = @{ ("'" ~ (!"'" ~ ANY)* ~ "'")
49		        		 | ("\"" ~ (!"\"" ~ ANY)* ~ "\"") }
50
51IntegerLiteral = @{ ("+" | "-")? ~ ASCII_DIGIT+ }
52FloatLiteral = @{ ("+" | "-")? ~ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ }
53
54BooleanLiteral = @{ "true" | "false" }
55
56Literal = { NilLiteral | EmptyLiteral | BlankLiteral | StringLiteral | FloatLiteral | IntegerLiteral | BooleanLiteral }
57
58Range = { "(" ~ Value ~ ".." ~ Value ~ ")" }
59
60TagToken = _{ Range | FilterChain | DoubleCharSymbol | SingleCharSymbol }
61
62// DoubleCharSymbol must be tried first, otherwise it could be parsed as two SingleCharSymbol instead
63SingleCharSymbol = _{ GreaterThan | LesserThan | Assign | Comma | Colon }
64DoubleCharSymbol = _{ Equals | NotEquals | LesserThanGreaterThan | GreaterThanEquals | LesserThanEquals }
65
66// Symbols - Names must be given for better error messages
67GreaterThan = { ">" }
68LesserThan = { "<" }
69Assign = { "=" }
70Comma = { "," }
71Colon = { ":" }
72
73Equals = { "==" }
74NotEquals = { "!=" }
75LesserThanGreaterThan = { "<>" }
76GreaterThanEquals = { ">=" }
77LesserThanEquals = { "<=" }
78