walrus 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. data/bin/walrus +44 -0
  2. data/ext/jindex/extconf.rb +11 -0
  3. data/ext/jindex/jindex.c +79 -0
  4. data/ext/mkdtemp/extconf.rb +11 -0
  5. data/ext/mkdtemp/mkdtemp.c +41 -0
  6. data/lib/walrus/additions/module.rb +36 -0
  7. data/lib/walrus/additions/string.rb +37 -0
  8. data/lib/walrus/additions/test/unit/error_collector.rb +62 -0
  9. data/lib/walrus/compile_error.rb +28 -0
  10. data/lib/walrus/compiler.rb +124 -0
  11. data/lib/walrus/contrib/spec/walruscloth_spec.rb +32 -0
  12. data/lib/walrus/contrib/walruscloth.rb +82 -0
  13. data/lib/walrus/diff.rb +89 -0
  14. data/lib/walrus/document.rb +98 -0
  15. data/lib/walrus/grammar/additions/proc.rb +20 -0
  16. data/lib/walrus/grammar/additions/regexp.rb +21 -0
  17. data/lib/walrus/grammar/additions/string.rb +52 -0
  18. data/lib/walrus/grammar/additions/symbol.rb +42 -0
  19. data/lib/walrus/grammar/and_predicate.rb +40 -0
  20. data/lib/walrus/grammar/array_result.rb +19 -0
  21. data/lib/walrus/grammar/continuation_wrapper_exception.rb +28 -0
  22. data/lib/walrus/grammar/left_recursion_exception.rb +27 -0
  23. data/lib/walrus/grammar/location_tracking.rb +105 -0
  24. data/lib/walrus/grammar/match_data_wrapper.rb +65 -0
  25. data/lib/walrus/grammar/memoizing.rb +41 -0
  26. data/lib/walrus/grammar/memoizing_cache.rb +94 -0
  27. data/lib/walrus/grammar/node.rb +60 -0
  28. data/lib/walrus/grammar/not_predicate.rb +40 -0
  29. data/lib/walrus/grammar/parse_error.rb +39 -0
  30. data/lib/walrus/grammar/parser_state.rb +181 -0
  31. data/lib/walrus/grammar/parslet.rb +28 -0
  32. data/lib/walrus/grammar/parslet_choice.rb +120 -0
  33. data/lib/walrus/grammar/parslet_combination.rb +26 -0
  34. data/lib/walrus/grammar/parslet_combining.rb +154 -0
  35. data/lib/walrus/grammar/parslet_merge.rb +88 -0
  36. data/lib/walrus/grammar/parslet_omission.rb +57 -0
  37. data/lib/walrus/grammar/parslet_repetition.rb +97 -0
  38. data/lib/walrus/grammar/parslet_repetition_default.rb +58 -0
  39. data/lib/walrus/grammar/parslet_sequence.rb +202 -0
  40. data/lib/walrus/grammar/predicate.rb +57 -0
  41. data/lib/walrus/grammar/proc_parslet.rb +52 -0
  42. data/lib/walrus/grammar/regexp_parslet.rb +73 -0
  43. data/lib/walrus/grammar/skipped_substring_exception.rb +36 -0
  44. data/lib/walrus/grammar/string_enumerator.rb +45 -0
  45. data/lib/walrus/grammar/string_parslet.rb +75 -0
  46. data/lib/walrus/grammar/string_result.rb +24 -0
  47. data/lib/walrus/grammar/symbol_parslet.rb +63 -0
  48. data/lib/walrus/grammar.rb +170 -0
  49. data/lib/walrus/no_parameter_marker.rb +19 -0
  50. data/lib/walrus/parser.rb +420 -0
  51. data/lib/walrus/runner.rb +356 -0
  52. data/lib/walrus/template.rb +75 -0
  53. data/lib/walrus/walrus_grammar/assignment_expression.rb +24 -0
  54. data/lib/walrus/walrus_grammar/block_directive.rb +28 -0
  55. data/lib/walrus/walrus_grammar/comment.rb +24 -0
  56. data/lib/walrus/walrus_grammar/def_directive.rb +64 -0
  57. data/lib/walrus/walrus_grammar/echo_directive.rb +44 -0
  58. data/lib/walrus/walrus_grammar/escape_sequence.rb +24 -0
  59. data/lib/walrus/walrus_grammar/import_directive.rb +44 -0
  60. data/lib/walrus/walrus_grammar/include_directive.rb +27 -0
  61. data/lib/walrus/walrus_grammar/instance_variable.rb +24 -0
  62. data/lib/walrus/walrus_grammar/literal.rb +24 -0
  63. data/lib/walrus/walrus_grammar/message_expression.rb +25 -0
  64. data/lib/walrus/walrus_grammar/multiline_comment.rb +54 -0
  65. data/lib/walrus/walrus_grammar/placeholder.rb +40 -0
  66. data/lib/walrus/walrus_grammar/raw_directive.rb +42 -0
  67. data/lib/walrus/walrus_grammar/raw_text.rb +45 -0
  68. data/lib/walrus/walrus_grammar/ruby_directive.rb +29 -0
  69. data/lib/walrus/walrus_grammar/ruby_expression.rb +31 -0
  70. data/lib/walrus/walrus_grammar/set_directive.rb +24 -0
  71. data/lib/walrus/walrus_grammar/silent_directive.rb +44 -0
  72. data/lib/walrus/walrus_grammar/slurp_directive.rb +25 -0
  73. data/lib/walrus/walrus_grammar/super_directive.rb +27 -0
  74. data/lib/walrus.rb +64 -0
  75. data/spec/acceptance/acceptance_spec.rb +97 -0
  76. data/spec/acceptance/block/basic_block.expected +1 -0
  77. data/spec/acceptance/block/basic_block.tmpl +3 -0
  78. data/spec/acceptance/block/nested_blocks.expected +5 -0
  79. data/spec/acceptance/block/nested_blocks.tmpl +11 -0
  80. data/spec/acceptance/comments/comments_and_text.expected +3 -0
  81. data/spec/acceptance/comments/comments_and_text.tmpl +6 -0
  82. data/spec/acceptance/comments/single_comment.expected +0 -0
  83. data/spec/acceptance/comments/single_comment.tmpl +1 -0
  84. data/spec/acceptance/def/alternative_def_calling_conventions.expected +3 -0
  85. data/spec/acceptance/def/alternative_def_calling_conventions.tmpl +18 -0
  86. data/spec/acceptance/def/basic_def_block_no_output.expected +0 -0
  87. data/spec/acceptance/def/basic_def_block_no_output.tmpl +17 -0
  88. data/spec/acceptance/def/defs_can_be_called_multiple_times.expected +3 -0
  89. data/spec/acceptance/def/defs_can_be_called_multiple_times.tmpl +6 -0
  90. data/spec/acceptance/def/defs_can_be_dynamic.expected +4 -0
  91. data/spec/acceptance/def/defs_can_be_dynamic.tmpl +12 -0
  92. data/spec/acceptance/echo/echo_directive_with_numeric_literal.expected +1 -0
  93. data/spec/acceptance/echo/echo_directive_with_numeric_literal.tmpl +1 -0
  94. data/spec/acceptance/echo/echo_expression_list.expected +1 -0
  95. data/spec/acceptance/echo/echo_expression_list.tmpl +1 -0
  96. data/spec/acceptance/echo/echo_short_notation.expected +1 -0
  97. data/spec/acceptance/echo/echo_short_notation.tmpl +1 -0
  98. data/spec/acceptance/echo/echo_simple_expression.expected +1 -0
  99. data/spec/acceptance/echo/echo_simple_expression.tmpl +1 -0
  100. data/spec/acceptance/echo/echo_single_quoted_string_literal.expected +1 -0
  101. data/spec/acceptance/echo/echo_single_quoted_string_literal.tmpl +1 -0
  102. data/spec/acceptance/echo/multiple_echo_statements.expected +1 -0
  103. data/spec/acceptance/echo/multiple_echo_statements.tmpl +2 -0
  104. data/spec/acceptance/includes/basic_included_file.txt +1 -0
  105. data/spec/acceptance/includes/basic_includer.complex +3 -0
  106. data/spec/acceptance/includes/basic_includer.expected +3 -0
  107. data/spec/acceptance/includes/basic_includer.rb +38 -0
  108. data/spec/acceptance/includes/complicated_included_file.txt +3 -0
  109. data/spec/acceptance/includes/complicated_includer.complex +3 -0
  110. data/spec/acceptance/includes/complicated_includer.expected +3 -0
  111. data/spec/acceptance/includes/complicated_includer.rb +41 -0
  112. data/spec/acceptance/includes/nested_include_1.txt +3 -0
  113. data/spec/acceptance/includes/nested_include_2.txt +1 -0
  114. data/spec/acceptance/includes/nested_includer.complex +3 -0
  115. data/spec/acceptance/includes/nested_includer.expected +4 -0
  116. data/spec/acceptance/includes/nested_includer.rb +41 -0
  117. data/spec/acceptance/inheritance/basic_child.complex +10 -0
  118. data/spec/acceptance/inheritance/basic_child.expected +9 -0
  119. data/spec/acceptance/inheritance/basic_child.rb +54 -0
  120. data/spec/acceptance/inheritance/basic_parent.complex +5 -0
  121. data/spec/acceptance/inheritance/basic_parent.expected +3 -0
  122. data/spec/acceptance/inheritance/basic_parent.rb +41 -0
  123. data/spec/acceptance/inheritance/importing_child.complex +8 -0
  124. data/spec/acceptance/inheritance/importing_child.expected +7 -0
  125. data/spec/acceptance/inheritance/importing_child.rb +46 -0
  126. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.complex +8 -0
  127. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.expected +7 -0
  128. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.rb +44 -0
  129. data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.expected +0 -0
  130. data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.tmpl +15 -0
  131. data/spec/acceptance/multiline_comments/simple_multiline_comment.expected +2 -0
  132. data/spec/acceptance/multiline_comments/simple_multiline_comment.tmpl +4 -0
  133. data/spec/acceptance/raw/complicated_raw_example.expected +57 -0
  134. data/spec/acceptance/raw/complicated_raw_example.tmpl +79 -0
  135. data/spec/acceptance/raw-text/UTF_8.expected +12 -0
  136. data/spec/acceptance/raw-text/UTF_8.tmpl +12 -0
  137. data/spec/acceptance/raw-text/empty_file.expected +0 -0
  138. data/spec/acceptance/raw-text/empty_file.tmpl +0 -0
  139. data/spec/acceptance/raw-text/multi_line.expected +4 -0
  140. data/spec/acceptance/raw-text/multi_line.tmpl +4 -0
  141. data/spec/acceptance/raw-text/single_line.expected +1 -0
  142. data/spec/acceptance/raw-text/single_line.tmpl +1 -0
  143. data/spec/acceptance/raw-text/single_line_whitespace.expected +1 -0
  144. data/spec/acceptance/raw-text/single_line_whitespace.tmpl +1 -0
  145. data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.expected +1 -0
  146. data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.tmpl +4 -0
  147. data/spec/acceptance/ruby/ruby_directive_using_here_doc.expected +1 -0
  148. data/spec/acceptance/ruby/ruby_directive_using_here_doc.tmpl +4 -0
  149. data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.expected +1 -0
  150. data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.tmpl +4 -0
  151. data/spec/acceptance/ruby/ruby_directive_with_accumulate.expected +1 -0
  152. data/spec/acceptance/ruby/ruby_directive_with_accumulate.tmpl +4 -0
  153. data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.expected +1 -0
  154. data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.tmpl +6 -0
  155. data/spec/acceptance/set/unused_set.expected +0 -0
  156. data/spec/acceptance/set/unused_set.tmpl +1 -0
  157. data/spec/acceptance/set/used_set.expected +1 -0
  158. data/spec/acceptance/set/used_set.tmpl +2 -0
  159. data/spec/acceptance/silent/silent_and_echo_combined.expected +1 -0
  160. data/spec/acceptance/silent/silent_and_echo_combined.tmpl +2 -0
  161. data/spec/acceptance/silent/silent_short_notation.expected +1 -0
  162. data/spec/acceptance/silent/silent_short_notation.tmpl +1 -0
  163. data/spec/acceptance/silent/simple_silent_directive.expected +0 -0
  164. data/spec/acceptance/silent/simple_silent_directive.tmpl +1 -0
  165. data/spec/acceptance/slurp/basic_slurp_demo.expected +1 -0
  166. data/spec/acceptance/slurp/basic_slurp_demo.tmpl +4 -0
  167. data/spec/acceptance/super/super_with_no_effect.expected +4 -0
  168. data/spec/acceptance/super/super_with_no_effect.tmpl +5 -0
  169. data/spec/additions/module_spec.rb +126 -0
  170. data/spec/additions/string_spec.rb +99 -0
  171. data/spec/compiler_spec.rb +55 -0
  172. data/spec/grammar/additions/proc_spec.rb +25 -0
  173. data/spec/grammar/additions/regexp_spec.rb +37 -0
  174. data/spec/grammar/additions/string_spec.rb +106 -0
  175. data/spec/grammar/and_predicate_spec.rb +29 -0
  176. data/spec/grammar/continuation_wrapper_exception_spec.rb +23 -0
  177. data/spec/grammar/match_data_wrapper_spec.rb +41 -0
  178. data/spec/grammar/memoizing_cache_spec.rb +112 -0
  179. data/spec/grammar/node_spec.rb +126 -0
  180. data/spec/grammar/not_predicate_spec.rb +29 -0
  181. data/spec/grammar/parser_state_spec.rb +172 -0
  182. data/spec/grammar/parslet_choice_spec.rb +49 -0
  183. data/spec/grammar/parslet_combining_spec.rb +287 -0
  184. data/spec/grammar/parslet_merge_spec.rb +33 -0
  185. data/spec/grammar/parslet_omission_spec.rb +58 -0
  186. data/spec/grammar/parslet_repetition_spec.rb +77 -0
  187. data/spec/grammar/parslet_sequence_spec.rb +49 -0
  188. data/spec/grammar/parslet_spec.rb +23 -0
  189. data/spec/grammar/predicate_spec.rb +53 -0
  190. data/spec/grammar/proc_parslet_spec.rb +52 -0
  191. data/spec/grammar/regexp_parslet_spec.rb +347 -0
  192. data/spec/grammar/string_enumerator_spec.rb +94 -0
  193. data/spec/grammar/string_parslet_spec.rb +143 -0
  194. data/spec/grammar/symbol_parslet_spec.rb +30 -0
  195. data/spec/grammar_spec.rb +545 -0
  196. data/spec/parser_spec.rb +1418 -0
  197. data/spec/spec_helper.rb +34 -0
  198. data/spec/walrus_grammar/comment_spec.rb +39 -0
  199. data/spec/walrus_grammar/echo_directive_spec.rb +63 -0
  200. data/spec/walrus_grammar/escape_sequence_spec.rb +85 -0
  201. data/spec/walrus_grammar/literal_spec.rb +41 -0
  202. data/spec/walrus_grammar/message_expression_spec.rb +37 -0
  203. data/spec/walrus_grammar/multiline_comment_spec.rb +58 -0
  204. data/spec/walrus_grammar/placeholder_spec.rb +48 -0
  205. data/spec/walrus_grammar/raw_directive_spec.rb +81 -0
  206. data/spec/walrus_grammar/raw_text_spec.rb +65 -0
  207. data/spec/walrus_grammar/silent_directive_spec.rb +34 -0
  208. metadata +291 -0
@@ -0,0 +1,57 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ # Predicates parse input without consuming it.
15
+ # On success they throw a subclass-specific symbol (see the AndPredicate and NotPredicate classes).
16
+ # On failure they raise a ParseError.
17
+ class Predicate
18
+
19
+ include Walrus::Grammar::ParsletCombining
20
+ include Walrus::Grammar::Memoizing
21
+
22
+ attr_reader :hash
23
+
24
+ # Raises if parseable is nil.
25
+ def initialize(parseable)
26
+ raise ArgumentError if parseable.nil?
27
+ @parseable = parseable
28
+ @hash = @parseable.hash + hash_offset # fixed offset to avoid collisions with @parseable objects
29
+ end
30
+
31
+ def to_parseable
32
+ self
33
+ end
34
+
35
+ def parse(string, options = {})
36
+ raise NotImplementedError # subclass responsibility
37
+ end
38
+
39
+ def eql?(other)
40
+ other.instance_of? self.class and other.parseable.eql? @parseable
41
+ end
42
+
43
+ protected
44
+
45
+ # for equality comparisons
46
+ attr_reader :parseable
47
+
48
+ private
49
+
50
+ def hash_offset
51
+ 10
52
+ end
53
+
54
+ end
55
+
56
+ end # class Grammar
57
+ end # module Walrus
@@ -0,0 +1,52 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class ProcParslet < Parslet
15
+
16
+ attr_reader :hash
17
+
18
+ def initialize(proc)
19
+ raise ArgumentError if proc.nil?
20
+ self.expected_proc = proc
21
+ end
22
+
23
+ def parse(string, options = {})
24
+ raise ArgumentError if string.nil?
25
+ @expected_proc.call string, options
26
+ end
27
+
28
+ def eql?(other)
29
+ other.instance_of? ProcParslet and other.expected_proc == @expected_proc
30
+ end
31
+
32
+ protected
33
+
34
+ # For equality comparisons.
35
+ attr_reader :expected_proc
36
+
37
+ private
38
+
39
+ def expected_proc=(proc)
40
+ @expected_proc = ( proc.clone rescue proc )
41
+ update_hash
42
+ end
43
+
44
+ def update_hash
45
+ @hash = @expected_proc.hash + 105 # fixed offset to avoid collisions with @parseable objects
46
+ end
47
+
48
+ end # class ProcParslet
49
+
50
+ end # class Grammar
51
+ end # module Walrus
52
+
@@ -0,0 +1,73 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class RegexpParslet < Parslet
15
+
16
+ attr_reader :hash
17
+
18
+ def initialize(regexp)
19
+ raise ArgumentError if regexp.nil?
20
+ self.expected_regexp = /\A#{regexp}/ # for efficiency, anchor all regexps to the start of the string
21
+ end
22
+
23
+ def parse(string, options = {})
24
+ raise ArgumentError if string.nil?
25
+ if (string =~ @expected_regexp)
26
+ wrapper = MatchDataWrapper.new($~)
27
+ match = $~[0]
28
+
29
+ if (line_count = match.scan(/\r\n|\r|\n/).length) != 0 # count number of newlines in match
30
+ column_end = match.jlength - match.jrindex(/\r|\n/) - 1 # calculate characters on last line
31
+ else # no newlines in match
32
+ column_end = match.jlength + (options[:column_start] || 0)
33
+ end
34
+
35
+ wrapper.start = [options[:line_start], options[:column_start]]
36
+ wrapper.end = [wrapper.line_start + line_count, column_end]
37
+ wrapper.source_text = match.to_s.clone
38
+ wrapper
39
+ else
40
+ raise ParseError.new('non-matching characters "%s" while parsing regular expression "%s"' % [string, @expected_regexp.inspect],
41
+ :line_end => (options[:line_start] || 0), :column_end => (options[:column_start] || 0))
42
+ end
43
+ end
44
+
45
+ def eql?(other)
46
+ other.instance_of? RegexpParslet and other.expected_regexp == @expected_regexp
47
+ end
48
+
49
+ def inspect
50
+ '#<%s:0x%x @expected_regexp=%s>' % [self.class.to_s, self.object_id, @expected_regexp.inspect]
51
+ end
52
+
53
+ protected
54
+
55
+ # For equality comparisons.
56
+ attr_reader :expected_regexp
57
+
58
+ private
59
+
60
+ def expected_regexp=(regexp)
61
+ @expected_regexp = ( regexp.clone rescue regexp )
62
+ update_hash
63
+ end
64
+
65
+ def update_hash
66
+ @hash = @expected_regexp.hash + 15 # fixed offset to avoid collisions with @parseable objects
67
+ end
68
+
69
+ end # class RegexpParslet
70
+
71
+ end # class Grammar
72
+ end # module Walrus
73
+
@@ -0,0 +1,36 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ # I don't really like using Exceptions for non-error situations, but it seems that using throw/catch here would not be adequate (not possible to embed information in the thrown symbol).
15
+ class SkippedSubstringException < Exception
16
+
17
+ include Walrus::Grammar::LocationTracking
18
+
19
+ def initialize(substring, info = {})
20
+ super substring
21
+
22
+ # TODO: this code is just like the code in ParseError. could save repeating it by setting up inheritance
23
+ # but would need to pay careful attention to the ordering of my rescue blocks
24
+ # and also change many instances of "kind_of" in my specs to "instance_of "
25
+ # alternatively, could look at using a mix-in
26
+ self.line_start = info[:line_start]
27
+ self.column_start = info[:column_start]
28
+ self.line_end = info[:line_end]
29
+ self.column_end = info[:column_end]
30
+ end
31
+
32
+ end # class SkippedSubstringException
33
+
34
+ end # class Grammar
35
+ end # module Walrus
36
+
@@ -0,0 +1,45 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'strscan'
10
+ require 'walrus'
11
+
12
+ module Walrus
13
+ class Grammar
14
+
15
+ # Unicode-aware (UTF-8) string enumerator.
16
+ # For Unicode support $KCODE must be set to 'U' (UTF-8).
17
+ class StringEnumerator
18
+
19
+ # Returns the char most recently scanned before the last "next" call, or nil if nothing previously scanned.
20
+ attr_reader :last
21
+
22
+ def initialize(string)
23
+ raise ArgumentError if string.nil?
24
+ @scanner = StringScanner.new(string)
25
+ @current = nil
26
+ @last = nil
27
+ end
28
+
29
+ # This method will only work as expected if $KCODE is set to 'U' (UTF-8).
30
+ def next
31
+ @last = @current
32
+ @current = @scanner.scan(/./m) # must use multiline mode or "." won't match newlines
33
+ end
34
+
35
+ # Take a peek at the next character without actually consuming it. Returns nil if there is no next character.
36
+ # TODO: consider deleting this method as it's not currently used.
37
+ def peek
38
+ if char = self.next : @scanner.unscan; end
39
+ char
40
+ end
41
+
42
+ end # class StringEnumerator
43
+
44
+ end # class Grammar
45
+ end # module Walrus
@@ -0,0 +1,75 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class StringParslet < Parslet
15
+
16
+ attr_reader :hash
17
+
18
+ def initialize(string)
19
+ raise ArgumentError if string.nil?
20
+ self.expected_string = string
21
+ end
22
+
23
+ def parse(string, options = {})
24
+ raise ArgumentError if string.nil?
25
+ chars = StringEnumerator.new(string)
26
+ parsed = StringResult.new
27
+ parsed.start = [options[:line_start], options[:column_start]]
28
+ parsed.end = parsed.start
29
+ expected_string.each_char do |expected_char|
30
+ actual_char = chars.next
31
+ if actual_char.nil?
32
+ raise ParseError.new('unexpected end-of-string (expected "%s") while parsing "%s"' % [ expected_char, expected_string ],
33
+ :line_end => parsed.line_end, :column_end => parsed.column_end)
34
+ elsif actual_char != expected_char
35
+ raise ParseError.new('unexpected character "%s" (expected "%s") while parsing "%s"' % [ actual_char, expected_char, expected_string],
36
+ :line_end => parsed.line_end, :column_end => parsed.column_end)
37
+ else
38
+ if actual_char == "\r" or (actual_char == "\n" and chars.last != "\r") # catches Mac, Windows and UNIX end-of-line markers
39
+ parsed.column_end = 0
40
+ parsed.line_end = parsed.line_end + 1
41
+ elsif actual_char != "\n" # \n is ignored if it is preceded by an \r (already counted above)
42
+ parsed.column_end = parsed.column_end + 1 # everything else gets counted
43
+ end
44
+ parsed << actual_char
45
+ end
46
+ end
47
+ parsed.source_text = parsed.to_s.clone
48
+ parsed
49
+ end
50
+
51
+ def eql?(other)
52
+ other.instance_of? StringParslet and other.expected_string == @expected_string
53
+ end
54
+
55
+ protected
56
+
57
+ # For equality comparisons.
58
+ attr_reader :expected_string
59
+
60
+ private
61
+
62
+ def expected_string=(string)
63
+ @expected_string = ( string.clone rescue string )
64
+ update_hash
65
+ end
66
+
67
+ def update_hash
68
+ @hash = @expected_string.hash + 20 # fixed offset to avoid collisions with @parseable objects
69
+ end
70
+
71
+ end # class StringParslet
72
+
73
+ end # class Grammar
74
+ end # module Walrus
75
+
@@ -0,0 +1,24 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+ class StringResult < String
14
+
15
+ include Walrus::Grammar::LocationTracking
16
+
17
+ def initialize(string = "")
18
+ self.source_text = string
19
+ super
20
+ end
21
+
22
+ end # class StringResult
23
+ end # class Grammar
24
+ end # module Walrus
@@ -0,0 +1,63 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ # A SymbolParslet allows for evaluation of a parslet to be deferred until runtime (or parse time, to be more precise).
15
+ class SymbolParslet < Parslet
16
+
17
+ attr_reader :hash
18
+
19
+ def initialize(symbol)
20
+ raise ArgumentError if symbol.nil?
21
+ @symbol = symbol
22
+ @hash = @symbol.hash + 20 # fixed offset to avoid collisions with @parseable objects
23
+ end
24
+
25
+ # SymbolParslets don't actually know what Grammar they are associated with at the time of their definition. They expect the Grammar to be passed in with the options hash under the ":grammar" key.
26
+ # Raises if string is nil, or if the options hash does not include a :grammar key.
27
+ def parse(string, options = {})
28
+ raise ArgumentError if string.nil?
29
+ raise ArgumentError unless options.has_key?(:grammar)
30
+ grammar = options[:grammar]
31
+ augmented_options = options.clone
32
+ augmented_options[:rule_name] = @symbol
33
+ augmented_options[:skipping_override] = grammar.skipping_overrides[@symbol] if grammar.skipping_overrides.has_key?(@symbol)
34
+ result = grammar.rules[@symbol].memoizing_parse(string, augmented_options)
35
+ grammar.wrap(result, @symbol)
36
+ end
37
+
38
+ # We override the to_s method as it can make parsing error messages more readable. Instead of messages like this:
39
+ # predicate not satisfied (expected "#<Walrus::Grammar::SymbolParslet:0x10cd504>") while parsing "hello world"
40
+ # We can print messages like this:
41
+ # predicate not satisfied (expected "rule: end_of_input") while parsing "hello world"
42
+ def to_s
43
+ 'rule: ' + @symbol.to_s
44
+ end
45
+
46
+ def ==(other)
47
+ eql?(other)
48
+ end
49
+
50
+ def eql?(other)
51
+ other.instance_of? SymbolParslet and other.symbol == @symbol
52
+ end
53
+
54
+ protected
55
+
56
+ # For equality comparisons.
57
+ attr_reader :symbol
58
+
59
+ end # class SymbolParslet
60
+
61
+ end # class Grammar
62
+ end # module Walrus
63
+
@@ -0,0 +1,170 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id: /mirrors/Walrus/trunk/walrus/lib/walrus/grammar.rb 6704 2007-04-09T18:30:00.421185Z wincent $
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ attr_accessor :memoizing
15
+ attr_reader :rules
16
+ attr_reader :skipping_overrides
17
+
18
+ # 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.
19
+ # The subclass_name parameter should be a String.
20
+ def self.subclass(subclass_name, &block)
21
+ raise ArgumentError if subclass_name.nil?
22
+ raise ArgumentError if Walrus::const_defined?(subclass_name)
23
+ Walrus::const_set(subclass_name, Class.new(Grammar))
24
+ subclass = Walrus::module_eval(subclass_name)
25
+ begin
26
+ subclass.new(&block)
27
+ rescue ContinuationWrapperException => c # a Symbol in a production rule wants to know what namespace its being used in
28
+ c.continuation.call(subclass)
29
+ end
30
+ end
31
+
32
+ def initialize(&block)
33
+ @rules = Hash.new { |hash, key| raise StandardError.new('no rule for key "%s"' % key.to_s) }
34
+ @productions = Hash.new { |hash, key| raise StandardError.new('no production for key "%s"' % key.to_s) }
35
+ @skipping_overrides = Hash.new { |hash, key| raise StandardError.new('no skipping override for key "%s"' % key.to_s) }
36
+ @memoizing = true
37
+ self.instance_eval(&block) if block_given?
38
+ end
39
+
40
+ # TODO: consider making grammars copiable (could be used in threaded context then)
41
+ #def initialize_copy(from); end
42
+ #def clone; end
43
+ #def dupe; end
44
+
45
+ # Starts with starting_symbol.
46
+ def parse(string, options = {})
47
+ raise ArgumentError if string.nil?
48
+ raise StandardError if @starting_symbol.nil?
49
+ options[:grammar] = self
50
+ options[:rule_name] = @starting_symbol
51
+ options[:skipping] = @skipping
52
+ options[:line_start] = 0 # "richer" information (more human-friendly) than that provided in "location"
53
+ options[:column_start] = 0 # "richer" information (more human-friendly) than that provided in "location"
54
+ options[:memoizer] = MemoizingCache.new if @memoizing
55
+ @starting_symbol.to_parseable.memoizing_parse(string, options)
56
+ end
57
+
58
+ # Defines a rule and stores it
59
+ # Expects an object that responds to the parse message, such as a Parslet or ParsletCombination.
60
+ # 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.
61
+ def rule(symbol, parseable)
62
+ raise ArgumentError if symbol.nil?
63
+ raise ArgumentError if parseable.nil?
64
+ raise ArgumentError if @rules.has_key?(symbol)
65
+ @rules[symbol] = parseable
66
+ end
67
+
68
+ # Dynamically creates a Node subclass inside the namespace of the current grammar. If parent_class is nil, Node is assumed.
69
+ # new_class_name must not be nil.
70
+ def node(new_class_name, parent_class = nil, *attributes)
71
+ raise ArgumentError if new_class_name.nil?
72
+ new_class_name = new_class_name.to_s.to_class_name # camel-case
73
+ if parent_class.nil?
74
+ Node.subclass(new_class_name, self.class, *attributes)
75
+ else
76
+ # convert parent_class to string, then camel case, then back to Symbol, then lookup the constant
77
+ self.class.const_get(parent_class.to_s.to_class_name.to_s).subclass(new_class_name, self.class, *attributes)
78
+ end
79
+ end
80
+
81
+ # Specifies the Node subclass that will be used to encapsulate results for the rule identified by the symbol, rule_name.
82
+ # 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.
83
+ # rule_name must not be nil.
84
+ #
85
+ # Example; specifying that the results of rule "string_literal" should be encapsulated in a "StringLiteral" instance:
86
+ #
87
+ # production :string_literal
88
+ #
89
+ # Example; specifying that the results of the rule "numeric_literal" should be encapsulated into a "RawToken" instance:
90
+ #
91
+ # production :numeric_literal, :raw_token
92
+ #
93
+ # 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":
94
+ #
95
+ # production :assignment_expression.build(:expression, :target, :value)
96
+ #
97
+ def production(rule_name, class_symbol = nil)
98
+ raise ArgumentError if rule_name.nil?
99
+ raise ArgumentError if @productions.has_key?(rule_name)
100
+ raise ArgumentError unless @rules.has_key?(rule_name)
101
+ class_symbol = rule_name if class_symbol.nil?
102
+ @productions[rule_name] = class_symbol
103
+ end
104
+
105
+ def wrap(result, rule_name)
106
+ if @productions.has_key?(rule_name.to_sym) # figure out arity of "initialize" method and wrap results in AST node
107
+ node_class = self.class.const_get(@productions[rule_name.to_sym].to_s.to_class_name)
108
+ param_count = node_class.instance_method(:initialize).arity
109
+ raise if param_count < 1
110
+
111
+ # dynamically build up a message send
112
+ if param_count == 1
113
+ params = 'result'
114
+ else
115
+ params = 'result[0]'
116
+ for i in 1..(param_count - 1)
117
+ params << ", result[#{i.to_s}]"
118
+ end
119
+ end
120
+
121
+ node = node_class.class_eval('new(%s)' % params)
122
+ node.start = (result.outer_start or result.start) # propagate the start information
123
+ node.end = (result.outer_end or result.end) # and the end information
124
+ node.source_text = (result.outer_source_text or result.source_text) # and the original source text
125
+ node
126
+ else
127
+ result.start = result.outer_start if result.outer_start
128
+ result.end = result.outer_end if result.outer_end
129
+ result.source_text = result.source_text if result.outer_source_text
130
+ result
131
+ end
132
+ end
133
+
134
+ # Sets the starting symbol.
135
+ # symbol must refer to a rule.
136
+ def starting_symbol(symbol)
137
+ @starting_symbol = symbol
138
+ end
139
+
140
+ # 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.
141
+ # This allows for simpler grammars which do not need to explicitly put optional whitespace parslets (or any other kind of parslet) between elements.
142
+ #
143
+ # 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.
144
+ #
145
+ # 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.
146
+ #
147
+ # 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.
148
+ # The initial, conservative implementation only performs this fallback skipping for ParsletSequence and ParsletRepetition combinations.
149
+ #
150
+ # Raises if rule_or_parslet is nil.
151
+ def skipping(rule_or_parslet, parslet = NoParameterMarker.instance)
152
+ raise ArgumentError if rule_or_parslet.nil?
153
+ if parslet == NoParameterMarker.instance # first mode of operation: set default parslet
154
+ raise if @skipping # should not set a default skipping parslet twice
155
+ @skipping = rule_or_parslet
156
+ else # second mode of operation: override default case
157
+ raise ArgumentError if @skipping_overrides.has_key?(rule_or_parslet)
158
+ raise ArgumentError unless @rules.has_key?(rule_or_parslet)
159
+ @skipping_overrides[rule_or_parslet] = parslet
160
+ end
161
+ end
162
+
163
+ # TODO: pretty print method?
164
+
165
+ end # class Grammar
166
+ end # module Walrus
167
+
168
+
169
+
170
+
@@ -0,0 +1,19 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+
13
+ class NoParameterMarker
14
+ require 'singleton'
15
+ include Singleton
16
+ end
17
+
18
+ end # module Walrus
19
+