add support for unary plus/minus

This commit is contained in:
2026-03-16 14:35:49 +03:00
parent 3f0f8ab5e7
commit 8a59e0a57f
2 changed files with 44 additions and 37 deletions

View File

@@ -151,23 +151,6 @@ module RubyAlgebra
end
end
class Subtraction < Addition
attr_reader :lhs, :rhs
def to_s
need_parentheses_left = lhs.op_priority < op_priority || (lhs.op_priority == op_priority && op_assoc_type == :right)
need_parentheses_right = rhs.op_priority < op_priority || (rhs.op_priority == op_priority && op_assoc_type == :left)
result = need_parentheses_left ? "(#{lhs}) - " : "#{lhs} - "
result + (need_parentheses_right ? "(#{rhs})" : rhs.to_s)
end
def type
:sub
end
end
class Constant < Expression
attr_reader :value

View File

@@ -24,7 +24,7 @@ module RubyAlgebra
end
end
class OperatorToken < Token
class BinaryOperatorToken < Token
attr_reader :op
def initialize(op)
@@ -33,6 +33,15 @@ module RubyAlgebra
end
end
class UnaryOperatorToken < Token
attr_reader :op
def initialize(op)
@type = :unary_op
@op = op
end
end
class ParenthesisToken < Token
attr_reader :closing
@@ -54,34 +63,42 @@ module RubyAlgebra
while i < e.length
c = e[i]
if c.match?(/\p{Alpha}/)
yield OperatorToken.new(:mult) if add_implicit_mul
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 OperatorToken.new(:mult) if c == "(" && add_implicit_mul
yield BinaryOperatorToken.new(:mult) if c == "(" && add_implicit_mul
i += 1
add_implicit_mul = c == ")"
case c
when "+"
yield OperatorToken.new(:add)
if add_implicit_mul
yield BinaryOperatorToken.new(:add)
else
yield UnaryOperatorToken.new(:positive)
end
when "-"
yield OperatorToken.new(:sub)
if add_implicit_mul
yield BinaryOperatorToken.new(:sub)
else
yield UnaryOperatorToken.new(:negative)
end
when "*"
yield OperatorToken.new(:mult)
yield BinaryOperatorToken.new(:mult)
when "/"
yield OperatorToken.new(:div)
yield BinaryOperatorToken.new(:div)
when "^"
yield OperatorToken.new(:pow)
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 OperatorToken.new(:mult) if add_implicit_mul
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]
@@ -109,16 +126,15 @@ module RubyAlgebra
def parse(expr)
operators = []
out_stack = []
prev = nil
tokenize(expr) do |token|
pp 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
elsif token.type == :op || token.type == :unary_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))
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)
@@ -128,7 +144,7 @@ module RubyAlgebra
_parse_make_op(operators, out_stack)
end
if operators.empty? || operators.last.type != :paren || operators.last.closing
puts "Missing ( in expression"
raise Exception.new
else
operators.pop
end
@@ -138,17 +154,21 @@ module RubyAlgebra
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
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
puts "make unary op #{oper.op} from (#{a})" if oper.type == :unary_op
puts "make binary op #{oper.op} from (#{a}), (#{b})" if oper.type == :op
case oper.op
when :add
out_stack.push Addition.new(a, b)
@@ -160,6 +180,10 @@ module RubyAlgebra
out_stack.push Division.new(a, b)
when :pow
out_stack.push Power.new(a, b)
when :positive
out_stack.push a
when :negative
out_stack.push Multiplication.new(Constant.new(-1), a)
end
end
@@ -169,7 +193,7 @@ module RubyAlgebra
1
when :mult, :div
2
when :pow
when :pow, :positive, :negative
3
end
end