1#!/usr/bin/ruby
2# encoding: utf-8
3
4require 'antlr3/test/functional'
5
6class TestCalcParser < ANTLR3::Test::Functional
7  inline_grammar( <<-'END' )
8    grammar TestCalc;
9    options { language = Ruby; }
10
11    @parser::init {
12      @reported_errors = []
13    }
14
15    @parser::members {
16      attr_reader :reported_errors
17
18      def emit_error_message(msg)
19        @reported_errors << msg
20      end
21    }
22
23    evaluate returns [result]: r=expression { $result = $r.result };
24
25    expression returns [result]:
26               r=mult { $result = $r.result }
27        (
28          '+' r2=mult { $result += $r2.result }
29        | '-' r2=mult { $result -= $r2.result }
30        )*
31        ;
32
33    mult returns [result]:
34               r=log { $result = $r.result }
35        (
36          '*' r2=log {$result *= $r2.result}
37        | '/' r2=log {$result /= $r2.result}
38        | '%' r2=log {$result \%= $r2.result}
39        )*
40        ;
41
42    log returns [result]: 'ln' r=exp {$result = Math.log($r.result)}
43        | r=exp {$result = $r.result}
44        ;
45
46    exp returns [result]: r=atom { $result = $r.result } ('^' r2=atom { $result **= $r2.result } )?
47        ;
48
49    atom returns [result]:
50        n=INTEGER {$result = Integer($n.text)}
51      | n=DECIMAL {$result = Float($n.text)}
52      | '(' r=expression {$result = $r.result} ')'
53      | 'PI' {$result = Math::PI}
54      | 'E' {$result = Math::E}
55      ;
56
57    INTEGER: DIGIT+;
58
59    DECIMAL: DIGIT+ '.' DIGIT+;
60
61    fragment
62    DIGIT: '0'..'9';
63
64    WS: (' ' | '\n' | '\t')+ {$channel = HIDDEN};
65  END
66
67  def evaluate( expression )
68    lexer  = TestCalc::Lexer.new( expression )
69    parser = TestCalc::Parser.new lexer
70    value = parser.evaluate
71    errors = parser.reported_errors
72    return [ value, errors ]
73  end
74
75  tests = %[
76    1 + 2            = 3
77    1 + 2 * 3        = 7
78    10 / 2           = 5
79    6 + 2*(3+1) - 4  = 10
80  ].strip!.split( /\n/ ).map { |line|
81    expr, val = line.strip.split( /\s+=\s+/, 2 )
82    [ expr, Integer( val ) ]
83  }
84
85  tests.each do |expression, true_value|
86    example "should parse '#{ expression }'" do
87      parser_value, errors = evaluate( expression )
88      parser_value.should == true_value
89    end
90  end
91
92  example "badly formed input" do
93    val, errors = evaluate "6 - (2*1"
94
95    errors.should have( 1 ).thing
96    errors.first.should =~ /mismatched/
97  end
98end
99