implement expression parser
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
require_relative "ruby_algebra/version"
|
||||
require_relative "ruby_algebra/expression"
|
||||
require_relative "ruby_algebra/parser"
|
||||
|
||||
module RubyAlgebra
|
||||
end
|
||||
|
||||
177
lib/ruby_algebra/parser.rb
Normal file
177
lib/ruby_algebra/parser.rb
Normal 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
|
||||
Reference in New Issue
Block a user