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.
Files changed (83) hide show
  1. data/CHANGELOG.md +24 -1
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +2 -0
  4. data/LICENCE.md +1 -1
  5. data/Manifest.txt +1 -0
  6. data/README.md +46 -31
  7. data/examples/bool_expr/bool_expr.citrus +72 -0
  8. data/examples/bool_expr/bool_expr.rb +86 -0
  9. data/examples/bool_expr/bool_expr.sexp.yml +23 -0
  10. data/lib/sexpr.rb +27 -20
  11. data/lib/sexpr/errors.rb +15 -0
  12. data/lib/sexpr/grammar.rb +21 -77
  13. data/lib/sexpr/grammar/matching.rb +56 -0
  14. data/lib/sexpr/grammar/options.rb +53 -0
  15. data/lib/sexpr/grammar/parsing.rb +20 -0
  16. data/lib/sexpr/grammar/tagging.rb +49 -0
  17. data/lib/sexpr/loader.rb +0 -1
  18. data/lib/sexpr/matcher.rb +15 -0
  19. data/lib/sexpr/matcher/alternative.rb +30 -0
  20. data/lib/sexpr/matcher/many.rb +54 -0
  21. data/lib/sexpr/matcher/reference.rb +32 -0
  22. data/lib/sexpr/matcher/rule.rb +31 -0
  23. data/lib/sexpr/matcher/sequence.rb +30 -0
  24. data/lib/sexpr/matcher/terminal.rb +37 -0
  25. data/lib/sexpr/node.rb +16 -0
  26. data/lib/sexpr/parser.rb +48 -0
  27. data/lib/sexpr/parser/citrus.rb +67 -0
  28. data/lib/sexpr/version.rb +2 -2
  29. data/sexpr.gemspec +1 -0
  30. data/sexpr.noespec +2 -1
  31. data/spec/grammar/matching/test_compile_rule.rb +23 -0
  32. data/spec/grammar/matching/test_compile_rule_defn.rb +103 -0
  33. data/spec/grammar/options/test_install_parser.rb +36 -0
  34. data/spec/grammar/options/test_install_path.rb +19 -0
  35. data/spec/grammar/options/test_install_root.rb +27 -0
  36. data/spec/grammar/tagging/test_looks_a_sexpr.rb +20 -0
  37. data/spec/grammar/tagging/test_mod2rulename.rb +19 -0
  38. data/spec/grammar/tagging/test_rule2modname.rb +19 -0
  39. data/spec/grammar/tagging/test_tag_sexpr.rb +28 -0
  40. data/spec/grammar/test_new.rb +15 -0
  41. data/spec/grammar/test_parse.rb +27 -18
  42. data/spec/grammar/test_sexpr.rb +53 -0
  43. data/spec/{alternative → matcher/alternative}/test_eat.rb +1 -1
  44. data/spec/{alternative → matcher/alternative}/test_match_q.rb +1 -1
  45. data/spec/{many → matcher/many}/test_eat.rb +1 -1
  46. data/spec/{many → matcher/many}/test_initialize.rb +1 -1
  47. data/spec/{many → matcher/many}/test_match_q.rb +1 -1
  48. data/spec/{reference → matcher/reference}/test_eat.rb +1 -1
  49. data/spec/{reference → matcher/reference}/test_match_q.rb +1 -1
  50. data/spec/{rule → matcher/rule}/test_eat.rb +1 -1
  51. data/spec/{rule → matcher/rule}/test_match_q.rb +1 -1
  52. data/spec/{sequence → matcher/sequence}/test_eat.rb +1 -1
  53. data/spec/{sequence → matcher/sequence}/test_match_q.rb +1 -1
  54. data/spec/{terminal → matcher/terminal}/test_eat.rb +1 -1
  55. data/spec/{terminal → matcher/terminal}/test_match_q.rb +1 -1
  56. data/spec/{terminal → matcher/terminal}/test_terminal_match.rb +1 -1
  57. data/spec/node/test_sexpr_body.rb +18 -0
  58. data/spec/node/test_sexpr_type.rb +14 -0
  59. data/spec/parser/citrus/test_new.rb +28 -0
  60. data/spec/parser/citrus/test_parse.rb +40 -0
  61. data/spec/parser/citrus/test_recognize.rb +18 -0
  62. data/spec/parser/citrus/test_registration.rb +20 -0
  63. data/spec/parser/citrus/test_to_sexpr.rb +16 -0
  64. data/spec/parser/test_factor.rb +17 -0
  65. data/spec/parser/test_input_text.rb +27 -0
  66. data/spec/spec_helper.rb +18 -1
  67. data/spec/test_load.rb +40 -13
  68. data/spec/test_readme_examples.rb +40 -30
  69. data/spec/test_sexpr.rb +1 -1
  70. metadata +118 -68
  71. data/lib/sexpr/alternative.rb +0 -28
  72. data/lib/sexpr/element.rb +0 -9
  73. data/lib/sexpr/many.rb +0 -52
  74. data/lib/sexpr/reference.rb +0 -30
  75. data/lib/sexpr/rule.rb +0 -29
  76. data/lib/sexpr/sequence.rb +0 -28
  77. data/lib/sexpr/terminal.rb +0 -35
  78. data/spec/bool_expr.yml +0 -19
  79. data/spec/grammar/test_compile_rule.rb +0 -25
  80. data/spec/grammar/test_compile_rule_defn.rb +0 -98
  81. data/spec/grammar/test_fetch.rb +0 -18
  82. data/spec/grammar/test_root.rb +0 -20
  83. data/spec/test_bool_expr.rb +0 -27
@@ -0,0 +1,67 @@
1
+ module Sexpr
2
+ module Parser
3
+ class Citrus
4
+ include Parser
5
+
6
+ class << self
7
+
8
+ def recognizes?(arg)
9
+ looks_a_citrus_grammar?(arg) or
10
+ looks_a_citrus_file?(arg)
11
+ end
12
+
13
+ def looks_a_citrus_file?(arg)
14
+ arg = arg.to_path if arg.respond_to?(:to_path)
15
+ arg.is_a?(String) && File.exists?(arg) && (File.extname(arg) == ".citrus")
16
+ end
17
+
18
+ def looks_a_citrus_grammar?(arg)
19
+ defined?(::Citrus::Grammar) && arg.is_a?(Module) && arg.include?(::Citrus::Grammar)
20
+ end
21
+
22
+ end # class << self
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}}
33
+ end
34
+
35
+ def parser
36
+ @citrus_parser ||= begin
37
+ if self.class.looks_a_citrus_grammar?(@parser)
38
+ @parser
39
+ elsif self.class.looks_a_citrus_file?(@parser)
40
+ @parser = @parser.to_path if @parser.respond_to?(:to_path)
41
+ @parser = @parser[0...-(".citrus".length)]
42
+ ::Citrus.load(@parser).last
43
+ else
44
+ raise UnrecognizedParserError, "Not a citrus parser #{@parser}"
45
+ end
46
+ end
47
+ end
48
+
49
+ def parse(input, options = {})
50
+ input = input_text(input)
51
+ parser.parse(input, options)
52
+ end
53
+
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)
62
+ end
63
+
64
+ Sexpr::Parser.register self
65
+ end # class Citrus
66
+ end # module Parser
67
+ end # module Sexpr
@@ -2,7 +2,7 @@ module Sexpr
2
2
  module Version
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 2
5
+ MINOR = 3
6
6
  TINY = 0
7
7
 
8
8
  def self.to_s
@@ -11,4 +11,4 @@ module Sexpr
11
11
 
12
12
  end
13
13
  VERSION = Version.to_s
14
- end
14
+ end
@@ -124,6 +124,7 @@ Gem::Specification.new do |s|
124
124
  # for each development dependency. These gems are required for developers
125
125
  #
126
126
  s.add_development_dependency("epath", "~> 0.0.1")
127
+ s.add_development_dependency("citrus", "~> 2.4")
127
128
  s.add_development_dependency("rake", "~> 0.9.2")
128
129
  s.add_development_dependency("rspec", "~> 2.8.0")
129
130
  s.add_development_dependency("wlang", "~> 0.10.2")
@@ -9,7 +9,7 @@ variables:
9
9
  upper:
10
10
  Sexpr
11
11
  version:
12
- 0.2.0
12
+ 0.3.0
13
13
  summary: |-
14
14
  A compilation framework around s-expressions
15
15
  description: |-
@@ -20,6 +20,7 @@ variables:
20
20
  - https://github.com/blambeau/sexp
21
21
  dependencies:
22
22
  - {name: epath, version: "~> 0.0.1", groups: [development]}
23
+ - {name: citrus, version: "~> 2.4", groups: [development]}
23
24
  - {name: rake, version: "~> 0.9.2", groups: [development]}
24
25
  - {name: rspec, version: "~> 2.8.0", groups: [development]}
25
26
  - {name: wlang, version: "~> 0.10.2", groups: [development]}
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ module Grammar
4
+ describe Matching, "compile_rule" do
5
+ include Matching
6
+
7
+ it 'keep alternatives unchanged' do
8
+ compile_rule(:hello, Matcher::Alternative.new([]) ).should be_a(Matcher::Alternative)
9
+ end
10
+
11
+ it 'keep terminals unchanged' do
12
+ compile_rule(:hello, Matcher::Terminal.new(true) ).should be_a(Matcher::Terminal)
13
+ end
14
+
15
+ it 'keep creates a Rule englobing sequences' do
16
+ compiled = compile_rule(:hello, Matcher::Sequence.new([]) )
17
+ compiled.should be_a(Matcher::Rule)
18
+ compiled.name.should eq(:hello)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ module Grammar
4
+ describe Matching, "compile_rule_defn" do
5
+ include Matching
6
+
7
+ subject{ compile_rule_defn(arg, :grammar) }
8
+
9
+ def rules
10
+ {:a_rule => "hello"}
11
+ end
12
+
13
+ context 'with an Element' do
14
+ let(:arg){ Matcher::Terminal.new(//) }
15
+ it 'returns arg itself' do
16
+ subject.should eq(arg)
17
+ end
18
+ end
19
+
20
+ context "with true" do
21
+ let(:arg){ true }
22
+ it 'gives it true' do
23
+ subject.should be_a(Matcher::Terminal)
24
+ subject.value.should eq(true)
25
+ end
26
+ end
27
+
28
+ context "with false" do
29
+ let(:arg){ false }
30
+ it 'gives it false' do
31
+ subject.should be_a(Matcher::Terminal)
32
+ subject.value.should eq(false)
33
+ end
34
+ end
35
+
36
+ context "with nil" do
37
+ let(:arg){ nil }
38
+ it 'gives it nil' do
39
+ subject.should be_a(Matcher::Terminal)
40
+ subject.value.should eq(nil)
41
+ end
42
+ end
43
+
44
+ context 'with an alternative array' do
45
+ let(:arg){ [true, false, nil] }
46
+ it 'factors an Alternative' do
47
+ subject.should be_a(Matcher::Alternative)
48
+ end
49
+ it 'compiles its elements' do
50
+ subject.terms.size.should eq(3)
51
+ subject.terms.all?{|x| x.is_a?(Matcher::Terminal)}.should be_true
52
+ end
53
+ end
54
+
55
+ context 'with a sequence array' do
56
+ let(:arg){ [[true, false, nil]] }
57
+ it 'factors a Sequence' do
58
+ subject.should be_a(Matcher::Sequence)
59
+ end
60
+ it 'compiles its elements' do
61
+ subject.terms.size.should eq(3)
62
+ subject.terms.all?{|x| x.is_a?(Matcher::Terminal)}.should be_true
63
+ end
64
+ end
65
+
66
+ context 'with subalternatives' do
67
+ let(:arg){ [ ["a_rule", [false, true, nil] ]] }
68
+ it 'compiles the last as an Alternative' do
69
+ subject.terms.last.should be_a(Matcher::Alternative)
70
+ end
71
+ end
72
+
73
+ context 'with a reference to a non-terminal' do
74
+ let(:arg){ "a_rule" }
75
+ it 'factors a Reference' do
76
+ subject.should be_a(Matcher::Reference)
77
+ end
78
+ it 'refers to the appropriate rule name' do
79
+ subject.rule_name.should eq(:a_rule)
80
+ end
81
+ it 'refers to the appropriate grammar' do
82
+ subject.grammar.should eq(:grammar)
83
+ end
84
+ end
85
+
86
+ context 'with a stared non-terminal' do
87
+ let(:arg){ "a_rule+" }
88
+ it 'factors a Many' do
89
+ subject.should be_a(Matcher::Many)
90
+ end
91
+ it 'refers to the appropriate rule' do
92
+ subject.term.should be_a(Matcher::Reference)
93
+ subject.term.rule_name.should eq(:a_rule)
94
+ end
95
+ it 'refers to the appropriate multiplicities' do
96
+ subject.min.should eq(1)
97
+ subject.max.should be_nil
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Grammar::Options, "install_parser" do
4
+ include Grammar::Options
5
+
6
+ it 'is nil by default' do
7
+ h = {}
8
+ install_options h do
9
+ parser.should be_nil
10
+ end
11
+ end
12
+
13
+ it 'can be specified through a :parser option' do
14
+ h = {:parser => bool_expr_parser}
15
+ install_options h do
16
+ parser.should be_a(Parser::Citrus)
17
+ end
18
+ end
19
+
20
+ it 'can be specified as a Path' do
21
+ h = {:parser => fixtures_path/"bool_expr.citrus"}
22
+ install_options h do
23
+ parser.should be_a(Parser::Citrus)
24
+ end
25
+ end
26
+
27
+ it 'can be specified as relative Path' do
28
+ h = {:path => fixtures_path/"bool_expr.sexp.yml",
29
+ :parser => "bool_expr.citrus"}
30
+ install_options h do
31
+ parser.should be_a(Parser::Citrus)
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Grammar::Options, "install_path" do
4
+ include Grammar::Options
5
+
6
+ it 'is nil by default' do
7
+ install_options({}) do
8
+ path.should be_nil
9
+ end
10
+ end
11
+
12
+ it 'keeps the specified value if any' do
13
+ install_options :path => "blah" do
14
+ path.should eq("blah")
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Grammar::Options, "install_root" do
4
+ include Grammar::Options
5
+
6
+ let(:rules){ {:t => /[a-z]+/, :nt => true} }
7
+
8
+ it 'is the first key by default' do
9
+ install_options :rules => rules do
10
+ root.should eq(:t)
11
+ end
12
+ end
13
+
14
+ it 'is the specified rule when specified' do
15
+ install_options :rules => rules, :root => :nt do
16
+ root.should eq(:nt)
17
+ end
18
+ end
19
+
20
+ it 'is converted to a Symbol is a String' do
21
+ install_options :root => "nt" do
22
+ root.should eq(:nt)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Grammar
3
+ describe Tagging, "looks_a_sexpr" do
4
+ include Tagging
5
+
6
+ it 'recognizes s-expressions' do
7
+ looks_a_sexpr?([:lit]).should be_true
8
+ end
9
+
10
+ it 'does not recognize empty arrays' do
11
+ looks_a_sexpr?([]).should be_false
12
+ end
13
+
14
+ it 'does not recognize others' do
15
+ looks_a_sexpr?(nil).should be_false
16
+ looks_a_sexpr?(:lit).should be_false
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Grammar
3
+ describe Tagging, "mod2rulename" do
4
+ include Tagging
5
+
6
+ it 'work on simple module name' do
7
+ mod2rulename(:Test).should eq(:test)
8
+ end
9
+
10
+ it 'work on complex module name' do
11
+ mod2rulename(:ThisIsATest).should eq(:this_is_a_test)
12
+ end
13
+
14
+ it 'works with a module' do
15
+ mod2rulename(::Sexpr::Grammar).should eq(:grammar)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Grammar
3
+ describe Tagging, "rule2modname" do
4
+ include Tagging
5
+
6
+ it 'work on simple rule name' do
7
+ rule2modname(:test).should eq(:Test)
8
+ end
9
+
10
+ it 'works when underscores are present' do
11
+ rule2modname(:a_rule_name).should eq(:ARuleName)
12
+ end
13
+
14
+ it 'works with a string' do
15
+ rule2modname("a_rule_name").should eq(:ARuleName)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Grammar
3
+ describe Tagging, "tag_sexpr" do
4
+ include Tagging
5
+
6
+ module TaggingReference
7
+ module Not; end
8
+ module Lit; end
9
+ end
10
+
11
+ def tag(x)
12
+ res = tag_sexpr(x, TaggingReference)
13
+ res.should eq(x)
14
+ res
15
+ end
16
+
17
+ it 'tags a sexpr at first level' do
18
+ tag([:lit]).should be_a(TaggingReference::Lit)
19
+ end
20
+
21
+ it 'tags sexpr recursively' do
22
+ res = tag([:not, [:lit, true]])
23
+ res.should be_a(TaggingReference::Not)
24
+ res.last.should be_a(TaggingReference::Lit)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Grammar, "new" do
4
+
5
+ subject{ Grammar.new }
6
+
7
+ it 'factors a module' do
8
+ subject.should be_a(Grammar)
9
+ subject.should be_a(Grammar::Options)
10
+ subject.should be_a(Grammar::Matching)
11
+ subject.should be_a(Grammar::Parsing)
12
+ end
13
+
14
+ end
15
+ end
@@ -2,31 +2,40 @@ require 'spec_helper'
2
2
  module Sexpr
3
3
  describe Grammar, "parse" do
4
4
 
5
- def parser
6
- Object.new.tap{|x|
7
- def x.parse(s)
8
- Struct.new(:value).new([:parsed, s])
9
- end
10
- }
5
+ def grammar
6
+ Sexpr.load(:parser => parser)
11
7
  end
12
8
 
13
- def grammar(options = {})
14
- Sexpr.load({}, {:parser => parser})
15
- end
9
+ context 'when no parser is set' do
10
+ let(:parser){ nil }
16
11
 
17
- it 'it accepts a string' do
18
- grammar.parse("Hello world").should eq([:parsed, "Hello world"])
19
- end
12
+ it 'raises an error' do
13
+ lambda{
14
+ grammar.parse("Hello world")
15
+ }.should raise_error(NoParserError)
16
+ end
20
17
 
21
- it 'it accepts a path' do
22
- grammar.parse(Path.here).should eq([:parsed, File.read(__FILE__)])
23
18
  end
24
19
 
25
- it 'it accepts an IO' do
26
- File.open(__FILE__, 'r') do |io|
27
- grammar.parse(io).should eq([:parsed, File.read(__FILE__)])
20
+ context 'when a parser is set' do
21
+ let(:parser){
22
+ Object.new.extend Module.new{
23
+ include Parser
24
+ def parse(s, options = {})
25
+ [options[:root] || :parsed, input_text(s)]
26
+ end
27
+ }
28
+ }
29
+
30
+ it 'delegates the call to the parser' do
31
+ grammar.parse("Hello world").should eq([:parsed, "Hello world"])
28
32
  end
29
- end
33
+
34
+ it 'passes options' do
35
+ grammar.parse("world", :root => :hello).should eq([:hello, "world"])
36
+ end
37
+
38
+ end # when a parser is set
30
39
 
31
40
  end
32
41
  end