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
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
|