walrus 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/bin/walrus +19 -12
  2. data/lib/walrus.rb +27 -70
  3. data/lib/walrus/additions/string.rb +21 -35
  4. data/lib/walrus/compile_error.rb +19 -16
  5. data/lib/walrus/compiler.rb +66 -55
  6. data/lib/walrus/contrib/spec/walruscloth_spec.rb +21 -17
  7. data/lib/walrus/contrib/walruscloth.rb +19 -11
  8. data/lib/walrus/document.rb +41 -33
  9. data/lib/walrus/grammar.rb +474 -162
  10. data/lib/walrus/grammar/assignment_expression.rb +33 -0
  11. data/lib/walrus/grammar/block_directive.rb +37 -0
  12. data/lib/walrus/grammar/comment.rb +33 -0
  13. data/lib/walrus/grammar/def_directive.rb +80 -0
  14. data/lib/walrus/grammar/echo_directive.rb +56 -0
  15. data/lib/walrus/grammar/escape_sequence.rb +33 -0
  16. data/lib/walrus/grammar/import_directive.rb +54 -0
  17. data/lib/walrus/grammar/include_directive.rb +36 -0
  18. data/lib/walrus/grammar/instance_variable.rb +33 -0
  19. data/lib/walrus/grammar/literal.rb +33 -0
  20. data/lib/walrus/grammar/message_expression.rb +34 -0
  21. data/lib/walrus/grammar/multiline_comment.rb +70 -0
  22. data/lib/walrus/grammar/placeholder.rb +47 -0
  23. data/lib/walrus/grammar/raw_directive.rb +50 -0
  24. data/lib/walrus/grammar/raw_text.rb +56 -0
  25. data/lib/walrus/grammar/ruby_directive.rb +41 -0
  26. data/lib/walrus/grammar/ruby_expression.rb +42 -0
  27. data/lib/walrus/grammar/set_directive.rb +34 -0
  28. data/lib/walrus/grammar/silent_directive.rb +51 -0
  29. data/lib/walrus/grammar/slurp_directive.rb +36 -0
  30. data/lib/walrus/grammar/super_directive.rb +34 -0
  31. data/lib/walrus/parser.rb +26 -408
  32. data/lib/walrus/runner.rb +37 -20
  33. data/lib/walrus/template.rb +34 -25
  34. data/lib/walrus/version.rb +24 -1
  35. metadata +57 -71
  36. data/ext/extconf.rb +0 -16
  37. data/ext/jindex.c +0 -92
  38. data/lib/walrus/diff.rb +0 -95
  39. data/lib/walrus/grammar/additions/proc.rb +0 -26
  40. data/lib/walrus/grammar/additions/regexp.rb +0 -27
  41. data/lib/walrus/grammar/additions/string.rb +0 -58
  42. data/lib/walrus/grammar/additions/symbol.rb +0 -49
  43. data/lib/walrus/grammar/and_predicate.rb +0 -46
  44. data/lib/walrus/grammar/array_result.rb +0 -25
  45. data/lib/walrus/grammar/continuation_wrapper_exception.rb +0 -34
  46. data/lib/walrus/grammar/left_recursion_exception.rb +0 -33
  47. data/lib/walrus/grammar/location_tracking.rb +0 -115
  48. data/lib/walrus/grammar/match_data_wrapper.rb +0 -71
  49. data/lib/walrus/grammar/memoizing.rb +0 -47
  50. data/lib/walrus/grammar/memoizing_cache.rb +0 -103
  51. data/lib/walrus/grammar/node.rb +0 -66
  52. data/lib/walrus/grammar/not_predicate.rb +0 -46
  53. data/lib/walrus/grammar/parse_error.rb +0 -45
  54. data/lib/walrus/grammar/parser_state.rb +0 -187
  55. data/lib/walrus/grammar/parslet.rb +0 -34
  56. data/lib/walrus/grammar/parslet_choice.rb +0 -128
  57. data/lib/walrus/grammar/parslet_combination.rb +0 -32
  58. data/lib/walrus/grammar/parslet_combining.rb +0 -160
  59. data/lib/walrus/grammar/parslet_merge.rb +0 -94
  60. data/lib/walrus/grammar/parslet_omission.rb +0 -63
  61. data/lib/walrus/grammar/parslet_repetition.rb +0 -106
  62. data/lib/walrus/grammar/parslet_repetition_default.rb +0 -64
  63. data/lib/walrus/grammar/parslet_sequence.rb +0 -214
  64. data/lib/walrus/grammar/predicate.rb +0 -63
  65. data/lib/walrus/grammar/proc_parslet.rb +0 -58
  66. data/lib/walrus/grammar/regexp_parslet.rb +0 -79
  67. data/lib/walrus/grammar/skipped_substring_exception.rb +0 -42
  68. data/lib/walrus/grammar/string_enumerator.rb +0 -53
  69. data/lib/walrus/grammar/string_parslet.rb +0 -81
  70. data/lib/walrus/grammar/string_result.rb +0 -30
  71. data/lib/walrus/grammar/symbol_parslet.rb +0 -69
  72. data/lib/walrus/no_parameter_marker.rb +0 -25
  73. data/lib/walrus/walrus_grammar/assignment_expression.rb +0 -30
  74. data/lib/walrus/walrus_grammar/block_directive.rb +0 -34
  75. data/lib/walrus/walrus_grammar/comment.rb +0 -30
  76. data/lib/walrus/walrus_grammar/def_directive.rb +0 -72
  77. data/lib/walrus/walrus_grammar/echo_directive.rb +0 -50
  78. data/lib/walrus/walrus_grammar/escape_sequence.rb +0 -30
  79. data/lib/walrus/walrus_grammar/import_directive.rb +0 -50
  80. data/lib/walrus/walrus_grammar/include_directive.rb +0 -33
  81. data/lib/walrus/walrus_grammar/instance_variable.rb +0 -30
  82. data/lib/walrus/walrus_grammar/literal.rb +0 -30
  83. data/lib/walrus/walrus_grammar/message_expression.rb +0 -31
  84. data/lib/walrus/walrus_grammar/multiline_comment.rb +0 -60
  85. data/lib/walrus/walrus_grammar/placeholder.rb +0 -46
  86. data/lib/walrus/walrus_grammar/raw_directive.rb +0 -48
  87. data/lib/walrus/walrus_grammar/raw_text.rb +0 -51
  88. data/lib/walrus/walrus_grammar/ruby_directive.rb +0 -35
  89. data/lib/walrus/walrus_grammar/ruby_expression.rb +0 -37
  90. data/lib/walrus/walrus_grammar/set_directive.rb +0 -30
  91. data/lib/walrus/walrus_grammar/silent_directive.rb +0 -50
  92. data/lib/walrus/walrus_grammar/slurp_directive.rb +0 -31
  93. data/lib/walrus/walrus_grammar/super_directive.rb +0 -33
@@ -1,38 +1,42 @@
1
- #!/usr/bin/env ruby
2
- # Copyright 2007 Wincent Colaiuta
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
7
4
  #
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
12
10
  #
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
15
22
 
16
23
  require 'rubygems'
17
24
  require 'spec'
18
25
  require File.join(File.dirname(__FILE__), '..', 'walruscloth')
19
26
 
20
27
  describe 'using WalrusCloth' do
21
-
22
28
  it 'should be able to create an unordered list' do
23
29
  # => "<ul>\n\t<li>hello</li>\n\t</ul>"
24
30
  WalrusCloth.new('* hello').to_html.should == RedCloth.new('* hello').to_html
25
31
  end
26
-
32
+
27
33
  it 'should be able to create an ordered list' do
28
34
  # => "<ol>\n\t<li>hello</li>\n\t</ol>"
29
35
  WalrusCloth.new('` hello').to_html.should == RedCloth.new('# hello').to_html
30
36
  end
31
-
37
+
32
38
  it 'should be able to nest lists' do
33
39
  # => "<ol>\n\t<li>hello\n\t<ul>\n\t<li>world</li>\n\t</ol></li>\n\t</ul>"
34
40
  WalrusCloth.new("` hello\n`* world").to_html.should == RedCloth.new("# hello\n#* world").to_html
35
41
  end
36
-
37
42
  end
38
-
@@ -1,16 +1,24 @@
1
- # Copyright 2007 Wincent Colaiuta
2
- # This program is free software: you can redistribute it and/or modify
3
- # it under the terms of the GNU General Public License as published by
4
- # the Free Software Foundation, either version 3 of the License, or
5
- # (at your option) any later version.
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
6
4
  #
7
- # This program is distributed in the hope that it will be useful,
8
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
- # GNU General Public License for more details.
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
11
10
  #
12
- # You should have received a copy of the GNU General Public License
13
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
14
22
 
15
23
  require 'rubygems'
16
24
  gem 'RedCloth', '= 3.0.4'; require 'redcloth'
@@ -1,56 +1,60 @@
1
- #!/usr/bin/env ruby
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
2
4
  #
3
- # Copyright 2007 Wincent Colaiuta
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
8
10
  #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
16
22
 
17
23
  require 'walrus'
18
24
  require 'pathname'
19
25
 
20
26
  module Walrus
21
-
22
27
  # All compiled templates inherit from this class.
23
28
  class Document
24
-
25
29
  def initialize
26
30
  @internal_hash = {}
27
31
  end
28
-
32
+
29
33
  def set_value(key, value)
30
34
  @internal_hash[key.to_sym] = value
31
35
  end
32
-
36
+
33
37
  def get_value(key)
34
38
  @internal_hash[key.to_sym]
35
39
  end
36
-
40
+
37
41
  def remove_value(key)
38
42
  @internal_hash.delete(key.to_sym)
39
43
  end
40
-
44
+
41
45
  # Expects a placeholder symbol or String.
42
46
  # The parameters are optional.
43
47
  def lookup_and_accumulate_placeholder(placeholder, *params)
44
48
  output = lookup_and_return_placeholder(placeholder, *params)
45
49
  accumulate(output) if output
46
50
  end
47
-
51
+
48
52
  def lookup_and_return_placeholder(placeholder, *params)
49
53
  # if exists a method responding to placeholder, call it
50
54
  if respond_to? placeholder
51
- @accumulators << nil # push new accumulator onto the stack
52
- output = send(placeholder, *params) # call method
53
- accumulated = @accumulators.pop # pop last accumulator from the stack
55
+ @accumulators << nil # push new accumulator onto the stack
56
+ output = send(placeholder, *params) # call method
57
+ accumulated = @accumulators.pop # pop last accumulator from the stack
54
58
  if accumulated
55
59
  return accumulated
56
60
  elsif output
@@ -60,10 +64,11 @@ module Walrus
60
64
  return get_value(placeholder)
61
65
  end
62
66
  end
63
-
67
+
64
68
  # Supports two calling methods:
65
69
  # - if passed a string it will be appended to the accumulator.
66
- # - if not passed a string but given a block will evaluate the block and append the (string) result to the accumulator.
70
+ # - if not passed a string but given a block will evaluate the block and
71
+ # append the (string) result to the accumulator.
67
72
  def accumulate(string = nil)
68
73
  if (@accumulators.last.nil?) # accumulator will be nil if hasn't been used yet
69
74
  @accumulators.pop # replace temporary nil accumulator
@@ -75,30 +80,33 @@ module Walrus
75
80
  @accumulators.last << string.to_s
76
81
  end
77
82
  end
78
-
79
- # Fills (executes) the template body of the receiver and returns the result.
83
+
84
+ # Fills (executes) the template body of the receiver and returns the
85
+ # result.
80
86
  def fill
81
87
  @accumulators = [nil] # reset accumulators stack
82
88
  template_body
83
89
  @accumulators.last or ""
84
90
  end
85
-
86
- # Prints to standard out the result of filling the receiver. Note that no trailing newline is printed. As a result, if running a template from the terminal be aware that the last line may not be visible or may be partly obscured by the command prompt that is drawn (starting at the first column) after execution completes.
91
+
92
+ # Prints to standard out the result of filling the receiver. Note that no
93
+ # trailing newline is printed. As a result, if running a template from the
94
+ # terminal be aware that the last line may not be visible or may be partly
95
+ # obscured by the command prompt that is drawn (starting at the first
96
+ # column) after execution completes.
87
97
  def run
88
98
  printf('%s', fill)
89
99
  $stdout.flush
90
100
  end
91
-
101
+
92
102
  # By default, there is nothing at all in the template body.
93
103
  def template_body
94
104
  end
95
-
105
+
96
106
  if __FILE__ == $0
97
107
  self.new.run # When run from the command line the default action is to call "run".
98
108
  else
99
109
  self.new.fill # in other cases, evaluate 'fill' (if run inside an eval, will return filled content)
100
110
  end
101
-
102
111
  end # class Document
103
112
  end # module Walrus
104
-
@@ -1,176 +1,488 @@
1
- # Copyright 2007 Wincent Colaiuta
2
- # This program is free software: you can redistribute it and/or modify
3
- # it under the terms of the GNU General Public License as published by
4
- # the Free Software Foundation, either version 3 of the License, or
5
- # (at your option) any later version.
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
6
4
  #
7
- # This program is distributed in the hope that it will be useful,
8
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
- # GNU General Public License for more details.
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
11
10
  #
12
- # You should have received a copy of the GNU General Public License
13
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
14
22
 
23
+ require 'walrat/grammar'
15
24
  require 'walrus'
25
+ require 'pathname'
16
26
 
17
27
  module Walrus
18
- class Grammar
19
-
20
- attr_accessor :memoizing
21
- attr_reader :rules
22
- attr_reader :skipping_overrides
23
-
24
- # Creates a Grammar subclass named according to subclass_name and instantiates an instance of the new class, returning it after evaluating the optional block in the context of the newly created instance. The advantage of working inside a new subclass is that any constants defined in the new grammar will be in a separate namespace.
25
- # The subclass_name parameter should be a String.
26
- def self.subclass(subclass_name, &block)
27
- raise ArgumentError if subclass_name.nil?
28
- raise ArgumentError if Walrus::const_defined?(subclass_name)
29
- Walrus::const_set(subclass_name, Class.new(Grammar))
30
- subclass = Walrus::module_eval(subclass_name)
31
- begin
32
- subclass.new(&block)
33
- rescue ContinuationWrapperException => c # a Symbol in a production rule wants to know what namespace its being used in
34
- c.continuation.call(subclass)
35
- end
36
- end
37
-
38
- def initialize(&block)
39
- @rules = Hash.new { |hash, key| raise StandardError.new('no rule for key "%s"' % key.to_s) }
40
- @productions = Hash.new { |hash, key| raise StandardError.new('no production for key "%s"' % key.to_s) }
41
- @skipping_overrides = Hash.new { |hash, key| raise StandardError.new('no skipping override for key "%s"' % key.to_s) }
42
- @memoizing = true
43
- self.instance_eval(&block) if block_given?
44
- end
45
-
46
- # TODO: consider making grammars copiable (could be used in threaded context then)
47
- #def initialize_copy(from); end
48
- #def clone; end
49
- #def dupe; end
50
-
51
- # Starts with starting_symbol.
52
- def parse(string, options = {})
53
- raise ArgumentError if string.nil?
54
- raise StandardError if @starting_symbol.nil?
55
- options[:grammar] = self
56
- options[:rule_name] = @starting_symbol
57
- options[:skipping] = @skipping
58
- options[:line_start] = 0 # "richer" information (more human-friendly) than that provided in "location"
59
- options[:column_start] = 0 # "richer" information (more human-friendly) than that provided in "location"
60
- options[:memoizer] = MemoizingCache.new if @memoizing
61
- @starting_symbol.to_parseable.memoizing_parse(string, options)
62
- end
63
-
64
- # Defines a rule and stores it
65
- # Expects an object that responds to the parse message, such as a Parslet or ParsletCombination.
66
- # As this is intended to work with Parsing Expression Grammars, each rule may only be defined once. Defining a rule more than once will raise an ArgumentError.
67
- def rule(symbol, parseable)
68
- raise ArgumentError if symbol.nil?
69
- raise ArgumentError if parseable.nil?
70
- raise ArgumentError if @rules.has_key?(symbol)
71
- @rules[symbol] = parseable
72
- end
73
-
74
- # Dynamically creates a Node subclass inside the namespace of the current grammar. If parent_class is nil, Node is assumed.
75
- # new_class_name must not be nil.
76
- def node(new_class_name, parent_class = nil, *attributes)
77
- raise ArgumentError if new_class_name.nil?
78
- new_class_name = new_class_name.to_s.to_class_name # camel-case
79
- if parent_class.nil?
80
- Node.subclass(new_class_name, self.class, *attributes)
28
+ # The parser is currently quite slow, although perfectly usable.
29
+ # The quickest route to optimizing it may be to replace it with a C parser
30
+ # inside a Ruby extension, possibly generated using Ragel
31
+ class Grammar < Walrat::Grammar
32
+ autoload :AssignmentExpression, 'walrus/grammar/assignment_expression'
33
+ autoload :BlockDirective, 'walrus/grammar/block_directive'
34
+ autoload :Comment, 'walrus/grammar/comment'
35
+ autoload :DefDirective, 'walrus/grammar/def_directive'
36
+ autoload :EchoDirective, 'walrus/grammar/echo_directive'
37
+ autoload :EscapeSequence, 'walrus/grammar/escape_sequence'
38
+ autoload :ImportDirective, 'walrus/grammar/import_directive'
39
+ autoload :IncludeDirective, 'walrus/grammar/include_directive'
40
+ autoload :InstanceVariable, 'walrus/grammar/instance_variable'
41
+ autoload :Literal, 'walrus/grammar/literal'
42
+ autoload :MessageExpression, 'walrus/grammar/message_expression'
43
+ autoload :MultilineComment, 'walrus/grammar/multiline_comment'
44
+ autoload :Placeholder, 'walrus/grammar/placeholder'
45
+ autoload :RawDirective, 'walrus/grammar/raw_directive'
46
+ autoload :RawText, 'walrus/grammar/raw_text'
47
+ autoload :RubyDirective, 'walrus/grammar/ruby_directive'
48
+ autoload :RubyExpression, 'walrus/grammar/ruby_expression'
49
+ autoload :SetDirective, 'walrus/grammar/set_directive'
50
+ autoload :SilentDirective, 'walrus/grammar/silent_directive'
51
+ autoload :SlurpDirective, 'walrus/grammar/slurp_directive'
52
+ autoload :SuperDirective, 'walrus/grammar/super_directive'
53
+
54
+ starting_symbol :template
55
+ skipping :whitespace_or_newlines
56
+ rule :whitespace_or_newlines,
57
+ /\s+/
58
+
59
+ # only spaces and tabs, not newlines
60
+ rule :whitespace,
61
+ /[ \t\v]+/
62
+ rule :newline,
63
+ /(\r\n|\r|\n)/
64
+
65
+ # optional whitespace (tabs and spaces only) followed by a
66
+ # backslash/newline (note: this is not escape-aware)
67
+ rule :line_continuation,
68
+ /[ \t\v]*\\\n/
69
+ rule :end_of_input,
70
+ /\z/
71
+
72
+ rule :template,
73
+ :template_element.zero_or_more &
74
+ :end_of_input.and?
75
+ rule :template_element,
76
+ :raw_text |
77
+ :comment |
78
+ :multiline_comment |
79
+ :directive |
80
+ :placeholder |
81
+ :escape_sequence
82
+
83
+ # anything at all other than the three characters which have special
84
+ # meaning in Walrus: $, \ and #
85
+ rule :raw_text, /[^\$\\#]+/
86
+ production :raw_text
87
+
88
+ rule :string_literal,
89
+ :single_quoted_string_literal |
90
+ :double_quoted_string_literal
91
+ skipping :string_literal, nil
92
+ node :string_literal, :literal
93
+
94
+ rule :single_quoted_string_literal,
95
+ "'".skip &
96
+ :single_quoted_string_content.optional &
97
+ "'".skip
98
+ node :single_quoted_string_literal,
99
+ :string_literal
100
+ production :single_quoted_string_literal
101
+ rule :single_quoted_string_content,
102
+ /(\\(?!').|\\'|[^'\\]+)+/
103
+ rule :double_quoted_string_literal,
104
+ '"'.skip &
105
+ :double_quoted_string_content.optional &
106
+ '"'.skip
107
+ node :double_quoted_string_literal, :string_literal
108
+ production :double_quoted_string_literal
109
+ rule :double_quoted_string_content,
110
+ /(\\(?!").|\\"|[^"\\]+)+/
111
+
112
+ # TODO: support 1_000_000 syntax for numeric_literals
113
+ rule :numeric_literal, /\d+\.\d+|\d+(?!\.)/
114
+ node :numeric_literal, :literal
115
+ production :numeric_literal
116
+
117
+ # this matches both "foo" and "Foo::bar"
118
+ rule :identifier, /([A-Z][a-zA-Z0-9_]*::)*[a-z_][a-zA-Z0-9_]*/
119
+ node :identifier, :literal
120
+ production :identifier
121
+
122
+ # this matches both "Foo" and "Foo::Bar"
123
+ rule :constant, /([A-Z][a-zA-Z0-9_]*::)*[A-Z][a-zA-Z0-9_]*/
124
+ node :constant, :literal
125
+ production :constant
126
+
127
+ rule :symbol_literal, /:[a-zA-Z_][a-zA-Z0-9_]*/
128
+ node :symbol_literal, :literal
129
+ production :symbol_literal
130
+
131
+ rule :escape_sequence, '\\'.skip & /[\$\\#]/
132
+ production :escape_sequence
133
+
134
+ rule :comment, '##'.skip & /.*$/
135
+ production :comment
136
+
137
+ # nested multiline comments
138
+ rule :multiline_comment, '#*'.skip & :comment_content.zero_or_more('') & '*#'.skip
139
+ skipping :multiline_comment, nil
140
+ production :multiline_comment, :content
141
+
142
+ rule :comment_content, (:comment & :newline.skip) |
143
+ :multiline_comment |
144
+ /( # three possibilities:
145
+ [^*#]+ | # 1. any run of characters other than # or *
146
+ \#(?!\#|\*) | # 2. any # not followed by another # or a *
147
+ \*(?!\#) # 3. any * not followed by a #
148
+ )+ # match the three possibilities repeatedly
149
+ /x
150
+
151
+ rule :directive, :block_directive |
152
+ :def_directive |
153
+ :echo_directive |
154
+ :extends_directive |
155
+ :import_directive |
156
+ :include_directive |
157
+ :raw_directive |
158
+ :ruby_directive |
159
+ :set_directive |
160
+ :silent_directive |
161
+ :slurp_directive |
162
+ :super_directive
163
+
164
+ node :directive
165
+
166
+ # directives may span multiple lines if and only if the last character on
167
+ # the line is a backslash \
168
+ skipping :directive, :whitespace | :line_continuation
169
+
170
+ # "Directive tags can be closed explicitly with #, or implicitly with the
171
+ # end of the line"
172
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/language.directives.closures.html
173
+ # This means that to follow a directive by a comment on the same line you
174
+ # must explicitly close the directive first (otherwise the grammar would
175
+ # be ambiguous).
176
+ # Note that "skipping" the end_of_input here is harmless as it isn't
177
+ # actually consumed.
178
+ rule :directive_end, ( /#/ | :newline | :end_of_input ).skip
179
+
180
+ rule :block_directive, '#block'.skip & :identifier & :def_parameter_list.optional([]) & :directive_end &
181
+ :template_element.zero_or_more([]) &
182
+ '#end'.skip & :directive_end
183
+ production :block_directive, :identifier, :params, :content
184
+
185
+ rule :def_directive, '#def'.skip & :identifier & :def_parameter_list.optional([]) & :directive_end &
186
+ :template_element.zero_or_more([]) &
187
+ '#end'.skip & :directive_end
188
+ production :def_directive, :identifier, :params, :content
189
+
190
+ # "The #echo directive is used to echo the output from expressions that
191
+ # can't be written as simple $placeholders."
192
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.echo.html
193
+ #
194
+ # Convenient alternative short syntax for the #echo directive, similar to
195
+ # ERB (http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/):
196
+ #
197
+ # #= expression(s) #
198
+ #
199
+ # Is a shortcut equivalent to:
200
+ #
201
+ # #echo expression(s) #
202
+ #
203
+ # This is similar to the ERB syntax, but even more concise:
204
+ #
205
+ # <%= expression(s) =>
206
+ #
207
+ # See also the #silent directive, which also has a shortcut syntax.
208
+ #
209
+ rule :echo_directive, '#echo'.skip & :ruby_expression_list & :directive_end | # long form
210
+ '#='.skip & :ruby_expression_list & '#'.skip # short form
211
+ production :echo_directive, :expression
212
+
213
+ rule :import_directive, '#import'.skip & :string_literal & :directive_end
214
+ production :import_directive, :class_name
215
+
216
+ rule :extends_directive, '#extends'.skip & :string_literal & :directive_end
217
+ node :extends_directive, :import_directive
218
+ production :extends_directive, :class_name
219
+
220
+ rule :include_directive, '#include'.skip & :include_subparslet
221
+ production :include_directive, :file_name, :subtree
222
+
223
+ rule :include_subparslet, lambda { |string, options|
224
+
225
+ # scans a string literal
226
+ parslet = :string_literal & :directive_end
227
+ file_name = parslet.parse(string, options)
228
+
229
+ # if options contains non-nil "origin" then try to construct relative
230
+ # path; otherwise just look in current working directory
231
+ if options[:origin]
232
+ current_location = Pathname.new(options[:origin]).dirname
233
+ include_target = current_location + file_name.to_s
81
234
  else
82
- # convert parent_class to string, then camel case, then back to Symbol, then lookup the constant
83
- self.class.const_get(parent_class.to_s.to_class_name.to_s).subclass(new_class_name, self.class, *attributes)
235
+ include_target = Pathname.new file_name.to_s
84
236
  end
85
- end
86
-
87
- # Specifies the Node subclass that will be used to encapsulate results for the rule identified by the symbol, rule_name.
88
- # class_symbol, if present, will be converted to camel-case and explicitly names the class to be used. If class_symbol is not specified then a camel-cased version of the rule_name itself is used.
89
- # rule_name must not be nil.
90
- #
91
- # Example; specifying that the results of rule "string_literal" should be encapsulated in a "StringLiteral" instance:
92
- #
93
- # production :string_literal
94
- #
95
- # Example; specifying that the results of the rule "numeric_literal" should be encapsulated into a "RawToken" instance:
96
- #
97
- # production :numeric_literal, :raw_token
98
- #
99
- # Example; using the "build" method to dynamically define an "AssigmentExpression" class with superclass "Expression" and assign the created class as the AST production class for the rule "assignment_expression":
100
- #
101
- # production :assignment_expression.build(:expression, :target, :value)
102
- #
103
- def production(rule_name, class_symbol = nil)
104
- raise ArgumentError if rule_name.nil?
105
- raise ArgumentError if @productions.has_key?(rule_name)
106
- raise ArgumentError unless @rules.has_key?(rule_name)
107
- class_symbol = rule_name if class_symbol.nil?
108
- @productions[rule_name] = class_symbol
109
- end
110
-
111
- def wrap(result, rule_name)
112
- if @productions.has_key?(rule_name.to_sym) # figure out arity of "initialize" method and wrap results in AST node
113
- node_class = self.class.const_get(@productions[rule_name.to_sym].to_s.to_class_name)
114
- param_count = node_class.instance_method(:initialize).arity
115
- raise if param_count < 1
116
-
117
- # dynamically build up a message send
118
- if param_count == 1
119
- params = 'result'
120
- else
121
- params = 'result[0]'
122
- for i in 1..(param_count - 1)
123
- params << ", result[#{i.to_s}]"
124
- end
125
- end
126
-
127
- node = node_class.class_eval('new(%s)' % params)
128
- node.start = (result.outer_start or result.start) # propagate the start information
129
- node.end = (result.outer_end or result.end) # and the end information
130
- node.source_text = (result.outer_source_text or result.source_text) # and the original source text
131
- node
132
- else
133
- result.start = result.outer_start if result.outer_start
134
- result.end = result.outer_end if result.outer_end
135
- result.source_text = result.source_text if result.outer_source_text
136
- result
237
+
238
+ # read file into string
239
+ content = include_target.read
240
+
241
+ # try to parse string in sub-parser
242
+ sub_options = { :origin => include_target.to_s }
243
+ sub_result = nil
244
+ catch :AndPredicateSuccess do
245
+ sub_result = Parser.new.parse(content, sub_options)
246
+ end
247
+
248
+ # want to insert a bunch of nodes (a subtree) into the parse tree
249
+ # without advancing the location counters
250
+ sub_tree = Walrat::ArrayResult.new [ file_name, sub_result ? sub_result : [] ]
251
+ sub_tree.start = file_name.start
252
+ sub_tree.end = file_name.end
253
+ sub_tree
254
+ }
255
+
256
+ # "Any section of a template definition that is inside a #raw ... #end
257
+ # raw tag pair will be printed verbatim without any parsing of
258
+ # $placeholders or other directives."
259
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.raw.html
260
+ # Unlike Cheetah, Walrus uses a bare "#end" marker and not an "#end raw"
261
+ # to mark the end of the raw block.
262
+ # The presence of a literal #end within a raw block is made possible by
263
+ # using an optional "here doc"-style delimiter:
264
+ #
265
+ # #raw <<END_MARKER
266
+ # content goes here
267
+ # END_MARKER
268
+ #
269
+ # Here the opening "END_MARKER" must be the last thing on the line
270
+ # (trailing whitespace up to and including the newline is allowed but it
271
+ # is not considered to be part of the quoted text). The final
272
+ # "END_MARKER" must be the very first and last thing on the line, or it
273
+ # will not be considered to be an end marker at all and will be
274
+ # considered part of the quoted text. The newline immediately prior to
275
+ # the end marker is included in the quoted text.
276
+ #
277
+ # Or, if the end marker is to be indented:
278
+ #
279
+ # #raw <<-END_MARKER
280
+ # content
281
+ # END_MARKER
282
+ #
283
+ # Here "END_MARKER" may be preceeded by whitespace (and whitespace only)
284
+ # but it must be the last thing on the line. The preceding whitespace is
285
+ # not considered to be part of the quoted text.
286
+ rule :raw_directive, '#raw'.skip &
287
+ ((:directive_end & /([^#]+|#(?!end)+)*/ & '#end'.skip & :directive_end) | :here_document)
288
+ production :raw_directive, :content
289
+
290
+ # In order to parse "here documents" we adopt a model similar to the one
291
+ # proposed in this message to the ANTLR interest list:
292
+ # http://www.antlr.org:8080/pipermail/antlr-interest/2005-September/013673.html
293
+ rule :here_document, lambda { |string, options|
294
+
295
+ # for the time-being, not sure if there is much benefit in calling
296
+ # memoizing_parse here
297
+ state = Walrat::ParserState.new(string, options)
298
+ parsed = /<<(-?)([a-zA-Z0-9_]+)[ \t\v]*\n/.to_parseable.parse(state.remainder, state.options)
299
+ state.skipped(parsed)
300
+ marker = parsed.match_data
301
+ indenting = (marker[1] == '') ? false : true
302
+
303
+ if indenting # whitespace allowed before end marker
304
+ end_marker = /^[ \t\v]*#{marker[2]}[ \t\v]*(\n|\z)/.to_parseable # will eat trailing newline
305
+ else # no whitespace allowed before end marker
306
+ end_marker = /^#{marker[2]}[ \t\v]*(\n|\z)/.to_parseable # will eat trailing newline
137
307
  end
138
- end
139
-
140
- # Sets the starting symbol.
141
- # symbol must refer to a rule.
142
- def starting_symbol(symbol)
143
- @starting_symbol = symbol
144
- end
145
-
146
- # Sets the default parslet that is used for skipping inter-token whitespace, and can be used to override the default on a rule-by-rule basis.
147
- # This allows for simpler grammars which do not need to explicitly put optional whitespace parslets (or any other kind of parslet) between elements.
148
- #
149
- # There are two modes of operation for this method. In the first mode (when only one parameter is passed) the rule_or_parslet parameter is used to define the default parslet for inter-token skipping. rule_or_parslet must refer to a rule which itself is a Parslet or ParsletCombination and which is responsible for skipping. Note that the ability to pass an arbitrary parslet means that the notion of what consitutes the "whitespace" that should be skipped is completely flexible. Raises if a default skipping parslet has already been set.
150
- #
151
- # In the second mode of operation (when two parameters are passed) the rule_or_parslet parameter is interpreted to be the rule to which an override should be applied, where the parslet parameter specifies the parslet to be used in this case. If nil is explicitly passed then this overrides the default parslet; no parslet will be used for the purposes of inter-token skipping. Raises if an override has already been set for the named rule.
152
- #
153
- # The inter-token parslet is passed inside the "options" hash when invoking the "parse" methods. Any parser which fails will retry after giving this inter-token parslet a chance to consume and discard intervening whitespace.
154
- # The initial, conservative implementation only performs this fallback skipping for ParsletSequence and ParsletRepetition combinations.
155
- #
156
- # Raises if rule_or_parslet is nil.
157
- def skipping(rule_or_parslet, parslet = NoParameterMarker.instance)
158
- raise ArgumentError if rule_or_parslet.nil?
159
- if parslet == NoParameterMarker.instance # first mode of operation: set default parslet
160
- raise if @skipping # should not set a default skipping parslet twice
161
- @skipping = rule_or_parslet
162
- else # second mode of operation: override default case
163
- raise ArgumentError if @skipping_overrides.has_key?(rule_or_parslet)
164
- raise ArgumentError unless @rules.has_key?(rule_or_parslet)
165
- @skipping_overrides[rule_or_parslet] = parslet
308
+
309
+ line = /^.*\n/.to_parseable # for gobbling a line
310
+
311
+ while true do
312
+ begin
313
+ skipped = end_marker.parse(state.remainder, state.options)
314
+ state.skipped(skipped) # found end marker, skip it
315
+ break # all done
316
+ rescue Walrat::ParseError # didn't find end marker yet, consume a line
317
+ parsed = line.parse(state.remainder, state.options)
318
+ state.parsed(parsed)
319
+ end
166
320
  end
167
- end
168
-
169
- # TODO: pretty print method?
170
-
171
- end # class Grammar
172
- end # module Walrus
173
321
 
322
+ # caller will want a String, not an Array
323
+ results = state.results
324
+ document = Walrat::StringResult.new(results.to_s)
325
+ document.start = results.start
326
+ document.end = results.end
327
+ document
328
+ }
329
+
330
+ rule :ruby_directive, '#ruby'.skip & ((:directive_end & /([^#]+|#(?!end)+)*/ & '#end'.skip & :directive_end) | :here_document)
331
+ production :ruby_directive, :content
332
+
333
+ # Unlike a normal Ruby assignement expression, the lvalue of a "#set"
334
+ # directive is an identifier preceded by a dollar sign.
335
+ rule :set_directive, '#set'.skip & /\$(?![ \r\n\t\v])/.skip & :placeholder_name & '='.skip & (:addition_expression | :unary_expression) & :directive_end
336
+ production :set_directive, :placeholder, :expression
337
+
338
+ # "#silent is the opposite of #echo. It executes an expression but
339
+ # discards the output."
340
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.silent.html
341
+ #
342
+ # Like the #echo directive, a convienient shorthand syntax is available:
343
+ #
344
+ # # expressions(s) #
345
+ #
346
+ # Equivalent to the long form:
347
+ #
348
+ # #silent expressions(s) #
349
+ #
350
+ # And similar to but more concise than the ERB syntax:
351
+ #
352
+ # <% expressions(s) %>
353
+ #
354
+ # Note that the space between the opening hash character and the
355
+ # expression(s) is required in order for Walrus to distinguish the
356
+ # shorthand for the #silent directive from the other directives. That is,
357
+ # the following is not legal:
358
+ #
359
+ # #expressions(s) #
360
+ #
361
+ rule :silent_directive, '#silent'.skip & :ruby_expression_list & :directive_end | # long form
362
+ '# '.skip & :ruby_expression_list & '#'.skip # short form
363
+ production :silent_directive, :expression
364
+
365
+ # Accept multiple expressions separated by a semi-colon.
366
+ rule :ruby_expression_list, :ruby_expression >> (';'.skip & :ruby_expression ).zero_or_more
367
+
368
+ # "The #slurp directive eats up the trailing newline on the line it
369
+ # appears in, joining the following line onto the current line."
370
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.slurp.html
371
+ # The "slurp" directive must be the last thing on the line (not followed
372
+ # by a comment or directive end marker)
373
+ rule :slurp_directive, '#slurp' & :whitespace.optional.skip & :newline.skip
374
+ production :slurp_directive
174
375
 
376
+ rule :super_directive, :super_with_parentheses | :super_without_parentheses
377
+ rule :super_with_parentheses, '#super'.skip & :parameter_list.optional & :directive_end
378
+ node :super_with_parentheses, :super_directive
379
+ production :super_with_parentheses, :params
380
+ rule :super_without_parentheses, '#super'.skip & :parameter_list_without_parentheses & :directive_end
381
+ node :super_without_parentheses, :super_directive
382
+ production :super_without_parentheses, :params
175
383
 
384
+ # The "def_parameter_list" is a special case of parameter list which
385
+ # disallows interpolated placeholders.
386
+ rule :def_parameter_list, '('.skip & ( :def_parameter >> ( ','.skip & :def_parameter ).zero_or_more ).optional & ')'.skip
387
+ rule :def_parameter, :assignment_expression | :identifier
176
388
 
389
+ rule :parameter_list, '('.skip & ( :parameter >> ( ','.skip & :parameter ).zero_or_more ).optional & ')'.skip
390
+ rule :parameter_list_without_parentheses, :parameter >> ( ','.skip & :parameter ).zero_or_more
391
+ rule :parameter, :placeholder | :ruby_expression
392
+
393
+ # placeholders may be in long form (${foo}) or short form ($foo)
394
+ rule :placeholder, :long_placeholder | :short_placeholder
395
+
396
+ # No whitespace allowed between the "$" and the opening "{"
397
+ rule :long_placeholder, '${'.skip & :placeholder_name & :placeholder_parameters.optional([]) & '}'.skip
398
+ node :long_placeholder, :placeholder
399
+ production :long_placeholder, :name, :params
400
+
401
+ # No whitespace allowed between the "$" and the placeholder_name
402
+ rule :short_placeholder, /\$(?![ \r\n\t\v])/.skip & :placeholder_name & :placeholder_parameters.optional([])
403
+ node :short_placeholder, :placeholder
404
+ production :short_placeholder, :name, :params
405
+
406
+ rule :placeholder_name, :identifier
407
+ rule :placeholder_parameters, '('.skip & (:placeholder_parameter >> (','.skip & :placeholder_parameter).zero_or_more).optional & ')'.skip
408
+ rule :placeholder_parameter, :placeholder | :ruby_expression
409
+
410
+ # simplified Ruby subset
411
+ rule :ruby_expression, :assignment_expression | :addition_expression | :unary_expression
412
+
413
+ rule :literal_expression, :string_literal |
414
+ :numeric_literal |
415
+ :array_literal |
416
+ :hash_literal |
417
+ :lvalue |
418
+ :symbol_literal
419
+ rule :unary_expression, :message_expression | :literal_expression
420
+
421
+ rule :lvalue, :class_variable | :instance_variable | :identifier | :constant
422
+
423
+ rule :array_literal, '['.skip & ( :ruby_expression >> (','.skip & :ruby_expression ).zero_or_more ).optional & ']'.skip
424
+ node :array_literal, :ruby_expression
425
+ production :array_literal, :elements
426
+
427
+ rule :hash_literal, '{'.skip & ( :hash_assignment >> (','.skip & :hash_assignment ).zero_or_more ).optional & '}'.skip
428
+ node :hash_literal, :ruby_expression
429
+ production :hash_literal, :pairs
430
+
431
+ rule :hash_assignment, :unary_expression & '=>'.skip & (:addition_expression | :unary_expression)
432
+ node :hash_assignment, :ruby_expression
433
+ production :hash_assignment, :lvalue, :expression
434
+
435
+ rule :assignment_expression, :lvalue & '='.skip & (:addition_expression | :unary_expression)
436
+ production :assignment_expression, :lvalue, :expression
437
+
438
+ # addition is left-associative (left-recursive)
439
+ rule :addition_expression, :addition_expression & '+'.skip & :unary_expression |
440
+ :unary_expression & '+'.skip & :unary_expression
441
+
442
+ node :addition_expression, :ruby_expression
443
+ production :addition_expression, :left, :right
444
+
445
+ # message expressions are left-associative (left-recursive)
446
+ rule :message_expression,
447
+ :message_expression & '.'.skip & :method_expression |
448
+ :literal_expression & '.'.skip & :method_expression
449
+ production :message_expression, :target, :message
450
+
451
+ rule :method_expression,
452
+ :method_with_parentheses | :method_without_parentheses
453
+ node :method_expression, :ruby_expression
454
+
455
+ rule :method_with_parentheses,
456
+ :identifier & :method_parameter_list.optional([])
457
+ node :method_with_parentheses, :method_expression
458
+ production :method_with_parentheses, :name, :params
459
+ rule :method_without_parentheses,
460
+ :identifier & :method_parameter_list_without_parentheses
461
+ node :method_without_parentheses, :method_expression
462
+ production :method_without_parentheses, :method_expression, :name, :params
463
+
464
+ rule :method_parameter_list,
465
+ '('.skip & ( :method_parameter >> ( ','.skip & :method_parameter ).zero_or_more ).optional & ')'.skip
466
+ rule :method_parameter,
467
+ :ruby_expression
468
+ rule :method_parameter_list_without_parentheses,
469
+ :method_parameter >> ( ','.skip & :method_parameter ).zero_or_more
470
+
471
+ rule :class_variable, '@@'.skip & :identifier
472
+ skipping :class_variable, nil
473
+ node :class_variable, :ruby_expression
474
+ production :class_variable
475
+
476
+ rule :instance_variable, '@'.skip & :identifier
477
+ skipping :instance_variable, nil
478
+ production :instance_variable
479
+
480
+ # TODO: regexp literal expression
481
+
482
+ # Ruby + allowing placeholders for unary expressions
483
+ rule :extended_ruby_expression,
484
+ :extended_unary_expression | :ruby_expression
485
+ rule :extended_unary_expression,
486
+ :placeholder | :unary_expression
487
+ end # class Grammar
488
+ end # module Walrus