formula_dsl 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,6 +1,9 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem 'parslet', '~> 1.3.0'
4
- gem 'jeweler', '~> 1.8.3'
5
- gem 'rake', '~> 0.9.2'
6
- gem 'rspec', '~> 2.9.0'
4
+
5
+ group :development, :test do
6
+ gem 'rspec', '~> 2.9.0'
7
+ gem 'jeweler', '~> 1.8.3'
8
+ gem 'rake', '> 0.9.2'
9
+ end
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GEM
12
12
  json (1.6.5)
13
13
  parslet (1.3.0)
14
14
  blankslate (~> 2.0)
15
- rake (0.9.2)
15
+ rake (0.9.2.2)
16
16
  rdoc (3.12)
17
17
  json (~> 1.4)
18
18
  rspec (2.9.0)
@@ -30,5 +30,5 @@ PLATFORMS
30
30
  DEPENDENCIES
31
31
  jeweler (~> 1.8.3)
32
32
  parslet (~> 1.3.0)
33
- rake (~> 0.9.2)
33
+ rake (> 0.9.2)
34
34
  rspec (~> 2.9.0)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
data/formula_dsl.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "formula_dsl"
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Sergio Junior"]
12
- s.date = "2012-03-22"
12
+ s.date = "2012-04-03"
13
13
  s.description = " External DSL to express math operations like you do in MSExcel formula bar. The parser generate a AST (Hash) for a given expression. "
14
14
  s.email = "sergior.jr@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -28,14 +28,26 @@ Gem::Specification.new do |s|
28
28
  "VERSION",
29
29
  "formula_dsl.gemspec",
30
30
  "lib/formula_dsl.rb",
31
- "lib/formula_dsl/formula_parser.rb",
32
- "spec/formula_dsl/formula_parser_spec.rb",
31
+ "lib/formula_dsl/binary_expression.rb",
32
+ "lib/formula_dsl/binary_expression_factory.rb",
33
+ "lib/formula_dsl/binary_operations.rb",
34
+ "lib/formula_dsl/function_expression.rb",
35
+ "lib/formula_dsl/function_expression_factory.rb",
36
+ "lib/formula_dsl/functions.rb",
37
+ "lib/formula_dsl/parser.rb",
38
+ "lib/formula_dsl/transformer.rb",
39
+ "spec/formula_dsl/binary_expression_factory_spec.rb",
40
+ "spec/formula_dsl/binary_expression_spec.rb",
41
+ "spec/formula_dsl/function_expression_factory_spec.rb",
42
+ "spec/formula_dsl/function_expression_spec.rb",
43
+ "spec/formula_dsl/parser_spec.rb",
44
+ "spec/formula_dsl/transformer_spec.rb",
33
45
  "spec/spec_helper.rb"
34
46
  ]
35
47
  s.homepage = "http://github.com/sergiojunior/formula_dsl"
36
48
  s.licenses = ["MIT"]
37
49
  s.require_paths = ["lib"]
38
- s.rubygems_version = "1.8.17"
50
+ s.rubygems_version = "1.8.21"
39
51
  s.summary = "External DSL that creates AST representation (Hash) for a given mathematical expression."
40
52
 
41
53
  if s.respond_to? :specification_version then
@@ -43,20 +55,20 @@ Gem::Specification.new do |s|
43
55
 
44
56
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
57
  s.add_runtime_dependency(%q<parslet>, ["~> 1.3.0"])
46
- s.add_runtime_dependency(%q<jeweler>, ["~> 1.8.3"])
47
- s.add_runtime_dependency(%q<rake>, ["~> 0.9.2"])
48
- s.add_runtime_dependency(%q<rspec>, ["~> 2.9.0"])
58
+ s.add_development_dependency(%q<rspec>, ["~> 2.9.0"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
60
+ s.add_development_dependency(%q<rake>, ["> 0.9.2"])
49
61
  else
50
62
  s.add_dependency(%q<parslet>, ["~> 1.3.0"])
51
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
52
- s.add_dependency(%q<rake>, ["~> 0.9.2"])
53
63
  s.add_dependency(%q<rspec>, ["~> 2.9.0"])
64
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
65
+ s.add_dependency(%q<rake>, ["> 0.9.2"])
54
66
  end
55
67
  else
56
68
  s.add_dependency(%q<parslet>, ["~> 1.3.0"])
57
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
58
- s.add_dependency(%q<rake>, ["~> 0.9.2"])
59
69
  s.add_dependency(%q<rspec>, ["~> 2.9.0"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
71
+ s.add_dependency(%q<rake>, ["> 0.9.2"])
60
72
  end
61
73
  end
62
74
 
@@ -0,0 +1,26 @@
1
+ #encoding: utf-8
2
+ module FormulaDSL
3
+
4
+ class BinaryExpression
5
+ attr_reader :operator, :left_term, :right_term
6
+
7
+ def initialize(operator, left_term, right_term)
8
+ @operator = operator
9
+ @left_term = left_term
10
+ @right_term = right_term
11
+ end
12
+
13
+ def apply
14
+ @left_term = @left_term.apply if @left_term.respond_to? :apply
15
+ @right_term = @right_term.apply if @right_term.respond_to? :apply
16
+ operation = BinaryExpressionFactory.new(@operator)
17
+
18
+ operation.call(@left_term, @right_term)
19
+ end
20
+
21
+ def == (other)
22
+ @operator == other.operator && @left_term == other.left_term && @right_term == other.right_term
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,22 @@
1
+ #encoding: utf-8
2
+
3
+ module FormulaDSL
4
+ class MissingBinaryOperationError < RuntimeError
5
+ end
6
+
7
+ module BinaryExpressionFactory
8
+
9
+ def self.new(operator)
10
+ operation = case operator
11
+ when :+ then ::FormulaDSL::BinaryOperations::ADDITION
12
+ when :- then ::FormulaDSL::BinaryOperations::SUBTRACTION
13
+ when :* then ::FormulaDSL::BinaryOperations::MULTIPLICATION
14
+ when :/ then ::FormulaDSL::BinaryOperations::DIVISION
15
+ else raise MissingBinaryOperationError, "Sorry, but we don't have a implementation for the operator ( #{operator} )."
16
+ end
17
+
18
+ operation
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ #encoding: utf-8
2
+
3
+ module FormulaDSL
4
+ module BinaryOperations
5
+
6
+ ADDITION = Proc.new{|left_term, right_term| "#{left_term} + #{right_term}" }
7
+ SUBTRACTION = Proc.new{|left_term, right_term| "#{left_term} - #{right_term}" }
8
+ DIVISION = Proc.new{|left_term, right_term| "#{left_term} / #{right_term}" }
9
+ MULTIPLICATION = Proc.new{|left_term, right_term| "#{left_term} * #{right_term}" }
10
+
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ #encoding: utf-8
2
+ module FormulaDSL
3
+
4
+ class FunctionExpression
5
+ attr_reader :name, :args
6
+ def initialize(name, *args)
7
+ @name = name
8
+ @args = args
9
+ end
10
+
11
+ def apply
12
+ function = FunctionExpressionFactory.new(@name)
13
+
14
+ function.call(@args)
15
+ end
16
+
17
+ def == (other)
18
+ @name == other.name && @args == other.args
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,31 @@
1
+ #encoding: utf-8
2
+
3
+ module FormulaDSL
4
+ class MissingFunctionError < RuntimeError
5
+ end
6
+
7
+ module FunctionExpressionFactory
8
+
9
+ def self.new(function_name)
10
+ begin
11
+ function = constantize("FormulaDSL::Functions::#{function_name.upcase}")
12
+
13
+ function
14
+ rescue(NameError)
15
+ raise MissingFunctionError, "If you want to use the function #{function_name} you must implement that as a proc named FormulaDSL::Functions::#{function_name.upcase}"
16
+ end
17
+ end
18
+
19
+ private
20
+ def self.constantize( string )
21
+ module_names = string.split('::')
22
+ constant = Module.const_get( module_names.first.to_sym )
23
+
24
+ module_names = module_names.drop(1)
25
+ module_names.each{ |name| constant = constant.const_get( name.to_sym ) }
26
+
27
+ constant
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ #encoding: utf-8
2
+ module FormulaDSL
3
+ module Functions
4
+
5
+ MONTH = Proc.new{|args| "this.#{args.first}.getMonth() + 1" }
6
+ YEAR = Proc.new{|args| "this.#{args.first}.getYear()" }
7
+
8
+ end
9
+ end
@@ -0,0 +1,92 @@
1
+ #encoding: utf-8
2
+ module FormulaDSL
3
+ class Parser < Parslet::Parser
4
+
5
+ # Single char rules
6
+ rule(:lparen) { str('(') >> space? }
7
+ rule(:rparen) { str(')') >> space? }
8
+ rule(:comma) { str(',') >> space? }
9
+
10
+ # non significant character's
11
+ rule(:space) { match('\s').repeat(1) }
12
+ rule(:space?) { space.maybe }
13
+
14
+ # Literal's
15
+ rule(:number) { match('\d').repeat(1) }
16
+ rule(:quote) { match('"') }
17
+ rule(:string) { quote >> space? >> match('.') >> space? >> quote}
18
+ rule(:identifier) { match['\w'].repeat(1) }
19
+
20
+ # Argument
21
+ rule (:argument) { identifier | number }
22
+ rule (:arglist) { (argument >> comma.maybe).repeat }
23
+
24
+ # Operators
25
+ rule(:add_operator ) { match(['+']) }
26
+ rule(:sub_operator ) { match('-') }
27
+ rule(:mult_operator) { match(['*']) }
28
+ rule(:div_operator ) { match(['/']) }
29
+
30
+ rule :operator do
31
+ add_operator | sub_operator | mult_operator | div_operator
32
+ end
33
+
34
+ #binary operations + - / *
35
+ rule :addition do
36
+ ( number.as(:left) >> space? >> add_operator >> space? >> ( number ).as(:right) ).as(:+)
37
+ end
38
+
39
+ rule :subtraction do
40
+ ( number.as(:left) >> space? >> sub_operator >> space? >> ( number ).as(:right) ).as(:-)
41
+ end
42
+
43
+ rule :multiplication do
44
+ ( number.as(:left) >> space? >> mult_operator >> space? >> ( number ).as(:right) ).as(:*)
45
+ end
46
+
47
+ rule :division do
48
+ ( number.as(:left) >> space? >> div_operator >> space? >> ( number ).as(:right) ).as(:/)
49
+ end
50
+
51
+ rule :function do
52
+ (identifier.as(:name) >> lparen >> arglist.as(:args) >> rparen).as(:function)
53
+ end
54
+
55
+ rule :string_concat do
56
+ ( string.as(:left) >> space? >> add_operator >> space? >> ( string ).as(:right) ).as(:+)
57
+ end
58
+
59
+ rule :single_expression do
60
+ function | addition | subtraction | multiplication | division | string_concat | number | string
61
+ end
62
+
63
+ # expression with other expression (compose expression)
64
+ # Ex: expression >> some_op >> number === [ Mont(data) - 1 ]
65
+ rule :subtraction_expression do
66
+ (single_expression.as(:left) >> space? >> sub_operator >> space? >> (single_expression | number).as(:right) ).as(:-)
67
+ end
68
+
69
+ rule :addition_expression do
70
+ (single_expression.as(:left) >> space? >> add_operator >> space? >> ( expression_list | number ).as(:right) ).as(:+)
71
+ end
72
+
73
+ rule :multiplication_expression do
74
+ (single_expression.as(:left) >> space? >> mult_operator >> space? >> (single_expression | number).as(:right) ).as(:*)
75
+ end
76
+
77
+ rule :division_expression do
78
+ (single_expression.as(:left) >> space? >> div_operator >> space? >> (single_expression | number).as(:right) ).as(:/)
79
+ end
80
+
81
+ rule :string_concat_expression do
82
+ (single_expression.as(:left) >> space? >> add_operator >> space? >> ( expression_list | string ).as(:right) ).as(:+)
83
+ end
84
+
85
+ rule :expression_list do
86
+ addition_expression | subtraction_expression | multiplication_expression | division_expression | string_concat_expression | single_expression
87
+ end
88
+
89
+ # Entry Point rule
90
+ root :expression_list
91
+ end
92
+ end
@@ -0,0 +1,65 @@
1
+ #encoding: utf-8
2
+ module FormulaDSL
3
+ class Transformer
4
+
5
+ def apply( ast )
6
+ expression = parse(ast)
7
+
8
+ expression
9
+ end
10
+
11
+ private
12
+ def parse( hash )
13
+ if hash.keys.size == 1
14
+ parse_single_key_hash( hash )
15
+ else
16
+ puts "como faz o parse de um hash com mais de 1 chave???"
17
+ end
18
+ end
19
+
20
+ def parse_single_key_hash( hash )
21
+ key = hash.keys.first
22
+ if is_operator? key
23
+ parse_operation key, hash[key]
24
+ elsif is_function? key
25
+ parse_function hash[key]
26
+ else
27
+ puts "como faz o parse de um hash com chave = #{key}???"
28
+ end
29
+ end
30
+
31
+ def is_operator?( key )
32
+ [:+, :-, :*, :/].include?(key)
33
+ end
34
+
35
+ def is_function?(key)
36
+ :function == key
37
+ end
38
+
39
+ def parse_operation(operator, values={left: '', right: ''})
40
+ left_side = extract_operation_side(values[:left])
41
+ right_side = extract_operation_side(values[:right])
42
+
43
+ BinaryExpression.new(operator, left_side, right_side)
44
+ end
45
+
46
+ def parse_function( hash = {name:'', args:''} )
47
+ FunctionExpression.new(hash[:name].to_str, hash[:args].to_str)
48
+ end
49
+
50
+ def extract_operation_side( side )
51
+ if side.respond_to?(:keys)
52
+ parse( side )
53
+ else
54
+ if (/"(?<value>.)"/ =~ side.to_str)
55
+ "'#{value}'"
56
+ elsif (/(?<value>\d+)/ =~ side.str)
57
+ value.to_i
58
+ else
59
+ side.to_str
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
data/lib/formula_dsl.rb CHANGED
@@ -4,4 +4,11 @@ require "bundler/setup"
4
4
  Bundler.require(:default)
5
5
 
6
6
  require 'parslet'
7
- require 'formula_dsl/formula_parser'
7
+ require 'formula_dsl/parser'
8
+ require 'formula_dsl/transformer'
9
+ require 'formula_dsl/functions'
10
+ require 'formula_dsl/binary_operations'
11
+ require 'formula_dsl/function_expression_factory'
12
+ require 'formula_dsl/binary_expression_factory'
13
+ require 'formula_dsl/binary_expression'
14
+ require 'formula_dsl/function_expression'
@@ -0,0 +1,31 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module FormulaDSL
5
+ describe BinaryExpressionFactory do
6
+ subject{ BinaryExpressionFactory }
7
+
8
+ specify { should respond_to(:new) }
9
+
10
+ it "should be return a ADDITON Proc to + operator " do
11
+ subject.new(:+).should be == FormulaDSL::BinaryOperations::ADDITION
12
+ end
13
+
14
+ it "should be return a SUBTRACTION Proc to - operator " do
15
+ subject.new(:-).should be == FormulaDSL::BinaryOperations::SUBTRACTION
16
+ end
17
+
18
+ it "should be return a MULTIPLICATON Proc to * operator " do
19
+ subject.new(:*).should be == FormulaDSL::BinaryOperations::MULTIPLICATION
20
+ end
21
+
22
+ it "should be return a DIVISION Proc to / operator " do
23
+ subject.new(:/).should be == FormulaDSL::BinaryOperations::DIVISION
24
+ end
25
+
26
+ it "should raise FormulaDSL::MissingBinaryOperation for a unknown operator " do
27
+ lambda{ subject.new('abc') }.should raise_error(MissingBinaryOperationError)
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module FormulaDSL
5
+ describe BinaryExpression do
6
+ subject{ BinaryExpression.new(:+, 3, 2) }
7
+
8
+ specify { should respond_to(:operator) }
9
+ specify { should respond_to(:left_term) }
10
+ specify { should respond_to(:right_term) }
11
+ specify { should respond_to(:apply) }
12
+
13
+ it "should be considered equal to a another BinaryExpression if that function contains same data" do
14
+ subject.should == BinaryExpression.new(:+, 3, 2)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module FormulaDSL
5
+ describe FunctionExpressionFactory do
6
+ subject{ FunctionExpressionFactory }
7
+
8
+ specify { should respond_to(:new) }
9
+
10
+ it "should return a correct function implementation for a given function name" do
11
+ constant = subject.new("month")
12
+ constant.should be FormulaDSL::Functions::MONTH
13
+ end
14
+
15
+ it "should raise MissingFunctionError for a not implemented function" do
16
+ lambda{ subject.new("sqrt") }.should raise_error(MissingFunctionError)
17
+ end
18
+
19
+
20
+ context "with private method #constantize" do
21
+ it "should receive a string with a full qualified function name and return the correspondent Constant" do
22
+ constant = subject.send(:constantize, "FormulaDSL::Functions::MONTH")
23
+ constant.should be FormulaDSL::Functions::MONTH
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module FormulaDSL
5
+ describe FunctionExpression do
6
+ subject{ FunctionExpression.new('SQRT', 4) }
7
+
8
+ specify { should respond_to(:name) }
9
+ specify { should respond_to(:args) }
10
+ specify { should respond_to(:apply) }
11
+
12
+ it "should be considered equal to a another Function if that function contains same data" do
13
+ subject.should == FunctionExpression.new('SQRT', 4)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,87 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module FormulaDSL
5
+ describe Parser do
6
+
7
+ let(:parser){ subject }
8
+
9
+ context "Single Expression's" do
10
+
11
+ it "should recognize expression '2 + 1' " do
12
+ ast = parser.parse('2 + 1')
13
+ ast.to_s.should == %Q({:+=>{:left=>"2"@0, :right=>"1"@4}})
14
+ end
15
+
16
+ it "should recognize expression '2 + 1 + 3' " do
17
+ ast = parser.parse('2 + 1 + 3')
18
+ ast.to_s.should == %Q({:+=>{:left=>{:+=>{:left=>"2"@0, :right=>"1"@4}}, :right=>"3"@8}})
19
+ end
20
+
21
+ it "should recognize expression '2 - 1" do
22
+ ast = parser.parse('2 - 1')
23
+ ast.to_s.should == %Q({:-=>{:left=>"2"@0, :right=>"1"@4}})
24
+ end
25
+
26
+ it "should recognize expression '2 * 1" do
27
+ ast = parser.parse('2 * 1')
28
+ ast.to_s.should == %Q({:*=>{:left=>"2"@0, :right=>"1"@4}})
29
+ end
30
+
31
+ it "should recognize expression '2 / 2" do
32
+ ast = parser.parse('2 / 2')
33
+ ast.to_s.should == %Q({:/=>{:left=>"2"@0, :right=>"2"@4}})
34
+ end
35
+ end
36
+
37
+ context "Composed expression's " do
38
+ it "should recognize a function like 'Month(data)' " do
39
+ ast = parser.parse('Month(data)')
40
+ ast.to_s.should == %Q({:function=>{:name=>"Month"@0, :args=>"data"@6}})
41
+ end
42
+
43
+ it "should recognize the expression 'Month(data) - 1' " do
44
+ ast = parser.parse('Month(data) - 1')
45
+ ast.to_s.should == %Q({:-=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"1"@14}})
46
+ end
47
+
48
+ it "should recognize the expression 'Month(data) * 1' " do
49
+ ast = parser.parse('Month(data) * 1')
50
+ ast.to_s.should == %Q({:*=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"1"@14}})
51
+ end
52
+
53
+ it "should recognize the expression 'Month(data) / 1' " do
54
+ ast = parser.parse('Month(data) / 1')
55
+ ast.to_s.should == %Q({:/=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"1"@14}})
56
+ end
57
+
58
+ it " should recognize expression with 2 functions 'Month(data) + Year(data)'" do
59
+ ast = parser.parse('Month(data) + Year(data)')
60
+ ast.to_s.should == %Q({:+=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>{:function=>{:name=>"Year"@14, :args=>"data"@19}}}})
61
+ end
62
+ end
63
+
64
+ context "Expressions with string concatenation" do
65
+ it " should recognize expression with string concat \"A\" + \"B\"'" do
66
+ ast = parser.parse('"A" + "B"')
67
+ ast.to_s.should == %Q({:+=>{:left=>"\\\"A\\\""@0, :right=>"\\\"B\\\""@6}})
68
+ end
69
+
70
+ it " should recognize expression with string concat '\"A\" + \"B\" + \"C\" ' " do
71
+ ast = parser.parse('"A" + "B" + "C"')
72
+ ast.to_s.should == %Q({:+=>{:left=>{:+=>{:left=>"\\\"A\\\""@0, :right=>"\\\"B\\\""@6}}, :right=>"\\\"C\\\""@12}})
73
+ end
74
+
75
+ it " should recognize expression with string concat 'Month(data) + \"/\"'" do
76
+ ast = parser.parse('Month(data) + "/"')
77
+ ast.to_s.should == %Q({:+=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"\\\"/\\\""@14}})
78
+ end
79
+
80
+ it " should recognize expression with string concat 'Month(data) + \"/\" + Year(data)'" do
81
+ ast = parser.parse('Month(data) + "/" + Year(data)')
82
+ ast.to_s.should == %Q({:+=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>{:+=>{:left=>"\\\"/\\\""@14, :right=>{:function=>{:name=>"Year"@20, :args=>"data"@25}}}}}})
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,60 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module FormulaDSL
5
+ describe Transformer do
6
+
7
+ let(:parser){ Parser.new }
8
+ let(:resolver){ subject }
9
+
10
+ context "Resolving Expression's" do
11
+
12
+ it "should resolve ast '2 + 1' as BinaryExpression" do
13
+ ast = parser.parse('2 + 1')
14
+ expression = resolver.apply( ast )
15
+ expression.should be_a_kind_of BinaryExpression
16
+ expression.operator.should == :+
17
+ expression.left_term.should == 2
18
+ expression.right_term.should == 1
19
+ end
20
+
21
+ it "should resolve ast '2 + 1 - 4' as BinaryExpression with contains antoher BinaryExpression" do
22
+ ast = parser.parse('2 + 1 - 4')
23
+ expression = resolver.apply( ast )
24
+ expression.should be_a_kind_of BinaryExpression
25
+ expression.left_term.should be_a_kind_of BinaryExpression
26
+ expression.left_term.should be == BinaryExpression.new(:+,2,1)
27
+ expression.right_term.should == 4
28
+ expression.operator.should == :-
29
+ end
30
+
31
+ it "should resolve ast 'Month(data) as FunctionExpression" do
32
+ ast = parser.parse('Month(data)')
33
+ expression = resolver.apply( ast )
34
+ expression.should be_a_kind_of FunctionExpression
35
+ expression.name.should == 'Month'
36
+ expression.args.should == ['data']
37
+ end
38
+
39
+ it "should resolve ast 'Month(data) + Year(data) as Binary operation wich conatins 2 FunctionExpression as left and right terms" do
40
+ ast = parser.parse('Month(data) + Year(data)')
41
+ expression = resolver.apply( ast )
42
+ expression.should be_a_kind_of BinaryExpression
43
+ expression.operator.should == :+
44
+ expression.left_term.should be == FunctionExpression.new('Month','data')
45
+ expression.right_term.should be == FunctionExpression.new('Year','data')
46
+ end
47
+
48
+ it "should resolve 'Month(data) + \"/\" + Year(data)' as BinaryExpression wich contains FunctionExpression and another BinaryExpression" do
49
+ ast = parser.parse('Month(data) + "/" + Year(data)')
50
+ expression = resolver.apply( ast )
51
+ expression.should be_a_kind_of BinaryExpression
52
+ expression.operator.should == :+
53
+ expression.left_term.should == FunctionExpression.new('Month','data')
54
+ expression.right_term.should == BinaryExpression.new(:+,"'/'",FunctionExpression.new('Year','data'))
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ end
data/spec/spec_helper.rb CHANGED
@@ -1 +1,2 @@
1
+ require 'rubygems'
1
2
  require './lib/formula_dsl'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formula_dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-22 00:00:00.000000000 Z
12
+ date: 2012-04-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parslet
16
- requirement: &70351729396520 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,40 +21,60 @@ dependencies:
21
21
  version: 1.3.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70351729396520
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.9.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.9.0
25
46
  - !ruby/object:Gem::Dependency
26
47
  name: jeweler
27
- requirement: &70351729395540 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
28
49
  none: false
29
50
  requirements:
30
51
  - - ~>
31
52
  - !ruby/object:Gem::Version
32
53
  version: 1.8.3
33
- type: :runtime
54
+ type: :development
34
55
  prerelease: false
35
- version_requirements: *70351729395540
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.8.3
36
62
  - !ruby/object:Gem::Dependency
37
63
  name: rake
38
- requirement: &70351729394860 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
39
65
  none: false
40
66
  requirements:
41
- - - ~>
67
+ - - ! '>'
42
68
  - !ruby/object:Gem::Version
43
69
  version: 0.9.2
44
- type: :runtime
70
+ type: :development
45
71
  prerelease: false
46
- version_requirements: *70351729394860
47
- - !ruby/object:Gem::Dependency
48
- name: rspec
49
- requirement: &70351729393780 !ruby/object:Gem::Requirement
72
+ version_requirements: !ruby/object:Gem::Requirement
50
73
  none: false
51
74
  requirements:
52
- - - ~>
75
+ - - ! '>'
53
76
  - !ruby/object:Gem::Version
54
- version: 2.9.0
55
- type: :runtime
56
- prerelease: false
57
- version_requirements: *70351729393780
77
+ version: 0.9.2
58
78
  description: ! ' External DSL to express math operations like you do in MSExcel formula
59
79
  bar. The parser generate a AST (Hash) for a given expression. '
60
80
  email: sergior.jr@gmail.com
@@ -75,8 +95,20 @@ files:
75
95
  - VERSION
76
96
  - formula_dsl.gemspec
77
97
  - lib/formula_dsl.rb
78
- - lib/formula_dsl/formula_parser.rb
79
- - spec/formula_dsl/formula_parser_spec.rb
98
+ - lib/formula_dsl/binary_expression.rb
99
+ - lib/formula_dsl/binary_expression_factory.rb
100
+ - lib/formula_dsl/binary_operations.rb
101
+ - lib/formula_dsl/function_expression.rb
102
+ - lib/formula_dsl/function_expression_factory.rb
103
+ - lib/formula_dsl/functions.rb
104
+ - lib/formula_dsl/parser.rb
105
+ - lib/formula_dsl/transformer.rb
106
+ - spec/formula_dsl/binary_expression_factory_spec.rb
107
+ - spec/formula_dsl/binary_expression_spec.rb
108
+ - spec/formula_dsl/function_expression_factory_spec.rb
109
+ - spec/formula_dsl/function_expression_spec.rb
110
+ - spec/formula_dsl/parser_spec.rb
111
+ - spec/formula_dsl/transformer_spec.rb
80
112
  - spec/spec_helper.rb
81
113
  homepage: http://github.com/sergiojunior/formula_dsl
82
114
  licenses:
@@ -93,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
125
  version: '0'
94
126
  segments:
95
127
  - 0
96
- hash: 348571483194901991
128
+ hash: 2530953314598159526
97
129
  required_rubygems_version: !ruby/object:Gem::Requirement
98
130
  none: false
99
131
  requirements:
@@ -102,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
134
  version: '0'
103
135
  requirements: []
104
136
  rubyforge_project:
105
- rubygems_version: 1.8.17
137
+ rubygems_version: 1.8.21
106
138
  signing_key:
107
139
  specification_version: 3
108
140
  summary: External DSL that creates AST representation (Hash) for a given mathematical
@@ -1,90 +0,0 @@
1
- #encoding: utf-8
2
- class FormulaParser < Parslet::Parser
3
-
4
- # Single char rules
5
- rule(:lparen) { str('(') >> space? }
6
- rule(:rparen) { str(')') >> space? }
7
- rule(:comma) { str(',') >> space? }
8
-
9
- # non significant character's
10
- rule(:space) { match('\s').repeat(1) }
11
- rule(:space?) { space.maybe }
12
-
13
- # Literal's
14
- rule(:number) { match('\d').repeat(1) }
15
- rule(:quote) { match('"') }
16
- rule(:string) { quote >> space? >> match('.') >> space? >> quote}
17
- rule(:identifier) { match['\w'].repeat(1) }
18
-
19
- # Argument
20
- rule (:argument) { identifier | number }
21
- rule (:arglist) { (argument >> comma.maybe).repeat }
22
-
23
- # Operators
24
- rule(:add_operator ) { match(['+']) }
25
- rule(:sub_operator ) { match('-') }
26
- rule(:mult_operator) { match(['*']) }
27
- rule(:div_operator ) { match(['/']) }
28
-
29
- rule :operator do
30
- add_operator | sub_operator | mult_operator | div_operator
31
- end
32
-
33
- #binary operations + - / *
34
- rule :addition do
35
- ( number.as(:left) >> space? >> add_operator >> space? >> ( number ).as(:right) ).as(:+)
36
- end
37
-
38
- rule :subtraction do
39
- ( number.as(:left) >> space? >> sub_operator >> space? >> ( number ).as(:right) ).as(:-)
40
- end
41
-
42
- rule :multiplication do
43
- ( number.as(:left) >> space? >> mult_operator >> space? >> ( number ).as(:right) ).as(:*)
44
- end
45
-
46
- rule :division do
47
- ( number.as(:left) >> space? >> div_operator >> space? >> ( number ).as(:right) ).as(:/)
48
- end
49
-
50
- rule :function do
51
- (identifier.as(:name) >> lparen >> arglist.as(:args) >> rparen).as(:function)
52
- end
53
-
54
- rule :string_concat do
55
- ( string.as(:left) >> space? >> add_operator >> space? >> ( string ).as(:right) ).as(:concat)
56
- end
57
-
58
- rule :single_expression do
59
- function | addition | subtraction | multiplication | division | string_concat | number | string
60
- end
61
-
62
- # expression with other expression (compose expression)
63
- # Ex: expression >> some_op >> number === [ Mont(data) - 1 ]
64
- rule :subtraction_expression do
65
- (single_expression.as(:left) >> space? >> sub_operator >> space? >> (single_expression | number).as(:right) ).as(:-)
66
- end
67
-
68
- rule :addition_expression do
69
- (single_expression.as(:left) >> space? >> add_operator >> space? >> (single_expression | number).as(:right) ).as(:+)
70
- end
71
-
72
- rule :multiplication_expression do
73
- (single_expression.as(:left) >> space? >> mult_operator >> space? >> (single_expression | number).as(:right) ).as(:*)
74
- end
75
-
76
- rule :division_expression do
77
- (single_expression.as(:left) >> space? >> div_operator >> space? >> (single_expression | number).as(:right) ).as(:/)
78
- end
79
-
80
- rule :string_concat_expression do
81
- (single_expression.as(:left) >> space? >> add_operator >> space? >> ( expression_list | string ).as(:right) ).as(:concat)
82
- end
83
-
84
- rule :expression_list do
85
- string_concat_expression | addition_expression | subtraction_expression | multiplication_expression | division_expression | single_expression
86
- end
87
-
88
- # Entry Point rule
89
- root :expression_list
90
- end
@@ -1,80 +0,0 @@
1
- #encoding: utf-8
2
- require 'spec_helper'
3
-
4
- describe FormulaParser do
5
-
6
- let(:parser){ subject }
7
-
8
- context "Single Expression's" do
9
-
10
- it "should recognize expression '2 + 1' " do
11
- ast = parser.parse('2 + 1')
12
- ast.to_s.should == %Q({:+=>{:left=>"2"@0, :right=>"1"@4}})
13
- end
14
-
15
- it "should recognize expression '2 - 1" do
16
- ast = parser.parse('2 - 1')
17
- ast.to_s.should == %Q({:-=>{:left=>"2"@0, :right=>"1"@4}})
18
- end
19
-
20
- it "should recognize expression '2 * 1" do
21
- ast = parser.parse('2 * 1')
22
- ast.to_s.should == %Q({:*=>{:left=>"2"@0, :right=>"1"@4}})
23
- end
24
-
25
- it "should recognize expression '2 / 2" do
26
- ast = parser.parse('2 / 2')
27
- ast.to_s.should == %Q({:/=>{:left=>"2"@0, :right=>"2"@4}})
28
- end
29
- end
30
-
31
- context "Composed expression's " do
32
- it "should recognize a function like 'Month(data)' " do
33
- ast = parser.parse('Month(data)')
34
- ast.to_s.should == %Q({:function=>{:name=>"Month"@0, :args=>"data"@6}})
35
- end
36
-
37
- it "should recognize the expression 'Month(data) - 1' " do
38
- ast = parser.parse('Month(data) - 1')
39
- ast.to_s.should == %Q({:-=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"1"@14}})
40
- end
41
-
42
- it "should recognize the expression 'Month(data) * 1' " do
43
- ast = parser.parse('Month(data) * 1')
44
- ast.to_s.should == %Q({:*=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"1"@14}})
45
- end
46
-
47
- it "should recognize the expression 'Month(data) / 1' " do
48
- ast = parser.parse('Month(data) / 1')
49
- ast.to_s.should == %Q({:/=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"1"@14}})
50
- end
51
-
52
- it " should recognize expression with 2 functions 'Month(data) + Year(data)'" do
53
- ast = parser.parse('Month(data) + Year(data)')
54
- ast.to_s.should == %Q({:concat=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>{:function=>{:name=>"Year"@14, :args=>"data"@19}}}})
55
- end
56
- end
57
-
58
- context "Expressions with string concatenation" do
59
- it " should recognize expression with string concat \"A\" + \"B\"'" do
60
- ast = parser.parse('"A" + "B"')
61
- ast.to_s.should == %Q({:concat=>{:left=>"\\\"A\\\""@0, :right=>"\\\"B\\\""@6}})
62
- end
63
-
64
- it " should recognize expression with string concat '\"A\" + \"B\" + \"C\" ' " do
65
- ast = parser.parse('"A" + "B" + "C"')
66
- ast.to_s.should == %Q({:concat=>{:left=>{:concat=>{:left=>"\\\"A\\\""@0, :right=>"\\\"B\\\""@6}}, :right=>"\\\"C\\\""@12}})
67
- end
68
-
69
- it " should recognize expression with string concat 'Month(data) + \"/\"'" do
70
- ast = parser.parse('Month(data) + "/"')
71
- ast.to_s.should == %Q({:concat=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>"\\\"/\\\""@14}})
72
- end
73
-
74
- it " should recognize expression with string concat 'Month(data) + \"/\" + Year(data)'" do
75
- ast = parser.parse('Month(data) + "/" + Year(data)')
76
- ast.to_s.should == %Q({:concat=>{:left=>{:function=>{:name=>"Month"@0, :args=>"data"@6}}, :right=>{:concat=>{:left=>"\\\"/\\\""@14, :right=>{:function=>{:name=>"Year"@20, :args=>"data"@25}}}}}})
77
- end
78
- end
79
-
80
- end