sexpr 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +29 -0
- data/README.md +28 -2
- data/examples/bool_expr/bool_expr.citrus +6 -6
- data/examples/bool_expr/bool_expr.rb +68 -1
- data/lib/sexpr.rb +29 -22
- data/lib/sexpr/errors.rb +3 -0
- data/lib/sexpr/grammar.rb +7 -6
- data/lib/sexpr/grammar/options.rb +3 -0
- data/lib/sexpr/grammar/parsing.rb +6 -1
- data/lib/sexpr/grammar/tagging.rb +30 -11
- data/lib/sexpr/node.rb +30 -0
- data/lib/sexpr/parser.rb +2 -2
- data/lib/sexpr/parser/citrus.rb +6 -17
- data/lib/sexpr/parser/ext.rb +9 -0
- data/lib/sexpr/processor.rb +61 -0
- data/lib/sexpr/processor/helper.rb +31 -0
- data/lib/sexpr/processor/null_helper.rb +11 -0
- data/lib/sexpr/processor/sexpr_coercions.rb +44 -0
- data/lib/sexpr/rewriter.rb +20 -0
- data/lib/sexpr/version.rb +1 -1
- data/sexpr.noespec +1 -1
- data/spec/grammar/test_parse.rb +10 -6
- data/spec/grammar/test_sexpr.rb +47 -13
- data/spec/node/test_sexpr_copy.rb +35 -0
- data/spec/node/test_tracking_markers.rb +21 -0
- data/spec/parser/citrus/test_new.rb +0 -4
- data/spec/parser/citrus/test_parse.rb +4 -0
- data/spec/parser/citrus/test_registration.rb +0 -6
- data/spec/parser/citrus/test_to_sexpr.rb +22 -7
- data/spec/processor/helper/test_call.rb +51 -0
- data/spec/processor/test_build_helper_chain.rb +24 -0
- data/spec/processor/test_call.rb +46 -0
- data/spec/processor/test_helper.rb +19 -0
- data/spec/processor/test_main_processor.rb +18 -0
- data/spec/processor/test_sexpr_coercions.rb +46 -0
- data/spec/rewriter/test_copy_and_apply.rb +29 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/test_readme_examples.rb +11 -0
- data/spec/test_rewriter.rb +16 -0
- metadata +106 -80
data/lib/sexpr/parser.rb
CHANGED
@@ -17,10 +17,10 @@ module Sexpr
|
|
17
17
|
}
|
18
18
|
end
|
19
19
|
|
20
|
-
def factor(external_parser
|
20
|
+
def factor(external_parser)
|
21
21
|
return external_parser if Parser===external_parser
|
22
22
|
if cl = find_parser_class(external_parser)
|
23
|
-
cl.new(external_parser
|
23
|
+
cl.new(external_parser)
|
24
24
|
else
|
25
25
|
raise UnrecognizedParserError, "Parser not recognized: #{external_parser}"
|
26
26
|
end
|
data/lib/sexpr/parser/citrus.rb
CHANGED
@@ -21,15 +21,9 @@ module Sexpr
|
|
21
21
|
|
22
22
|
end # class << self
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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}}
|
24
|
+
def initialize(parser)
|
25
|
+
require_relative 'ext'
|
26
|
+
@parser = parser
|
33
27
|
end
|
34
28
|
|
35
29
|
def parser
|
@@ -47,18 +41,13 @@ module Sexpr
|
|
47
41
|
end
|
48
42
|
|
49
43
|
def parse(input, options = {})
|
44
|
+
return input if input.is_a?(::Citrus::Match)
|
50
45
|
input = input_text(input)
|
51
46
|
parser.parse(input, options)
|
52
47
|
end
|
53
48
|
|
54
|
-
def
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def from_match_to_sexpr(match)
|
61
|
-
options[:from_match_to_sexpr].call(match)
|
49
|
+
def to_sexpr(parsed)
|
50
|
+
parsed.sexpr
|
62
51
|
end
|
63
52
|
|
64
53
|
Sexpr::Parser.register self
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'processor/helper'
|
2
|
+
require_relative 'processor/null_helper'
|
3
|
+
require_relative 'processor/sexpr_coercions'
|
4
|
+
module Sexpr
|
5
|
+
class Processor
|
6
|
+
|
7
|
+
### class methods
|
8
|
+
|
9
|
+
def self.helpers
|
10
|
+
@helpers ||= superclass.helpers.dup rescue [ ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.helper(helper_class)
|
14
|
+
methods = helper_class.const_get(:Methods) rescue nil
|
15
|
+
module_eval{ include methods } if methods
|
16
|
+
helpers << helper_class
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build_helper_chain(helpers = self.helpers)
|
20
|
+
return NullHelper.new if helpers.empty?
|
21
|
+
helpers[0...-1].reverse.inject(helpers.last.new) do |chain, h_class|
|
22
|
+
prepended = h_class.new
|
23
|
+
prepended.next_in_chain = chain
|
24
|
+
prepended
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
### instance methods
|
29
|
+
|
30
|
+
attr_reader :main_processor
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
@main_processor = options.delete(:main_processor) || self
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(sexpr)
|
37
|
+
help(sexpr) do |n|
|
38
|
+
meth = :"on_#{n.first}"
|
39
|
+
meth = :"on_missing" unless respond_to?(meth)
|
40
|
+
send(meth, n)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_missing(sexpr)
|
45
|
+
raise UnexpectedSexprError, "Unexpected sexpr: #{sexpr.inspect}"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def helper_chain
|
51
|
+
@helper_chain ||= self.class.build_helper_chain
|
52
|
+
end
|
53
|
+
|
54
|
+
def help(sexpr)
|
55
|
+
helper_chain.call(self, sexpr) do |_,n|
|
56
|
+
yield(n)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end # class Processor
|
61
|
+
end # module Sexpr
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Sexpr
|
2
|
+
class Processor
|
3
|
+
class Helper
|
4
|
+
|
5
|
+
attr_accessor :next_in_chain
|
6
|
+
|
7
|
+
def call(processor, sexpr, &bl)
|
8
|
+
meth = :"on_#{sexpr.first}"
|
9
|
+
meth = :"on_missing" unless respond_to?(meth)
|
10
|
+
send(meth, processor, sexpr) do |r,n|
|
11
|
+
next_call(r, n, bl)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_missing(processor, sexpr)
|
16
|
+
yield(processor, sexpr)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def next_call(processor, sexpr, toplevel)
|
22
|
+
if nic = next_in_chain
|
23
|
+
nic.call(processor, sexpr, &toplevel)
|
24
|
+
else
|
25
|
+
toplevel.call(processor, sexpr)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class Helper
|
30
|
+
end # module Processor
|
31
|
+
end # module Sexpr
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Sexpr
|
2
|
+
class Processor
|
3
|
+
class SexprCoercions < Helper
|
4
|
+
|
5
|
+
module Methods
|
6
|
+
|
7
|
+
def parse(*args)
|
8
|
+
sexpr_grammar.parse(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def sexpr(*args)
|
12
|
+
sexpr_grammar.sexpr(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(processor, sexpr, &bl)
|
18
|
+
# input coercion
|
19
|
+
sexpr = grammar(processor).sexpr(sexpr)
|
20
|
+
|
21
|
+
# recursive call
|
22
|
+
sexpr = next_call(processor, sexpr, bl)
|
23
|
+
|
24
|
+
# output coercion
|
25
|
+
if sexpr.is_a?(Array) and sexpr.first.is_a?(Symbol)
|
26
|
+
grammar(processor).sexpr(sexpr)
|
27
|
+
else
|
28
|
+
sexpr
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def grammar(processor)
|
35
|
+
if processor.respond_to?(:sexpr_grammar)
|
36
|
+
processor.sexpr_grammar
|
37
|
+
else
|
38
|
+
Sexpr
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end # class SexprCoercions
|
43
|
+
end # class Processor
|
44
|
+
end # module Sexpr
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sexpr
|
2
|
+
class Rewriter < Processor
|
3
|
+
helper SexprCoercions
|
4
|
+
|
5
|
+
def self.grammar(sexpr_grammar)
|
6
|
+
@sexpr_grammar = sexpr_grammar
|
7
|
+
end
|
8
|
+
|
9
|
+
def sexpr_grammar
|
10
|
+
(self.class.instance_variable_get(:"@sexpr_grammar") || super) rescue Sexpr
|
11
|
+
end
|
12
|
+
|
13
|
+
def copy_and_apply(sexpr)
|
14
|
+
sexpr.sexpr_copy do |copy, child|
|
15
|
+
copy << (Sexpr===child ? call(child) : child)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end # class Rewriter
|
20
|
+
end # module Sexpr
|
data/lib/sexpr/version.rb
CHANGED
data/sexpr.noespec
CHANGED
data/spec/grammar/test_parse.rb
CHANGED
@@ -3,7 +3,11 @@ module Sexpr
|
|
3
3
|
describe Grammar, "parse" do
|
4
4
|
|
5
5
|
def grammar
|
6
|
-
Sexpr.load(:parser => parser)
|
6
|
+
Sexpr.load(:parser => parser).extend Module.new{
|
7
|
+
def default_parse_options
|
8
|
+
{:root => :root_rule, :hello => "world"}
|
9
|
+
end
|
10
|
+
}
|
7
11
|
end
|
8
12
|
|
9
13
|
context 'when no parser is set' do
|
@@ -22,17 +26,17 @@ module Sexpr
|
|
22
26
|
Object.new.extend Module.new{
|
23
27
|
include Parser
|
24
28
|
def parse(s, options = {})
|
25
|
-
[options[:root]
|
29
|
+
[options[:root], "#{s} #{options[:hello]}"]
|
26
30
|
end
|
27
31
|
}
|
28
32
|
}
|
29
33
|
|
30
|
-
it '
|
31
|
-
grammar.parse("Hello
|
34
|
+
it 'uses default options when no options are passed' do
|
35
|
+
grammar.parse("Hello").should eq([:root_rule, "Hello world"])
|
32
36
|
end
|
33
37
|
|
34
|
-
it '
|
35
|
-
grammar.parse("
|
38
|
+
it 'merge passed options with default ones' do
|
39
|
+
grammar.parse("Hello", :root => :another_rule).should eq([:another_rule, "Hello world"])
|
36
40
|
end
|
37
41
|
|
38
42
|
end # when a parser is set
|
data/spec/grammar/test_sexpr.rb
CHANGED
@@ -2,21 +2,47 @@ require 'spec_helper'
|
|
2
2
|
module Sexpr
|
3
3
|
describe Grammar, "sexpr" do
|
4
4
|
|
5
|
-
def sexpr(expr,
|
6
|
-
@sexpr = Sexpr.load(:parser => parser).sexpr(expr,
|
5
|
+
def sexpr(expr, markers = nil)
|
6
|
+
@sexpr = Sexpr.load(:parser => parser).sexpr(expr, markers)
|
7
7
|
end
|
8
8
|
|
9
9
|
after{
|
10
10
|
@sexpr.should be_a(Sexpr) if @sexpr
|
11
11
|
}
|
12
12
|
|
13
|
-
context '
|
13
|
+
context 'on an array' do
|
14
14
|
let(:parser){ nil }
|
15
15
|
|
16
|
-
it '
|
16
|
+
it 'returns the sexpr array' do
|
17
17
|
sexpr([:sexpr, "world"]).should eq([:sexpr, "world"])
|
18
18
|
end
|
19
19
|
|
20
|
+
it 'extends it with the Sexpr module' do
|
21
|
+
sexpr([:sexpr, "world"]).should be_a(Sexpr)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets the markers if any' do
|
25
|
+
markers = {:hello => "world"}
|
26
|
+
sexpr([:sexpr, "world"], markers).tracking_markers.should eq(markers)
|
27
|
+
end
|
28
|
+
|
29
|
+
end # on an array
|
30
|
+
|
31
|
+
context 'on a Sexpr' do
|
32
|
+
let(:parser){ nil }
|
33
|
+
|
34
|
+
it 'merges the markers if provided' do
|
35
|
+
sexpr = sexpr([:sexpr, "world"], :hello => true, :who => "world")
|
36
|
+
sexpr.tracking_markers.should eq(:hello => true, :who => "world")
|
37
|
+
sexpr = sexpr(sexpr, :who => "WORLD")
|
38
|
+
sexpr.tracking_markers.should eq(:hello => true, :who => "WORLD")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when no parser is set and a String' do
|
44
|
+
let(:parser){ nil }
|
45
|
+
|
20
46
|
it 'raises an error when parser is needed' do
|
21
47
|
lambda{
|
22
48
|
sexpr("Hello world")
|
@@ -25,26 +51,34 @@ module Sexpr
|
|
25
51
|
|
26
52
|
end
|
27
53
|
|
28
|
-
context 'when a parser is set' do
|
54
|
+
context 'when a parser is set and a String' do
|
29
55
|
let(:parser){
|
30
56
|
Object.new.extend Module.new{
|
31
57
|
include Parser
|
32
|
-
def
|
33
|
-
|
58
|
+
def parse(s, options)
|
59
|
+
s.upcase
|
60
|
+
end
|
61
|
+
def to_sexpr(s)
|
62
|
+
Sexpr.sexpr([:parsed, s], {:hello => "world"})
|
34
63
|
end
|
35
64
|
}
|
36
65
|
}
|
37
66
|
|
38
|
-
it '
|
39
|
-
sexpr(
|
67
|
+
it 'delegates the call to the parser' do
|
68
|
+
sexpr("Hello world").should eq([:parsed, "HELLO WORLD"])
|
40
69
|
end
|
41
70
|
|
42
|
-
it '
|
43
|
-
sexpr("Hello world").should
|
71
|
+
it 'extends it with the Sexpr module' do
|
72
|
+
sexpr("Hello world").should be_a(Sexpr)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'sets the markers through recursive application' do
|
76
|
+
sexpr("Hello world").tracking_markers.should eq({:hello => "world"})
|
44
77
|
end
|
45
78
|
|
46
|
-
it '
|
47
|
-
|
79
|
+
it 'merge the provided markers' do
|
80
|
+
expected = {:hello => "world", :who => "world"}
|
81
|
+
sexpr("Hello world", {:who => "world"}).tracking_markers.should eq(expected)
|
48
82
|
end
|
49
83
|
|
50
84
|
end # when a parser is set
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
describe Node, "sexpr_copy" do
|
4
|
+
|
5
|
+
let(:markers){
|
6
|
+
{:hello => 'world'}
|
7
|
+
}
|
8
|
+
let(:array){
|
9
|
+
[:bool_not, [:bool_lit, true]]
|
10
|
+
}
|
11
|
+
let(:the_sexpr){
|
12
|
+
sexpr(array, markers)
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'degenerates to a tag preserving dup without a block' do
|
16
|
+
[the_sexpr.sexpr_copy, the_sexpr.dup].each do |copy|
|
17
|
+
copy.object_id.should_not eq(the_sexpr.object_id)
|
18
|
+
copy.should eq(array)
|
19
|
+
copy.should be_a(Sexpr)
|
20
|
+
copy.tracking_markers.should eq(markers)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'works ala inject with a block' do
|
25
|
+
copy = the_sexpr.sexpr_copy do |base, child|
|
26
|
+
base << [:bool_lit, false]
|
27
|
+
end
|
28
|
+
copy.object_id.should_not eq(the_sexpr.object_id)
|
29
|
+
copy.should eq([:bool_not, [:bool_lit, false]])
|
30
|
+
copy.should be_a(Sexpr)
|
31
|
+
copy.tracking_markers.should eq(markers)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Sexpr
|
3
|
+
describe Node, "tracking_markers" do
|
4
|
+
|
5
|
+
it 'defaults to {}' do
|
6
|
+
sexpr([:lit, true]).tracking_markers.should eq({})
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'can be installed through a private writer' do
|
10
|
+
sexpr, markers = sexpr([:lit, true]), {:hello => "world"}
|
11
|
+
sexpr.tracking_markers = markers
|
12
|
+
sexpr.tracking_markers.should eq(markers)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'are installed through the second argument of sexpr' do
|
16
|
+
markers = {:hello => "World"}
|
17
|
+
sexpr([:lit, true], markers).tracking_markers.should eq(markers)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|