Created
July 19, 2012 22:10
-
-
Save akirill0v/3147222 to your computer and use it in GitHub Desktop.
Polish Calculator and Parser (https://github.com/saratovsource/r_improvement/tree/develop)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require File.join(File.dirname(__FILE__), 'token') | |
class Lexer | |
attr_reader :grammar_rules, :regex_pattern, :output, :buffer, :priority_hash | |
DEFAULT_GRAMMAR_RULES = { | |
:operators => %w[\+ \- \* \/ \^], | |
:brackets => %w[\( \)], | |
:operands => ['\d*\.\d+|0\.\d+|-0\.\d*[1-9]\d*', '\d+'] | |
} | |
PRIORITY_HASH = { | |
"+" => 1, | |
"-" => 1, | |
"*" => 2, | |
"/" => 2, | |
"^" => 3 | |
} | |
def initialize(args = {}) | |
@grammar_rules = args.fetch(:grammar_rules, DEFAULT_GRAMMAR_RULES) | |
@regex_pattern = Regexp.new(@grammar_rules.values.join('|')) | |
@priority_hash = args.fetch(:priority, PRIORITY_HASH) | |
end | |
def split(formula, args = {}) | |
formula.scan(@regex_pattern).flatten | |
end | |
def tokenize(formula) | |
case formula | |
when String then split(formula) | |
else formula | |
end.collect{|str| put_token(str)} | |
end | |
# Pice of http://goo.gl/O95cb | |
def sort_station(formula) | |
@output, @buffer = [], [] | |
tokenized_form = tokenize(formula) | |
for token in tokenized_form do | |
# If the character is a number | |
if token.equal_rule?(@grammar_rules[:operands]) | |
@output << token | |
end | |
# If the character is a bracket | |
if token.equal_rule?(@grammar_rules[:brackets]) | |
if token.to_s == "(" #If opened bracket | |
@buffer.push(token) | |
else #If closed Bracket | |
stack_token = @buffer.pop | |
while(stack_token.to_s != "(" && @buffer.any?) | |
@output << stack_token unless stack_token.equal_rule?(@grammar_rules[:brackets]) | |
stack_token = @buffer.pop | |
end | |
end | |
end | |
# If character is operator | |
if token.equal_rule?(@grammar_rules[:operators]) | |
if @buffer.any? | |
while(priority(token) <= priority(@buffer.last)) | |
@output << @buffer.pop | |
end | |
@buffer.push(token) | |
else | |
@buffer.push(token) | |
end | |
end | |
end | |
#When tokenized_form empty | |
while(stack_token = @buffer.pop) | |
@output << stack_token | |
end | |
@output.join(' ') | |
end | |
protected | |
def priority(token) | |
@priority_hash[token.to_s] || 0 | |
end | |
def put_token(str) | |
token_type = (str =~ %r[#{@grammar_rules[:operators].join('|')}]) ? :operator : :operand | |
Token.new(str, token_type) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require File.join(File.dirname(__FILE__), 'lexer') | |
class PolishCalculator | |
attr_reader :lexer | |
def initialize(args = {}) | |
@lexer = args.fetch(:lexer, Lexer.new) | |
end | |
def calculate(formula) | |
@buffer = [] | |
tokenized_array = @lexer.tokenize(formula) | |
for token in tokenized_array do | |
if token.operator? | |
operator = token | |
op_r = @buffer.pop | |
op_l = @buffer.pop | |
if (op_l && op_r) | |
@buffer.push(eval("#{op_l} #{operator} #{op_r}")) | |
else | |
raise Exception.new "Operands count < 2. Can not eval operator #{token} !" | |
end | |
else | |
@buffer.push(token) | |
end | |
end | |
@buffer[0] | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require File.join(File.dirname(__FILE__), 'token') | |
class Token | |
attr_reader :token | |
attr_accessor :kind | |
def initialize(str, kind = :operand) | |
@token, @type = str, kind | |
end | |
def to_s | |
@token | |
end | |
def equal_rule?(rule) | |
@token =~ %r[#{rule.join('|')}] | |
end | |
def operator? | |
@type == :operator | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'minitest/autorun' | |
require File.join(File.dirname(__FILE__), '..', 'calculator', 'lexer') | |
class LexerTest < MiniTest::Unit::TestCase | |
def setup | |
@lexer = Lexer.new | |
end | |
def test_lexet_split | |
formula = "1+2*3.3/4" | |
parsed_array = @lexer.split(formula) | |
assert_equal %w[1 + 2 * 3.3 / 4], parsed_array | |
end | |
def test_tokenaize_array | |
formula = "1+2*3.3/4" | |
tokenaize_array = @lexer.tokenize(formula) | |
tokenaize_array.each do |token| | |
assert token.kind_of?(Token) | |
end | |
end | |
def test_tokenized_array_should_equal_string_array | |
formula = "1+2*3.3/4" | |
tokenaize_array = @lexer.tokenize(formula) | |
string_array = %w[1 + 2 * 3.3 / 4] | |
assert_equal string_array, tokenaize_array.map(&:to_s) | |
end | |
def test_sort_station_1 | |
infix_notation = "(19 + 2.14) * (4.5 - 2 / 4.3)" | |
postfix_notation = "19 2.14 + 4.5 2 4.3 / - *" | |
converted_notation = @lexer.sort_station(infix_notation) | |
assert_equal postfix_notation, converted_notation | |
end | |
def test_sort_station_2 | |
infix_notation = "2 / (3 - (4 + (5 * 6)))" | |
postfix_notation = "2 3 4 5 6 * + - /" | |
converted_notation = @lexer.sort_station(infix_notation) | |
assert_equal postfix_notation, converted_notation | |
end | |
def test_sort_station_3 | |
infix_notation = "(8+2*5)/(1+3*2-4)" | |
postfix_notation = "8 2 5 * + 1 3 2 * + 4 - /" | |
converted_notation = @lexer.sort_station(infix_notation) | |
assert_equal postfix_notation, converted_notation | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'minitest/autorun' | |
require File.join(File.dirname(__FILE__), '..', 'calculator', 'polish_calculator') | |
class PolishCalculatorTest < MiniTest::Unit::TestCase | |
def setup | |
@calculator = PolishCalculator.new | |
end | |
def test_calculate | |
result = 6 | |
formula = "8 2 5 * + 1 3 2 * + 4 - /" | |
assert_equal result, @calculator.calculate(formula) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment