neg 0.3.0 → 1.0.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.
@@ -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
+