require ’Stack’ class Compf < Stack def compile(str)
"(#{str})".each_byte { |c| processSymbol(c.chr) }
end
private def symType(c) case c
when ’(’
SYM_LEFT when ’)’
SYM_RIGHT when ’+’, ’-’, ’*’, ’/’
SYM_OPER
else
symOther(c)
end
end
def processSymbol(c)
case symType(c)
when SYM_LEFT
push(c)
when SYM_RIGHT
processSuspendedSymbol(c)
pop
when SYM_OPER
processSuspendedSymbol(c)
push(c)
when SYM_OTHER
nextOther(c)
end
end
def processSuspendedSymbol(c)
while precedes(top, c)
nextOper(pop)
end
end
Работа начинается с вызова метода compile, в котором все символы строки str последовательно передаются методу processSymbol.
def priority(c)
(c == ’+’ or c == ’-’) ? 1 : 2 end
def precedes(a, b)
return false if symType(a) == SYM_LEFT
return true
if symType(b) == SYM_RIGHT
priority(a) >= priority(b) end
protected
SYM_LEFT = 0; SYM_RIGHT = 1; SYM_OPER = 2; SYM_OTHER = 3
def symOther(c)
# Сравнение символа с образцом (регулярным выражением).
raise "Недопустимый символ #{c}" if c !~ /[a-z]/
SYM_OTHER
end
def nextOper(c)
print "#{c} "
end
def nextOther(c)
nextOper(c)
end
end
Квалификатор доступа protected и метод nextOther нужны для создания на базе класса Compf нового класса Calc, реализующего калькулятор формул[13].
Класс Calc (калькулятор числовых формул) выведен из класса Compf, переопределяет некоторые методы последнего, и имеет дополнительный стек для размещения в нём чисел. Калькулятор работает только с цифрами (числами от 0 до 9).
Несколько комментариев к методу nextOper(c) класса Calc. Множественное присваивание в первой строке метода корректно, т.к. в языке Ruby при выполнении множественного (параллельного) присваивания сначала последовательно вычисляются все выражения в правой части оператора присваивания.
require ’Compf’
class Calc < Compf def initialize
# Вызов метода initialize базового класса Compf. super
# Создание стека результатов операций.
@s = Stack.new
end
def compile(str) super
return @s.top end
protected
def symOther(c)
raise "Недопустимый символ #{c}" if c !~ /[0-9]/ SYM_OTHER end
def nextOper(c)
second, first = @s.pop, @s.pop @s.push(first.method(c).call(second)) end
def nextOther(c)
@s.push(c.to_i)
end
end
Конструкция first.method(c).call(second) во второй строке метода может быть объяснена таким примером: выражение 3.metod(’-’).call(2), эквивалентно выражению 3. – (2) или просто 3-2.
Задача 1. Добавьте операции sin, cos и унарный минус.
Задача 2. Добавьте правоассоциативную операцию ~ возведения в степень.
Задача 3. Добавьте квадратные и фигурные скобки.
Задача 4. Измените программу так, чтобы допускались в качестве имен переменных произвольные идентификаторы языка Ruby.
Задача 5. Добавьте левоассоциативную операцию % с приоритетом, равным приоритету операции /.
Задача 6. Добавьте возможность записи формулы с пробелами и комментариями двух типов (/* */ и //).
Задача 7. Измените программу так, чтобы ввод, содержащий в качестве аргументов только восьмеричные числа (начинающиеся с нуля, например 056), компилировался в программу, содержащую десятичные числа.
Задача 8. Измените программу так, чтобы ввод, содержащий в качестве аргументов только шестнадцатеричные числа (начинающиеся с 0x, например 0x56), компилировался в программу, содержащую восьмеричные числа.
Задача 9. Измените программу так, чтобы ввод, содержащий в качестве аргументов только римские числа, не превосходящие 5000, компилировался в программу, содержащую десятичные числа.
Задача 10. Измените программу так, чтобы для коммутативной операции аргументы выдавались в алфавитном порядке.
Задача 11. Добавьте фигурные скобки, означающие возведение в квадрат. Используйте операцию DUP стекового калькулятора.
Задача 12. Считая, что a = 0, оптимизируйте формулу (уберите лишние сложения).
Задача 13. Считая, что b = 1, оптимизируйте формулу (уберите лишние умножения).
Задача 14. Добавьте возможность ввода формулы на нескольких строках.
[13]
Хотя в языке Ruby в данном случае можно убрать "protected", тем самым размещая все нижеописываемые константы и методы в зоне действия квалификатора private, в языках Java и C++ здесь нужен именно квалификатор protected.