implement expression parser

This commit is contained in:
2026-03-16 13:07:49 +03:00
parent 43a1dc1c12
commit 3f0f8ab5e7
2 changed files with 178 additions and 0 deletions

177
lib/ruby_algebra/parser.rb Normal file
View File

@@ -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