Adam Shelly
11/6/2006 11:53:00 PM
Here is my solution. I tried to keep it short and simple.
Unfortunately, it got a little more complicated when I added code to
pass Marcel's test cases. But now it can handle '--++-++-2+-2'....
module Compiler
# The whole thing wrapped up into one function....
#
# The first half tokenizes into single chars or fixnums: it uses gsub
# to convert '**' to '^', binary '-'' to '~', and to remove pointless '+'s.
# Digits and most remaining '-'s are collected and converted using to_i.
# A few pesky unary '-'s are converted to the token 'n'
#
# The second half uses the shunting yard algorithm to build the RPN,
# and does the token->bytecode translation inline.
#
# The Tokens array contains 4 items: the character token, the bytecode,
# and two precedence values - one for a token on the stack,
# and another for a token in the stream. The values are only different
# if the operator is right-associative - this simplifies the conditional
# for deciding when to pop operators.
# The 'n' token is expanded to [0,swap,sub]
Tokens = [['+',10,3,3],['~',11,3,3],['*',12,2,2],['/',14,2,2],['%',15,2,2],
['^',13,1,0],['n',[1,0,0,0xa0,11],-1,-2],[nil,'(',9,9]]
def self.compile expr
num,tokens,output,stack='',[],[],[]
expr.gsub(/(\d|\))-/,'\1~').gsub(/(^|[^\d)])\++/,'\1').gsub("**","^").each_byte{|b|
if /\d|-/ =~ c=b.chr
num+=c
num.gsub!('--','')
else
tokens << (/\d/=~num ? num.to_i : 'n') and num='' unless num.empty?
tokens << c
end
}
tokens << num.to_i unless num.empty?
tokens.each{|token|
case token
when Integer
output += (-2**15...2**15)===token ?
[1]+[token].pack('n').unpack('C*') :
[2]+[token].pack('N').unpack('C*')
when '('
stack << token
when ')'
output << stack.pop until stack.last == '('
stack.pop
else
tokdef = Tokens.assoc(token)
output << stack.pop while t=stack.last and Tokens.rassoc(t)[2]
<= tokdef[3]
stack << tokdef[1]
end
}
(output+stack.reverse).flatten
end
end
--Adam