From 4da7b3613995ea3135f726da18d280bb8101a011 Mon Sep 17 00:00:00 2001 From: Tristan Hume Date: Thu, 25 Jul 2013 11:38:57 -0400 Subject: [PATCH] New variable parser! --- lib/liquid/lexer.rb | 9 ++++++- lib/liquid/parser.rb | 38 +++++++++++++++++++++++++----- lib/liquid/variable.rb | 26 ++++++++++++++++++++ test/liquid/parsing_quirks_test.rb | 6 +++-- test/liquid/variable_test.rb | 12 +++++----- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb index 420bd0f..0d011b1 100644 --- a/lib/liquid/lexer.rb +++ b/lib/liquid/lexer.rb @@ -14,6 +14,10 @@ module Liquid out << ": \'#{@contents}\'" if contents out << '>' end + + def to_s + self.inspect + end end class Lexer @@ -40,7 +44,10 @@ module Liquid loop do tok = next_token - return @output unless tok + unless tok + @output << Token[:end_of_string] + return @output + end @output << tok end end diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index 2477d27..b803edb 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -8,6 +8,10 @@ module Liquid @p = 0 # pointer to current location end + def jump(point) + @p = point + end + def consume(type = nil) token = @tokens[@p] if type && token.type != type @@ -17,14 +21,24 @@ module Liquid token.contents end + # Only consumes the token if it matches the type + # Returns the token's contents if it was consumed + # or false otherwise. + def consume?(type) + token = @tokens[@p] + return false unless token && token.type == type + @p += 1 + token.contents + end + def cur_token() tok = @tokens[@p] raise SyntaxError, 'Expected more input.' unless tok tok end - def look(type) - tok = @tokens[@p] + def look(type, ahead = 0) + tok = @tokens[@p + ahead] return false unless tok tok.type == type end @@ -36,22 +50,34 @@ module Liquid if token.type == :id variable_signature elsif [:string, :integer, :float].include? token.type + consume token.contents else raise SyntaxError, "#{token} is not a valid expression." end end + def argument + str = "" + # might be a keyword argument (identifier: expression) + if look(:id) && look(:colon, 1) + str << consume << consume << ' ' + end + + str << expression + end + def variable_signature str = consume(:id) - if look(:dot) - str << consume - str << variable_signature - elsif look(:open_square) + if look(:open_square) str << consume str << expression str << consume(:close_square) end + if look(:dot) + str << consume + str << variable_signature + end str end end diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 883e3ea..ac0ad72 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -18,6 +18,10 @@ module Liquid @markup = markup @name = nil @filters = [] + parse(markup) + end + + def old_parse(markup) if match = markup.match(/\s*(#{QuotedFragment})(.*)/o) @name = match[1] if match[2].match(/#{FilterSeparator}\s*(.*)/o) @@ -33,6 +37,28 @@ module Liquid end end + def parse(markup) + p = Parser.new(markup) + # Could be just filters with no input + @name = p.look(:pipe) ? '' : p.expression + while p.consume?(:pipe) + filtername = p.consume(:id) + filterargs = p.consume?(:colon) ? parse_filterargs(p) : [] + @filters << [filtername, filterargs] + end + p.consume(:end_of_string) + end + + def parse_filterargs(p) + # first argument + filterargs = [p.argument] + # followed by comma separated others + while p.consume?(:comma) + filterargs << p.argument + end + filterargs + end + def render(context) return '' if @name.nil? @filters.inject(context[@name]) do |output, filter| diff --git a/test/liquid/parsing_quirks_test.rb b/test/liquid/parsing_quirks_test.rb index f5c4426..9f39321 100644 --- a/test/liquid/parsing_quirks_test.rb +++ b/test/liquid/parsing_quirks_test.rb @@ -31,9 +31,11 @@ class ParsingQuirksTest < Test::Unit::TestCase def test_error_on_empty_filter assert_nothing_raised do - Template.parse("{{test |a|b|}}") Template.parse("{{test}}") - Template.parse("{{|test|}}") + Template.parse("{{|test}}") + end + assert_raise(SyntaxError) do + Template.parse("{{test |a|b|}}") end end diff --git a/test/liquid/variable_test.rb b/test/liquid/variable_test.rb index 7554e00..882c157 100644 --- a/test/liquid/variable_test.rb +++ b/test/liquid/variable_test.rb @@ -73,8 +73,8 @@ class VariableTest < Test::Unit::TestCase end def test_symbol - var = Variable.new("http://disney.com/logo.gif | image: 'med' ") - assert_equal 'http://disney.com/logo.gif', var.name + var = Variable.new("'http://disney.com/logo.gif' | image: 'med' ") + assert_equal "'http://disney.com/logo.gif'", var.name assert_equal [["image",["'med'"]]], var.filters end @@ -114,10 +114,10 @@ class VariableTest < Test::Unit::TestCase assert_equal [['things',["greeting: \"world\"","farewell: 'goodbye'"]]], var.filters end - def test_lax_filter_argument_parsing - var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !) - assert_equal 'number_of_comments', var.name - assert_equal [['pluralize',["'comment'","'comments'"]]], var.filters + def test_strict_filter_argument_parsing + assert_raises(SyntaxError) do + Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !) + end end end