diff --git a/bin/demo b/bin/demo index 4f0f08a..c08072a 100755 --- a/bin/demo +++ b/bin/demo @@ -4,21 +4,27 @@ require 'bundler/setup' require 'ruby_algebra' -puts 'RubyMaple © RubyMaple Creators, 2026. No rights reserved.' +print "RubyMaple © RubyMaple Creators, 2026. No rights reserved.\nPress Ctrl+D to quit.\n" +interp = RubyAlgebra::Interpreter.new loop do - puts 'Введите формулу (пустая строка для выхода): ' - inp = $stdin.gets.chomp - exit if inp.empty? - begin - formula = RubyAlgebra::Parser.parse(inp) - rescue StandardError - puts 'Ошибка синтаксиса' - end - puts "Формула: #{formula}" - puts "Производная по x: #{formula.diff('x')}" - puts "Производная по y: #{formula.diff('y')}" + print '> ' + inp = $stdin.gets + break if inp.nil? - value = formula.evaluate - puts "Числовое значение: #{value}" unless value.nil? + inp = inp.chomp + next if inp.empty? + + begin + command = RubyAlgebra::Parser.parse_command(inp) + rescue StandardError => e + puts "Syntax error: #{e.message}" + next + end + begin + puts "< #{interp.execute(command)}" + rescue StandardError => e + puts "Calculation error: #{e.message}" + end end +puts diff --git a/lib/ruby_algebra.rb b/lib/ruby_algebra.rb index 2664ef9..d36913d 100644 --- a/lib/ruby_algebra.rb +++ b/lib/ruby_algebra.rb @@ -4,3 +4,4 @@ require_relative 'ruby_algebra/version' require_relative 'ruby_algebra/command' require_relative 'ruby_algebra/polynomial' require_relative 'ruby_algebra/parser' +require_relative 'ruby_algebra/interpreter' diff --git a/lib/ruby_algebra/command.rb b/lib/ruby_algebra/command.rb index 3a7c643..0a551d4 100644 --- a/lib/ruby_algebra/command.rb +++ b/lib/ruby_algebra/command.rb @@ -3,6 +3,11 @@ module RubyAlgebra # Команда интерпретатора class Command + attr_reader :type + + def type + raise NotImplementedError + end end # Команда присвоения @@ -39,20 +44,29 @@ module RubyAlgebra end result end + + def type + :assignment + end end # Команда вывода на экран class DisplayCommand < Command - attr_reader :polynomial + attr_reader :item - def initialize(polynomial) - @polynomial = polynomial + def initialize(item) + @type = :display + @item = item end def to_s result = "Display command: " - result += @polynomial.to_s + result += @item.to_s result end + + def type + :display + end end end diff --git a/lib/ruby_algebra/interpreter.rb b/lib/ruby_algebra/interpreter.rb new file mode 100644 index 0000000..f8e8098 --- /dev/null +++ b/lib/ruby_algebra/interpreter.rb @@ -0,0 +1,70 @@ +module RubyAlgebra + class Interpreter + def initialize + @locals = {} + end + + def execute(command) + case command.type + when :assignment + unless command.operand1.is_a?(Polynomial) + operand1 = @locals[command.operand1] + return "Undeclared polynomial: #{command.operand1}" if operand1.nil? + else + operand1 = command.operand1 + end + + unless [:assign, :scale, :diff, :subs].include?(command.operation) + operand2 = @locals[command.operand2] + return "Undeclared polynomial: #{command.operand2}" if operand2.nil? + else + operand2 = command.operand2 + end + + case command.operation + when :assign + @locals[command.lhs] = operand1 + return "#{command.lhs} = #{operand1}" + when :add + result = operand1 + operand2 + @locals[command.lhs] = result + return "#{command.lhs} = #{result}" + when :sub + result = operand1 - operand2 + @locals[command.lhs] = result + return "#{command.lhs} = #{result}" + when :mult, :scale + result = operand1 * operand2 + @locals[command.lhs] = result + return "#{command.lhs} = #{result}" + when :div + result = operand1 / operand2 + @locals[command.lhs[0]] = result[0] + @locals[command.lhs[1]] = result[1] + return "Quotient: #{command.lhs[0]} = #{result[0]}, remainder: #{command.lhs[1]} = #{result[1]}" + when :diff + result = operand1 + operand2.each do |variable| + result = result.diff(variable) + end + @locals[command.lhs] = result + return "#{command.lhs} = #{result}" + when :subs + result = operand1.evaluate(operand2) + @locals[command.lhs] = result + return "#{command.lhs} = #{result}" + end + when :display + unless command.item.is_a? Polynomial + unless @locals[command.item].nil? + return @locals[command.item].to_s + else + return "Undeclared polynomial: #{command.item}" + end + else + return command.item.to_s + end + end + end + end +end diff --git a/lib/ruby_algebra/parser.rb b/lib/ruby_algebra/parser.rb index 5ff60d5..0c8f328 100644 --- a/lib/ruby_algebra/parser.rb +++ b/lib/ruby_algebra/parser.rb @@ -118,6 +118,9 @@ module RubyAlgebra end end + class TokenizerError < StandardError; end + class ParserError < StandardError; end + # Класс для разбора выражений на токены class Tokenizer attr_reader :lookahead @@ -135,22 +138,16 @@ module RubyAlgebra end def _next_token - puts '_next_token' - loop do return EndToken.new unless @i < @expr.length - puts "i = #{@i}" c = @expr[@i] - puts "c = #{c}" if c.match?(/\p{Alpha}/) - puts 'letter' j = @i + 1 j += 1 while j < @expr.length && @expr[j].match?(/\p{Alnum}/) token = IdentifierToken.new(@expr[@i...j]) @i = j return token elsif c.match(%r{[+\-*/^():,=]}) - puts 'operator' token = nil case c when '+' @@ -179,21 +176,17 @@ module RubyAlgebra end return token elsif c.match?(/\p{Digit}|\./) - puts 'digit' j = @i + 1 j += 1 while j < @expr.length && @expr[j].match?(/\p{Digit}|\./) s = @expr[@i...j] @i = j - puts "ssss: #{s}" fl = s.count '.' - raise StandardError, 'malformed number' if fl > 1 + raise TokenizerError, 'malformed number' if fl > 1 return fl.zero? ? NumberToken.new(s.to_i) : NumberToken.new(s.to_f) elsif c == ' ' - puts 'whitespace' @i += 1 else - puts 'unknown' - @i += 1 + raise TokenizerError, "unrecognized character '#{c}'" end end end @@ -217,7 +210,7 @@ module RubyAlgebra tokenizer.next_token n = tokenizer.next_token unless n.type == :paren && n.closing == false - raise StandardError, "unexpected token #{n.type}, expected (" + raise ParserError, "unexpected token #{n.type}, expected (" end n = tokenizer.lookahead if n.type == :id && !n.variable? @@ -231,23 +224,23 @@ module RubyAlgebra tokenizer.next_token n = tokenizer.next_token unless n.type == :id && n.variable? - raise StandardError, "unexpected token #{n.type}, expected variable" + raise ParserError, "unexpected token #{n.type}, expected variable" end diff_variables << n.symbol end n = tokenizer.next_token unless n.type == :paren && n.closing == true - raise StandardError, "unexpected token #{n.type}, expected )" + raise ParserError, "unexpected token #{n.type}, expected )" end unless tokenizer.next_token.type == :end - raise StandardError, "unexpected token at the end" + raise ParserError, "unexpected token at the end" end return AssignmentCommand.new(left_hand_side, :diff, target, diff_variables) elsif operand1.symbol == 'Subs' tokenizer.next_token n = tokenizer.next_token unless n.type == :paren && n.closing == false - raise StandardError, "unexpected token #{n.type}, expected (" + raise ParserError, "unexpected token #{n.type}, expected (" end n = tokenizer.lookahead if n.type == :id && !n.variable? @@ -261,35 +254,35 @@ module RubyAlgebra tokenizer.next_token n = tokenizer.next_token unless n.type == :id && n.variable? - raise StandardError, "unexpected token #{n.type}, expected variable" + raise ParserError, "unexpected token #{n.type}, expected variable" end variable = n.symbol unless tokenizer.next_token.type == :equals - raise StandardError, "unexpected token #{n.type}, expected =" + raise ParserError, "unexpected token #{n.type}, expected =" end n = tokenizer.next_token unless n.type == :num - raise StandardError, "unexpected token #{n.type}, expected number" + raise ParserError, "unexpected token #{n.type}, expected number" end substitutions[variable] = n.value end n = tokenizer.next_token unless n.type == :paren && n.closing == true - raise StandardError, "unexpected token #{n.type}, expected )" + raise ParserError, "unexpected token #{n.type}, expected )" end unless tokenizer.next_token.type == :end - raise StandardError, "unexpected token at the end" + raise ParserError, "unexpected token at the end" end return AssignmentCommand.new(left_hand_side, :subs, target, substitutions) else tokenizer.next_token operator = tokenizer.next_token - raise StandardError, "unexpected token #{operator.type}, expected +, -, * or /" unless [:plus, :minus, :mult, :div].include?(operator.type) + raise ParserError, "unexpected token #{operator.type}, expected +, -, * or /" unless [:plus, :minus, :mult, :div].include?(operator.type) operand2 = tokenizer.next_token unless ([:plus, :minus, :mult].include?(operator.type) && operand2.type == :id && !operand2.variable?) || ([:mult, :div].include?(operator.type) && operand2.type == :num) - raise StandardError, 'unsupported operation or invalid syntax' + raise ParserError, 'unsupported operation or invalid syntax' end if operand2.type == :num if operator.type == :mult @@ -315,49 +308,52 @@ module RubyAlgebra n = tokenizer.next_token second_lhs = tokenizer.next_token unless second_lhs.type == :id && !second_lhs.variable? - raise StandardError, "unexpected token #{second_lhs.type}, expected polynomial variable" + raise ParserError, "unexpected token #{second_lhs.type}, expected polynomial variable" end unless tokenizer.next_token.type == :assign - raise StandardError, "unexpected token, expected :=" + raise ParserError, "unexpected token, expected :=" end operand1 = tokenizer.next_token unless operand1.type == :id && !operand1.variable? - raise StandardError, "unexpected token #{operand1.type}, expected polynomial variable" + raise ParserError, "unexpected token #{operand1.type}, expected polynomial variable" end unless tokenizer.next_token.type == :div - raise StandardError, "unexpected token, expected /" + raise ParserError, "unexpected token, expected /" end operand2 = tokenizer.next_token unless operand2.type == :id && !operand2.variable? - raise StandardError, "unexpected token #{operand2.type}, expected polynomial variable" + raise ParserError, "unexpected token #{operand2.type}, expected polynomial variable" end - return AssignmentCommand.new([left_hand_side, second_lhs.symbol], :div, operand1.symbol) + return AssignmentCommand.new([left_hand_side, second_lhs.symbol], :div, operand1.symbol, operand2.symbol) elsif tokenizer.lookahead.type == :end return DisplayCommand.new(left_hand_side) else - raise StandardError, "unexpected token #{n.type}, expected ':=' or ','" + raise ParserError, "unexpected token #{n.type}, expected ':=' or ','" end end def self.parse_polynomial(tokenizer) - puts "parse_polynomial " - terms = [parse_term(tokenizer)] + first_term_negative = false + if tokenizer.lookahead.type == :plus || tokenizer.lookahead.type == :minus + first_term_negative = tokenizer.lookahead.type == :minus + tokenizer.next_token + end + first_term = parse_term(tokenizer) + first_term *= -1 if first_term_negative + terms = [first_term] while [:plus, :minus].include?(tokenizer.lookahead.type) sign = tokenizer.next_token.type == :plus ? +1 : -1 terms << parse_term(tokenizer) * sign end - puts 'parse_polynomial end' Polynomial.new(terms) end def self.parse_term(tokenizer) - puts "parse_term " n = tokenizer.lookahead unless n.type == :id && n.variable? || n.type == :num - raise StandardError, "unexpected token #{n.type}, expected number or variable" + raise ParserError, "unexpected token #{n.type}, expected number or variable" end if n.type == :num - puts "eat coeff" tokenizer.next_token coeff = n.value variables = {} @@ -368,44 +364,34 @@ module RubyAlgebra end loop do n = tokenizer.lookahead - puts "" unless n.type == :mult || n.type == :id && n.variable? break end v = parse_variable_factor(tokenizer) variables[v[0]] = v[1] end - puts 'parse_term end' - pp [coeff, variables] Term.new(coeff, variables) end def self.parse_variable_factor(tokenizer) - puts "parse_term " n = tokenizer.lookahead tokenizer.next_token - puts 'eat variable' if n.type == :mult n = tokenizer.next_token - puts 'eat *' end unless n.type == :id && n.variable? - raise StandardError, "unexpected token #{n.type}, expected variable" + raise ParserError, "unexpected token #{n.type}, expected variable" end variable = n.symbol if tokenizer.lookahead.type == :pow tokenizer.next_token - puts 'eat ^' unless tokenizer.lookahead.type == :num - raise StandardError, "unexpected token #{n.type}, expected number" + raise ParserError, "unexpected token #{n.type}, expected number" end power = tokenizer.next_token.value - puts 'eat exponent' else power = 1 end - puts 'parse_variable_factor end' - pp [variable, power] [variable, power] end end diff --git a/lib/ruby_algebra/polynomial.rb b/lib/ruby_algebra/polynomial.rb index 5d5faa6..7c2fb9e 100644 --- a/lib/ruby_algebra/polynomial.rb +++ b/lib/ruby_algebra/polynomial.rb @@ -17,7 +17,7 @@ module RubyAlgebra def to_s if @terms.empty? - result = '' + result = '0' else result = @terms[-1].to_s i = @terms.length - 2 @@ -37,7 +37,7 @@ module RubyAlgebra raise NotImplementedError end - def evaluate(values) + def substitute(values) result = 0 @terms.each do |term| # ... @@ -67,6 +67,50 @@ module RubyAlgebra end end + def /(other) + raise StandardError, "cannot divide by zero" if other.zero? + return [Polynomial.new, Polynomial.new] if self.zero? + + # Сначала проверить, что оба многочлена от одной и той же переменной + t = if @terms.empty? || @terms[-1].variables.empty? then other.terms[-1] else @terms[-1] end + unless t.variables.empty? + only_variable = t.variables.keys[0] + @terms.each do |term| + next if term.variables.empty? + unless term.variables.length == 1 && term.variables.keys[0] == only_variable + raise StandardError, "cannot divide polynomials with different variables" + end + end + other.terms.each do |term| + next if term.variables.empty? + unless term.variables.length == 1 && term.variables.keys[0] == only_variable + raise StandardError, "cannot divide polynomials with different variables" + end + end + end + + quotient = Polynomial.new + remainder = self + divisor_power = unless other.terms.last.variables.empty? then other.terms.last.variables.first[1] else 0 end + loop do + break if remainder.zero? + + dividend_power = unless remainder.terms.last.variables.empty? then remainder.terms.last.variables.first[1] else 0 end + break if dividend_power < divisor_power + + result_power = dividend_power - divisor_power + result_coeff = remainder.terms.last.coeff.to_f / other.terms.last.coeff + if result_power > 0 + q = Term.new(result_coeff, {only_variable => result_power}) + else + q = Term.new(result_coeff, {}) + end + quotient += q + remainder -= other * q + end + [quotient, remainder] + end + def **(other) result = Polynomial.new([Term.new(1)]) other.times do @@ -91,50 +135,40 @@ module RubyAlgebra @terms == other.terms end - def simplify + def _simplify i = 0 new_terms = @terms.filter { |t| !t.zero? } while i < new_terms.length j = i + 1 + annihilated = false while j < new_terms.length if new_terms[i].similar_to?(new_terms[j]) new_terms[i] += new_terms[j] + new_terms.delete_at(j) if new_terms[i].zero? new_terms.delete_at(i) - i -= 1 - j -= 1 + annihilated = true + break end - new_terms.delete_at(j) - j -= 1 + else + j += 1 end - j += 1 end - i += 1 + i += 1 unless annihilated end - Polynomial.new(new_terms.sort_by { |t| t.total_power }) + new_terms.sort_by { |t| t.total_power } + end + + def simplify + Polynomial.new(_simplify) end def simplify! - i = 0 - new_terms = @terms.filter { |t| !t.zero? } - while i < new_terms.length - j = i + 1 - while j < new_terms.length - if new_terms[i].similar_to?(new_terms[j]) - new_terms[i] += new_terms[j] - if new_terms[i].zero? - new_terms.delete_at(i) - i -= 1 - j -= 1 - end - new_terms.delete_at(j) - j -= 1 - end - j += 1 - end - i += 1 - end - @terms = new_terms.sort_by { |t| t.total_power } + @terms = _simplify + end + + def zero? + @terms.all? { |t| t.zero? } end end @@ -160,7 +194,7 @@ module RubyAlgebra raise NotImplementedError end - def evaluate(values) + def substitute(values) raise NotImplementedError end @@ -186,6 +220,11 @@ module RubyAlgebra Term.new(@coeff * other.coeff, new_variables) end + def /(other) + raise StandardError, "cannot divide by zero" if other.zero? + + end + def +@ Term.new(@coeff, @variables) end @@ -203,7 +242,13 @@ module RubyAlgebra end def to_s - result = @coeff != 1 ? @coeff.to_s : '' + if @coeff == 1 && !@variables.empty? + result = '' + elsif @coeff == -1 && !@variables.empty? + result = '-' + else + result = @coeff.to_s + end unless @variables.empty? first = true @variables.each do |var, power|