walrus 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/walrus +44 -0
- data/ext/jindex/extconf.rb +11 -0
- data/ext/jindex/jindex.c +79 -0
- data/ext/mkdtemp/extconf.rb +11 -0
- data/ext/mkdtemp/mkdtemp.c +41 -0
- data/lib/walrus/additions/module.rb +36 -0
- data/lib/walrus/additions/string.rb +37 -0
- data/lib/walrus/additions/test/unit/error_collector.rb +62 -0
- data/lib/walrus/compile_error.rb +28 -0
- data/lib/walrus/compiler.rb +124 -0
- data/lib/walrus/contrib/spec/walruscloth_spec.rb +32 -0
- data/lib/walrus/contrib/walruscloth.rb +82 -0
- data/lib/walrus/diff.rb +89 -0
- data/lib/walrus/document.rb +98 -0
- data/lib/walrus/grammar/additions/proc.rb +20 -0
- data/lib/walrus/grammar/additions/regexp.rb +21 -0
- data/lib/walrus/grammar/additions/string.rb +52 -0
- data/lib/walrus/grammar/additions/symbol.rb +42 -0
- data/lib/walrus/grammar/and_predicate.rb +40 -0
- data/lib/walrus/grammar/array_result.rb +19 -0
- data/lib/walrus/grammar/continuation_wrapper_exception.rb +28 -0
- data/lib/walrus/grammar/left_recursion_exception.rb +27 -0
- data/lib/walrus/grammar/location_tracking.rb +105 -0
- data/lib/walrus/grammar/match_data_wrapper.rb +65 -0
- data/lib/walrus/grammar/memoizing.rb +41 -0
- data/lib/walrus/grammar/memoizing_cache.rb +94 -0
- data/lib/walrus/grammar/node.rb +60 -0
- data/lib/walrus/grammar/not_predicate.rb +40 -0
- data/lib/walrus/grammar/parse_error.rb +39 -0
- data/lib/walrus/grammar/parser_state.rb +181 -0
- data/lib/walrus/grammar/parslet.rb +28 -0
- data/lib/walrus/grammar/parslet_choice.rb +120 -0
- data/lib/walrus/grammar/parslet_combination.rb +26 -0
- data/lib/walrus/grammar/parslet_combining.rb +154 -0
- data/lib/walrus/grammar/parslet_merge.rb +88 -0
- data/lib/walrus/grammar/parslet_omission.rb +57 -0
- data/lib/walrus/grammar/parslet_repetition.rb +97 -0
- data/lib/walrus/grammar/parslet_repetition_default.rb +58 -0
- data/lib/walrus/grammar/parslet_sequence.rb +202 -0
- data/lib/walrus/grammar/predicate.rb +57 -0
- data/lib/walrus/grammar/proc_parslet.rb +52 -0
- data/lib/walrus/grammar/regexp_parslet.rb +73 -0
- data/lib/walrus/grammar/skipped_substring_exception.rb +36 -0
- data/lib/walrus/grammar/string_enumerator.rb +45 -0
- data/lib/walrus/grammar/string_parslet.rb +75 -0
- data/lib/walrus/grammar/string_result.rb +24 -0
- data/lib/walrus/grammar/symbol_parslet.rb +63 -0
- data/lib/walrus/grammar.rb +170 -0
- data/lib/walrus/no_parameter_marker.rb +19 -0
- data/lib/walrus/parser.rb +420 -0
- data/lib/walrus/runner.rb +356 -0
- data/lib/walrus/template.rb +75 -0
- data/lib/walrus/walrus_grammar/assignment_expression.rb +24 -0
- data/lib/walrus/walrus_grammar/block_directive.rb +28 -0
- data/lib/walrus/walrus_grammar/comment.rb +24 -0
- data/lib/walrus/walrus_grammar/def_directive.rb +64 -0
- data/lib/walrus/walrus_grammar/echo_directive.rb +44 -0
- data/lib/walrus/walrus_grammar/escape_sequence.rb +24 -0
- data/lib/walrus/walrus_grammar/import_directive.rb +44 -0
- data/lib/walrus/walrus_grammar/include_directive.rb +27 -0
- data/lib/walrus/walrus_grammar/instance_variable.rb +24 -0
- data/lib/walrus/walrus_grammar/literal.rb +24 -0
- data/lib/walrus/walrus_grammar/message_expression.rb +25 -0
- data/lib/walrus/walrus_grammar/multiline_comment.rb +54 -0
- data/lib/walrus/walrus_grammar/placeholder.rb +40 -0
- data/lib/walrus/walrus_grammar/raw_directive.rb +42 -0
- data/lib/walrus/walrus_grammar/raw_text.rb +45 -0
- data/lib/walrus/walrus_grammar/ruby_directive.rb +29 -0
- data/lib/walrus/walrus_grammar/ruby_expression.rb +31 -0
- data/lib/walrus/walrus_grammar/set_directive.rb +24 -0
- data/lib/walrus/walrus_grammar/silent_directive.rb +44 -0
- data/lib/walrus/walrus_grammar/slurp_directive.rb +25 -0
- data/lib/walrus/walrus_grammar/super_directive.rb +27 -0
- data/lib/walrus.rb +64 -0
- data/spec/acceptance/acceptance_spec.rb +97 -0
- data/spec/acceptance/block/basic_block.expected +1 -0
- data/spec/acceptance/block/basic_block.tmpl +3 -0
- data/spec/acceptance/block/nested_blocks.expected +5 -0
- data/spec/acceptance/block/nested_blocks.tmpl +11 -0
- data/spec/acceptance/comments/comments_and_text.expected +3 -0
- data/spec/acceptance/comments/comments_and_text.tmpl +6 -0
- data/spec/acceptance/comments/single_comment.expected +0 -0
- data/spec/acceptance/comments/single_comment.tmpl +1 -0
- data/spec/acceptance/def/alternative_def_calling_conventions.expected +3 -0
- data/spec/acceptance/def/alternative_def_calling_conventions.tmpl +18 -0
- data/spec/acceptance/def/basic_def_block_no_output.expected +0 -0
- data/spec/acceptance/def/basic_def_block_no_output.tmpl +17 -0
- data/spec/acceptance/def/defs_can_be_called_multiple_times.expected +3 -0
- data/spec/acceptance/def/defs_can_be_called_multiple_times.tmpl +6 -0
- data/spec/acceptance/def/defs_can_be_dynamic.expected +4 -0
- data/spec/acceptance/def/defs_can_be_dynamic.tmpl +12 -0
- data/spec/acceptance/echo/echo_directive_with_numeric_literal.expected +1 -0
- data/spec/acceptance/echo/echo_directive_with_numeric_literal.tmpl +1 -0
- data/spec/acceptance/echo/echo_expression_list.expected +1 -0
- data/spec/acceptance/echo/echo_expression_list.tmpl +1 -0
- data/spec/acceptance/echo/echo_short_notation.expected +1 -0
- data/spec/acceptance/echo/echo_short_notation.tmpl +1 -0
- data/spec/acceptance/echo/echo_simple_expression.expected +1 -0
- data/spec/acceptance/echo/echo_simple_expression.tmpl +1 -0
- data/spec/acceptance/echo/echo_single_quoted_string_literal.expected +1 -0
- data/spec/acceptance/echo/echo_single_quoted_string_literal.tmpl +1 -0
- data/spec/acceptance/echo/multiple_echo_statements.expected +1 -0
- data/spec/acceptance/echo/multiple_echo_statements.tmpl +2 -0
- data/spec/acceptance/includes/basic_included_file.txt +1 -0
- data/spec/acceptance/includes/basic_includer.complex +3 -0
- data/spec/acceptance/includes/basic_includer.expected +3 -0
- data/spec/acceptance/includes/basic_includer.rb +38 -0
- data/spec/acceptance/includes/complicated_included_file.txt +3 -0
- data/spec/acceptance/includes/complicated_includer.complex +3 -0
- data/spec/acceptance/includes/complicated_includer.expected +3 -0
- data/spec/acceptance/includes/complicated_includer.rb +41 -0
- data/spec/acceptance/includes/nested_include_1.txt +3 -0
- data/spec/acceptance/includes/nested_include_2.txt +1 -0
- data/spec/acceptance/includes/nested_includer.complex +3 -0
- data/spec/acceptance/includes/nested_includer.expected +4 -0
- data/spec/acceptance/includes/nested_includer.rb +41 -0
- data/spec/acceptance/inheritance/basic_child.complex +10 -0
- data/spec/acceptance/inheritance/basic_child.expected +9 -0
- data/spec/acceptance/inheritance/basic_child.rb +54 -0
- data/spec/acceptance/inheritance/basic_parent.complex +5 -0
- data/spec/acceptance/inheritance/basic_parent.expected +3 -0
- data/spec/acceptance/inheritance/basic_parent.rb +41 -0
- data/spec/acceptance/inheritance/importing_child.complex +8 -0
- data/spec/acceptance/inheritance/importing_child.expected +7 -0
- data/spec/acceptance/inheritance/importing_child.rb +46 -0
- data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.complex +8 -0
- data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.expected +7 -0
- data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.rb +44 -0
- data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.expected +0 -0
- data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.tmpl +15 -0
- data/spec/acceptance/multiline_comments/simple_multiline_comment.expected +2 -0
- data/spec/acceptance/multiline_comments/simple_multiline_comment.tmpl +4 -0
- data/spec/acceptance/raw/complicated_raw_example.expected +57 -0
- data/spec/acceptance/raw/complicated_raw_example.tmpl +79 -0
- data/spec/acceptance/raw-text/UTF_8.expected +12 -0
- data/spec/acceptance/raw-text/UTF_8.tmpl +12 -0
- data/spec/acceptance/raw-text/empty_file.expected +0 -0
- data/spec/acceptance/raw-text/empty_file.tmpl +0 -0
- data/spec/acceptance/raw-text/multi_line.expected +4 -0
- data/spec/acceptance/raw-text/multi_line.tmpl +4 -0
- data/spec/acceptance/raw-text/single_line.expected +1 -0
- data/spec/acceptance/raw-text/single_line.tmpl +1 -0
- data/spec/acceptance/raw-text/single_line_whitespace.expected +1 -0
- data/spec/acceptance/raw-text/single_line_whitespace.tmpl +1 -0
- data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.tmpl +6 -0
- data/spec/acceptance/set/unused_set.expected +0 -0
- data/spec/acceptance/set/unused_set.tmpl +1 -0
- data/spec/acceptance/set/used_set.expected +1 -0
- data/spec/acceptance/set/used_set.tmpl +2 -0
- data/spec/acceptance/silent/silent_and_echo_combined.expected +1 -0
- data/spec/acceptance/silent/silent_and_echo_combined.tmpl +2 -0
- data/spec/acceptance/silent/silent_short_notation.expected +1 -0
- data/spec/acceptance/silent/silent_short_notation.tmpl +1 -0
- data/spec/acceptance/silent/simple_silent_directive.expected +0 -0
- data/spec/acceptance/silent/simple_silent_directive.tmpl +1 -0
- data/spec/acceptance/slurp/basic_slurp_demo.expected +1 -0
- data/spec/acceptance/slurp/basic_slurp_demo.tmpl +4 -0
- data/spec/acceptance/super/super_with_no_effect.expected +4 -0
- data/spec/acceptance/super/super_with_no_effect.tmpl +5 -0
- data/spec/additions/module_spec.rb +126 -0
- data/spec/additions/string_spec.rb +99 -0
- data/spec/compiler_spec.rb +55 -0
- data/spec/grammar/additions/proc_spec.rb +25 -0
- data/spec/grammar/additions/regexp_spec.rb +37 -0
- data/spec/grammar/additions/string_spec.rb +106 -0
- data/spec/grammar/and_predicate_spec.rb +29 -0
- data/spec/grammar/continuation_wrapper_exception_spec.rb +23 -0
- data/spec/grammar/match_data_wrapper_spec.rb +41 -0
- data/spec/grammar/memoizing_cache_spec.rb +112 -0
- data/spec/grammar/node_spec.rb +126 -0
- data/spec/grammar/not_predicate_spec.rb +29 -0
- data/spec/grammar/parser_state_spec.rb +172 -0
- data/spec/grammar/parslet_choice_spec.rb +49 -0
- data/spec/grammar/parslet_combining_spec.rb +287 -0
- data/spec/grammar/parslet_merge_spec.rb +33 -0
- data/spec/grammar/parslet_omission_spec.rb +58 -0
- data/spec/grammar/parslet_repetition_spec.rb +77 -0
- data/spec/grammar/parslet_sequence_spec.rb +49 -0
- data/spec/grammar/parslet_spec.rb +23 -0
- data/spec/grammar/predicate_spec.rb +53 -0
- data/spec/grammar/proc_parslet_spec.rb +52 -0
- data/spec/grammar/regexp_parslet_spec.rb +347 -0
- data/spec/grammar/string_enumerator_spec.rb +94 -0
- data/spec/grammar/string_parslet_spec.rb +143 -0
- data/spec/grammar/symbol_parslet_spec.rb +30 -0
- data/spec/grammar_spec.rb +545 -0
- data/spec/parser_spec.rb +1418 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/walrus_grammar/comment_spec.rb +39 -0
- data/spec/walrus_grammar/echo_directive_spec.rb +63 -0
- data/spec/walrus_grammar/escape_sequence_spec.rb +85 -0
- data/spec/walrus_grammar/literal_spec.rb +41 -0
- data/spec/walrus_grammar/message_expression_spec.rb +37 -0
- data/spec/walrus_grammar/multiline_comment_spec.rb +58 -0
- data/spec/walrus_grammar/placeholder_spec.rb +48 -0
- data/spec/walrus_grammar/raw_directive_spec.rb +81 -0
- data/spec/walrus_grammar/raw_text_spec.rb +65 -0
- data/spec/walrus_grammar/silent_directive_spec.rb +34 -0
- 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
|
+
|