diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index e01117a2..df4d6a27 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -13,6 +13,8 @@ module Liquid @p = point end + # Consumes a token of specific type. + # Throws SyntaxError if token doesn't match type expectation. def consume(type = nil) token = @tokens[@p] if type && token[0] != type @@ -41,6 +43,7 @@ module Liquid token[1] end + # Peeks the ahead token, returning true if matching expectation def look(type, ahead = 0) tok = @tokens[@p + ahead] return false unless tok @@ -51,7 +54,7 @@ module Liquid # logical := equality (("and" | "or") equality)* # equality := comparison (("==" | "!=" | "<>") comparison)* # comparison := primary ((">=" | ">" | "<" | "<=" | ... ) primary)* - # primary := string | number | variable_lookup | range | boolean + # primary := string | number | variable_lookup | range | boolean | grouping def expression logical end @@ -60,7 +63,8 @@ module Liquid # associative. This creates a right-leaning tree and is why the method # looks a bit more complicated # - # `a == b and b or c` is evaluated like (a and (b or c)) + # `a and b or c` is evaluated like (a and (b or c)) + # logical := equality (("and" | "or") equality)* def logical operator = nil expr = equality @@ -69,6 +73,7 @@ module Liquid expr end + # equality := comparison (("==" | "!=" | "<>") comparison)* def equality expr = comparison while look(:equality) @@ -88,11 +93,12 @@ module Liquid expr end + # primary := string | number | variable_lookup | range | boolean | grouping def primary token = @tokens[@p] case token[0] when :id - variable_lookup + variable_lookup_or_literal when :open_square unnamed_variable_lookup when :string @@ -115,7 +121,18 @@ module Liquid consume(:string)[1..-2] end + # variable_lookup := id (lookup)* + # lookup := indexed_lookup | dot_lookup + # indexed_lookup := "[" expression "]" + # dot_lookup := "." id def variable_lookup + name = consume(:id) + lookups, command_flags = variable_lookups + VariableLookup.new(name, lookups, command_flags) + end + + # a variable_lookup without lookups could be a literal + def variable_lookup_or_literal name = consume(:id) lookups, command_flags = variable_lookups if Expression::LITERALS.key?(name) && lookups.empty? @@ -125,6 +142,7 @@ module Liquid end end + # unnamed_variable_lookup := indexed_lookup (lookup)* def unnamed_variable_lookup name = indexed_lookup lookups, command_flags = variable_lookups @@ -132,6 +150,7 @@ module Liquid end # Parenthesized expressions are recursive + # grouping := "(" expression ")" def grouping_or_range_lookup consume(:open_round) expr = expression @@ -144,6 +163,7 @@ module Liquid consume(:close_round) end + # range_lookup := "(" expression ".." expression ")" def range_lookup consume(:open_round) first = expression