neg 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,76 @@
1
+ #--
2
+ # Copyright (c) 2012-2013, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Neg
27
+
28
+ class Translator
29
+
30
+ def self.on(node_name, &block)
31
+
32
+ @rules ||=
33
+ { nil => lambda { |n| n.results.empty? ? throw(nil) : n.results } }
34
+ #
35
+ # the default rule for anonymous nodes...
36
+
37
+ @rules[node_name] = block
38
+ end
39
+
40
+ def self.translate(parse_tree)
41
+
42
+ results =
43
+ parse_tree[4].each_with_object([]) { |node, a|
44
+ catch(nil) { a << translate(node) }
45
+ }
46
+
47
+ apply(parse_tree, results)
48
+ end
49
+
50
+ def self.apply(parse_tree, results)
51
+
52
+ rule = (@rules || {})[parse_tree[0]]
53
+
54
+ rule ? rule.call(Node.new(parse_tree, results)) : results
55
+ end
56
+
57
+ class Node
58
+
59
+ attr_reader :parse_tree, :results
60
+
61
+ def initialize(parse_tree, results)
62
+
63
+ @parse_tree = parse_tree
64
+ @results = results
65
+ end
66
+
67
+ def position; @parse_tree[1]; end
68
+ def offset; @parse_tree[1][0]; end
69
+ def line; @parse_tree[1][1]; end
70
+ def column; @parse_tree[1][2]; end
71
+
72
+ def result; @parse_tree[3]; end
73
+ end
74
+ end
75
+ end
76
+
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2012-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2012-2013, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -25,6 +25,6 @@
25
25
 
26
26
  module Neg
27
27
 
28
- VERSION = '0.3.0'
28
+ VERSION = '1.0.0'
29
29
  end
30
30
 
@@ -11,14 +11,19 @@ describe Neg::Parser::AlternativeParser do
11
11
  it 'parses' do
12
12
 
13
13
  AltParser.parse('x').should ==
14
- [ :text, [ 0, 1, 1 ], true, nil, [
15
- [ nil, [ 0, 1, 1 ], true, 'x', [] ] ] ]
14
+ [ :text, [ 0, 1, 1 ], true, 'x', [] ]
16
15
  end
17
16
 
18
17
  it 'parses (2nd alternative succeeds)' do
19
18
 
20
19
  AltParser.parse('y').should ==
21
- [ :text, [ 0, 1, 1 ], true, nil, [
20
+ [ :text, [ 0, 1, 1 ], true, 'y', [] ]
21
+ end
22
+
23
+ it 'parses (2nd alternative succeeds) :noreduce => true' do
24
+
25
+ AltParser.parse('y', :noreduce => true).should ==
26
+ [ :text, [ 0, 1, 1 ], true, 'y', [
22
27
  [ nil, [ 0, 1, 1 ], false, 'expected "x", got "y"', [] ],
23
28
  [ nil, [ 0, 1, 1 ], true, 'y', [] ] ] ]
24
29
  end
@@ -15,12 +15,7 @@ describe Neg::Parser::CharacterParser do
15
15
  it 'parses "xy"' do
16
16
 
17
17
  parser.parse('xy').should ==
18
- [ :text,
19
- [ 0, 1, 1 ],
20
- true,
21
- nil,
22
- [ [ nil, [ 0, 1, 1 ], true, "x", [] ],
23
- [ nil, [ 1, 1, 2 ], true, "y", [] ] ] ]
18
+ [ :text, [ 0, 1, 1 ], true, 'xy', [] ]
24
19
  end
25
20
 
26
21
  it 'fails gracefully' do
@@ -55,19 +50,7 @@ describe Neg::Parser::CharacterParser do
55
50
  it 'parses "tel:0-99"' do
56
51
 
57
52
  parser.parse('tel:0-99').should ==
58
- [ :text,
59
- [ 0, 1, 1 ],
60
- true,
61
- nil,
62
- [ [ nil, [ 0, 1, 1 ], true, "tel:", [] ],
63
- [ nil,
64
- [ 4, 1, 5 ],
65
- true,
66
- nil,
67
- [ [ nil, [ 4, 1, 5 ], true, "0", [] ],
68
- [ nil, [ 5, 1, 6 ], true, "-", [] ],
69
- [ nil, [ 6, 1, 7 ], true, "9", [] ],
70
- [ nil, [ 7, 1, 8 ], true, "9", [] ] ] ] ] ]
53
+ [ :text, [ 0, 1, 1 ], true, 'tel:0-99', [] ]
71
54
  end
72
55
 
73
56
  it 'fails gracefully' do
@@ -17,6 +17,17 @@ describe Neg::Parser::LookaheadParser do
17
17
  it 'parses' do
18
18
 
19
19
  parser.parse('xz').should ==
20
+ [ :root,
21
+ [ 0, 1, 1 ],
22
+ true,
23
+ nil,
24
+ [ [ :x, [ 0, 1, 1 ], true, 'x', [] ],
25
+ [ :z, [ 1, 1, 2 ], true, 'z', [] ] ] ]
26
+ end
27
+
28
+ it 'parses (:noreduce => true)' do
29
+
30
+ parser.parse('xz', :noreduce => true).should ==
20
31
  [ :root,
21
32
  [ 0, 1, 1 ],
22
33
  true,
@@ -24,11 +35,11 @@ describe Neg::Parser::LookaheadParser do
24
35
  [ [ :x,
25
36
  [ 0, 1, 1 ],
26
37
  true,
27
- nil,
28
- [ [ nil, [ 0, 1, 1 ], true, "x", [] ],
29
- [ nil, [ 1, 1, 2 ], true, nil, [
30
- [ nil, [ 1, 1, 2 ], true, "z", [] ] ] ] ] ],
31
- [ :z, [ 1, 1, 2 ], true, "z", [] ] ] ]
38
+ 'x',
39
+ [ [ nil, [ 0, 1, 1 ], true, 'x', [] ],
40
+ [ nil, [ 1, 1, 2 ], true, '', [
41
+ [ nil, [ 1, 1, 2 ], true, 'z', [] ] ] ] ] ],
42
+ [ :z, [ 1, 1, 2 ], true, 'z', [] ] ] ]
32
43
  end
33
44
 
34
45
  it 'fails gracefully' do
@@ -79,17 +90,8 @@ describe Neg::Parser::LookaheadParser do
79
90
  [ 0, 1, 1 ],
80
91
  true,
81
92
  nil,
82
- [ [ :x,
83
- [ 0, 1, 1 ],
84
- true,
85
- nil,
86
- [ [ nil, [ 0, 1, 1 ], true, "x", [] ],
87
- [ nil,
88
- [ 1, 1, 2 ],
89
- true,
90
- nil,
91
- [ [ nil, [ 1, 1, 2 ], false, "expected \"y\", got \"z\"", [] ] ] ] ] ],
92
- [ :z, [ 1, 1, 2 ], true, "z", [] ] ] ]
93
+ [ [ :x, [ 0, 1, 1 ], true, 'x', [] ],
94
+ [ :z, [ 1, 1, 2 ], true, 'z', [] ] ] ]
93
95
  end
94
96
 
95
97
  it 'fails gracefully' do
@@ -27,9 +27,7 @@ describe Neg::Parser::NonTerminalParser do
27
27
  end
28
28
 
29
29
  parser.parse('X').should ==
30
- [ :x, [ 0, 1, 1 ], true, nil, [
31
- [ nil, [ 0, 1, 1 ], false, "expected \"x\", got \"X\"", [] ],
32
- [ nil, [ 0, 1, 1 ], true, "X", [] ] ] ]
30
+ [ :x, [ 0, 1, 1 ], true, 'X', [] ]
33
31
  end
34
32
 
35
33
  it 'is rendered as x when on the right side' do
@@ -49,14 +47,15 @@ describe Neg::Parser::NonTerminalParser do
49
47
  }.strip
50
48
  end
51
49
 
52
- it 'flips burgers' do
50
+ it 'does not bind if the name starts with _' do
53
51
 
54
52
  parser = Class.new(Neg::Parser) do
55
- sentence == (word + ` `) * 1
56
- word == _("a-z") * 1
53
+ sentence == (_word + ` `) * 1
54
+ _word == _("a-z") * 1
57
55
  end
58
56
 
59
- pp parser.parse("ab cd ")
57
+ parser.parse("ab cd ").should ==
58
+ [ :sentence, [ 0, 1, 1 ], true, 'ab cd ', [] ]
60
59
  end
61
60
  end
62
61
 
@@ -87,11 +86,9 @@ describe Neg::Parser::NonTerminalParser do
87
86
  [ 0, 1, 1 ],
88
87
  true,
89
88
  nil,
90
- [ [ 'vehicle', [ 0, 1, 1 ], true, nil, [
91
- [ nil, [ 0, 1, 1 ], true, 'car', [] ] ] ],
92
- [ nil, [ 3, 1, 4 ], true, '_', [] ],
93
- [ 'city', [ 4, 1, 5 ], true, nil, [
94
- [ nil, [ 4, 1, 5 ], true, 'cluj', [] ] ] ] ] ]
89
+ [ [ 'vehicle', [ 0, 1, 1 ], true, 'car', [] ],
90
+ [ nil, [ 3, 1, 4 ], true, '_', [] ],
91
+ [ 'city', [ 4, 1, 5 ], true, 'cluj', [] ] ] ]
95
92
  end
96
93
  end
97
94
  end
@@ -15,7 +15,7 @@ describe Neg::Parser::RepetitionParser do
15
15
  it 'parses the empty string' do
16
16
 
17
17
  parser.parse('').should ==
18
- [ :text, [ 0, 1, 1 ], true, nil, [] ]
18
+ [ :text, [ 0, 1, 1 ], true, '', [] ]
19
19
  end
20
20
 
21
21
  it 'fails gracefully' do
@@ -48,16 +48,13 @@ describe Neg::Parser::RepetitionParser do
48
48
  it 'parses the empty string' do
49
49
 
50
50
  parser.parse('').should ==
51
- [ :text, [ 0, 1, 1 ], true, nil, [] ]
51
+ [ :text, [ 0, 1, 1 ], true, '', [] ]
52
52
  end
53
53
 
54
54
  it 'parses' do
55
55
 
56
56
  parser.parse('xxx').should ==
57
- [ :text, [ 0, 1, 1 ], true, nil, [
58
- [ nil, [ 0, 1, 1 ], true, 'x', [] ],
59
- [ nil, [ 1, 1, 2 ], true, 'x', [] ],
60
- [ nil, [ 2, 1, 3 ], true, 'x', [] ] ] ]
57
+ [ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
61
58
  end
62
59
 
63
60
  it 'fails gracefully' do
@@ -81,10 +78,7 @@ describe Neg::Parser::RepetitionParser do
81
78
  it 'parses' do
82
79
 
83
80
  parser.parse('xxx').should ==
84
- [ :text, [ 0, 1, 1 ], true, nil, [
85
- [ nil, [ 0, 1, 1 ], true, 'x', [] ],
86
- [ nil, [ 1, 1, 2 ], true, 'x', [] ],
87
- [ nil, [ 2, 1, 3 ], true, 'x', [] ] ] ]
81
+ [ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
88
82
  end
89
83
 
90
84
  it 'fails gracefully' do
@@ -107,10 +101,7 @@ describe Neg::Parser::RepetitionParser do
107
101
  it 'parses' do
108
102
 
109
103
  parser.parse('xxx').should ==
110
- [ :text, [ 0, 1, 1 ], true, nil, [
111
- [ nil, [ 0, 1, 1 ], true, 'x', [] ],
112
- [ nil, [ 1, 1, 2 ], true, 'x', [] ],
113
- [ nil, [ 2, 1, 3 ], true, 'x', [] ] ] ]
104
+ [ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
114
105
  end
115
106
 
116
107
  it 'fails gracefully' do
@@ -11,9 +11,7 @@ describe Neg::Parser::SequenceParser do
11
11
  it 'parses' do
12
12
 
13
13
  SeqParser.parse('xy').should ==
14
- [ :text, [ 0, 1, 1 ], true, nil, [
15
- [ nil, [ 0, 1, 1 ], true, 'x', [] ],
16
- [ nil, [ 1, 1, 2 ], true, 'y', [] ] ] ]
14
+ [ :text, [ 0, 1, 1 ], true, 'xy', [] ]
17
15
  end
18
16
 
19
17
  it 'fails gracefully' do
@@ -51,18 +49,7 @@ describe Neg::Parser::SequenceParser do
51
49
  [ 0, 1, 1 ],
52
50
  true,
53
51
  nil,
54
- [ [ :poodle,
55
- [ 0, 1, 1 ],
56
- false,
57
- nil,
58
- [ [ nil, [ 0, 1, 1 ], true, "poo", [] ],
59
- [ nil, [ 3, 1, 4 ], false, "expected \"dle\", got \"l\"", [] ] ] ],
60
- [ :pool,
61
- [ 0, 1, 1 ],
62
- true,
63
- nil,
64
- [ [ nil, [ 0, 1, 1 ], true, "poo", [] ],
65
- [ nil, [ 3, 1, 4 ], true, "l", [] ] ] ] ] ]
52
+ [ [ :pool, [ 0, 1, 1 ], true, "pool", [] ] ] ]
66
53
  end
67
54
  end
68
55
 
@@ -18,6 +18,21 @@ describe 'Neg::Parser' do
18
18
  end
19
19
  end
20
20
 
21
+ # describe 'the blankslate' do
22
+ #
23
+ # it 'lets through node names like "send"' do
24
+ #
25
+ # parser =
26
+ # Class.new(Neg::Parser) do
27
+ # send == `x`
28
+ # end
29
+ #
30
+ # parser.parse("x").should == [ :send, [ 0, 1, 1 ], true, 'x', [] ]
31
+ # end
32
+ # end
33
+ #
34
+ # not worth the pain for now
35
+
21
36
  describe '.parse' do
22
37
 
23
38
  let(:parser) {
@@ -10,7 +10,7 @@ describe Neg::Parser::StringParser do
10
10
 
11
11
  parser = Neg::Parser::StringParser.new('xxx')
12
12
 
13
- parser.parse('xxx').should ==
13
+ parser.parse('xxx', {}).should ==
14
14
  [ nil, [ 0, 1, 1 ], true, 'xxx', [] ]
15
15
  end
16
16
 
@@ -18,7 +18,7 @@ describe Neg::Parser::StringParser do
18
18
 
19
19
  parser = Neg::Parser::StringParser.new('xxx')
20
20
 
21
- parser.parse('yyy').should ==
21
+ parser.parse('yyy', {}).should ==
22
22
  [ nil, [ 0, 1, 1 ], false, 'expected "xxx", got "yyy"', [] ]
23
23
  end
24
24
  end
@@ -33,7 +33,7 @@ describe Neg::Parser::StringParser do
33
33
 
34
34
  it 'parses an exact string' do
35
35
 
36
- parser.parse('x').should ==
36
+ parser.parse('x', {}).should ==
37
37
  [ :root, [ 0, 1, 1 ], true, 'x', [] ]
38
38
  end
39
39
  end
@@ -0,0 +1,90 @@
1
+
2
+ require 'spec_helper'
3
+
4
+
5
+ describe 'sample math parser' do
6
+
7
+ class ArithParser < Neg::Parser
8
+
9
+ expression == operation
10
+
11
+ operator == `+` | `-` | `*` | `/`
12
+ operation == value + (operator + value) * 0
13
+ value == parenthese | number
14
+ parenthese == `(` + expression + `)`
15
+ number == `-` * -1 + _('0-9') * 1
16
+ end
17
+
18
+ class ArithTranslator < Neg::Translator
19
+
20
+ on(:number) { |n| n.result.to_i }
21
+ on(:operator) { |n| n.result }
22
+ on(:value) { |n| n.results.first }
23
+
24
+ on(:expression) { |n|
25
+ results = n.results.flatten(2)
26
+ results.size == 1 ? results.first : results
27
+ }
28
+ end
29
+
30
+ def parse(s, opts={})
31
+
32
+ r = ArithParser.parse(s, opts)
33
+
34
+ if ENV['DEBUG'] == 'true' # /!\ not $DEBUG
35
+ puts "--#{s.inspect}-->"
36
+ pp r
37
+ end
38
+
39
+ r[2]
40
+ end
41
+
42
+ def translate(s)
43
+
44
+ ArithTranslator.translate(ArithParser.parse(s))
45
+ end
46
+
47
+ it 'parses numbers' do
48
+
49
+ parse("0").should == true
50
+ parse("-0").should == true
51
+ parse("12").should == true
52
+ parse("-3").should == true
53
+ parse("-12").should == true
54
+ end
55
+
56
+ it 'parses parentheses' do
57
+
58
+ parse("(1)").should == true
59
+ parse("((1))").should == true
60
+ end
61
+
62
+ it 'parses operations' do
63
+
64
+ parse("1+1").should == true
65
+ parse("1+-1").should == true
66
+ end
67
+
68
+ it 'parses at large' do
69
+
70
+ parse("1+(1+1)").should == true
71
+ parse("12+(34-(56/78))").should == true
72
+ end
73
+
74
+ it 'translates numbers' do
75
+
76
+ translate("0").should == 0
77
+ translate("101").should == 101
78
+ end
79
+
80
+ it 'translates parentheses' do
81
+
82
+ translate("(12)").should == 12
83
+ end
84
+
85
+ it 'translates operations' do
86
+
87
+ translate("1+2+3").should == [ 1, '+', 2, '+', 3 ]
88
+ end
89
+ end
90
+