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,53 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Grammar, "sexpr" do
4
+
5
+ def sexpr(expr, opts = {})
6
+ @sexpr = Sexpr.load(:parser => parser).sexpr(expr, opts)
7
+ end
8
+
9
+ after{
10
+ @sexpr.should be_a(Sexpr) if @sexpr
11
+ }
12
+
13
+ context 'when no parser is set' do
14
+ let(:parser){ nil }
15
+
16
+ it 'silently returns a sexpr array' do
17
+ sexpr([:sexpr, "world"]).should eq([:sexpr, "world"])
18
+ end
19
+
20
+ it 'raises an error when parser is needed' do
21
+ lambda{
22
+ sexpr("Hello world")
23
+ }.should raise_error(NoParserError)
24
+ end
25
+
26
+ end
27
+
28
+ context 'when a parser is set' do
29
+ let(:parser){
30
+ Object.new.extend Module.new{
31
+ include Parser
32
+ def sexpr(s, options = {})
33
+ [options[:root] || :parsed, s]
34
+ end
35
+ }
36
+ }
37
+
38
+ it 'silently returns a sexpr array' do
39
+ sexpr([:sexpr, "world"]).should eq([:sexpr, "world"])
40
+ end
41
+
42
+ it 'delegates the call to the parser' do
43
+ sexpr("Hello world").should eq([:parsed, "Hello world"])
44
+ end
45
+
46
+ it 'passes options' do
47
+ sexpr("world", :root => :hello).should eq([:hello, "world"])
48
+ end
49
+
50
+ end # when a parser is set
51
+
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Alternative, "eat" do
4
4
 
5
5
  let(:alt1){ Terminal.new(nil) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Alternative, "match?" do
4
4
 
5
5
  let(:alt1){ Terminal.new(nil) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Many, "eat" do
4
4
 
5
5
  let(:term){ Terminal.new(/^[a-z]+$/) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Many, "initialize" do
4
4
 
5
5
  it 'understands a single min' do
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Many do
4
4
 
5
5
  let(:term){ Terminal.new(/^[a-z]+$/) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Reference, "eat" do
4
4
 
5
5
  let(:grammar){ {:hello => Terminal.new(/^[a-z]+$/)} }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Reference, "match?" do
4
4
 
5
5
  let(:grammar){ {:hello => Terminal.new(/^[a-z]+$/)} }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Rule, "eat" do
4
4
 
5
5
  let(:defn){ Sequence.new [Terminal.new(/^[a-z]+$/)] }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Rule, 'match?' do
4
4
 
5
5
  let(:defn){ Sequence.new [Terminal.new(/^[a-z]+$/)] }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Sequence, "eat" do
4
4
 
5
5
  let(:alt1){ Terminal.new(nil) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Sequence, 'match?' do
4
4
 
5
5
  let(:alt1){ Terminal.new(nil) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Terminal, "eat" do
4
4
 
5
5
  let(:rule){ Terminal.new(/^[a-z]+$/) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Terminal, "match?" do
4
4
 
5
5
  let(:rule){ Terminal.new(arg) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- module Sexpr
2
+ module Sexpr::Matcher
3
3
  describe Terminal, "terminal_match?" do
4
4
 
5
5
  before do
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Node, "sexpr_body" do
4
+
5
+ it 'returns the head' do
6
+ sexpr([:lit, true]).sexpr_body.should eq([true])
7
+ end
8
+
9
+ it 'returns an empty array if no body' do
10
+ sexpr([:lit]).sexpr_body.should eq([])
11
+ end
12
+
13
+ it 'is aliased as sexp_body' do
14
+ sexpr([:lit, true]).sexp_body.should eq([true])
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Node, "sexpr_type" do
4
+
5
+ it 'returns the head' do
6
+ sexpr([:lit, true]).sexpr_type.should eq(:lit)
7
+ end
8
+
9
+ it 'is aliased as sexp_type' do
10
+ sexpr([:lit, true]).sexp_type.should eq(:lit)
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Parser
3
+ describe Citrus, "new" do
4
+
5
+ it 'factors a Parser instance when a Citrus::Grammar' do
6
+ p = Citrus.new(bool_expr_parser)
7
+ p.should be_a(Citrus)
8
+ p.parser.should eq(bool_expr_parser)
9
+ end
10
+
11
+ it 'factors a Parser instance when a Path to a .citrus file' do
12
+ p = Citrus.new(fixtures_path/"bool_expr.citrus")
13
+ p.should be_a(Citrus)
14
+ p.parser.should eq(bool_expr_parser)
15
+ end
16
+
17
+ it 'factors a Parser instance when a Path to a .citrus file' do
18
+ p = Citrus.new (fixtures_path/"bool_expr.citrus").to_s
19
+ p.should be_a(Citrus)
20
+ p.parser.should eq(bool_expr_parser)
21
+ end
22
+
23
+ it 'passes the options' do
24
+ Citrus.new(bool_expr_parser, {:hello => "World"}).options[:hello].should eq("World")
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Parser
3
+ describe Citrus, "parse" do
4
+
5
+ let(:parser){ Citrus.new(bool_expr_parser) }
6
+
7
+ it 'delegates the call to the Citrus parser' do
8
+ parser.parse("true").should be_a(::Citrus::Match)
9
+ end
10
+
11
+ it 'raises a Citrus::ParserError when parsing fails' do
12
+ lambda{
13
+ parser.parse("bl and or")
14
+ }.should raise_error(::Citrus::ParseError)
15
+ end
16
+
17
+ it 'recognizes the :root option' do
18
+ parser.parse("true", :root => :bool_lit).should be_a(::Citrus::Match)
19
+ parser.parse("x", :root => :var_ref).should be_a(::Citrus::Match)
20
+ lambda{
21
+ parser.parse("x", :root => :bool_lit)
22
+ }.should raise_error(::Citrus::ParseError)
23
+ end
24
+
25
+ it 'recognizes the :consume option' do
26
+ lambda{
27
+ parser.parse("true or")
28
+ }.should raise_error(::Citrus::ParseError)
29
+ parser.parse("true or", :consume => false).should eq("true")
30
+ end
31
+
32
+ it 'can parse from a Path' do
33
+ Path.tmpfile do |tmp|
34
+ tmp.write "x and y"
35
+ parser.parse(tmp).should be_a(::Citrus::Match)
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Parser
3
+ describe Citrus, "recognize?" do
4
+
5
+ it 'returns true on Citrus parsers' do
6
+ Citrus.should be_recognize(bool_expr_parser)
7
+ end
8
+
9
+ it 'returns true on a Citrus Path' do
10
+ Citrus.should be_recognize(fixtures_path/"bool_expr.citrus")
11
+ end
12
+
13
+ it 'returns false when not recognized' do
14
+ (Citrus.recognizes?(self)).should be_false
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Parser
3
+ describe Citrus do
4
+
5
+ it 'should be registered' do
6
+ Sexpr::Parser.find_parser_class(bool_expr_parser).should eq(Citrus)
7
+ end
8
+
9
+ it 'should be served for a Citrus grammar' do
10
+ Sexpr::Parser.factor(bool_expr_parser).should be_a(Citrus)
11
+ end
12
+
13
+ it 'should accept factor options, defaulting to defaults' do
14
+ cit = Sexpr::Parser.factor(bool_expr_parser, {:hello => "World"})
15
+ cit.options[:hello].should eq("World")
16
+ cit.options.should have_key(:from_match_to_sexpr)
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ module Sexpr::Parser
3
+ describe Citrus, "sexpr" do
4
+
5
+ it 'calls value by default' do
6
+ parser = Citrus.new(bool_expr_parser)
7
+ parser.sexpr("true").should eq([:bool_lit, true])
8
+ end
9
+
10
+ it 'delegates to from_match_to_sexpr if specified' do
11
+ parser = Citrus.new(bool_expr_parser, :from_match_to_sexpr => lambda{|x| 12})
12
+ parser.sexpr("true").should eq(12)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Parser, "factor" do
4
+
5
+ it 'silently returns when given a Parser' do
6
+ p = Object.new.extend(Parser)
7
+ Parser.factor(p).should eq(p)
8
+ end
9
+
10
+ it 'raises a UnrecognizedParserError when no class can be found' do
11
+ lambda{
12
+ Parser.factor(self)
13
+ }.should raise_error(UnrecognizedParserError)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ module Sexpr
3
+ describe Parser, "input_text" do
4
+ include Parser
5
+
6
+ it 'recognizes a string' do
7
+ input_text("Hello world").should eq("Hello world")
8
+ end
9
+
10
+ it 'accepts a path' do
11
+ input_text(Path.here).should eq(File.read(__FILE__))
12
+ end
13
+
14
+ it 'accepts an IO' do
15
+ File.open(__FILE__, 'r') do |io|
16
+ input_text(io).should eq(File.read(__FILE__))
17
+ end
18
+ end
19
+
20
+ it 'raises a InvalidParseSourceError when invalid source' do
21
+ lambda{
22
+ input_text(self)
23
+ }.should raise_error(InvalidParseSourceError)
24
+ end
25
+
26
+ end
27
+ end
@@ -1,3 +1,20 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
1
  require 'epath'
2
+ root = Path.backfind('.[lib]')
3
+
4
+ require 'citrus'
5
+
6
+ $LOAD_PATH.unshift (root/"lib").to_s
3
7
  require 'sexpr'
8
+ require (root/"examples/bool_expr/bool_expr").to_s
9
+
10
+ def fixtures_path
11
+ Path.dir/"../examples/bool_expr"
12
+ end
13
+
14
+ def bool_expr_parser
15
+ BoolExpr.parser.parser
16
+ end
17
+
18
+ def sexpr(*args)
19
+ Sexpr.sexpr(*args)
20
+ end
@@ -2,31 +2,58 @@ require 'spec_helper'
2
2
  module Sexpr
3
3
  describe Sexpr, "load" do
4
4
 
5
- let(:grammar){ Sexpr.load(arg) }
5
+ subject{ Sexpr.load(arg) }
6
6
 
7
- context "on grammar.yml" do
8
- let(:arg){ Path.dir/"grammar.yml" }
7
+ after do
8
+ subject.should be_a(Grammar)
9
+ subject[:bool_expr].should be_a(Matcher::Alternative)
10
+ end
11
+
12
+ context "on a YAML path" do
13
+ let(:arg){ fixtures_path/"bool_expr.sexp.yml" }
14
+
15
+ it 'sets the path on the grammar' do
16
+ subject.path.should eq(arg)
17
+ end
18
+
19
+ it 'finds the parser with the relative path' do
20
+ subject.parser.should be_a(Parser::Citrus)
21
+ end
9
22
 
10
- it 'returns a Grammar' do
11
- grammar.should be_a(Grammar)
23
+ end # grammar.yml
24
+
25
+ context "on a YAML path as a String" do
26
+ let(:arg){ (fixtures_path/"bool_expr.sexp.yml").to_s }
27
+
28
+ it 'sets the path on the grammar' do
29
+ subject.path.should eq(arg)
30
+ end
31
+
32
+ it 'finds the parser with the relative path' do
33
+ subject.parser.should be_a(Parser::Citrus)
12
34
  end
13
35
 
14
36
  end # grammar.yml
15
37
 
16
- context 'with a Hash' do
17
- let(:arg){ {:hello => /[a-z]+/} }
38
+ context 'with an explicit Hash' do
39
+ let(:arg){ {:rules => {:bool_expr => [true, false]}} }
18
40
 
19
- it 'returns a Grammar' do
20
- grammar.should be_a(Grammar)
41
+ it 'does not set a path' do
42
+ subject.path.should be_nil
21
43
  end
22
44
 
23
45
  end
24
46
 
25
47
  context 'with a String' do
26
- let(:arg){ "hello: true" }
27
-
28
- it 'returns a Grammar' do
29
- grammar.should be_a(Grammar)
48
+ let(:arg){
49
+ <<-YAML
50
+ rules:
51
+ bool_expr: [true, false]
52
+ YAML
53
+ }
54
+
55
+ it 'does not set a path' do
56
+ subject.path.should be_nil
30
57
  end
31
58
 
32
59
  end