sexpr 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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