Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Use consistent test style for examples

All example test suites are run with the default Rake test task. This
ensures that examples are always working with the current version of the
code.

Also, Citrus.require should raise Citrus::LoadError if it cannot find a
suitable file to load.
  • Loading branch information...
commit 30e9072d0a28a88b8d44ae4ed79329906f7af307 1 parent a901d1d
Michael Jackson authored
18  README
@@ -567,6 +567,24 @@ To install the [Vim](http://www.vim.org/) scripts, copy the files in
567 567
 [runtimepath](http://vimdoc.sourceforge.net/htmldoc/options.html#\'runtimepath\').
568 568
 
569 569
 
  570
+# Examples
  571
+
  572
+
  573
+The project source directory contains several example scripts that demonstrate
  574
+how grammars are to be constructed and used. Each Citrus file in the examples
  575
+directory has an accompanying Ruby file with the same name that contains a suite
  576
+of tests for that particular file.
  577
+
  578
+The best way to run any of these examples is to pass the name of the Ruby file
  579
+directly to the Ruby interpreter on the command line, e.g.:
  580
+
  581
+    $ ruby -Ilib examples/calc.rb
  582
+
  583
+This particular invocation uses the `-I` flag to ensure that you are using the
  584
+version of Citrus that was bundled with that particular example file (i.e. the
  585
+version that is contained in the `lib` directory).
  586
+
  587
+
570 588
 # Links
571 589
 
572 590
 
2  Rakefile
@@ -6,7 +6,7 @@ task :default => :test
6 6
 # TESTS #######################################################################
7 7
 
8 8
 Rake::TestTask.new(:test) do |t|
9  
-  t.test_files = FileList['test/*_test.rb']
  9
+  t.test_files = FileList['test/*_test.rb'] + FileList['examples/*.rb']
10 10
 end
11 11
 
12 12
 # DOCS ########################################################################
5  examples/calc.citrus
... ...
@@ -1,8 +1,7 @@
1 1
 # A grammar for mathematical formulas that apply basic mathematical operations
2 2
 # to all numbers, respecting operator precedence and grouping of expressions
3  
-# while ignoring whitespace.
4  
-#
5  
-# An identical grammar that is written using pure Ruby can be found in calc.rb.
  3
+# while ignoring whitespace. This grammar should provide the same interpretation
  4
+# as Ruby for all mathematical expressions.
6 5
 grammar Calc
7 6
 
8 7
   ## Hierarchical syntax
151  examples/calc.rb
... ...
@@ -1,114 +1,121 @@
  1
+# This file contains a suite of tests for the Calc grammar found in calc.citrus.
  2
+
1 3
 require 'citrus'
  4
+Citrus.require File.expand_path('../calc', __FILE__)
  5
+require 'test/unit'
  6
+
  7
+class CalcTest < Test::Unit::TestCase
  8
+  # A helper method that tests the successful parsing and evaluation of the
  9
+  # given mathematical expression.
  10
+  def do_test(expr)
  11
+    match = ::Calc.parse(expr)
  12
+    assert(match)
  13
+    assert_equal(expr, match)
  14
+    assert_equal(expr.length, match.length)
  15
+    assert_equal(eval(expr), match.value)
  16
+  end
  17
+
  18
+  def test_int
  19
+    do_test('3')
  20
+  end
2 21
 
3  
-# A grammar for mathematical formulas that apply basic mathematical operations
4  
-# to all numbers, respecting operator precedence and grouping of expressions
5  
-# while ignoring whitespace.
6  
-#
7  
-# An identical grammar that is written using Citrus' own grammar syntax can be
8  
-# found in calc.citrus.
9  
-grammar :Calc do
  22
+  def test_float
  23
+    do_test('1.5')
  24
+  end
10 25
 
11  
-  ## Hierarchical syntax
  26
+  def test_addition
  27
+    do_test('1+2')
  28
+  end
12 29
 
13  
-  rule :term do
14  
-    any(:additive, :factor)
  30
+  def test_addition_multi
  31
+    do_test('1+2+3')
15 32
   end
16 33
 
17  
-  rule :additive do
18  
-    all(:factor, :additive_operator, :term) {
19  
-      additive_operator.value(factor.value, term.value)
20  
-    }
  34
+  def test_addition_float
  35
+    do_test('1.5+3')
21 36
   end
22 37
 
23  
-  rule :factor do
24  
-    any(:multiplicative, :prefix)
  38
+  def test_subtraction
  39
+    do_test('3-2')
25 40
   end
26 41
 
27  
-  rule :multiplicative do
28  
-    all(:prefix, :multiplicative_operator, :factor) {
29  
-      multiplicative_operator.value(prefix.value, factor.value)
30  
-    }
  42
+  def test_subtraction_float
  43
+    do_test('4.5-3')
31 44
   end
32 45
 
33  
-  rule :prefix do
34  
-    any(:prefixed, :exponent)
  46
+  def test_multiplication
  47
+    do_test('2*5')
35 48
   end
36 49
 
37  
-  rule :prefixed do
38  
-    all(:unary_operator, :prefix) {
39  
-      unary_operator.value(prefix.value)
40  
-    }
  50
+  def test_multiplication_float
  51
+    do_test('1.5*3')
41 52
   end
42 53
 
43  
-  rule :exponent do
44  
-    any(:exponential, :primary)
  54
+  def test_division
  55
+    do_test('20/5')
45 56
   end
46 57
 
47  
-  rule :exponential do
48  
-    all(:primary, :exponential_operator, :prefix) {
49  
-      exponential_operator.value(primary.value, prefix.value)
50  
-    }
  58
+  def test_division_float
  59
+    do_test('4.5/3')
51 60
   end
52 61
 
53  
-  rule :primary do
54  
-    any(:group, :number)
  62
+  def test_complex
  63
+    do_test('7*4+3.5*(4.5/3)')
55 64
   end
56 65
 
57  
-  rule :group do
58  
-    all(:lparen, :term, :rparen) {
59  
-      term.value
60  
-    }
  66
+  def test_complex_spaced
  67
+    do_test('7 * 4 + 3.5 * (4.5 / 3)')
61 68
   end
62 69
 
63  
-  ## Lexical syntax
  70
+  def test_complex_with_underscores
  71
+    do_test('(12_000 / 3) * 2.5')
  72
+  end
64 73
 
65  
-  rule :number do
66  
-    any(:float, :integer)
  74
+  def test_modulo
  75
+    do_test('3 % 2 + 4')
67 76
   end
68 77
 
69  
-  rule :float do
70  
-    all(:digits, '.', :digits, zero_or_more(:space)) {
71  
-      strip.to_f
72  
-    }
  78
+  def test_exponent
  79
+    do_test('2**9')
73 80
   end
74 81
 
75  
-  rule :integer do
76  
-    all(:digits, zero_or_more(:space)) {
77  
-      strip.to_i
78  
-    }
  82
+  def test_exponent_float
  83
+    do_test('2**2.2')
79 84
   end
80 85
 
81  
-  rule :digits do
82  
-    # Numbers may contain underscores in Ruby.
83  
-    /[0-9]+(?:_[0-9]+)*/
  86
+  def test_negative_exponent
  87
+    do_test('2**-3')
84 88
   end
85 89
 
86  
-  rule :additive_operator do
87  
-    all(any('+', '-'), zero_or_more(:space)) { |a, b|
88  
-      a.send(strip, b)
89  
-    }
  90
+  def test_exponent_exponent
  91
+    do_test('2**2**2')
90 92
   end
91 93
 
92  
-  rule :multiplicative_operator do
93  
-    all(any('*', '/', '%'), zero_or_more(:space)) { |a, b|
94  
-      a.send(strip, b)
95  
-    }
  94
+  def test_exponent_group
  95
+    do_test('2**(3+1)')
96 96
   end
97 97
 
98  
-  rule :exponential_operator do
99  
-    all('**', zero_or_more(:space)) { |a, b|
100  
-      a ** b
101  
-    }
  98
+  def test_negative
  99
+    do_test('-5')
102 100
   end
103 101
 
104  
-  rule :unary_operator do
105  
-    all(any('~', '+', '-'), zero_or_more(:space)) { |n|
106  
-      # Unary + and - require an @.
107  
-      n.send(strip == '~' ? strip : '%s@' % strip)
108  
-    }
  102
+  def test_double_negative
  103
+    do_test('--5')
109 104
   end
110 105
 
111  
-  rule :lparen, ['(', zero_or_more(:space)]
112  
-  rule :rparen, [')', zero_or_more(:space)]
113  
-  rule :space,  /[ \t\n\r]/
  106
+  def test_complement
  107
+    do_test('~4')
  108
+  end
  109
+
  110
+  def test_double_complement
  111
+    do_test('~~4')
  112
+  end
  113
+
  114
+  def test_mixed_unary
  115
+    do_test('~-4')
  116
+  end
  117
+
  118
+  def test_complex_with_negatives
  119
+    do_test('4 * -7 / (8.0 + 1_2)**2')
  120
+  end
114 121
 end
92  examples/ipaddress.rb
... ...
@@ -1,81 +1,23 @@
1  
-# This file contains a small suite of tests for the grammars found in
2  
-# ipaddress.citrus. If this file is run directly (i.e. using `ruby ip.rb') the
3  
-# tests will run. Otherwise, this file may be required by another that needs
4  
-# access to the IP address grammars just as any other file would be.
  1
+examples = File.expand_path('..', __FILE__)
  2
+$LOAD_PATH.unshift(examples) unless $LOAD_PATH.include?(examples)
5 3
 
6  
-# Always use the current version of Citrus with this example.
7  
-$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
  4
+# This file contains a suite of tests for the IPAddress grammar found in
  5
+# ipaddress.citrus.
8 6
 
9 7
 require 'citrus'
  8
+Citrus.require 'ipaddress'
  9
+require 'test/unit'
  10
+
  11
+class IPAddressTest < Test::Unit::TestCase
  12
+  def test_v4
  13
+    match = IPAddress.parse('1.2.3.4')
  14
+    assert(match)
  15
+    assert_equal(4, match.version)
  16
+  end
10 17
 
11  
-# Make sure that the require statements in ip*address.citrus files can find
12  
-# one another.
13  
-$LOAD_PATH.unshift(File.expand_path('..', __FILE__))
14  
-
15  
-# Load and evaluate the grammars contained in ipaddress.citrus.
16  
-Citrus.require('ipaddress')
17  
-
18  
-if $0 == __FILE__
19  
-  require 'test/unit'
20  
-
21  
-  class IPAddressTest < Test::Unit::TestCase
22  
-    def test_dec_octet
23  
-      match = IPv4Address.parse('0', :root => :'dec-octet')
24  
-      assert(match)
25  
-
26  
-      match = IPv4Address.parse('255', :root => :'dec-octet')
27  
-      assert(match)
28  
-    end
29  
-
30  
-    def test_hexdig
31  
-      match = IPv6Address.parse('0', :root => :HEXDIG)
32  
-      assert(match)
33  
-
34  
-      match = IPv6Address.parse('A', :root => :HEXDIG)
35  
-      assert(match)
36  
-    end
37  
-
38  
-    def test_v4
39  
-      match = IPv4Address.parse('0.0.0.0')
40  
-      assert(match)
41  
-
42  
-      match = IPv4Address.parse('255.255.255.255')
43  
-      assert(match)
44  
-
45  
-      assert_raise Citrus::ParseError do
46  
-        IPv4Address.parse('255.255.255')
47  
-      end
48  
-    end
49  
-
50  
-    def test_v6
51  
-      match = IPv6Address.parse('1:2:3:4:5:6:7:8')
52  
-      assert(match)
53  
-
54  
-      match = IPv6Address.parse('12AD:34FC:A453:1922::')
55  
-      assert(match)
56  
-
57  
-      match = IPv6Address.parse('12AD::34FC')
58  
-      assert(match)
59  
-
60  
-      match = IPv6Address.parse('12AD::')
61  
-      assert(match)
62  
-
63  
-      match = IPv6Address.parse('::')
64  
-      assert(match)
65  
-
66  
-      assert_raise Citrus::ParseError do
67  
-        IPv6Address.parse('1:2')
68  
-      end
69  
-    end
70  
-
71  
-    def test_all
72  
-      match = IPAddress.parse('1.2.3.4')
73  
-      assert(match)
74  
-      assert_equal(4, match.version)
75  
-
76  
-      match = IPAddress.parse('1:2:3:4::')
77  
-      assert(match)
78  
-      assert_equal(6, match.version)
79  
-    end
  18
+  def test_v6
  19
+    match = IPAddress.parse('1:2:3:4::')
  20
+    assert(match)
  21
+    assert_equal(6, match.version)
80 22
   end
81 23
 end
49  examples/ipv4address.rb
... ...
@@ -0,0 +1,49 @@
  1
+examples = File.expand_path('..', __FILE__)
  2
+$LOAD_PATH.unshift(examples) unless $LOAD_PATH.include?(examples)
  3
+
  4
+# This file contains a suite of tests for the IPv4Address grammar found in
  5
+# ipv4address.citrus.
  6
+
  7
+require 'citrus'
  8
+Citrus.require 'ipv4address'
  9
+require 'test/unit'
  10
+
  11
+class IPv4AddressTest < Test::Unit::TestCase
  12
+  def test_dec_octet
  13
+    match = IPv4Address.parse('0', :root => :'dec-octet')
  14
+    assert(match)
  15
+
  16
+    match = IPv4Address.parse('255', :root => :'dec-octet')
  17
+    assert(match)
  18
+  end
  19
+
  20
+  def test_1
  21
+    match = IPv4Address.parse('0.0.0.0')
  22
+    assert(match)
  23
+    assert_equal(4, match.version)
  24
+  end
  25
+
  26
+  def test_2
  27
+    match = IPv4Address.parse('255.255.255.255')
  28
+    assert(match)
  29
+    assert_equal(4, match.version)
  30
+  end
  31
+
  32
+  def test_invalid
  33
+    assert_raise Citrus::ParseError do
  34
+      IPv4Address.parse('255.255.255.256')
  35
+    end
  36
+  end
  37
+
  38
+  def test_invalid_short
  39
+    assert_raise Citrus::ParseError do
  40
+      IPv4Address.parse('255.255.255')
  41
+    end
  42
+  end
  43
+
  44
+  def test_invalid_long
  45
+    assert_raise Citrus::ParseError do
  46
+      IPv4Address.parse('255.255.255.255.255')
  47
+    end
  48
+  end
  49
+end
55  examples/ipv6address.rb
... ...
@@ -0,0 +1,55 @@
  1
+examples = File.expand_path('..', __FILE__)
  2
+$LOAD_PATH.unshift(examples) unless $LOAD_PATH.include?(examples)
  3
+
  4
+# This file contains a suite of tests for the IPv6Address grammar found in
  5
+# ipv6address.citrus.
  6
+
  7
+require 'citrus'
  8
+Citrus.require 'ipv6address'
  9
+require 'test/unit'
  10
+
  11
+class IPv6AddressTest < Test::Unit::TestCase
  12
+  def test_hexdig
  13
+    match = IPv6Address.parse('0', :root => :HEXDIG)
  14
+    assert(match)
  15
+
  16
+    match = IPv6Address.parse('A', :root => :HEXDIG)
  17
+    assert(match)
  18
+  end
  19
+
  20
+  def test_1
  21
+    match = IPv6Address.parse('1:2:3:4:5:6:7:8')
  22
+    assert(match)
  23
+    assert_equal(6, match.version)
  24
+  end
  25
+
  26
+  def test_2
  27
+    match = IPv6Address.parse('12AD:34FC:A453:1922::')
  28
+    assert(match)
  29
+    assert_equal(6, match.version)
  30
+  end
  31
+
  32
+  def test_3
  33
+    match = IPv6Address.parse('12AD::34FC')
  34
+    assert(match)
  35
+    assert_equal(6, match.version)
  36
+  end
  37
+
  38
+  def test_4
  39
+    match = IPv6Address.parse('12AD::')
  40
+    assert(match)
  41
+    assert_equal(6, match.version)
  42
+  end
  43
+
  44
+  def test_5
  45
+    match = IPv6Address.parse('::')
  46
+    assert(match)
  47
+    assert_equal(6, match.version)
  48
+  end
  49
+
  50
+  def test_invalid
  51
+    assert_raise Citrus::ParseError do
  52
+      IPv6Address.parse('1:2')
  53
+    end
  54
+  end
  55
+end
14  lib/citrus.rb
... ...
@@ -1,4 +1,5 @@
1 1
 require 'strscan'
  2
+require 'pathname'
2 3
 require 'citrus/version'
3 4
 
4 5
 # Citrus is a compact and powerful parsing library for Ruby that combines the
@@ -98,12 +99,15 @@ def self.require(file, options={})
98 99
     file += '.citrus' unless file =~ /\.citrus$/
99 100
     found = nil
100 101
 
101  
-    $LOAD_PATH.each do |dir|
  102
+    (Pathname.new(file).absolute? ? [''] : $LOAD_PATH).each do |dir|
102 103
       found = Dir[::File.join(dir, file)].first
103  
-      if found
104  
-        Citrus.load(found, options)
105  
-        break
106  
-      end
  104
+      break if found
  105
+    end
  106
+
  107
+    if found
  108
+      Citrus.load(found, options)
  109
+    else
  110
+      raise LoadError, "Cannot find file #{file}"
107 111
     end
108 112
 
109 113
     found
6  lib/citrus/file.rb
@@ -41,10 +41,8 @@ def parse(*args)
41 41
           file = req.value
42 42
           begin
43 43
             require file
44  
-          rescue ::LoadError => e
45  
-            # Re-raise the original LoadError unless Citrus.require finds a
46  
-            # suitable candidate.
47  
-            raise e unless Citrus.require(file)
  44
+          rescue ::LoadError
  45
+            Citrus.require(file)
48 46
           end
49 47
         end
50 48
 
16  test/calc_file_test.rb
... ...
@@ -1,16 +0,0 @@
1  
-require File.expand_path('../helper', __FILE__)
2  
-
3  
-Citrus.load File.expand_path('../../examples/calc', __FILE__)
4  
-
5  
-class CalcFileTest < Test::Unit::TestCase
6  
-  include CalcTestMethods
7  
-
8  
-  # It's a bit hacky, but since this test runs before calc_test.rb we can
9  
-  # avoid getting the "already defined constant" error by renaming the Calc
10  
-  # constant here.
11  
-  CalcFile = Object.__send__(:remove_const, :Calc)
12  
-
13  
-  def do_test(expr)
14  
-    super(expr, CalcFile)
15  
-  end
16  
-end
11  test/calc_test.rb
... ...
@@ -1,11 +0,0 @@
1  
-require File.expand_path('../helper', __FILE__)
2  
-
3  
-require File.expand_path('../../examples/calc', __FILE__)
4  
-
5  
-class CalcTest < Test::Unit::TestCase
6  
-  include CalcTestMethods
7  
-
8  
-  def do_test(expr)
9  
-    super(expr, Calc)
10  
-  end
11  
-end
116  test/helper.rb
@@ -44,120 +44,4 @@ class Test::Unit::TestCase
44 44
       [ :word, zero_or_more([ ' ', :word ]) ]
45 45
     end
46 46
   end
47  
-
48  
-  module CalcTestMethods
49  
-    # A helper method that tests the successful parsing and evaluation of the
50  
-    # given mathematical expression.
51  
-    def do_test(expr, grammar)
52  
-      match = grammar.parse(expr)
53  
-      assert(match)
54  
-      assert_equal(expr, match)
55  
-      assert_equal(expr.length, match.length)
56  
-      assert_equal(eval(expr), match.value)
57  
-    end
58  
-
59  
-    def test_int
60  
-      do_test('3')
61  
-    end
62  
-
63  
-    def test_float
64  
-      do_test('1.5')
65  
-    end
66  
-
67  
-    def test_addition
68  
-      do_test('1+2')
69  
-    end
70  
-
71  
-    def test_addition_multi
72  
-      do_test('1+2+3')
73  
-    end
74  
-
75  
-    def test_addition_float
76  
-      do_test('1.5+3')
77  
-    end
78  
-
79  
-    def test_subtraction
80  
-      do_test('3-2')
81  
-    end
82  
-
83  
-    def test_subtraction_float
84  
-      do_test('4.5-3')
85  
-    end
86  
-
87  
-    def test_multiplication
88  
-      do_test('2*5')
89  
-    end
90  
-
91  
-    def test_multiplication_float
92  
-      do_test('1.5*3')
93  
-    end
94  
-
95  
-    def test_division
96  
-      do_test('20/5')
97  
-    end
98  
-
99  
-    def test_division_float
100  
-      do_test('4.5/3')
101  
-    end
102  
-
103  
-    def test_complex
104  
-      do_test('7*4+3.5*(4.5/3)')
105  
-    end
106  
-
107  
-    def test_complex_spaced
108  
-      do_test('7 * 4 + 3.5 * (4.5 / 3)')
109  
-    end
110  
-
111  
-    def test_complex_with_underscores
112  
-      do_test('(12_000 / 3) * 2.5')
113  
-    end
114  
-
115  
-    def test_modulo
116  
-      do_test('3 % 2 + 4')
117  
-    end
118  
-
119  
-    def test_exponent
120  
-      do_test('2**9')
121  
-    end
122  
-
123  
-    def test_exponent_float
124  
-      do_test('2**2.2')
125  
-    end
126  
-
127  
-    def test_negative_exponent
128  
-      do_test('2**-3')
129  
-    end
130  
-
131  
-    def test_exponent_exponent
132  
-      do_test('2**2**2')
133  
-    end
134  
-
135  
-    def test_exponent_group
136  
-      do_test('2**(3+1)')
137  
-    end
138  
-
139  
-    def test_negative
140  
-      do_test('-5')
141  
-    end
142  
-
143  
-    def test_double_negative
144  
-      do_test('--5')
145  
-    end
146  
-
147  
-    def test_complement
148  
-      do_test('~4')
149  
-    end
150  
-
151  
-    def test_double_complement
152  
-      do_test('~~4')
153  
-    end
154  
-
155  
-    def test_mixed_unary
156  
-      do_test('~-4')
157  
-    end
158  
-
159  
-    def test_complex_with_negatives
160  
-      do_test('4 * -7 / (8.0 + 1_2)**2')
161  
-    end
162  
-  end
163 47
 end

0 notes on commit 30e9072

Please sign in to comment.
Something went wrong with that request. Please try again.