sexpr 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG.md +29 -0
  2. data/README.md +28 -2
  3. data/examples/bool_expr/bool_expr.citrus +6 -6
  4. data/examples/bool_expr/bool_expr.rb +68 -1
  5. data/lib/sexpr.rb +29 -22
  6. data/lib/sexpr/errors.rb +3 -0
  7. data/lib/sexpr/grammar.rb +7 -6
  8. data/lib/sexpr/grammar/options.rb +3 -0
  9. data/lib/sexpr/grammar/parsing.rb +6 -1
  10. data/lib/sexpr/grammar/tagging.rb +30 -11
  11. data/lib/sexpr/node.rb +30 -0
  12. data/lib/sexpr/parser.rb +2 -2
  13. data/lib/sexpr/parser/citrus.rb +6 -17
  14. data/lib/sexpr/parser/ext.rb +9 -0
  15. data/lib/sexpr/processor.rb +61 -0
  16. data/lib/sexpr/processor/helper.rb +31 -0
  17. data/lib/sexpr/processor/null_helper.rb +11 -0
  18. data/lib/sexpr/processor/sexpr_coercions.rb +44 -0
  19. data/lib/sexpr/rewriter.rb +20 -0
  20. data/lib/sexpr/version.rb +1 -1
  21. data/sexpr.noespec +1 -1
  22. data/spec/grammar/test_parse.rb +10 -6
  23. data/spec/grammar/test_sexpr.rb +47 -13
  24. data/spec/node/test_sexpr_copy.rb +35 -0
  25. data/spec/node/test_tracking_markers.rb +21 -0
  26. data/spec/parser/citrus/test_new.rb +0 -4
  27. data/spec/parser/citrus/test_parse.rb +4 -0
  28. data/spec/parser/citrus/test_registration.rb +0 -6
  29. data/spec/parser/citrus/test_to_sexpr.rb +22 -7
  30. data/spec/processor/helper/test_call.rb +51 -0
  31. data/spec/processor/test_build_helper_chain.rb +24 -0
  32. data/spec/processor/test_call.rb +46 -0
  33. data/spec/processor/test_helper.rb +19 -0
  34. data/spec/processor/test_main_processor.rb +18 -0
  35. data/spec/processor/test_sexpr_coercions.rb +46 -0
  36. data/spec/rewriter/test_copy_and_apply.rb +29 -0
  37. data/spec/spec_helper.rb +22 -0
  38. data/spec/test_readme_examples.rb +11 -0
  39. data/spec/test_rewriter.rb +16 -0
  40. metadata +106 -80
@@ -17,10 +17,10 @@ module Sexpr
17
17
  }
18
18
  end
19
19
 
20
- def factor(external_parser, options = {})
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, options)
23
+ cl.new(external_parser)
24
24
  else
25
25
  raise UnrecognizedParserError, "Parser not recognized: #{external_parser}"
26
26
  end
@@ -21,15 +21,9 @@ module Sexpr
21
21
 
22
22
  end # class << self
23
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}}
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 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)
49
+ def to_sexpr(parsed)
50
+ parsed.sexpr
62
51
  end
63
52
 
64
53
  Sexpr::Parser.register self
@@ -0,0 +1,9 @@
1
+ module Citrus
2
+ class Match
3
+
4
+ def sexpr
5
+ Sexpr.sexpr(value, {:citrus_match => self})
6
+ end
7
+
8
+ end
9
+ end
@@ -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,11 @@
1
+ module Sexpr
2
+ class Processor
3
+ class NullHelper
4
+
5
+ def call(processor, sexpr, &bl)
6
+ bl.call(processor, sexpr)
7
+ end
8
+
9
+ end # class NullHelper
10
+ end # class Processor
11
+ 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
@@ -2,7 +2,7 @@ module Sexpr
2
2
  module Version
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 3
5
+ MINOR = 4
6
6
  TINY = 0
7
7
 
8
8
  def self.to_s
@@ -9,7 +9,7 @@ variables:
9
9
  upper:
10
10
  Sexpr
11
11
  version:
12
- 0.3.0
12
+ 0.4.0
13
13
  summary: |-
14
14
  A compilation framework around s-expressions
15
15
  description: |-
@@ -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] || :parsed, input_text(s)]
29
+ [options[:root], "#{s} #{options[:hello]}"]
26
30
  end
27
31
  }
28
32
  }
29
33
 
30
- it 'delegates the call to the parser' do
31
- grammar.parse("Hello world").should eq([:parsed, "Hello world"])
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 'passes options' do
35
- grammar.parse("world", :root => :hello).should eq([:hello, "world"])
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
@@ -2,21 +2,47 @@ require 'spec_helper'
2
2
  module Sexpr
3
3
  describe Grammar, "sexpr" do
4
4
 
5
- def sexpr(expr, opts = {})
6
- @sexpr = Sexpr.load(:parser => parser).sexpr(expr, opts)
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 'when no parser is set' do
13
+ context 'on an array' do
14
14
  let(:parser){ nil }
15
15
 
16
- it 'silently returns a sexpr array' do
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 sexpr(s, options = {})
33
- [options[:root] || :parsed, s]
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 'silently returns a sexpr array' do
39
- sexpr([:sexpr, "world"]).should eq([:sexpr, "world"])
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 'delegates the call to the parser' do
43
- sexpr("Hello world").should eq([:parsed, "Hello world"])
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 'passes options' do
47
- sexpr("world", :root => :hello).should eq([:hello, "world"])
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