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