- implement interpreter and demo - replace StandardError with appropriate subclasses - implement division - rewrite Polynomial.simplify()
399 lines
12 KiB
Ruby
399 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module RubyAlgebra
|
|
module Parser
|
|
# Токен выражения или команды
|
|
class Token
|
|
attr_reader :type
|
|
|
|
def initialize; end
|
|
end
|
|
|
|
# Знак сложения
|
|
class PlusToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :plus
|
|
end
|
|
end
|
|
|
|
# Знак вычитания
|
|
class MinusToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :minus
|
|
end
|
|
end
|
|
|
|
# Знак умножения
|
|
class MultiplyToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :mult
|
|
end
|
|
end
|
|
|
|
# Знак деления
|
|
class DivideToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :div
|
|
end
|
|
end
|
|
|
|
# Знак возведения в степень
|
|
class PowerToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :pow
|
|
end
|
|
end
|
|
|
|
# Скобка
|
|
class ParenthesisToken < Token
|
|
attr_reader :closing
|
|
|
|
def initialize(closing)
|
|
super()
|
|
@type = :paren
|
|
@closing = closing
|
|
end
|
|
end
|
|
|
|
# Знак присвоения :=
|
|
class AssignmentToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :assign
|
|
end
|
|
end
|
|
|
|
# Знак равенства =
|
|
class EqualsToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :equals
|
|
end
|
|
end
|
|
|
|
# Запятая
|
|
class CommaToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :comma
|
|
end
|
|
end
|
|
|
|
# Имя переменной или функции
|
|
class IdentifierToken < Token
|
|
attr_reader :symbol
|
|
|
|
def initialize(symbol)
|
|
super()
|
|
@type = :id
|
|
@symbol = symbol
|
|
end
|
|
|
|
def variable?
|
|
@symbol.match?(/^\p{Ll}/)
|
|
end
|
|
end
|
|
|
|
# Целое или дробное число
|
|
class NumberToken < Token
|
|
attr_reader :value
|
|
|
|
def initialize(value)
|
|
super()
|
|
@type = :num
|
|
@value = value
|
|
end
|
|
end
|
|
|
|
# Специальный токен, означающий конец ввода
|
|
class EndToken < Token
|
|
def initialize
|
|
super()
|
|
@type = :end
|
|
end
|
|
end
|
|
|
|
class TokenizerError < StandardError; end
|
|
class ParserError < StandardError; end
|
|
|
|
# Класс для разбора выражений на токены
|
|
class Tokenizer
|
|
attr_reader :lookahead
|
|
|
|
def initialize(expr)
|
|
@expr = expr
|
|
@i = 0
|
|
@lookahead = _next_token
|
|
end
|
|
|
|
def next_token
|
|
token = @lookahead
|
|
@lookahead = _next_token unless token.type == :end
|
|
token
|
|
end
|
|
|
|
def _next_token
|
|
loop do
|
|
return EndToken.new unless @i < @expr.length
|
|
c = @expr[@i]
|
|
if c.match?(/\p{Alpha}/)
|
|
j = @i + 1
|
|
j += 1 while j < @expr.length && @expr[j].match?(/\p{Alnum}/)
|
|
token = IdentifierToken.new(@expr[@i...j])
|
|
@i = j
|
|
return token
|
|
elsif c.match(%r{[+\-*/^():,=]})
|
|
token = nil
|
|
case c
|
|
when '+'
|
|
token = PlusToken.new
|
|
when '-'
|
|
token = MinusToken.new
|
|
when '*'
|
|
token = MultiplyToken.new
|
|
when '/'
|
|
token = DivideToken.new
|
|
when '^'
|
|
token = PowerToken.new
|
|
when '('
|
|
token = ParenthesisToken.new(false)
|
|
when ')'
|
|
token = ParenthesisToken.new(true)
|
|
when ','
|
|
token = CommaToken.new
|
|
when '='
|
|
token = EqualsToken.new
|
|
end
|
|
@i += 1
|
|
if c == ':' && @expr[@i] == '='
|
|
token = AssignmentToken.new
|
|
@i += 1
|
|
end
|
|
return token
|
|
elsif c.match?(/\p{Digit}|\./)
|
|
j = @i + 1
|
|
j += 1 while j < @expr.length && @expr[j].match?(/\p{Digit}|\./)
|
|
s = @expr[@i...j]
|
|
@i = j
|
|
fl = s.count '.'
|
|
raise TokenizerError, 'malformed number' if fl > 1
|
|
return fl.zero? ? NumberToken.new(s.to_i) : NumberToken.new(s.to_f)
|
|
elsif c == ' '
|
|
@i += 1
|
|
else
|
|
raise TokenizerError, "unrecognized character '#{c}'"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.parse_command(expr)
|
|
tokenizer = Tokenizer.new(expr)
|
|
|
|
n = tokenizer.lookahead
|
|
if n.type == :id && !n.variable?
|
|
left_hand_side = tokenizer.next_token.symbol
|
|
else
|
|
return DisplayCommand.new(parse_polynomial(tokenizer))
|
|
end
|
|
|
|
if tokenizer.lookahead.type == :assign
|
|
n = tokenizer.next_token
|
|
operand1 = tokenizer.lookahead
|
|
if operand1.type == :id && !operand1.variable?
|
|
if operand1.symbol == 'Diff'
|
|
tokenizer.next_token
|
|
n = tokenizer.next_token
|
|
unless n.type == :paren && n.closing == false
|
|
raise ParserError, "unexpected token #{n.type}, expected ("
|
|
end
|
|
n = tokenizer.lookahead
|
|
if n.type == :id && !n.variable?
|
|
tokenizer.next_token
|
|
target = n.symbol
|
|
else
|
|
target = parse_polynomial(tokenizer)
|
|
end
|
|
diff_variables = []
|
|
while tokenizer.lookahead.type == :comma
|
|
tokenizer.next_token
|
|
n = tokenizer.next_token
|
|
unless n.type == :id && n.variable?
|
|
raise ParserError, "unexpected token #{n.type}, expected variable"
|
|
end
|
|
diff_variables << n.symbol
|
|
end
|
|
n = tokenizer.next_token
|
|
unless n.type == :paren && n.closing == true
|
|
raise ParserError, "unexpected token #{n.type}, expected )"
|
|
end
|
|
unless tokenizer.next_token.type == :end
|
|
raise ParserError, "unexpected token at the end"
|
|
end
|
|
return AssignmentCommand.new(left_hand_side, :diff, target, diff_variables)
|
|
elsif operand1.symbol == 'Subs'
|
|
tokenizer.next_token
|
|
n = tokenizer.next_token
|
|
unless n.type == :paren && n.closing == false
|
|
raise ParserError, "unexpected token #{n.type}, expected ("
|
|
end
|
|
n = tokenizer.lookahead
|
|
if n.type == :id && !n.variable?
|
|
tokenizer.next_token
|
|
target = n.symbol
|
|
else
|
|
target = parse_polynomial(tokenizer)
|
|
end
|
|
substitutions = {}
|
|
while tokenizer.lookahead.type == :comma
|
|
tokenizer.next_token
|
|
n = tokenizer.next_token
|
|
unless n.type == :id && n.variable?
|
|
raise ParserError, "unexpected token #{n.type}, expected variable"
|
|
end
|
|
variable = n.symbol
|
|
unless tokenizer.next_token.type == :equals
|
|
raise ParserError, "unexpected token #{n.type}, expected ="
|
|
end
|
|
n = tokenizer.next_token
|
|
unless n.type == :num
|
|
raise ParserError, "unexpected token #{n.type}, expected number"
|
|
end
|
|
substitutions[variable] = n.value
|
|
end
|
|
n = tokenizer.next_token
|
|
unless n.type == :paren && n.closing == true
|
|
raise ParserError, "unexpected token #{n.type}, expected )"
|
|
end
|
|
unless tokenizer.next_token.type == :end
|
|
raise ParserError, "unexpected token at the end"
|
|
end
|
|
return AssignmentCommand.new(left_hand_side, :subs, target, substitutions)
|
|
else
|
|
tokenizer.next_token
|
|
operator = tokenizer.next_token
|
|
raise ParserError, "unexpected token #{operator.type}, expected +, -, * or /" unless [:plus, :minus, :mult, :div].include?(operator.type)
|
|
|
|
operand2 = tokenizer.next_token
|
|
unless ([:plus, :minus, :mult].include?(operator.type) && operand2.type == :id && !operand2.variable?) ||
|
|
([:mult, :div].include?(operator.type) && operand2.type == :num)
|
|
raise ParserError, 'unsupported operation or invalid syntax'
|
|
end
|
|
if operand2.type == :num
|
|
if operator.type == :mult
|
|
return AssignmentCommand.new(left_hand_side, :scale, operand1.symbol, operand2.value)
|
|
else
|
|
return AssignmentCommand.new(left_hand_side, :scale, operand1.symbol, 1.0 / operand2.value)
|
|
end
|
|
elsif operand2.type == :id
|
|
case operator.type
|
|
when :plus
|
|
return AssignmentCommand.new(left_hand_side, :add, operand1.symbol, operand2.symbol)
|
|
when :minus
|
|
return AssignmentCommand.new(left_hand_side, :sub, operand1.symbol, operand2.symbol)
|
|
when :mult
|
|
return AssignmentCommand.new(left_hand_side, :mult, operand1.symbol, operand2.symbol)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
return AssignmentCommand.new(left_hand_side, :assign, parse_polynomial(tokenizer), nil)
|
|
end
|
|
elsif tokenizer.lookahead.type == :comma
|
|
n = tokenizer.next_token
|
|
second_lhs = tokenizer.next_token
|
|
unless second_lhs.type == :id && !second_lhs.variable?
|
|
raise ParserError, "unexpected token #{second_lhs.type}, expected polynomial variable"
|
|
end
|
|
unless tokenizer.next_token.type == :assign
|
|
raise ParserError, "unexpected token, expected :="
|
|
end
|
|
operand1 = tokenizer.next_token
|
|
unless operand1.type == :id && !operand1.variable?
|
|
raise ParserError, "unexpected token #{operand1.type}, expected polynomial variable"
|
|
end
|
|
unless tokenizer.next_token.type == :div
|
|
raise ParserError, "unexpected token, expected /"
|
|
end
|
|
operand2 = tokenizer.next_token
|
|
unless operand2.type == :id && !operand2.variable?
|
|
raise ParserError, "unexpected token #{operand2.type}, expected polynomial variable"
|
|
end
|
|
return AssignmentCommand.new([left_hand_side, second_lhs.symbol], :div, operand1.symbol, operand2.symbol)
|
|
elsif tokenizer.lookahead.type == :end
|
|
return DisplayCommand.new(left_hand_side)
|
|
else
|
|
raise ParserError, "unexpected token #{n.type}, expected ':=' or ','"
|
|
end
|
|
end
|
|
|
|
def self.parse_polynomial(tokenizer)
|
|
first_term_negative = false
|
|
if tokenizer.lookahead.type == :plus || tokenizer.lookahead.type == :minus
|
|
first_term_negative = tokenizer.lookahead.type == :minus
|
|
tokenizer.next_token
|
|
end
|
|
first_term = parse_term(tokenizer)
|
|
first_term *= -1 if first_term_negative
|
|
terms = [first_term]
|
|
while [:plus, :minus].include?(tokenizer.lookahead.type)
|
|
sign = tokenizer.next_token.type == :plus ? +1 : -1
|
|
terms << parse_term(tokenizer) * sign
|
|
end
|
|
Polynomial.new(terms)
|
|
end
|
|
|
|
def self.parse_term(tokenizer)
|
|
n = tokenizer.lookahead
|
|
unless n.type == :id && n.variable? || n.type == :num
|
|
raise ParserError, "unexpected token #{n.type}, expected number or variable"
|
|
end
|
|
if n.type == :num
|
|
tokenizer.next_token
|
|
coeff = n.value
|
|
variables = {}
|
|
else
|
|
coeff = 1
|
|
v = parse_variable_factor(tokenizer)
|
|
variables = {v[0] => v[1]}
|
|
end
|
|
loop do
|
|
n = tokenizer.lookahead
|
|
unless n.type == :mult || n.type == :id && n.variable?
|
|
break
|
|
end
|
|
v = parse_variable_factor(tokenizer)
|
|
variables[v[0]] = v[1]
|
|
end
|
|
Term.new(coeff, variables)
|
|
end
|
|
|
|
def self.parse_variable_factor(tokenizer)
|
|
n = tokenizer.lookahead
|
|
tokenizer.next_token
|
|
if n.type == :mult
|
|
n = tokenizer.next_token
|
|
end
|
|
unless n.type == :id && n.variable?
|
|
raise ParserError, "unexpected token #{n.type}, expected variable"
|
|
end
|
|
variable = n.symbol
|
|
if tokenizer.lookahead.type == :pow
|
|
tokenizer.next_token
|
|
unless tokenizer.lookahead.type == :num
|
|
raise ParserError, "unexpected token #{n.type}, expected number"
|
|
end
|
|
power = tokenizer.next_token.value
|
|
else
|
|
power = 1
|
|
end
|
|
[variable, power]
|
|
end
|
|
end
|
|
end
|