# frozen_string_literal: true module RubyAlgebra module Parser class Token attr_reader :type end class NumberToken < Token attr_reader :value def initialize(value) @type = :num @value = value end end class IdentifierToken < Token attr_reader :name def initialize(name) @type = :id @name = name end end class BinaryOperatorToken < Token attr_reader :op def initialize(op) @type = :op @op = op end end class UnaryOperatorToken < Token attr_reader :op def initialize(op) @type = :unary_op @op = op end end class ParenthesisToken < Token attr_reader :closing def initialize(closing) @type = :paren @closing = closing end end class EndToken < Token def initialize() @type = :end end end def self.tokenize(e) i = 0 add_implicit_mul = false while i < e.length c = e[i] if c.match?(/\p{Alpha}/) yield BinaryOperatorToken.new(:mult) if add_implicit_mul j = i + 1 j += 1 while j < e.length && e[j].match?(/\p{Alnum}/) add_implicit_mul = true yield IdentifierToken.new(e[i...j]) i = j elsif c.match?(/[+\-*\/^()]/) yield BinaryOperatorToken.new(:mult) if c == "(" && add_implicit_mul i += 1 case c when "+" if add_implicit_mul yield BinaryOperatorToken.new(:add) else yield UnaryOperatorToken.new(:positive) end when "-" if add_implicit_mul yield BinaryOperatorToken.new(:sub) else yield UnaryOperatorToken.new(:negative) end when "*" yield BinaryOperatorToken.new(:mult) when "/" yield BinaryOperatorToken.new(:div) when "^" yield BinaryOperatorToken.new(:pow) when "(" yield ParenthesisToken.new(false) when ")" yield ParenthesisToken.new(true) end add_implicit_mul = c == ")" elsif c.match?(/\p{Digit}|\./) yield BinaryOperatorToken.new(:mult) if add_implicit_mul j = i + 1 j += 1 while j < e.length && e[j].match?(/\p{Digit}|\./) s = e[i...j] fl = s.count "." if fl > 1 raise Exception.new end add_implicit_mul = true if fl == 0 yield NumberToken.new(s.to_i) else yield NumberToken.new(s.to_f) end i = j elsif c == " " i += 1 else i += 1 end end yield EndToken.new end def self.parse(expr) operators = [] out_stack = [] tokenize(expr) do |token| if token.type == :num out_stack.push(Constant.new(token.value)) elsif token.type == :end _parse_make_op(operators, out_stack) until operators.empty? elsif token.type == :op || token.type == :unary_op p = _parse_op_prio token while operators.length != 0 && (operators.last.type == :op || operators.last.type == :unary_op) && (_parse_op_prio(operators.last) > p || (_parse_op_prio(operators.last) == p && token.op != :pow && token.op != :positive && token.op != :negative)) _parse_make_op(operators, out_stack) end operators.push(token) elsif token.type == :paren if token.closing until operators.empty? || (operators.last.type == :paren && !operators.last.closing) _parse_make_op(operators, out_stack) end if operators.empty? || operators.last.type != :paren || operators.last.closing raise Exception.new else operators.pop end else operators.push token end elsif token.type == :id out_stack.push(Variable.new(token.name)) end end out_stack[0] end def self._parse_make_op(operators, out_stack) oper = operators.pop unless (oper.type == :op && out_stack.length >= 2) || (oper.type == :unary_op && out_stack.length >= 1) raise Exception.new end b = out_stack.pop if oper.type == :op a = out_stack.pop case oper.op when :add out_stack.push Addition.new(a, b) when :sub out_stack.push Subtraction.new(a, b) when :mult out_stack.push Multiplication.new(a, b) when :div out_stack.push Division.new(a, b) when :pow out_stack.push Power.new(a, b) when :positive out_stack.push a when :negative if a.type == :constant out_stack.push Constant.new(-a.value) else out_stack.push Multiplication.new(Constant.new(-1), a) end end end def self._parse_op_prio(op_token) case op_token.op when :add, :sub 1 when :mult, :div 2 when :pow, :positive, :negative 3 end end end end