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