sexpr 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +24 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/LICENCE.md +1 -1
- data/Manifest.txt +1 -0
- data/README.md +46 -31
- data/examples/bool_expr/bool_expr.citrus +72 -0
- data/examples/bool_expr/bool_expr.rb +86 -0
- data/examples/bool_expr/bool_expr.sexp.yml +23 -0
- data/lib/sexpr.rb +27 -20
- data/lib/sexpr/errors.rb +15 -0
- data/lib/sexpr/grammar.rb +21 -77
- data/lib/sexpr/grammar/matching.rb +56 -0
- data/lib/sexpr/grammar/options.rb +53 -0
- data/lib/sexpr/grammar/parsing.rb +20 -0
- data/lib/sexpr/grammar/tagging.rb +49 -0
- data/lib/sexpr/loader.rb +0 -1
- data/lib/sexpr/matcher.rb +15 -0
- data/lib/sexpr/matcher/alternative.rb +30 -0
- data/lib/sexpr/matcher/many.rb +54 -0
- data/lib/sexpr/matcher/reference.rb +32 -0
- data/lib/sexpr/matcher/rule.rb +31 -0
- data/lib/sexpr/matcher/sequence.rb +30 -0
- data/lib/sexpr/matcher/terminal.rb +37 -0
- data/lib/sexpr/node.rb +16 -0
- data/lib/sexpr/parser.rb +48 -0
- data/lib/sexpr/parser/citrus.rb +67 -0
- data/lib/sexpr/version.rb +2 -2
- data/sexpr.gemspec +1 -0
- data/sexpr.noespec +2 -1
- data/spec/grammar/matching/test_compile_rule.rb +23 -0
- data/spec/grammar/matching/test_compile_rule_defn.rb +103 -0
- data/spec/grammar/options/test_install_parser.rb +36 -0
- data/spec/grammar/options/test_install_path.rb +19 -0
- data/spec/grammar/options/test_install_root.rb +27 -0
- data/spec/grammar/tagging/test_looks_a_sexpr.rb +20 -0
- data/spec/grammar/tagging/test_mod2rulename.rb +19 -0
- data/spec/grammar/tagging/test_rule2modname.rb +19 -0
- data/spec/grammar/tagging/test_tag_sexpr.rb +28 -0
- data/spec/grammar/test_new.rb +15 -0
- data/spec/grammar/test_parse.rb +27 -18
- data/spec/grammar/test_sexpr.rb +53 -0
- data/spec/{alternative → matcher/alternative}/test_eat.rb +1 -1
- data/spec/{alternative → matcher/alternative}/test_match_q.rb +1 -1
- data/spec/{many → matcher/many}/test_eat.rb +1 -1
- data/spec/{many → matcher/many}/test_initialize.rb +1 -1
- data/spec/{many → matcher/many}/test_match_q.rb +1 -1
- data/spec/{reference → matcher/reference}/test_eat.rb +1 -1
- data/spec/{reference → matcher/reference}/test_match_q.rb +1 -1
- data/spec/{rule → matcher/rule}/test_eat.rb +1 -1
- data/spec/{rule → matcher/rule}/test_match_q.rb +1 -1
- data/spec/{sequence → matcher/sequence}/test_eat.rb +1 -1
- data/spec/{sequence → matcher/sequence}/test_match_q.rb +1 -1
- data/spec/{terminal → matcher/terminal}/test_eat.rb +1 -1
- data/spec/{terminal → matcher/terminal}/test_match_q.rb +1 -1
- data/spec/{terminal → matcher/terminal}/test_terminal_match.rb +1 -1
- data/spec/node/test_sexpr_body.rb +18 -0
- data/spec/node/test_sexpr_type.rb +14 -0
- data/spec/parser/citrus/test_new.rb +28 -0
- data/spec/parser/citrus/test_parse.rb +40 -0
- data/spec/parser/citrus/test_recognize.rb +18 -0
- data/spec/parser/citrus/test_registration.rb +20 -0
- data/spec/parser/citrus/test_to_sexpr.rb +16 -0
- data/spec/parser/test_factor.rb +17 -0
- data/spec/parser/test_input_text.rb +27 -0
- data/spec/spec_helper.rb +18 -1
- data/spec/test_load.rb +40 -13
- data/spec/test_readme_examples.rb +40 -30
- data/spec/test_sexpr.rb +1 -1
- metadata +118 -68
- data/lib/sexpr/alternative.rb +0 -28
- data/lib/sexpr/element.rb +0 -9
- data/lib/sexpr/many.rb +0 -52
- data/lib/sexpr/reference.rb +0 -30
- data/lib/sexpr/rule.rb +0 -29
- data/lib/sexpr/sequence.rb +0 -28
- data/lib/sexpr/terminal.rb +0 -35
- data/spec/bool_expr.yml +0 -19
- data/spec/grammar/test_compile_rule.rb +0 -25
- data/spec/grammar/test_compile_rule_defn.rb +0 -98
- data/spec/grammar/test_fetch.rb +0 -18
- data/spec/grammar/test_root.rb +0 -20
- data/spec/test_bool_expr.rb +0 -27
@@ -0,0 +1,67 @@
|
|
1
|
+
module Sexpr
|
2
|
+
module Parser
|
3
|
+
class Citrus
|
4
|
+
include Parser
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def recognizes?(arg)
|
9
|
+
looks_a_citrus_grammar?(arg) or
|
10
|
+
looks_a_citrus_file?(arg)
|
11
|
+
end
|
12
|
+
|
13
|
+
def looks_a_citrus_file?(arg)
|
14
|
+
arg = arg.to_path if arg.respond_to?(:to_path)
|
15
|
+
arg.is_a?(String) && File.exists?(arg) && (File.extname(arg) == ".citrus")
|
16
|
+
end
|
17
|
+
|
18
|
+
def looks_a_citrus_grammar?(arg)
|
19
|
+
defined?(::Citrus::Grammar) && arg.is_a?(Module) && arg.include?(::Citrus::Grammar)
|
20
|
+
end
|
21
|
+
|
22
|
+
end # class << self
|
23
|
+
|
24
|
+
attr_reader :options
|
25
|
+
|
26
|
+
def initialize(parser, options = {})
|
27
|
+
@parser = parser
|
28
|
+
@options = default_options.merge(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_options
|
32
|
+
{:from_match_to_sexpr => lambda{|match| match.value}}
|
33
|
+
end
|
34
|
+
|
35
|
+
def parser
|
36
|
+
@citrus_parser ||= begin
|
37
|
+
if self.class.looks_a_citrus_grammar?(@parser)
|
38
|
+
@parser
|
39
|
+
elsif self.class.looks_a_citrus_file?(@parser)
|
40
|
+
@parser = @parser.to_path if @parser.respond_to?(:to_path)
|
41
|
+
@parser = @parser[0...-(".citrus".length)]
|
42
|
+
::Citrus.load(@parser).last
|
43
|
+
else
|
44
|
+
raise UnrecognizedParserError, "Not a citrus parser #{@parser}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(input, options = {})
|
50
|
+
input = input_text(input)
|
51
|
+
parser.parse(input, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sexpr(input, parse_options = {})
|
55
|
+
from_match_to_sexpr parse(input, parse_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def from_match_to_sexpr(match)
|
61
|
+
options[:from_match_to_sexpr].call(match)
|
62
|
+
end
|
63
|
+
|
64
|
+
Sexpr::Parser.register self
|
65
|
+
end # class Citrus
|
66
|
+
end # module Parser
|
67
|
+
end # module Sexpr
|
data/lib/sexpr/version.rb
CHANGED
data/sexpr.gemspec
CHANGED
@@ -124,6 +124,7 @@ Gem::Specification.new do |s|
|
|
124
124
|
# for each development dependency. These gems are required for developers
|
125
125
|
#
|
126
126
|
s.add_development_dependency("epath", "~> 0.0.1")
|
127
|
+
s.add_development_dependency("citrus", "~> 2.4")
|
127
128
|
s.add_development_dependency("rake", "~> 0.9.2")
|
128
129
|
s.add_development_dependency("rspec", "~> 2.8.0")
|
129
130
|
s.add_development_dependency("wlang", "~> 0.10.2")
|
data/sexpr.noespec
CHANGED
@@ -9,7 +9,7 @@ variables:
|
|
9
9
|
upper:
|
10
10
|
Sexpr
|
11
11
|
version:
|
12
|
-
0.
|
12
|
+
0.3.0
|
13
13
|
summary: |-
|
14
14
|
A compilation framework around s-expressions
|
15
15
|
description: |-
|
@@ -20,6 +20,7 @@ variables:
|
|
20
20
|
- https://github.com/blambeau/sexp
|
21
21
|
dependencies:
|
22
22
|
- {name: epath, version: "~> 0.0.1", groups: [development]}
|
23
|
+
- {name: citrus, version: "~> 2.4", groups: [development]}
|
23
24
|
- {name: rake, version: "~> 0.9.2", groups: [development]}
|
24
25
|
- {name: rspec, version: "~> 2.8.0", groups: [development]}
|
25
26
|
- {name: wlang, version: "~> 0.10.2", groups: [development]}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
module Grammar
|
4
|
+
describe Matching, "compile_rule" do
|
5
|
+
include Matching
|
6
|
+
|
7
|
+
it 'keep alternatives unchanged' do
|
8
|
+
compile_rule(:hello, Matcher::Alternative.new([]) ).should be_a(Matcher::Alternative)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'keep terminals unchanged' do
|
12
|
+
compile_rule(:hello, Matcher::Terminal.new(true) ).should be_a(Matcher::Terminal)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'keep creates a Rule englobing sequences' do
|
16
|
+
compiled = compile_rule(:hello, Matcher::Sequence.new([]) )
|
17
|
+
compiled.should be_a(Matcher::Rule)
|
18
|
+
compiled.name.should eq(:hello)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
module Grammar
|
4
|
+
describe Matching, "compile_rule_defn" do
|
5
|
+
include Matching
|
6
|
+
|
7
|
+
subject{ compile_rule_defn(arg, :grammar) }
|
8
|
+
|
9
|
+
def rules
|
10
|
+
{:a_rule => "hello"}
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'with an Element' do
|
14
|
+
let(:arg){ Matcher::Terminal.new(//) }
|
15
|
+
it 'returns arg itself' do
|
16
|
+
subject.should eq(arg)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with true" do
|
21
|
+
let(:arg){ true }
|
22
|
+
it 'gives it true' do
|
23
|
+
subject.should be_a(Matcher::Terminal)
|
24
|
+
subject.value.should eq(true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with false" do
|
29
|
+
let(:arg){ false }
|
30
|
+
it 'gives it false' do
|
31
|
+
subject.should be_a(Matcher::Terminal)
|
32
|
+
subject.value.should eq(false)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "with nil" do
|
37
|
+
let(:arg){ nil }
|
38
|
+
it 'gives it nil' do
|
39
|
+
subject.should be_a(Matcher::Terminal)
|
40
|
+
subject.value.should eq(nil)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with an alternative array' do
|
45
|
+
let(:arg){ [true, false, nil] }
|
46
|
+
it 'factors an Alternative' do
|
47
|
+
subject.should be_a(Matcher::Alternative)
|
48
|
+
end
|
49
|
+
it 'compiles its elements' do
|
50
|
+
subject.terms.size.should eq(3)
|
51
|
+
subject.terms.all?{|x| x.is_a?(Matcher::Terminal)}.should be_true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with a sequence array' do
|
56
|
+
let(:arg){ [[true, false, nil]] }
|
57
|
+
it 'factors a Sequence' do
|
58
|
+
subject.should be_a(Matcher::Sequence)
|
59
|
+
end
|
60
|
+
it 'compiles its elements' do
|
61
|
+
subject.terms.size.should eq(3)
|
62
|
+
subject.terms.all?{|x| x.is_a?(Matcher::Terminal)}.should be_true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with subalternatives' do
|
67
|
+
let(:arg){ [ ["a_rule", [false, true, nil] ]] }
|
68
|
+
it 'compiles the last as an Alternative' do
|
69
|
+
subject.terms.last.should be_a(Matcher::Alternative)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'with a reference to a non-terminal' do
|
74
|
+
let(:arg){ "a_rule" }
|
75
|
+
it 'factors a Reference' do
|
76
|
+
subject.should be_a(Matcher::Reference)
|
77
|
+
end
|
78
|
+
it 'refers to the appropriate rule name' do
|
79
|
+
subject.rule_name.should eq(:a_rule)
|
80
|
+
end
|
81
|
+
it 'refers to the appropriate grammar' do
|
82
|
+
subject.grammar.should eq(:grammar)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'with a stared non-terminal' do
|
87
|
+
let(:arg){ "a_rule+" }
|
88
|
+
it 'factors a Many' do
|
89
|
+
subject.should be_a(Matcher::Many)
|
90
|
+
end
|
91
|
+
it 'refers to the appropriate rule' do
|
92
|
+
subject.term.should be_a(Matcher::Reference)
|
93
|
+
subject.term.rule_name.should eq(:a_rule)
|
94
|
+
end
|
95
|
+
it 'refers to the appropriate multiplicities' do
|
96
|
+
subject.min.should eq(1)
|
97
|
+
subject.max.should be_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
describe Grammar::Options, "install_parser" do
|
4
|
+
include Grammar::Options
|
5
|
+
|
6
|
+
it 'is nil by default' do
|
7
|
+
h = {}
|
8
|
+
install_options h do
|
9
|
+
parser.should be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can be specified through a :parser option' do
|
14
|
+
h = {:parser => bool_expr_parser}
|
15
|
+
install_options h do
|
16
|
+
parser.should be_a(Parser::Citrus)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can be specified as a Path' do
|
21
|
+
h = {:parser => fixtures_path/"bool_expr.citrus"}
|
22
|
+
install_options h do
|
23
|
+
parser.should be_a(Parser::Citrus)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'can be specified as relative Path' do
|
28
|
+
h = {:path => fixtures_path/"bool_expr.sexp.yml",
|
29
|
+
:parser => "bool_expr.citrus"}
|
30
|
+
install_options h do
|
31
|
+
parser.should be_a(Parser::Citrus)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
describe Grammar::Options, "install_path" do
|
4
|
+
include Grammar::Options
|
5
|
+
|
6
|
+
it 'is nil by default' do
|
7
|
+
install_options({}) do
|
8
|
+
path.should be_nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'keeps the specified value if any' do
|
13
|
+
install_options :path => "blah" do
|
14
|
+
path.should eq("blah")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
describe Grammar::Options, "install_root" do
|
4
|
+
include Grammar::Options
|
5
|
+
|
6
|
+
let(:rules){ {:t => /[a-z]+/, :nt => true} }
|
7
|
+
|
8
|
+
it 'is the first key by default' do
|
9
|
+
install_options :rules => rules do
|
10
|
+
root.should eq(:t)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'is the specified rule when specified' do
|
15
|
+
install_options :rules => rules, :root => :nt do
|
16
|
+
root.should eq(:nt)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'is converted to a Symbol is a String' do
|
21
|
+
install_options :root => "nt" do
|
22
|
+
root.should eq(:nt)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr::Grammar
|
3
|
+
describe Tagging, "looks_a_sexpr" do
|
4
|
+
include Tagging
|
5
|
+
|
6
|
+
it 'recognizes s-expressions' do
|
7
|
+
looks_a_sexpr?([:lit]).should be_true
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'does not recognize empty arrays' do
|
11
|
+
looks_a_sexpr?([]).should be_false
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'does not recognize others' do
|
15
|
+
looks_a_sexpr?(nil).should be_false
|
16
|
+
looks_a_sexpr?(:lit).should be_false
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr::Grammar
|
3
|
+
describe Tagging, "mod2rulename" do
|
4
|
+
include Tagging
|
5
|
+
|
6
|
+
it 'work on simple module name' do
|
7
|
+
mod2rulename(:Test).should eq(:test)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'work on complex module name' do
|
11
|
+
mod2rulename(:ThisIsATest).should eq(:this_is_a_test)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'works with a module' do
|
15
|
+
mod2rulename(::Sexpr::Grammar).should eq(:grammar)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr::Grammar
|
3
|
+
describe Tagging, "rule2modname" do
|
4
|
+
include Tagging
|
5
|
+
|
6
|
+
it 'work on simple rule name' do
|
7
|
+
rule2modname(:test).should eq(:Test)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'works when underscores are present' do
|
11
|
+
rule2modname(:a_rule_name).should eq(:ARuleName)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'works with a string' do
|
15
|
+
rule2modname("a_rule_name").should eq(:ARuleName)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr::Grammar
|
3
|
+
describe Tagging, "tag_sexpr" do
|
4
|
+
include Tagging
|
5
|
+
|
6
|
+
module TaggingReference
|
7
|
+
module Not; end
|
8
|
+
module Lit; end
|
9
|
+
end
|
10
|
+
|
11
|
+
def tag(x)
|
12
|
+
res = tag_sexpr(x, TaggingReference)
|
13
|
+
res.should eq(x)
|
14
|
+
res
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'tags a sexpr at first level' do
|
18
|
+
tag([:lit]).should be_a(TaggingReference::Lit)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'tags sexpr recursively' do
|
22
|
+
res = tag([:not, [:lit, true]])
|
23
|
+
res.should be_a(TaggingReference::Not)
|
24
|
+
res.last.should be_a(TaggingReference::Lit)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
describe Grammar, "new" do
|
4
|
+
|
5
|
+
subject{ Grammar.new }
|
6
|
+
|
7
|
+
it 'factors a module' do
|
8
|
+
subject.should be_a(Grammar)
|
9
|
+
subject.should be_a(Grammar::Options)
|
10
|
+
subject.should be_a(Grammar::Matching)
|
11
|
+
subject.should be_a(Grammar::Parsing)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
data/spec/grammar/test_parse.rb
CHANGED
@@ -2,31 +2,40 @@ require 'spec_helper'
|
|
2
2
|
module Sexpr
|
3
3
|
describe Grammar, "parse" do
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
def x.parse(s)
|
8
|
-
Struct.new(:value).new([:parsed, s])
|
9
|
-
end
|
10
|
-
}
|
5
|
+
def grammar
|
6
|
+
Sexpr.load(:parser => parser)
|
11
7
|
end
|
12
8
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
9
|
+
context 'when no parser is set' do
|
10
|
+
let(:parser){ nil }
|
16
11
|
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
it 'raises an error' do
|
13
|
+
lambda{
|
14
|
+
grammar.parse("Hello world")
|
15
|
+
}.should raise_error(NoParserError)
|
16
|
+
end
|
20
17
|
|
21
|
-
it 'it accepts a path' do
|
22
|
-
grammar.parse(Path.here).should eq([:parsed, File.read(__FILE__)])
|
23
18
|
end
|
24
19
|
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
context 'when a parser is set' do
|
21
|
+
let(:parser){
|
22
|
+
Object.new.extend Module.new{
|
23
|
+
include Parser
|
24
|
+
def parse(s, options = {})
|
25
|
+
[options[:root] || :parsed, input_text(s)]
|
26
|
+
end
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
it 'delegates the call to the parser' do
|
31
|
+
grammar.parse("Hello world").should eq([:parsed, "Hello world"])
|
28
32
|
end
|
29
|
-
|
33
|
+
|
34
|
+
it 'passes options' do
|
35
|
+
grammar.parse("world", :root => :hello).should eq([:hello, "world"])
|
36
|
+
end
|
37
|
+
|
38
|
+
end # when a parser is set
|
30
39
|
|
31
40
|
end
|
32
41
|
end
|