Skip to content

Commit

Permalink
add string functions
Browse files Browse the repository at this point in the history
  • Loading branch information
rubysolo committed Apr 4, 2016
1 parent 5f117cd commit d881952
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change Log

## [Unreleased]
- add `LEFT`, `RIGHT`, `MID`, `LEN`, `FIND`, `SUBSTITUTE`, and `CONCAT` string functions

## [v2.0.7] 2016-02-25
- fail with gem-specific error for parsing issues
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,16 @@ application, AST caching will consume more memory with each new formula.
BUILT-IN OPERATORS AND FUNCTIONS
---------------------------------

Math: `+ - * / %`
Math: `+`, `-`, `*`, `/`, `%`

Logic: `< > <= >= <> != = AND OR`
Logic: `<`, `>`, `<=`, `>=`, `<>`, `!=`, `=`, `AND`, `OR`

Functions: `IF NOT MIN MAX ROUND ROUNDDOWN ROUNDUP`
Functions: `IF`, `NOT`, `MIN`, `MAX`, `ROUND`, `ROUNDDOWN`, `ROUNDUP`

Math: all functions from Ruby's Math module, including `SIN`, `COS`, `TAN`, etc.

String: `LEFT`, `RIGHT`, `MID`, `LEN`, `FIND`, `SUBSTITUTE`, `CONCAT`

Math: all functions from Ruby's Math module, including `SIN, COS, TAN, ...`

RESOLVING DEPENDENCIES
----------------------
Expand Down
1 change: 1 addition & 0 deletions lib/dentaku/ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
require_relative './ast/functions/roundup'
require_relative './ast/functions/rounddown'
require_relative './ast/functions/ruby_math'
require_relative './ast/functions/string_functions'
104 changes: 104 additions & 0 deletions lib/dentaku/ast/functions/string_functions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
require_relative '../function'

module Dentaku
module AST
module StringFunctions
class Left < Function
def initialize(string, length)
@string = string
@length = length
end

def value(context={})
@string.value(context)[0, @length.value(context)]
end
end

class Right < Function
def initialize(string, length)
@string = string
@length = length
end

def value(context={})
string = @string.value(context)
length = @length.value(context)
string[length * -1, length] || string
end
end

class Mid < Function
def initialize(string, offset, length)
@string = string
@offset = offset
@length = length
end

def value(context={})
string = @string.value(context)
offset = @offset.value(context)
length = @length.value(context)
string[offset - 1, length].to_s
end
end

class Len < Function
def initialize(string)
@string = string
end

def value(context={})
@string.value(context).length
end
end

class Find < Function
def initialize(needle, haystack)
@needle = needle
@haystack = haystack
end

def value(context={})
needle = @needle.value(context)
haystack = @haystack.value(context)
pos = haystack.index(needle)
pos && pos + 1
end
end

class Substitute < Function
def initialize(original, search, replacement)
@original = original
@search = search
@replacement = replacement
end

def value(context={})
original = @original.value(context)
search = @search.value(context)
replacement = @replacement.value(context)
original.sub(search, replacement)
end
end

class Concat < Function
def initialize(left, right)
@left = left
@right = right
end

def value(context={})
@left.value(context) + @right.value(context)
end
end
end
end
end

Dentaku::AST::Function.register_class(:left, Dentaku::AST::StringFunctions::Left)
Dentaku::AST::Function.register_class(:right, Dentaku::AST::StringFunctions::Right)
Dentaku::AST::Function.register_class(:mid, Dentaku::AST::StringFunctions::Mid)
Dentaku::AST::Function.register_class(:len, Dentaku::AST::StringFunctions::Len)
Dentaku::AST::Function.register_class(:find, Dentaku::AST::StringFunctions::Find)
Dentaku::AST::Function.register_class(:substitute, Dentaku::AST::StringFunctions::Substitute)
Dentaku::AST::Function.register_class(:concat, Dentaku::AST::StringFunctions::Concat)
135 changes: 135 additions & 0 deletions spec/ast/string_functions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
require 'spec_helper'
require 'dentaku/ast/functions/string_functions'

describe Dentaku::AST::StringFunctions::Left do
let(:string) { identifier('string') }
let(:length) { identifier('length') }

subject { described_class.new(string, length) }

it 'returns the left N characters of the string' do
expect(subject.value('string' => 'ABCDEFG', 'length' => 4)).to eq 'ABCD'
end

it 'works correctly with literals' do
left = literal('ABCD')
len = literal(2)
fn = described_class.new(left, len)
expect(fn.value).to eq 'AB'
end

it 'handles an empty string correctly' do
expect(subject.value('string' => '', 'length' => 4)).to eq ''
end

it 'handles size greater than input string length correctly' do
expect(subject.value('string' => 'abcdefg', 'length' => 40)).to eq 'abcdefg'
end
end

describe Dentaku::AST::StringFunctions::Right do
it 'returns the right N characters of the string' do
subject = described_class.new(literal('ABCDEFG'), literal(4))
expect(subject.value).to eq 'DEFG'
end

it 'handles an empty string correctly' do
subject = described_class.new(literal(''), literal(4))
expect(subject.value).to eq ''
end

it 'handles size greater than input string length correctly' do
subject = described_class.new(literal('abcdefg'), literal(40))
expect(subject.value).to eq 'abcdefg'
end
end

describe Dentaku::AST::StringFunctions::Mid do
it 'returns a substring from the middle of the string' do
subject = described_class.new(literal('ABCDEFG'), literal(4), literal(2))
expect(subject.value).to eq 'DE'
end

it 'handles an empty string correctly' do
subject = described_class.new(literal(''), literal(4), literal(2))
expect(subject.value).to eq ''
end

it 'handles offset greater than input string length correctly' do
subject = described_class.new(literal('abcdefg'), literal(40), literal(4))
expect(subject.value).to eq ''
end

it 'handles size greater than input string length correctly' do
subject = described_class.new(literal('abcdefg'), literal(4), literal(40))
expect(subject.value).to eq 'defg'
end
end

describe Dentaku::AST::StringFunctions::Len do
it 'returns the length of a string' do
subject = described_class.new(literal('ABCDEFG'))
expect(subject.value).to eq 7
end

it 'handles an empty string correctly' do
subject = described_class.new(literal(''))
expect(subject.value).to eq 0
end
end

describe Dentaku::AST::StringFunctions::Find do
it 'returns the position of a substring within a string' do
subject = described_class.new(literal('DE'), literal('ABCDEFG'))
expect(subject.value).to eq 4
end

it 'handles an empty substring correctly' do
subject = described_class.new(literal(''), literal('ABCDEFG'))
expect(subject.value).to eq 1
end

it 'handles an empty string correctly' do
subject = described_class.new(literal('DE'), literal(''))
expect(subject.value).to be_nil
end
end

describe Dentaku::AST::StringFunctions::Substitute do
it 'replaces a substring within a string' do
subject = described_class.new(literal('ABCDEFG'), literal('DE'), literal('xy'))
expect(subject.value).to eq 'ABCxyFG'
end

it 'handles an empty search string correctly' do
subject = described_class.new(literal('ABCDEFG'), literal(''), literal('xy'))
expect(subject.value).to eq 'xyABCDEFG'
end

it 'handles an empty replacement string correctly' do
subject = described_class.new(literal('ABCDEFG'), literal('DE'), literal(''))
expect(subject.value).to eq 'ABCFG'
end
end

describe Dentaku::AST::StringFunctions::Concat do
it 'concatenates two strings' do
subject = described_class.new(literal('ABC'), literal('DEF'))
expect(subject.value).to eq 'ABCDEF'
end

it 'concatenates a string onto an empty string' do
subject = described_class.new(literal(''), literal('ABC'))
expect(subject.value).to eq 'ABC'
end

it 'concatenates an empty string onto a string' do
subject = described_class.new(literal('ABC'), literal(''))
expect(subject.value).to eq 'ABC'
end

it 'concatenates two empty strings' do
subject = described_class.new(literal(''), literal(''))
expect(subject.value).to eq ''
end
end
8 changes: 8 additions & 0 deletions spec/calculator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,12 @@
end
end
end

describe 'string functions' do
it 'concatenates two strings' do
expect(
calculator.evaluate('CONCAT(s1, s2)', 's1' => 'abc', 's2' => 'def')
).to eq 'abcdef'
end
end
end
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ def type_for(value)
:identifier
end
end

def identifier(name)
Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, name))
end

def literal(value)
Dentaku::AST::Literal.new(Dentaku::Token.new(type_for(value), value))
end

0 comments on commit d881952

Please sign in to comment.