From 3f0f8ab5e71f2b625b863581bb22c1dca08ba11a Mon Sep 17 00:00:00 2001 From: Slavasil Date: Mon, 16 Mar 2026 13:07:49 +0300 Subject: [PATCH] implement expression parser --- lib/ruby_algebra.rb | 1 + lib/ruby_algebra/parser.rb | 177 +++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 lib/ruby_algebra/parser.rb diff --git a/lib/ruby_algebra.rb b/lib/ruby_algebra.rb index cd719d2..b9b54c4 100644 --- a/lib/ruby_algebra.rb +++ b/lib/ruby_algebra.rb @@ -2,6 +2,7 @@ require_relative "ruby_algebra/version" require_relative "ruby_algebra/expression" +require_relative "ruby_algebra/parser" module RubyAlgebra end diff --git a/lib/ruby_algebra/parser.rb b/lib/ruby_algebra/parser.rb new file mode 100644 index 0000000..6d58044 --- /dev/null +++ b/lib/ruby_algebra/parser.rb @@ -0,0 +1,177 @@ +# 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 OperatorToken < Token + attr_reader :op + + def initialize(op) + @type = :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 tokenize(e) + i = 0 + add_implicit_mul = false + while i < e.length + c = e[i] + if c.match?(/\p{Alpha}/) + yield OperatorToken.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 OperatorToken.new(:mult) if c == "(" && add_implicit_mul + i += 1 + add_implicit_mul = c == ")" + case c + when "+" + yield OperatorToken.new(:add) + when "-" + yield OperatorToken.new(:sub) + when "*" + yield OperatorToken.new(:mult) + when "/" + yield OperatorToken.new(:div) + when "^" + yield OperatorToken.new(:pow) + when "(" + yield ParenthesisToken.new(false) + when ")" + yield ParenthesisToken.new(true) + end + elsif c.match?(/\p{Digit}|\./) + yield OperatorToken.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 + puts "Unrecognized character at pos #{i}" + i += 1 + end + end + yield EndToken.new + end + + def parse(expr) + operators = [] + out_stack = [] + prev = nil + 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 + p = _parse_op_prio token + while operators.length != 0 && operators.last.type == :op && (_parse_op_prio(operators.last) > p || (_parse_op_prio(operators.last) == p && token.op != :pow)) + _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 + puts "Missing ( in expression" + else + operators.pop + end + else + operators.push token + end + elsif token.type == :id + out_stack.push(Variable.new(token.name)) + end + prev = token + end + out_stack[0] + end + + def _parse_make_op(operators, out_stack) + oper = operators.pop + raise Exception.new unless oper.type == :op + raise Exception.new unless out_stack.length >= 2 + b = out_stack.pop + 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) + end + end + + def _parse_op_prio(op_token) + case op_token.op + when :add, :sub + 1 + when :mult, :div + 2 + when :pow + 3 + end + end + end +end