sexpr 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# 0.3.0 / 2012-02-21
|
2
|
+
|
3
|
+
* Breaking changes
|
4
|
+
|
5
|
+
* `Sexpr.load` only takes one argument, merging both the rules and the options inside a
|
6
|
+
unique Hash object (or something coercible to a Hash, such as YAML).
|
7
|
+
* The YAML grammar format now requires rules to be specified under a "rules" entry. See
|
8
|
+
the README for an example.
|
9
|
+
|
10
|
+
* Major enhancements
|
11
|
+
|
12
|
+
* A lexical parser can now be specified under a "parser" entry. Only Citrus is supported
|
13
|
+
for now.
|
14
|
+
* A loaded grammar (i.e. returned by `Sexpr.load`) is now a module. Therefore assigning
|
15
|
+
the result to a constant makes perfect sense and benefits from the ruby's magic naming
|
16
|
+
feature.
|
17
|
+
* A loaded grammar now respond to a :sexpr method that parses (if needed) and returns a
|
18
|
+
s-expression. The latter, and all its sub-expressions are automatically tagged with the
|
19
|
+
Sexpr module, as well as with user-defined modules. The latter are automatically discovered
|
20
|
+
with a convention over configuration heuristics that associates rule names to module names.
|
21
|
+
That convention may however be overridden with specific grammar methods (see the BoolExpr
|
22
|
+
example).
|
23
|
+
|
1
24
|
# 0.2.0 / 2012-02-21
|
2
25
|
|
3
26
|
* Enhancements
|
@@ -10,4 +33,4 @@
|
|
10
33
|
|
11
34
|
* Enhancements
|
12
35
|
|
13
|
-
* Birthday!
|
36
|
+
* Birthday!
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/LICENCE.md
CHANGED
@@ -19,4 +19,4 @@
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
21
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -4,43 +4,58 @@ A ruby compilation framework around s-expressions.
|
|
4
4
|
|
5
5
|
## Example
|
6
6
|
|
7
|
+
# Let load a grammar defined in YAML
|
7
8
|
grammar = SexpGrammar.load(<<-YAML)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
9
|
+
rules:
|
10
|
+
# alternative rule
|
11
|
+
bool_expr:
|
12
|
+
- bool_and
|
13
|
+
- bool_or
|
14
|
+
- bool_not
|
15
|
+
- var_ref
|
16
|
+
- bool_lit
|
17
|
+
|
18
|
+
# non-terminal
|
19
|
+
bool_and:
|
20
|
+
- [ bool_expr, bool_expr ]
|
21
|
+
bool_or:
|
22
|
+
- [ bool_expr, bool_expr ]
|
23
|
+
bool_not:
|
24
|
+
- [ bool_expr ]
|
25
|
+
bool_lit:
|
26
|
+
- [ truth_value ]
|
27
|
+
var_ref:
|
28
|
+
- [ var_name ]
|
29
|
+
|
30
|
+
# terminals
|
31
|
+
var_name:
|
32
|
+
!ruby/regexp /^[a-z]+$/
|
33
|
+
truth_value:
|
34
|
+
- true
|
35
|
+
- false
|
36
36
|
YAML
|
37
37
|
|
38
|
-
|
38
|
+
### Checking the structure of s-expressions
|
39
|
+
|
40
|
+
# the grammar can be used to verify the structure of s-expressions
|
41
|
+
grammar === [:bool_and, [:bool_not, [:var_ref, "x"]], [:bool_lit, true]]
|
39
42
|
# => true
|
40
43
|
|
41
|
-
grammar === [:bool_and, [:
|
44
|
+
grammar === [:bool_and, [:bool_lit, "true"]]
|
42
45
|
# => false (second term is missing)
|
43
46
|
|
47
|
+
### Including s-expression tools
|
48
|
+
|
49
|
+
# the grammar can also be used to automatically have support on top of
|
50
|
+
# such s-expressions
|
51
|
+
expr = grammar.sexpr([:bool_lit, true])
|
52
|
+
|
53
|
+
expr.sexpr_type
|
54
|
+
# => :bool_lit
|
55
|
+
|
56
|
+
expr.sexpr_body
|
57
|
+
# => [true]
|
58
|
+
|
44
59
|
## Links
|
45
60
|
|
46
|
-
https://github.com/blambeau/sexpr
|
61
|
+
https://github.com/blambeau/sexpr
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#
|
2
|
+
# This is a Citrus grammar for boolean expressions.
|
3
|
+
#
|
4
|
+
# The parser is automatically loaded under the BoolExpr::Parser constant by Citrus
|
5
|
+
# itself. It is also automatically registered under BoolExpr::Grammar.parser by
|
6
|
+
# `Sexpr.load` when invoked on the bool_expr.sexp.yml file.
|
7
|
+
#
|
8
|
+
# The coupon codes returns s-expressions that correctly refer to the abstract grammar
|
9
|
+
# definition in that file (AST).
|
10
|
+
#
|
11
|
+
grammar BoolExpr::Parser
|
12
|
+
|
13
|
+
rule bool_expr
|
14
|
+
bool_or
|
15
|
+
end
|
16
|
+
|
17
|
+
rule bool_or
|
18
|
+
(l:bool_and spaces 'or' spaces r:bool_or){
|
19
|
+
[:bool_or, l.value, r.value]
|
20
|
+
}
|
21
|
+
| bool_and
|
22
|
+
end
|
23
|
+
|
24
|
+
rule bool_and
|
25
|
+
(l:bool_not spaces 'and' spaces r:bool_and){
|
26
|
+
[:bool_and, l.value, r.value]
|
27
|
+
}
|
28
|
+
| bool_not
|
29
|
+
end
|
30
|
+
|
31
|
+
rule bool_not
|
32
|
+
('not' spacing e:bool_not){
|
33
|
+
[:bool_not, e.value]
|
34
|
+
}
|
35
|
+
| bool_term
|
36
|
+
end
|
37
|
+
|
38
|
+
rule bool_term
|
39
|
+
bool_paren | bool_lit | var_ref
|
40
|
+
end
|
41
|
+
|
42
|
+
rule bool_paren
|
43
|
+
('(' spacing e:bool_or spacing ')'){
|
44
|
+
e.value
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
rule bool_lit
|
49
|
+
("true" | "false"){
|
50
|
+
[:bool_lit, ::Kernel.eval(strip) ]
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
rule var_ref
|
55
|
+
(!(keyword spacing) [a-z]+){
|
56
|
+
[:var_ref, strip]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
rule spacing
|
61
|
+
[ \t]+ | &'(' | !.
|
62
|
+
end
|
63
|
+
|
64
|
+
rule spaces
|
65
|
+
[ \t]+
|
66
|
+
end
|
67
|
+
|
68
|
+
rule keyword
|
69
|
+
"true" | "false" | "or" | "and" | "not"
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'sexpr'
|
2
|
+
|
3
|
+
# Let load the grammar from the .yml definition file.
|
4
|
+
BoolExpr = Sexpr.load File.expand_path('../bool_expr.sexp.yml', __FILE__)
|
5
|
+
|
6
|
+
# A Sexpr grammar is simply a module. Ruby allows us to re-open it later.
|
7
|
+
module BoolExpr
|
8
|
+
|
9
|
+
# These are the modules automatically installed on the s-expressions
|
10
|
+
# that the grammar produces
|
11
|
+
module And; end
|
12
|
+
module Or; end
|
13
|
+
module Not; end
|
14
|
+
module Lit; end
|
15
|
+
module VarRef; end
|
16
|
+
|
17
|
+
# The two following methods allows converting rule names (e.g. bool_and)
|
18
|
+
# to module names (And). A default implementation is provided by Sexpr
|
19
|
+
# that enforces convention over configuration (BoolAnd <-> bool_and). We
|
20
|
+
# override the methods here for the sake of the example/documentation.
|
21
|
+
|
22
|
+
def rule2modname(rule)
|
23
|
+
(rule.to_s =~ /^bool_(.*)$/) ? $1.capitalize.to_sym : super
|
24
|
+
end
|
25
|
+
|
26
|
+
def mod2rulename(mod)
|
27
|
+
rule = super
|
28
|
+
(rule.to_s =~ /^bool_(.*)$/) ? const_get($1.to_sym) : rule
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe BoolExpr do
|
34
|
+
subject{ BoolExpr }
|
35
|
+
|
36
|
+
describe "the parsing feature" do
|
37
|
+
|
38
|
+
it 'parses boolean expressions without error' do
|
39
|
+
subject.parse("x and y").should be_a(Citrus::Match)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'provides a shortcut to get s-expressions directly' do
|
43
|
+
subject.sexpr("x and y").should eq([:bool_and, [:var_ref, "x"], [:var_ref, "y"]])
|
44
|
+
end
|
45
|
+
|
46
|
+
end # parsing
|
47
|
+
|
48
|
+
describe "the tagging feature" do
|
49
|
+
|
50
|
+
it 'tags parsing results with the Sexpr module' do
|
51
|
+
sexpr = subject.sexpr("x and y")
|
52
|
+
sexpr.should be_a(Sexpr)
|
53
|
+
sexpr.sexpr_type.should eq(:bool_and)
|
54
|
+
sexpr.sexpr_body.should eq([[:var_ref, "x"], [:var_ref, "y"]])
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'tags parsing results with user modules' do
|
58
|
+
subject.sexpr("x and y").should be_a(BoolExpr::And)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'allows tagging manually' do
|
62
|
+
subject.sexpr([:bool_lit, true]).should be_a(BoolExpr::Lit)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'applies tagging recursively' do
|
66
|
+
sexpr = subject.sexpr([:bool_not, [:bool_lit, true]])
|
67
|
+
sexpr.last.should be_a(BoolExpr::Lit)
|
68
|
+
end
|
69
|
+
|
70
|
+
end # taggging
|
71
|
+
|
72
|
+
describe 'the validation feature' do
|
73
|
+
|
74
|
+
it 'validates s-expressions' do
|
75
|
+
subject.match?([:bool_lit, true]).should be_true
|
76
|
+
subject.match?([:bool_lit, "x"]).should be_false
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'validates s-expressions against specific rules' do
|
80
|
+
subject[:bool_lit].match?([:bool_lit, true]).should be_true
|
81
|
+
subject[:bool_and].match?([:bool_lit, true]).should be_false
|
82
|
+
end
|
83
|
+
|
84
|
+
end # validating
|
85
|
+
|
86
|
+
end if defined?(RSpec)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
parser:
|
2
|
+
bool_expr.citrus
|
3
|
+
rules:
|
4
|
+
bool_expr:
|
5
|
+
- bool_and
|
6
|
+
- bool_or
|
7
|
+
- bool_not
|
8
|
+
- var_ref
|
9
|
+
- bool_lit
|
10
|
+
bool_and:
|
11
|
+
- [ bool_expr+ ]
|
12
|
+
bool_or:
|
13
|
+
- [ bool_expr+ ]
|
14
|
+
bool_not:
|
15
|
+
- [ bool_expr ]
|
16
|
+
bool_lit:
|
17
|
+
- [ literal ]
|
18
|
+
var_ref:
|
19
|
+
- [ var_name ]
|
20
|
+
var_name:
|
21
|
+
!ruby/regexp /^[a-z]+$/
|
22
|
+
literal:
|
23
|
+
[true, false]
|
data/lib/sexpr.rb
CHANGED
@@ -1,31 +1,38 @@
|
|
1
1
|
require_relative "sexpr/version"
|
2
2
|
require_relative "sexpr/loader"
|
3
|
+
require_relative "sexpr/errors"
|
3
4
|
#
|
4
5
|
# A helper to manipulate sexp grammars
|
5
6
|
#
|
6
7
|
module Sexpr
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
PathLike = lambda{|x|
|
10
|
+
x.respond_to?(:to_path) or (x.is_a?(String) and File.exists?(x))
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.load(input)
|
14
|
+
defn = case input
|
15
|
+
when PathLike
|
16
|
+
require 'yaml'
|
17
|
+
path = input.respond_to?(:to_path) ? input.to_path : input.to_s
|
18
|
+
YAML.load_file(path).merge(:path => input)
|
19
|
+
when String
|
20
|
+
require 'yaml'
|
21
|
+
YAML.load(input)
|
22
|
+
when Hash
|
23
|
+
input
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Invalid argument for Sexpr::Grammar: #{input}"
|
26
|
+
end
|
27
|
+
Grammar.new defn
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.sexpr(arg)
|
31
|
+
Object.new.extend(Sexpr::Grammar::Tagging).sexpr(arg)
|
21
32
|
end
|
22
33
|
|
23
34
|
end # module Sexpr
|
35
|
+
require_relative "sexpr/node"
|
24
36
|
require_relative "sexpr/grammar"
|
25
|
-
require_relative "sexpr/
|
26
|
-
require_relative "sexpr/
|
27
|
-
require_relative "sexpr/many"
|
28
|
-
require_relative "sexpr/reference"
|
29
|
-
require_relative "sexpr/rule"
|
30
|
-
require_relative "sexpr/sequence"
|
31
|
-
require_relative "sexpr/terminal"
|
37
|
+
require_relative "sexpr/matcher"
|
38
|
+
require_relative "sexpr/parser"
|
data/lib/sexpr/errors.rb
ADDED
data/lib/sexpr/grammar.rb
CHANGED
@@ -1,82 +1,26 @@
|
|
1
|
+
require_relative 'grammar/options'
|
2
|
+
require_relative 'grammar/matching'
|
3
|
+
require_relative 'grammar/parsing'
|
4
|
+
require_relative 'grammar/tagging'
|
1
5
|
module Sexpr
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def [](rule_name)
|
12
|
-
@rules[rule_name]
|
13
|
-
end
|
14
|
-
|
15
|
-
def root
|
16
|
-
@root ||= begin
|
17
|
-
root = options[:root] || rules.keys.first
|
18
|
-
root = self[root] if root.is_a?(Symbol)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def parser
|
23
|
-
options[:parser]
|
24
|
-
end
|
25
|
-
|
26
|
-
def parse(input)
|
27
|
-
case input
|
28
|
-
when lambda{|x| x.respond_to?(:to_path)}
|
29
|
-
parse(File.read(input.to_path))
|
30
|
-
when IO
|
31
|
-
parse(input.read)
|
32
|
-
when String
|
33
|
-
unless p = parser
|
34
|
-
raise NoParserError, "No parser set.", caller
|
35
|
-
end
|
36
|
-
p.parse(input).value
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def match?(sexp)
|
41
|
-
root.match?(sexp)
|
42
|
-
end
|
43
|
-
alias :=== :match?
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def compile_rules(rules)
|
48
|
-
Hash[rules.map{|k,v|
|
49
|
-
[k.to_sym, compile_rule(k.to_sym, v)]
|
50
|
-
}]
|
51
|
-
end
|
52
|
-
|
53
|
-
def compile_rule(name, defn)
|
54
|
-
case rule = compile_rule_defn(defn)
|
55
|
-
when Terminal, Alternative
|
56
|
-
rule
|
57
|
-
else
|
58
|
-
Rule.new(name, rule)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def compile_rule_defn(arg)
|
63
|
-
case arg
|
64
|
-
when Element
|
65
|
-
arg
|
66
|
-
when Regexp, TrueClass, FalseClass, NilClass
|
67
|
-
Terminal.new arg
|
68
|
-
when lambda{|x| x.is_a?(Array) && x.size == 1 && x.first.is_a?(Array)}
|
69
|
-
Sequence.new arg.first.map{|s| compile_rule_defn(s) }
|
70
|
-
when Array
|
71
|
-
Alternative.new arg.map{|s| compile_rule_defn(s)}
|
72
|
-
when /([\?\+\*])$/
|
73
|
-
Many.new compile_rule_defn($`), $1
|
74
|
-
when /^[a-z][a-z_]+$/
|
75
|
-
Reference.new arg.to_sym, self
|
76
|
-
else
|
77
|
-
raise ArgumentError, "Invalid rule definition: #{arg.inspect}", caller
|
6
|
+
module Grammar
|
7
|
+
include Options
|
8
|
+
include Matching
|
9
|
+
include Parsing
|
10
|
+
include Tagging
|
11
|
+
|
12
|
+
def self.new(options = {})
|
13
|
+
unless options.is_a?(Hash)
|
14
|
+
raise ArgumentError, "Invalid grammar definition: #{options.inspect}"
|
78
15
|
end
|
16
|
+
Module.new.tap{|g|
|
17
|
+
g.instance_eval{
|
18
|
+
include(Grammar)
|
19
|
+
extend(self)
|
20
|
+
install_options(options)
|
21
|
+
}
|
22
|
+
}
|
79
23
|
end
|
80
24
|
|
81
|
-
end #
|
25
|
+
end # module Grammar
|
82
26
|
end # module Sexpr
|