sexpr 0.3.0 → 0.4.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 +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
|