# 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 || n.type == :plus || n.type == :minus raise ParserError, "unexpected token #{n.type}, expected -, + or number" end if n.type == :plus || n.type == :minus negative = true if n.type == :minus n = tokenizer.next_token end unless n.type == :num raise ParserError, "unexpected token #{n.type}, expected number" end substitutions[variable] = if negative then -n.value else n.value end 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