formula_dsl 0.1.1 → 0.2.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.
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