rubypeg 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,67 @@
1
+ = RubyPeg
2
+
3
+ RubyPeg helps you to create readable Parsing Expression Grammars (PEG) in ruby.
4
+
5
+ This software is (c) 2010 Green on Black Ltd and distributed under the open source MIT[http://www.opensource.org/licenses/mit-license.php] licence. (See LICENCE for the wording).
6
+
7
+ If you like this code, employ us: http://www.greenonblack.com
8
+
9
+ = An example
10
+
11
+ class Arithmetic < RubyPeg
12
+
13
+ def root
14
+ arithmetic
15
+ end
16
+
17
+ def arithmetic
18
+ node :arithmetic do
19
+ one_or_more { expression } && ignore { terminal(/\z/) }
20
+ end
21
+ end
22
+
23
+ def expression
24
+ subtraction || addition || division || multiplication || brackets || number
25
+ end
26
+
27
+ def brackets
28
+ node :brackets do
29
+ ignore { terminal("(") } && spacing && expression && spacing && ignore { terminal(")") }
30
+ end
31
+ end
32
+
33
+ def multiplication
34
+ node :multiplication do
35
+ (brackets || number) && spacing && ignore { terminal("*") } && spacing && expression
36
+ end
37
+ end
38
+
39
+ def division
40
+ node :division do
41
+ (brackets || number) && spacing && ignore { terminal("/") } && spacing && expression
42
+ end
43
+ end
44
+
45
+ def addition
46
+ node :addition do
47
+ (brackets || number) && spacing && ignore { terminal("+") } && spacing && expression
48
+ end
49
+ end
50
+
51
+ def subtraction
52
+ node :subtraction do
53
+ (brackets || number) && spacing && ignore { terminal("-") } && spacing && expression
54
+ end
55
+ end
56
+
57
+ def number
58
+ terminal(/[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/)
59
+ end
60
+
61
+ def spacing
62
+ ignore { terminal(/[ \t]*/) }
63
+ end
64
+
65
+ end
66
+
67
+ Arithmetic.parse("(1+1)+2").to_ast # [:arithmetic,[:addition,[:brackets,[:addition,'1','1']],'2']]
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+
4
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
5
+ require 'textpeg2rubypeg'
6
+ require 'textpeg'
7
+ require 'rubypeg'
8
+
9
+ if ARGV[0]
10
+ input = IO.readlines(ARGV[0]).join
11
+ ruby = TextPeg2RubyPeg.new
12
+ peg = TextPeg.parse(input)
13
+ peg.visit(ruby)
14
+ if ARGV[1]
15
+ File.open(ARGV[1],'w') { |f| f.puts ruby.to_ruby }
16
+ else
17
+ $stdout.puts ruby.to_ruby
18
+ end
19
+ else
20
+ $stdout.puts "Usage: text-peg2ruby-peg [text-peg-input-filename] [ruby-peg-output-filename]"
21
+ $stdout.puts "Help: Compiles text format parsing expression grammars into a ruby parser."
22
+ $stdout.puts " See http://github.com/tamc/ruby-peg for more information."
23
+ $stdout.puts "Version: 0.0.1"
24
+ end
@@ -0,0 +1,57 @@
1
+ require 'rubypeg'
2
+
3
+ class Arithmetic < RubyPeg
4
+
5
+ def root
6
+ arithmetic
7
+ end
8
+
9
+ def arithmetic
10
+ node :arithmetic do
11
+ one_or_more { expression } && ignore { terminal(/\z/) }
12
+ end
13
+ end
14
+
15
+ def expression
16
+ subtraction || addition || division || multiplication || brackets || number
17
+ end
18
+
19
+ def brackets
20
+ node :brackets do
21
+ ignore { terminal("(") } && spacing && expression && spacing && ignore { terminal(")") }
22
+ end
23
+ end
24
+
25
+ def multiplication
26
+ node :multiplication do
27
+ (brackets || number) && spacing && ignore { terminal("*") } && spacing && expression
28
+ end
29
+ end
30
+
31
+ def division
32
+ node :division do
33
+ (brackets || number) && spacing && ignore { terminal("/") } && spacing && expression
34
+ end
35
+ end
36
+
37
+ def addition
38
+ node :addition do
39
+ (brackets || number) && spacing && ignore { terminal("+") } && spacing && expression
40
+ end
41
+ end
42
+
43
+ def subtraction
44
+ node :subtraction do
45
+ (brackets || number) && spacing && ignore { terminal("-") } && spacing && expression
46
+ end
47
+ end
48
+
49
+ def number
50
+ terminal(/[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/)
51
+ end
52
+
53
+ def spacing
54
+ ignore { terminal(/[ \t]*/) }
55
+ end
56
+
57
+ end
@@ -0,0 +1,9 @@
1
+ arithmetic := expression+ `/\z/
2
+ expression = subtraction | addition | division | multiplication | brackets | number
3
+ brackets := `"(" spacing expression spacing `")"
4
+ multiplication := (brackets | number ) spacing `'*' spacing expression
5
+ division := (brackets | number ) spacing `'/' spacing expression
6
+ addition := (brackets | number ) spacing `'+' spacing expression
7
+ subtraction := (brackets | number ) spacing `'-' spacing expression
8
+ number = /[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/
9
+ spacing = `/[ \t]*/
@@ -0,0 +1,49 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.])
2
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. .. lib])
3
+ require 'rubypeg'
4
+ require 'arithmetic_peg'
5
+
6
+ describe Arithmetic do
7
+
8
+ def check(text)
9
+ puts
10
+ e = Arithmetic.new
11
+ e.parse(text)
12
+ e.pretty_print_cache
13
+ puts
14
+ end
15
+
16
+ it "parses decimal numbers, including those in scientific notation" do
17
+ Arithmetic.parse("1").to_ast.should == [:arithmetic,'1']
18
+ Arithmetic.parse("103.287").to_ast.should == [:arithmetic,'103.287']
19
+ Arithmetic.parse("-1.0E-27").to_ast.should == [:arithmetic,'-1.0E-27']
20
+ end
21
+
22
+ it "parses sums of decimal numbers" do
23
+ Arithmetic.parse("1+1").to_ast.should == [:arithmetic,[:addition,'1','1']]
24
+ end
25
+
26
+ it "parses series of sums" do
27
+ Arithmetic.parse("1+1+2").to_ast.should == [:arithmetic,[:addition,'1',[:addition,'1','2']]]
28
+ end
29
+
30
+ it "parses a series of sums with brackets" do
31
+ Arithmetic.parse("(1+1)+2").to_ast.should == [:arithmetic,[:addition,[:brackets,[:addition,'1','1']],'2']]
32
+ Arithmetic.parse("1+(1+2)").to_ast.should == [:arithmetic,[:addition,'1',[:brackets,[:addition,'1','2']]]]
33
+ Arithmetic.parse("(1+1)+(1+2)").to_ast.should == [:arithmetic,[:addition,[:brackets,[:addition,'1','1']],[:brackets,[:addition,'1','2']]]]
34
+ Arithmetic.parse("((1+1)+(1+2))").to_ast.should == [:arithmetic,[:brackets,[:addition,[:brackets,[:addition,'1','1']],[:brackets,[:addition,'1','2']]]]]
35
+ end
36
+
37
+ it "parses subtractions" do
38
+ Arithmetic.parse("1-1").to_ast.should == [:arithmetic,[:subtraction,'1','1']]
39
+ end
40
+
41
+ it "parses multiplication" do
42
+ Arithmetic.parse("1*1").to_ast.should == [:arithmetic,[:multiplication,'1','1']]
43
+ end
44
+
45
+ it "parses division" do
46
+ Arithmetic.parse("1/1").to_ast.should == [:arithmetic,[:division,'1','1']]
47
+ end
48
+
49
+ end
@@ -0,0 +1,40 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. .. lib])
2
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.])
3
+ require 'rubypeg'
4
+ require 'arithmetic_peg'
5
+
6
+ module TerminalNode
7
+ def build(builder)
8
+ self.to_f
9
+ end
10
+ end
11
+
12
+ class CalculationEngine
13
+
14
+ def addition(left,right)
15
+ left.build(self) + right.build(self)
16
+ end
17
+
18
+ def subtraction(left,right)
19
+ left.build(self) - right.build(self)
20
+ end
21
+
22
+ def multiplication(left,right)
23
+ left.build(self) * right.build(self)
24
+ end
25
+
26
+ def division(left,right)
27
+ left.build(self) / right.build(self)
28
+ end
29
+
30
+ end
31
+
32
+ class Calculator
33
+
34
+ def self.calculate(sum)
35
+ ast = Arithmetic.parse(sum)
36
+ answer = ast.build(CalculationEngine.new)
37
+ answer
38
+ end
39
+
40
+ end
@@ -0,0 +1,26 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.])
2
+ require 'calculator'
3
+
4
+ describe Calculator do
5
+
6
+ it "should know that 1 + 1 = 2.0" do
7
+ Calculator.calculate("1+1").should == 2.0
8
+ end
9
+
10
+ it "should know that 5.0 - 1.3 = 3.7" do
11
+ Calculator.calculate("5.0 - 1.3").should == 3.7
12
+ end
13
+
14
+ it "should know that -12.3e-27 * 5 = 6.15e-26" do
15
+ Calculator.calculate("-12.3e-27*5").should == -6.15e-26
16
+ end
17
+
18
+ it "should know that 1000/10 = 100" do
19
+ Calculator.calculate("1000/10").should == 100
20
+ end
21
+
22
+ it "should know that (1+3)*(8*2) = 64" do
23
+ Calculator.calculate("(1+3)*(8*2)").should == 64
24
+ end
25
+
26
+ end
@@ -0,0 +1,35 @@
1
+ formula := expression+
2
+ expression = string_join | arithmetic | comparison | thing
3
+ thing = function | brackets | string | any_reference | percentage | number | boolean | named_reference | prefix
4
+ function := /[A-Z]+/ `'(' expression? (`',' expression)* `')'
5
+ brackets := `'(' expression `')'
6
+ string_join := thing (`"&" thing)+
7
+ arithmetic := thing (operator thing)+
8
+ comparison := thing comparator thing
9
+ comparator := '>=' | '<=' | '<>' | '>' | '<' | '='
10
+ string := `'"' /[^"]*/ `'"'
11
+ any_reference = table_reference | quoted_sheet_reference | sheet_reference | sheetless_reference
12
+ percentage := /[-+]?[0-9]+\.?[0-9]*/ `'%'
13
+ number := /[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/
14
+ operator := '+' | '-' | '/' | '*' | '^'
15
+ table_reference = qualified_table_reference | local_table_reference
16
+ qualified_table_reference := /[a-zA-Z0-9]+/ table_column
17
+ local_table_reference := table_column
18
+ table_column = `'[' /[^\u005d]*/ `']'
19
+ named_reference := /[a-zA-Z][\w_.]+/
20
+ quoted_sheet_reference := single_quoted_string `'!' sheetless_reference
21
+ sheet_reference := /[a-zA-Z][\w_]+/ `'!' sheetless_reference
22
+ single_quoted_string = `"'" /[^']*/ `"'"
23
+ sheetless_reference = column_range | row_range | area | cell
24
+ column_range := column `':' column
25
+ row_range := row `':' row
26
+ area := reference `':' reference
27
+ cell := reference
28
+ row = /\$?\d+/
29
+ column = /\$?[A-Z]+/
30
+ reference = /\$?[A-Z]+\$?[0-9]+/
31
+ boolean = boolean_true | boolean_false
32
+ boolean_true := `'TRUE'
33
+ boolean_false := `'FALSE'
34
+ prefix := /[-+]/
35
+
@@ -0,0 +1,139 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.])
2
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. .. lib])
3
+ require 'textpeg2rubypeg'
4
+ text_peg = IO.readlines(File.join(File.dirname(__FILE__),'excel_peg.txt')).join
5
+ ast = TextPeg.parse(text_peg)
6
+ # puts ast.to_ast
7
+ # exit
8
+ builder = TextPeg2RubyPeg.new
9
+ ruby = ast.build(builder)
10
+ Kernel.eval(ruby)
11
+
12
+ describe Formula do
13
+
14
+ def check(text)
15
+ puts
16
+ e = Formula.new
17
+ e.parse(text)
18
+ e.pretty_print_cache
19
+ puts
20
+ end
21
+
22
+ it "returns formulas" do
23
+ Formula.parse('1+1').to_ast.first.should == :formula
24
+ end
25
+
26
+ it "returns cells" do
27
+ Formula.parse('$A$1').to_ast.should == [:formula,[:cell,'$A$1']]
28
+ Formula.parse('A1').to_ast.should == [:formula,[:cell,'A1']]
29
+ Formula.parse('$A1').to_ast.should == [:formula,[:cell,'$A1']]
30
+ Formula.parse('A$1').to_ast.should == [:formula,[:cell,'A$1']]
31
+ Formula.parse('AAA1123').to_ast.should == [:formula,[:cell,'AAA1123']]
32
+ end
33
+
34
+ it "returns areas" do
35
+ Formula.parse('$A$1:$Z$1').to_ast.should == [:formula,[:area,'$A$1','$Z$1']]
36
+ Formula.parse('A1:$Z$1').to_ast.should == [:formula,[:area,'A1','$Z$1']]
37
+ end
38
+
39
+ it "returns row ranges" do
40
+ Formula.parse('$1:$1000').to_ast.should == [:formula,[:row_range,'$1','$1000']]
41
+ Formula.parse('1000:1').to_ast.should == [:formula,[:row_range,'1000','1']]
42
+ end
43
+
44
+ it "returns column ranges" do
45
+ Formula.parse('$C:$AZ').to_ast.should == [:formula,[:column_range,'$C','$AZ']]
46
+ Formula.parse('C:AZ').to_ast.should == [:formula,[:column_range,'C','AZ']]
47
+ end
48
+
49
+ it "returns references to other sheets" do
50
+ Formula.parse('sheet1!$A$1').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:cell,'$A$1']]]
51
+ Formula.parse('sheet1!$A$1:$Z$1').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:area,'$A$1','$Z$1']]]
52
+ Formula.parse('sheet1!$1:$1000').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:row_range,'$1','$1000']]]
53
+ Formula.parse('sheet1!$C:$AZ').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:column_range,'$C','$AZ']]]
54
+ end
55
+
56
+ it "returns references to other sheets with extended names" do
57
+ Formula.parse("'sheet 1'!$A$1").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:cell,'$A$1']]]
58
+ Formula.parse("'sheet 1'!$A$1:$Z$1").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:area,'$A$1','$Z$1']]]
59
+ Formula.parse("'sheet 1'!$1:$1000").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:row_range,'$1','$1000']]]
60
+ Formula.parse("'sheet 1'!$C:$AZ").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:column_range,'$C','$AZ']]]
61
+ end
62
+
63
+ it "returns numbers" do
64
+ Formula.parse("1").to_ast.should == [:formula,[:number,'1']]
65
+ Formula.parse("103.287").to_ast.should == [:formula,[:number,'103.287']]
66
+ Formula.parse("-1.0E-27").to_ast.should == [:formula,[:number,'-1.0E-27']]
67
+ end
68
+
69
+ it "returns percentages" do
70
+ Formula.parse("1%").to_ast.should == [:formula,[:percentage,'1']]
71
+ Formula.parse("103.287%").to_ast.should == [:formula,[:percentage,'103.287']]
72
+ Formula.parse("-1.0%").to_ast.should == [:formula,[:percentage,'-1.0']]
73
+ end
74
+
75
+ it "returns strings" do
76
+ Formula.parse('"A handy string"').to_ast.should == [:formula,[:string,"A handy string"]]
77
+ Formula.parse('"$A$1"').to_ast.should == [:formula,[:string,"$A$1"]]
78
+ end
79
+
80
+ it "returns string joins" do
81
+ Formula.parse('"A handy string"&$A$1').to_ast.should == [:formula,[:string_join,[:string,"A handy string"],[:cell,'$A$1']]]
82
+ Formula.parse('$A$1&"A handy string"').to_ast.should == [:formula,[:string_join,[:cell,'$A$1'],[:string,"A handy string"]]]
83
+ Formula.parse('$A$1&"A handy string"&$A$1').to_ast.should == [:formula,[:string_join,[:cell,'$A$1'],[:string,"A handy string"],[:cell,'$A$1'],]]
84
+ Formula.parse('$A$1&$A$1&$A$1').to_ast.should == [:formula,[:string_join,[:cell,'$A$1'],[:cell,'$A$1'],[:cell,'$A$1'],]]
85
+ Formula.parse('"GW"&ISERR($AA$1)').to_ast.should == [:formula,[:string_join,[:string,'GW'],[:function,'ISERR',[:cell,'$AA$1']]]]
86
+ end
87
+
88
+ it "returns numeric operations" do
89
+ Formula.parse('$A$1+$A$2+1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"+"],[:cell,'$A$2'],[:operator,"+"],[:number,'1']]]
90
+ Formula.parse('$A$1-$A$2-1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"-"],[:cell,'$A$2'],[:operator,"-"],[:number,'1']]]
91
+ Formula.parse('$A$1*$A$2*1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"*"],[:cell,'$A$2'],[:operator,"*"],[:number,'1']]]
92
+ Formula.parse('$A$1/$A$2/1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"/"],[:cell,'$A$2'],[:operator,"/"],[:number,'1']] ]
93
+ Formula.parse('$A$1^$A$2^1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"^"],[:cell,'$A$2'],[:operator,"^"],[:number,'1']]]
94
+ end
95
+
96
+ it "returns expressions in brackets" do
97
+ Formula.parse('($A$1+$A$2)').to_ast.should == [:formula,[:brackets,[:arithmetic,[:cell,'$A$1'],[:operator,"+"],[:cell,'$A$2']]]]
98
+ Formula.parse('($A$1+$A$2)+2').to_ast.should == [:formula, [:arithmetic, [:brackets, [:arithmetic, [:cell,'$A$1'], [:operator,"+"], [:cell,'$A$2']]], [:operator,"+"], [:number,'2']]]
99
+ Formula.parse('($A$1+$A$2)+(2+(1*2))').to_ast.should == [:formula, [:arithmetic, [:brackets, [:arithmetic, [:cell,'$A$1'], [:operator,"+"], [:cell,'$A$2']]], [:operator,"+"], [:brackets, [:arithmetic, [:number,'2'], [:operator,'+'] ,[:brackets, [:arithmetic, [:number,'1'], [:operator,'*'], [:number,'2']]]]]]]
100
+ end
101
+
102
+ it "returns comparisons" do
103
+ Formula.parse('$A$1>$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,">"],[:cell,'$A$2']]]
104
+ Formula.parse('$A$1<$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"<"],[:cell,'$A$2']]]
105
+ Formula.parse('$A$1=$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"="],[:cell,'$A$2']]]
106
+ Formula.parse('$A$1>=$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,">="],[:cell,'$A$2']]]
107
+ Formula.parse('$A$1<=$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"<="],[:cell,'$A$2']]]
108
+ Formula.parse('$A$1<>$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"<>"],[:cell,'$A$2']]]
109
+ end
110
+
111
+ it "returns functions" do
112
+ Formula.parse('PI()').to_ast.should == [:formula,[:function,'PI']]
113
+ Formula.parse('ERR($A$1)').to_ast.should == [:formula,[:function,'ERR',[:cell,'$A$1']]]
114
+ Formula.parse('SUM($A$1,sheet1!$1:$1000,1)').to_ast.should == [:formula,[:function,'SUM',[:cell,'$A$1'],[:sheet_reference,'sheet1',[:row_range,'$1','$1000']],[:number,'1']]]
115
+ end
116
+
117
+ it "returns fully qualified structured references (i.e., Table[column])" do
118
+ Formula.parse('DeptSales[Sale Amount]').to_ast.should == [:formula,[:qualified_table_reference,'DeptSales','Sale Amount']]
119
+ #Formula.parse("DeptSales[Sale'] Amount]").to_ast.should == [:formula,[:qualified_table_reference,'DeptSales','Sale Amount']]
120
+ end
121
+
122
+ it "returns booleans" do
123
+ Formula.parse("TRUE*FALSE").to_ast.should == [:formula,[:arithmetic,[:boolean_true],[:operator,'*'],[:boolean_false]]]
124
+ end
125
+
126
+ it "returns prefixes (+/-)" do
127
+ Formula.parse("-(3-1)").to_ast.should == [:formula,[:prefix,'-'],[:brackets,[:arithmetic,[:number,'3'],[:operator,'-'],[:number,'1']]]]
128
+ end
129
+
130
+ it "returns local structured references (i.e., [column])" do
131
+ Formula.parse('[Sale Amount]').to_ast.should == [:formula,[:local_table_reference,'Sale Amount']]
132
+ #Formula.parse("DeptSales[Sale'] Amount]").to_ast.should == [:formula,[:qualified_table_reference,'DeptSales','Sale Amount']]
133
+ end
134
+
135
+ it "returns named references" do
136
+ Formula.parse('EF.NaturalGas.N2O').to_ast.should == [:formula,[:named_reference,'EF.NaturalGas.N2O']]
137
+ end
138
+
139
+ end