This 4th version also passes K Bloom's testcases.
Like some other solutions The converter is itself a stack-based
interpreter. Instead of evaluating the expression, the expression gets
transformed.
# The converter is itself a stack-based interpreter. Instead of
# evaluating the expression, the expression gets transformed.
class Quiz148
class << self
def run(args)
iqueue = args.map {|e| e.split(/\s+/)}.flatten
return Quiz148.new(iqueue).process
end
def test(input, expected)
observed = run(input)
ok = observed == expected
if ok
print '.'
else
puts "\n%s != %s\n" % [expected, observed]
end
end
end
def initialize(iqueue)
# Tokenized input
@iqueue = iqueue
# The stack of [operator, element] tuples
@stack = []
# Pre-defined operators
# Fields:
# - precedence
# - arity
# - left associative
# - right associative
# - template
@ops = {
'+' => [10, 2, true, true],
'-' => [10, 2, true, false],
'*' => [5, 2, true, false],
'/' => [5, 2, true, false],
'%' => [5, 2, true, false],
'<<' => [3, 2, true, false],
'^' => [5, 2, false, true],
'**' => [5, 2, false, true],
'sqrt' => [0, 1, true, true, '#{op}(#{vals})'],
'mean' => [0, 2, true, true, '#{op}
(#{vals.join(\', \')})'],
'sum3' => [0, 3, true, true],
'Array' => [0, -1, true, true, '[#{vals.join(\',
\')}]'],
}
@opnames = @ops.keys
end
def process
@iqueue.each do |token|
# Check whether the token is an operator.
if @opnames.include?(token)
op = token
opp, arity, assoc_left, assoc_right, fmt = @ops[op]
case arity
when -1
ap, arity = @stack.pop
when nil
arity = 2
end
case arity
when 1
fmt ||= '#{op}#{vals}'
when 2
fmt ||= '#{vals.join(\' \' + op + \' \')}'
else
fmt ||= '#{op}(#{vals.join(\', \')})'
end
vals = []
# Get the arguments.
arity.times do
if @stack.empty?
puts 'Malformed expression: too few argument'
end
vals.unshift(@stack.pop)
end
idx = 0
# Rewrite the operator's arguments.
vals.map! do |ap, val|
# If opp is <= 0, the operator is a function and
we
# can ignore precedence values. If the value is
an
# atom, ditto.
if ap and opp > 0
app, *rest = @ops[ap]
# If the other operator is a function, it's
considered atomic.
if app > 0
# Put the value in parentheses if the
# operator isn't left or right-
associative
# of if the other operator's precedence
is
# greater than the current operator's one.
if (idx == 0 and !assoc_left) or (idx == 1
and !assoc_right) or app > opp
val = '(%s)' % val
end
end
end
idx += 1
val
end
# Format the values.
@stack << [op, eval("\"#{fmt}\"")]
else
@stack << [nil, eval(token)]
end
end
o, v = @stack.pop
unless @stack.empty?
puts 'Malformed expression: too many argument'
end
v
end
end
if __FILE__ == $0
if ARGV.empty?
Quiz148.test('2 3 +', '2 + 3')
Quiz148.test('56 34 213.7 + * 678 -', '56 * (34 + 213.7) -
678')
Quiz148.test('1 56 35 + 16 9 - / +', '1 + (56 + 35) / (16 -
9)')
Quiz148.test('1 2 + 3 4 + +', '1 + 2 + 3 + 4')
Quiz148.test('1 2 - 3 4 - -', '1 - 2 - (3 - 4)')
Quiz148.test('1 3 4 - -', '1 - (3 - 4)')
Quiz148.test('2 2 ^ 2 ^', '(2 ^ 2) ^ 2')
Quiz148.test('2 2 2 ^ ^', '2 ^ 2 ^ 2')
Quiz148.test('2 sqrt 2 2 ^ ^', 'sqrt(2) ^ 2 ^ 2')
Quiz148.test('2 3 2 2 ^ ^ sqrt 3 + *', '2 * (sqrt(3 ^ 2 ^ 2) +
3)')
Quiz148.test('2 3 mean 2 2 ^ ^', 'mean(2, 3) ^ 2 ^ 2')
Quiz148.test('1 2 3 2 2 ^ ^ mean + 3 *', '(1 + mean(2, 3 ^ 2 ^
2)) * 3')
Quiz148.test('2 3 2 2 ^ ^ mean', 'mean(2, 3 ^ 2 ^ 2)')
Quiz148.test('1 2 2 mean 3 2 ^ sum3', 'sum3(1, mean(2, 2), 3 ^
2)')
Quiz148.test('1 2 2 mean 3 3 Array', '[1, mean(2, 2), 3]')
Quiz148.test('1 2 3 3 Array 4 <<', '[1, 2, 3] << 4')
Quiz148.test('1 2 3 3 Array 4 2 * <<', '[1, 2, 3] << (4 * 2)')
Quiz148.test('1 1 Array 1 2 3 3 Array 4 2 * << -', '[1] - ([1,
2, 3] << (4 * 2))')
Quiz148.test('3 5 * 5 8 * /', '3 * 5 / (5 * 8)')
Quiz148.test('3 5 + 5 8 + -', '3 + 5 - (5 + 8)')
else
puts Quiz148.run(ARGV)
end
end