# 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 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 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 puts '_next_token' loop do return EndToken.new unless @i < @expr.length puts "i = #{@i}" c = @expr[@i] puts "c = #{c}" if c.match?(/\p{Alpha}/) puts 'letter' 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{[+\-*/^():]}) puts 'operator' 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) end @i += 1 if c == ':' && @expr[@i] == '=' token = AssignmentToken.new @i += 1 end return token elsif c.match?(/\p{Digit}|\./) puts 'digit' j = @i + 1 j += 1 while j < @expr.length && @expr[j].match?(/\p{Digit}|\./) s = @expr[@i...j] @i = j puts "ssss: #{s}" fl = s.count '.' raise StandardError, 'malformed number' if fl > 1 return fl.zero? ? NumberToken.new(s.to_i) : NumberToken.new(s.to_f) elsif c == ' ' puts 'whitespace' @i += 1 else puts 'unknown' @i += 1 end end end end def self.parse_command(expr) tokenizer = Tokenizer.new(expr) n = tokenizer.lookahead if n.type == :id && !n.variable? parse_assignment_command(tokenizer) else parse_display_command(tokenizer) end end def self.parse_assignment_command(tokenizer) left_hand_side = tokenizer.next_token n = tokenizer.next_token if n.type == :assign operand1 = tokenizer.lookahead unless operand1.type == :id && !operand1.variable? return AssignmentCommand.new(left_hand_side.symbol, :assign, parse_polynomial(tokenizer), nil) end tokenizer.next_token operator = tokenizer.next_token raise StandardError, "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 StandardError, 'unsupported operation or invalid syntax' end if operand2.type == :num if operator.type == :mult return AssignmentCommand.new(left_hand_side.symbol, :scale, operand1.symbol, operand2.value) else return AssignmentCommand.new(left_hand_side.symbol, :scale, operand1.symbol, 1.0 / operand2.value) end elsif operand2.type == :id case operator.type when :plus return AssignmentCommand.new(left_hand_side.symbol, :add, operand1.symbol, operand2.symbol) when :minus return AssignmentCommand.new(left_hand_side.symbol, :sub, operand1.symbol, operand2.symbol) when :mult return AssignmentCommand.new(left_hand_side.symbol, :mult, operand1.symbol, operand2.symbol) end end elsif n.type == :comma second_lhs = tokenizer.next_token unless second_lhs.type == :id && !second_lhs.variable? raise StandardError, "unexpected token #{second_lhs.type}, expected polynomial variable" end unless tokenizer.next_token.type == :assign raise StandardError, "unexpected token, expected :=" end operand1 = tokenizer.next_token unless operand1.type == :id && !operand1.variable? raise StandardError, "unexpected token #{operand1.type}, expected polynomial variable" end unless tokenizer.next_token.type == :div raise StandardError, "unexpected token, expected /" end operand2 = tokenizer.next_token unless operand2.type == :id && !operand2.variable? raise StandardError, "unexpected token #{operand2.type}, expected polynomial variable" end return AssignmentCommand.new([left_hand_side.symbol, second_lhs.symbol], :div, operand1.symbol) else raise StandardError, "unexpected token #{n.type}, expected ':=' or ','" end end def self.parse_display_command(tokenizer) DisplayCommand.new(parse_polynomial(tokenizer)) end def self.parse_polynomial(tokenizer) puts "parse_polynomial " terms = [parse_term(tokenizer)] while [:plus, :minus].include?(tokenizer.lookahead.type) sign = tokenizer.next_token.type == :plus ? +1 : -1 terms << parse_term(tokenizer) * sign end puts 'parse_polynomial end' Polynomial.new(terms) end def self.parse_term(tokenizer) puts "parse_term " n = tokenizer.lookahead unless n.type == :id && n.variable? || n.type == :num raise StandardError, "unexpected token #{n.type}, expected number or variable" end if n.type == :num puts "eat coeff" 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 puts "" unless n.type == :mult || n.type == :id && n.variable? break end v = parse_variable_factor(tokenizer) variables[v[0]] = v[1] end puts 'parse_term end' pp [coeff, variables] Term.new(coeff, variables) end def self.parse_variable_factor(tokenizer) puts "parse_term " n = tokenizer.lookahead tokenizer.next_token puts 'eat variable' #n = tokenizer.next_token if n.type == :mult if n.type == :mult n = tokenizer.next_token puts 'eat *' end unless n.type == :id && n.variable? raise StandardError, "unexpected token #{n.type}, expected variable" end variable = n.symbol if tokenizer.lookahead.type == :pow tokenizer.next_token puts 'eat ^' unless tokenizer.lookahead.type == :num raise StandardError, "unexpected token #{n.type}, expected number" end power = tokenizer.next_token.value puts 'eat exponent' else power = 1 end puts 'parse_variable_factor end' pp [variable, power] [variable, power] end end end