<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

describe "Lexing expressions" do
  it "should have the correct tokens for simple addition" do
    subject = MathEngine::Lexer.new("1 + 1")
    expect(subject.next).to eq(Lexr::Token.number(1))
    expect(subject.next).to eq(Lexr::Token.addition('+'))
    expect(subject.next).to eq(Lexr::Token.number(1))
  end

  it "should have the correct tokens for simple addition without whitespace" do
    subject = MathEngine::Lexer.new("1+1")
    expect(subject.next).to eq(Lexr::Token.number(1))
    expect(subject.next).to eq(Lexr::Token.addition('+'))
    expect(subject.next).to eq(Lexr::Token.number(1))
  end

  it "should have the correct tokens for simple addition without whitespace" do
    subject = MathEngine::Lexer.new("-10*(-3--2)")
    expect(subject.next).to eq(Lexr::Token.number(-10))
    expect(subject.next).to eq(Lexr::Token.multiplication("*"))

    expect(subject.next).to eq(Lexr::Token.open_parenthesis("("))
    expect(subject.next).to eq(Lexr::Token.number(-3))
    expect(subject.next).to eq(Lexr::Token.subtraction("-"))
    expect(subject.next).to eq(Lexr::Token.number(-2))
    expect(subject.next).to eq(Lexr::Token.close_parenthesis(")"))

    expect(subject.next).to eq(Lexr::Token.end)
  end

  it "should do this expression properly" do
    subject = MathEngine::Lexer.new("(3+2)-2")

    expect(subject.next).to eq(Lexr::Token.open_parenthesis("("))
    expect(subject.next).to eq(Lexr::Token.number(3))
    expect(subject.next).to eq(Lexr::Token.addition("+"))
    expect(subject.next).to eq(Lexr::Token.number(2))
    expect(subject.next).to eq(Lexr::Token.close_parenthesis(")"))

    expect(subject.next).to eq(Lexr::Token.subtraction("-"))
    expect(subject.next).to eq(Lexr::Token.number(2))

    expect(subject.next).to eq(Lexr::Token.end)
  end
end
<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

describe "Evaluating expressions" do
  it "should have the correct value for some simple calculations" do
    expect(evaluate("1 + 1")).to eq(2)
    expect(evaluate("2 - 1")).to eq(1)
    expect(evaluate("2 * 2")).to eq(4)
    expect(evaluate("4 / 2")).to eq(2)
    expect(evaluate("10 * 2 + 4")).to eq(24)
    expect(evaluate("10 * (2 + 4)")).to eq(60)
    expect(evaluate("10 + 10 + 10 + 10")).to eq(40)
    expect(evaluate("10 * 10 + 5 / 5")).to eq(101)
    expect(evaluate("3.14159 * (200 / 180) + 1")).to be_within(0.001).of(4.49)
    expect(evaluate("10 * (3 * 2) + (55 - 5) / (2.5 * 2)")).to eq(70)
    expect(evaluate("10 * (3 * 2) + (55 - 5) / (2.5 * (3 + 1))")).to eq(65)
    expect(evaluate("2 * 2 ^ 5")).to eq(64)
    expect(evaluate("(2 * 2) ^ 5")).to eq(1024)
    expect(evaluate("2 * 2 % 5")).to eq(4)
    expect(evaluate("2 * 4 / 3")).to be_within(0.001).of(2.66666666666667)
  end

  it "should have the correct values when using variables" do
    subject = MathEngine.new
    # expect(subject.evaluate("x = 1 * 2")).to eq(2)
    # expect(subject.evaluate("x + 1")).to eq(3)

    expect(subject.evaluate("i = 2 * 2")).to eq(4)
    expect(subject.evaluate("i + 1")).to eq(5)
  end

  it "should be able to call functions and use the result in calculations" do
    subject = MathEngine.new
    subject.context.define(:double_it, lambda { |x| x * 2 })
    subject.context.define(:add_em, lambda { |x, y| x + y })
    expect(subject.evaluate("10 * double_it(2)")).to eq(40)
    expect(subject.evaluate("10 * double_it(2 * 4 / 3)")).to be_within(0.001).of(53.3333333334)
    expect(subject.evaluate("10 * double_it(2 * 4 / 3 + (5 ^ 5))")).to be_within(0.001).of(62553.333333)
    expect(subject.evaluate("double_it(5) + add_em(10, add_em(2.5, 2.5)) + 5 + add_em(20, 20)")).to eq(70)
  end
end
<file_sep>#require "mathn"
require "bigdecimal"

require_relative "errors"
require_relative "lexer"
require_relative "parser"
require_relative "context"

require_relative "evaluators/finders"
require_relative "evaluators/calculate"

class MathEngine
  DEFAULT_OPTIONS = {evaluator: :calculate,
                     case_sensitive: true}
  
  def initialize(opts = {})
    @opts = DEFAULT_OPTIONS.merge(opts)
    @opts[:context] = Context.new(@opts) unless @opts[:context]
  end
  
  def evaluate(expression)
    evaluator = MathEngine::Evaluators.find_by_name(@opts[:evaluator]).new(context)
    Parser.new(Lexer.new(expression)).parse.evaluate(evaluator)
  end
  
  def context
    @opts[:context]
  end
end
<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), 'nodes'))

class MathEngine
  class Parser
    def initialize(lexer)
      @lexer = lexer
    end

    def parse
      #statement = { <identifier> <assignment> } expression <end>
      #expression = term { ( <addition> | <subtraction> ) term }
      #term = exp { ( <multiplication> | <division> ) exp }
      #exp = factor { ( <exponent> | <modulus> ) factor }
      #factor = <call> | <identifier> | <number> | ( <open_parenthesis> expression <close_parenthesis> )
      #call = <identifier> <open_parenthesis> { call_parameter } <close_parenthesis>
      #call_parameter = <expression> { <comma> call_parameter }
      statement
    end

    private

    def statement
      next!
      if current.type == :identifier && peek.type == :assignment
        variable_name = current.value
        next!
        expect_current :assignment
        next!
        result = MathEngine::AssignmentNode.new(MathEngine::IdentifierNode.new(variable_name), expression)
      else
        result = expression
      end
      next!
      expect_current :end
      result
    end

    def expression
      left = term
      result = nil
      while [:addition, :subtraction].include? current.type
        node_type = current.type == :addition ? MathEngine::AdditionNode : MathEngine::SubtractionNode
        next!
        left = node_type.new(left, term)
      end
      result = MathEngine::ExpressionNode.new(result || left)
      result
    end

    def term
      left = exp
      result = nil
      while [:multiplication, :division].include? current.type
        node_type = current.type == :multiplication ? MathEngine::MultiplicationNode : MathEngine::DivisionNode
        next!
        left = node_type.new(left, exp)
      end
      result || left
    end

    def exp
      left = factor
      result = nil
      while [:exponent, :modulus].include? current.type
        node_type = current.type == :exponent ? MathEngine::ExponentNode : MathEngine::ModulusNode
        next!
        left = node_type.new(left, factor)
      end
      result || left
    end

    def factor
      if current.type == :number
        result = MathEngine::LiteralNumberNode.new(current.value)
        next!
        return result
      elsif current.type == :identifier
        result = peek.type == :open_parenthesis ? call : MathEngine::IdentifierNode.new(current.value)
        next!
        return result
      end

      expect_current :open_parenthesis, "number, variable or open_parenthesis"
      next!
      result = MathEngine::ParenthesisedExpressionNode.new(expression)
      expect_current :close_parenthesis
      next!
      result
    end

    def call
      expect_current :identifier
      function_name = current.value
      next!
      expect_current :open_parenthesis
      next!
      result = MathEngine::FunctionCallNode.new(function_name, current.type == :close_parenthesis ? nil : call_parameter)
      expect_current :close_parenthesis
      result
    end

    def call_parameter
      left = expression
      right = nil
      if current.type == :comma
        next!
        right = call_parameter
      end
      MathEngine::ParametersNode.new(left, right)
    end

    def current
      @lexer.current
    end

    def peek
      @lexer.peek
    end

    def next!
      @lexer.next
    end

    def expect_current(type, friendly = nil)
      raise MathEngine::ParseError.new("Unexpected #{current}, expected: #{friendly ? friendly : type}") unless current.type == type
    end
  end
end
<file_sep># MathEngine

MathEngine is a lightweight mathematical expression parser and evaluator. It currently handles addition, subtraction, multiplication, division, exponent, modulus and the use of variables and pre-defined functions.

Install with

	gem install math_engine

The only dependency is [lexr](http://github.com/michaelbaldry/lexr), which is really lightweight and has no external dependencies.

## An example: Expressions

	require 'rubygems'
	require 'math_engine'

	engine = MathEngine.new

	puts "#{engine.evaluate("x = 10 * (3 * 2) + (55 - 5) / (2.5 * (3 + 1))")}"
	puts "#{engine.evaluate("x + 5")}"

results in an output of

	65.0
	70.0

extending is easy using functions, you can add single functions using MathEngine.define
	
	engine.context.define :add_em do |x, y|
		x + y
	end
	
or you can write all your functions in a class and add the class

	class SomeFunctions
		def add_em(x, y)
			x + y
		end
	end

	engine.context.include_library SomeFunctions.new
	
and calling them with

	engine.evaluate("1 + 1 + add_em(2, 2)")
	
All functions are pulled in from the built in Math class by default, so all the standard ruby math functions are available (cos, sin, tan etc)

if you missed a closing parenthesis, had an operator where it wasn't meant to be, you might get something like this:

	Unexpected multiplication(*), expected: number, variable name or open_parenthesis
	
and that is pretty much every feature so far. Please let me know of any bugs or additions that you'd like to see!

## Contributors

<NAME> (mdelaossa): Handling of complex numbers and upgraded to work with 1.9.X

## License

See the LICENSE file included with the distribution for licensing and
copyright details.
<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

describe "Contextual scope for variables and functions" do
  it "should be able to set a variable and get it again" do
    subject = MathEngine::Context.new
    subject.set("test", "abc123")
    expect(subject.get("test")).to eq("abc123")
  end

  it "should be able to change a variable and get it again" do
    subject = MathEngine::Context.new
    subject.set("test", "abc123")
    subject.set("test", "abc12345")
    expect(subject.get("test")).to eq("abc12345")
  end

  it "should be able to set a constant (all uppercase) and get it again" do
    subject = MathEngine::Context.new
    subject.set("PI", 3.14159)
    expect(subject.get("PI")).to eq(3.14159)
  end

  it "should not be possible to set a constant (all uppercase) more than once" do
    subject = MathEngine::Context.new
    subject.set("PI", 3.14159)
    expect { subject.set("PI", 3.2) }.to raise_error MathEngine::UnableToModifyConstantError
  end

  it "should diferentiate between variables and constants" do
    subject = MathEngine::Context.new
    subject.set("blah", 123)
    subject.set("PI", 3.14159)
    expect(subject.variables).to eq(["blah"])
    expect(subject.constants).to eq(["PI"])
  end

  it "should raise an error if a function is called with the wrong number of arguments" do
    subject = MathEngine::Context.new
    subject.define :abc, lambda { |x, y, z| x + y + z }
    expect { subject.call(:abc, 123) }.to raise_error ArgumentError
  end

  it "should be possible to define a function with a lambda and call it" do
    subject = MathEngine::Context.new
    subject.define(:double_it, lambda { |x| x * 2 })
    expect(subject.call(:double_it, 2)).to eq(4)

    subject.define(:add_em, lambda { |x, y| x + y })
    expect(subject.call(:add_em, 2, 2)).to eq(4)
  end

  it "should be possible to define a function with a block and call it" do
    subject = MathEngine::Context.new
    subject.define :double_it do |x|
      x * 2
    end
    expect(subject.call(:double_it, 2)).to eq(4)

    subject.define :add_em do |x, y|
      x + y
    end
    expect(subject.call(:add_em, 2, 2)).to eq(4)
  end

  it "should be able to pull in a library and use its functions" do
    subject = MathEngine::Context.new

    Blah = Class.new do
      def test
        12345
      end
    end

    subject.include_library Blah.new
    expect(subject.call(:test)).to eq(12345)
  end

  it "should be able to pull in a library and use its functions, even if they are dynamic, via method_missing" do
    subject = MathEngine::Context.new

    Blah = Class.new do
      def method_missing(sym, *args, &block)
        return 12345 if sym == :test_12345
        raise NoMethodError(sym)
      end

      def respond_to_missing?(sym, include_private)
        sym == :test_12345
      end
    end

    subject.include_library Blah.new
    expect(subject.call(:test_12345)).to eq(12345)
  end

  it "should be able to use functions from Math by default" do
    subject = MathEngine::Context.new
    expect(subject.call(:sin, 0.5)).to be_within(0.001).of(0.4794255386)
  end
end

describe "Contextual scope for variables and functions with case sensetivity turned off" do
  it "should be able to set a variable and get it again" do
    subject = MathEngine::Context.new(case_sensitive: false)
    subject.set("teSt", "abc123")
    expect(subject.get("test")).to eq("abc123")
  end

  it "should be able to change a variable and get it again" do
    subject = MathEngine::Context.new(case_sensitive: false)
    subject.set("tesT", "abc123")
    subject.set("test", "abc12345")
    expect(subject.get("test")).to eq("abc12345")
  end

  it "should be able to set a constant (all uppercase) and get it again" do
    subject = MathEngine::Context.new(case_sensitive: false)
    subject.set("PI", 3.14159)
    expect(subject.get("Pi")).to eq(3.14159)
  end

  it "should not be possible to set a constant (all uppercase) more than once" do
    subject = MathEngine::Context.new(case_sensitive: false)
    subject.set("PI", 3.14159)
    expect { subject.set("Pi", 3.2) }.to raise_error MathEngine::UnableToModifyConstantError
  end

  it "should diferentiate between variables and constants" do
    subject = MathEngine::Context.new(case_sensitive: false)
    subject.set("blah", 123)
    subject.set("PI", 3.14159)
    expect(subject.variables).to eq(["blah"])
    expect(subject.constants).to eq(["PI"])
  end
end
<file_sep># -*- encoding: utf-8 -*-
# stub: math_engine 0.7.0 ruby lib

Gem::Specification.new do |s|
  s.name = "math_engine".freeze
  s.version = "0.7.0"

  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
  s.require_paths = ["lib".freeze]
  s.authors = ["<NAME>".freeze]
  s.date = "2017-02-24"
  s.description = "Lightweight matematical expression parser that is easy to extend".freeze
  s.email = "<EMAIL>".freeze
  s.extra_rdoc_files = ["README.md".freeze]
  s.files = ["README.md".freeze, "lib/context.rb".freeze, "lib/errors.rb".freeze, "lib/evaluators/calculate.rb".freeze, "lib/evaluators/finders.rb".freeze, "lib/lexer.rb".freeze, "lib/math_engine.rb".freeze, "lib/nodes.rb".freeze, "lib/parser.rb".freeze]
  s.homepage = "http://tech.buyapowa.com".freeze
  s.rdoc_options = ["--main".freeze, "README.md".freeze]
  s.rubygems_version = "2.5.2".freeze
  s.summary = "Lightweight mathematical expression parser".freeze

  if s.respond_to? :specification_version then
    s.specification_version = 4

    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
      s.add_runtime_dependency(%q<lexr>.freeze, [">= 0.4.0"])
      s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
    else
      s.add_dependency(%q<lexr>.freeze, [">= 0.4.0"])
      s.add_dependency(%q<rspec>.freeze, [">= 0"])
    end
  else
    s.add_dependency(%q<lexr>.freeze, [">= 0.4.0"])
    s.add_dependency(%q<rspec>.freeze, [">= 0"])
  end
end
<file_sep>class MathEngine
  module Evaluators
    class Calculate
      def initialize(context)
        @context = context
      end
    
      def literal_number(node)
        node.value
      end
    
      def expression(node)
        node.left.evaluate(self)
      end

      def parenthesised_expression(node)
        expression(node)
      end
    
      def identifier(node)
        @context.get node.value
      end
    
      def assignment(node)
        result = node.right.evaluate(self)
        @context.set(node.left.value, result)
        result
      end
    
      def addition(node)
        node.left.evaluate(self) + node.right.evaluate(self)
      end
    
      def subtraction(node)
        node.left.evaluate(self) - node.right.evaluate(self)
      end
    
      def multiplication(node)
        node.left.evaluate(self) * node.right.evaluate(self)
      end

      def division(node)
        node.left.evaluate(self) / node.right.evaluate(self)
      end

      def exponent(node)
        node.left.evaluate(self) ** node.right.evaluate(self)
      end
    
      def modulus(node)
        node.left.evaluate(self) % node.right.evaluate(self)
      end

      def function_call(node)
        parameters = node.right ? node.right.to_a.collect { |p| p.evaluate(self) } : []
        @context.call(node.left, *parameters)
      end
    
      def parameters(node)
        node.left.evaluate(self)
      end
    end
  end
end<file_sep>require "lexr"
class MathEngine
  Lexer = Lexr.that {
    ignores /\s/ => :whitespace

    legal_place_for_binary_operator = lambda { |prev| [:addition,
                                                       :subtraction,
                                                       :multiplication,
                                                       :division,
                                                       :open_parenthesis,
                                                       :start].include? prev.type }

    matches ',' => :comma
    matches '=' => :assignment
    matches '+' => :addition, unless: legal_place_for_binary_operator
    matches '-' => :subtraction, unless: legal_place_for_binary_operator
    matches '*' => :multiplication, unless: legal_place_for_binary_operator
    matches '/' => :division, unless: legal_place_for_binary_operator
    matches '^' => :exponent, unless: legal_place_for_binary_operator
    matches '%' => :modulus, unless: legal_place_for_binary_operator
    matches '(' => :open_parenthesis
    matches ')' => :close_parenthesis

    matches /[a-z][a-z0-9_]*/i => :identifier, convert_with: lambda { |v| v.to_sym }
    matches /([-+]?(\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?[r]?|[-+]?((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?)?[i]|[-+]?(\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?[r]?[-+]((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?)?[i])/ => :number, convert_with: lambda { |v| BigDecimal(v) }
  }
end
<file_sep>source "https://rubygems.org"

gem "lexr", ">= 0.4.0"

group :development, :test do
  gem "rspec", "~> 3.5.0"
  gem "rake"
end
<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

describe "Parsing expressions" do
  it "should have the correct ast for simple addition" do
    subject = build_ast("1 + 2")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::AdditionNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(1)
    expect(subject.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.value).to eq(2)
  end

  it "should have the correct ast for stringed addition" do
    subject = build_ast("1 + 2 + 3")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::AdditionNode)
    expect(subject.left.left.class).to eq(MathEngine::AdditionNode)
    expect(subject.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.left.value).to eq(1)
    expect(subject.left.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.right.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.value).to eq(3)
  end

  it "should handle precedence of multiplication correctly" do
    subject = build_ast("10 * 2 - 3")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::SubtractionNode)
    expect(subject.left.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.left.value).to eq(10)
    expect(subject.left.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.right.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.value).to eq(3)
  end

  it "should handle precedence of division correctly" do
    subject = build_ast("10 / 2 - 3")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::SubtractionNode)
    expect(subject.left.left.class).to eq(MathEngine::DivisionNode)
    expect(subject.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.left.value).to eq(10)
    expect(subject.left.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.right.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.value).to eq(3)
  end

  it "should handle precedence of enclosed expressions" do
    subject = build_ast("10 * (2 + 3)")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(10)
    expect(subject.left.right.class).to eq(MathEngine::ParenthesisedExpressionNode)
    expect(subject.left.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.left.left.class).to eq(MathEngine::AdditionNode)
    expect(subject.left.right.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.left.value).to eq(2)
    expect(subject.left.right.left.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.right.value).to eq(3)
  end

  it "should handle assignment of expressions" do
    subject = build_ast("x = 10 * 10")
    expect(subject.class).to eq(MathEngine::AssignmentNode)
    expect(subject.left.class).to eq(MathEngine::IdentifierNode)
    expect(subject.left.value).to eq(:x)
    expect(subject.right.class).to eq(MathEngine::ExpressionNode)
    expect(subject.right.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.right.left.left.value).to eq(10)
    expect(subject.right.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.right.left.right.value).to eq(10)
  end

  it "should handle exponents" do
    subject = build_ast("1 ^ 5")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::ExponentNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(1)
    expect(subject.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.value).to eq(5)
  end

  it "should handle exponent precendence as higher than multiplication" do
    subject = build_ast("2 * 2 ^ 5")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    subject.left.left.value == 2
    expect(subject.left.right.class).to eq(MathEngine::ExponentNode)
    expect(subject.left.right.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.value).to eq(2)
    expect(subject.left.right.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.value).to eq(5)
  end

  it "should handle modulus precendence as higher than multiplication" do
    subject = build_ast("2 * 2 % 5")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    subject.left.left.value == 2
    expect(subject.left.right.class).to eq(MathEngine::ModulusNode)
    expect(subject.left.right.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.value).to eq(2)
    expect(subject.left.right.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.value).to eq(5)
  end

  it "should handle function calls with no parameters" do
    subject = build_ast("2 * sin()")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.right.left).to eq(:sin)
  end

  it "should handle function calls with 1 literal parameter" do
    subject = build_ast("2 * sin(2)")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.right.left).to eq(:sin)
    expect(subject.left.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.left.left.value).to eq(2)
  end

  it "should handle function calls with 2 literal parameter" do
    subject = build_ast("2 * something(1, 2)")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.right.left).to eq(:something)
    expect(subject.left.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.left.left.value).to eq(1)
    expect(subject.left.right.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.right.left.left.value).to eq(2)
  end

  it "should handle function calls with more literal parameter" do
    subject = build_ast("2 * something(1, 2, 3)")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(2)
    expect(subject.left.right.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.right.left).to eq(:something)
    expect(subject.left.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.left.left.value).to eq(1)
    expect(subject.left.right.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.right.left.left.value).to eq(2)
    expect(subject.left.right.right.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.right.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.right.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.right.right.right.left.left.value).to eq(3)
  end

  it "should handle function calls with 1 expression parameter" do
    subject = build_ast("sin(2 * 4)")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.left).to eq(:sin)
    expect(subject.left.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.left.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.right.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.left.value).to eq(2)
    expect(subject.left.right.left.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.right.value).to eq(4)
  end

  it "should handle nested function calls" do
    subject = build_ast("sin(2 * sin(4))")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.left).to eq(:sin)
    expect(subject.left.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.left.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.right.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.left.value).to eq(2)
    expect(subject.left.right.left.left.right.class).to eq(MathEngine::FunctionCallNode)
    expect(subject.left.right.left.left.right.left).to eq(:sin)
    expect(subject.left.right.left.left.right.right.class).to eq(MathEngine::ParametersNode)
    expect(subject.left.right.left.left.right.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.left.left.right.right.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.right.right.left.left.value).to eq(4)
  end

  it "should parse numbers correctly when no whitespace separates them (bug)" do
    subject = build_ast("10 * (2+3)")
    expect(subject.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.class).to eq(MathEngine::MultiplicationNode)
    expect(subject.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.left.value).to eq(10)
    expect(subject.left.right.class).to eq(MathEngine::ParenthesisedExpressionNode)
    expect(subject.left.right.left.class).to eq(MathEngine::ExpressionNode)
    expect(subject.left.right.left.left.class).to eq(MathEngine::AdditionNode)
    expect(subject.left.right.left.left.left.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.left.value).to eq(2)
    expect(subject.left.right.left.left.right.class).to eq(MathEngine::LiteralNumberNode)
    expect(subject.left.right.left.left.right.value).to eq(3)
  end
end

describe "Invalid syntax" do
  it "should raise an exception when an expression is incomplete" do
    expect { build_ast("1 *") }.to raise_error MathEngine::ParseError
  end

  it "should raise an exception when an extra operator is specified" do
    expect { build_ast("1 ** 2") }.to raise_error Lexr::UnmatchableTextError
  end

  it "should raise an exception when missing a closing parenthesis" do
    expect { build_ast("(((123))") }.to raise_error MathEngine::ParseError
  end

  it "should raise an exception when missing a parameter" do
    expect { build_ast("abc(1, )") }.to raise_error MathEngine::ParseError
  end
end
<file_sep>class MathEngine
  module Evaluators
    def self.find_by_name(name)
      class_name = name.to_s.sub(%r{^[a-z\d]}) { $&.upcase }
      class_name.gsub!(%r{(?:_|(\/))([a-z\d]*)}) { "#{$1}#{$2.capitalize}" }
      MathEngine::Evaluators.const_get(class_name) rescue raise MathEngine::UnknownEvaluatorError.new(name, "MathEngine::Evaluators::#{class_name}")
    end
  end
end<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

describe "Finding evaluators by name" do
  it "should by able to find the calculate evaluator" do
    expect(MathEngine::Evaluators.find_by_name(:calculate)).to eq(MathEngine::Evaluators::Calculate)
  end

  it "should raise an error if unable to find the evaluator" do
    expect { MathEngine::Evaluators.find_by_name(:not_there) }.to raise_error MathEngine::UnknownEvaluatorError.new(:not_there, "MathEngine::Evaluators::NotThere")
  end
end
<file_sep>class MathEngine
  class Node
    attr_reader :left, :right
    alias :value :left

    def initialize(left, right = nil)
      @left, @right = left, right
    end
    
    def evaluate(evaluator)
      evaluator.send(method_name, self)
    end
    
    private
    
    def method_name
      class_name = self.class.name[0..-5]
      class_name = class_name[class_name.rindex("::")+2..-1] if class_name.rindex("::")
      method_name = class_name.gsub(%r{([A-Z\d]+)([A-Z][a-z])},'\1_\2').gsub(%r{([a-z\d])([A-Z])},'\1_\2').downcase  
    end
  end

  class LiteralNumberNode < Node ; end
  class ExpressionNode < Node ; end
  class ParenthesisedExpressionNode < Node ; end
  class IdentifierNode < Node ; end
  class AssignmentNode < Node ; end

  class AdditionNode < Node ; end
  class SubtractionNode < Node ; end
  class MultiplicationNode < Node ; end
  class DivisionNode < Node ; end
  class ExponentNode < Node ; end
  class ModulusNode < Node ; end

  class FunctionCallNode < Node ; end

  class ParametersNode < Node
    def to_a
      [left] + (right ? right.to_a : [])
    end
  end
end<file_sep>require "rubygems"
require "bundler"

Bundler.require :default, :test

require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'math_engine'))

def evaluate(line)
  MathEngine.new.evaluate(line)
end

def build_ast(line)
  MathEngine::Parser.new(MathEngine::Lexer.new(line)).parse
end

def print_ast(node, indent = "")
  puts "#{node.class.name} #{node.is_a?(MathEngine::Node) ? "" : "(#{node})"}"
  return unless node.is_a? MathEngine::Node
  
  print "#{indent}left: " if node.left
  print_ast(node.left, indent + "  ") if node.left
  
  print "#{indent}right: " if node.right
  print_ast(node.right, indent + "  ") if node.right
end<file_sep>class MathEngine
  class ParseError < StandardError
    def initialize(message)
      @message = message
    end

    def to_s
      @message
    end
  end
  
  class UnknownVariableError < StandardError
    def initialize(variable_name)
      @variable_name = variable_name
    end

    def to_s
      "Variable '#{@variable_name}' was referenced but does not exist"
    end
  end
  
  class UnknownFunctionError < StandardError
    def initialize(function_name)
      @function_name = function_name
    end

    def to_s
      "Function '#{@function_name}' was referenced but does not exist"
    end
  end
  
  class UnableToModifyConstantError < StandardError
    def initialize(constant_name)
      @constant_name = constant_name
    end

    def to_s
      "Unable to modify value of constant '#{@constant_name}'"
    end
  end
  
  class UnknownEvaluatorError < StandardError
    def initialize(evaluator_name, expected_const)
      @evaluator_name = evaluator_name
      @expected_const = expected_const
    end
    
    def to_s
      "Unable to find an evaluator called #{@evaluator_name}(#{@expected_const})"
    end
    
    def ==(other)
      self.to_s == other.to_s
    end
  end
end<file_sep>require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

class RandomlyNamedNode < MathEngine::Node ; end
module AnotherTest
  class RandomlyNamedNode < MathEngine::Node ; end
  module AndAgain
    class RandomlyNamedNode < MathEngine::Node ; end
  end
end


class MockEvaluator
  attr_reader :call_list

  def initialize
    @call_list = []
  end

  def method_missing(name, *args, &block)
    @call_list << {name: name, args: args}
  end
end

describe "Node evaluation against the evaluator" do
  it "should call the correct method on the evaluator based on its own name when not in a module" do
    evaluator = MockEvaluator.new
    node = RandomlyNamedNode.new(Object.new)
    node.evaluate(evaluator)
    expect(evaluator.call_list).to eq([{name: :randomly_named, args: [node]}])
  end

  it "should call the correct method on the evaluator based on its own name when in a module" do
    evaluator = MockEvaluator.new
    node = AnotherTest::RandomlyNamedNode.new(Object.new)
    node.evaluate(evaluator)
    expect(evaluator.call_list).to eq([{name: :randomly_named, args: [node]}])
  end

  it "should call the correct method on the evaluator based on its own name when in a module multiple levels deep" do
    evaluator = MockEvaluator.new
    node = AnotherTest::AndAgain::RandomlyNamedNode.new(Object.new)
    node.evaluate(evaluator)
    expect(evaluator.call_list).to eq([{name: :randomly_named, args: [node]}])
  end
end
<file_sep>class MathEngine
  class Context
    DEFAULT_OPTIONS = {case_sensitive: true}
    
    def initialize(opts = {})
      @opts = DEFAULT_OPTIONS.merge(opts)
      @variables = {}
      @dynamic_library = Class.new.new
      @libraries = [@dynamic_library, Math]
    end
    
    def get(variable_name)
      @variables[key variable_name]
    end
    
    def set(variable_name, value)
      raise MathEngine::UnableToModifyConstantError.new(key variable_name) if constant?(key variable_name) && 
                                                                              get(variable_name)
                                                                              
      @variables[key variable_name] = value
    end
    
    def define(function_name, func = nil, &block)
      @dynamic_library.class.send :define_method, function_name.to_sym do |*args|
        func ? func.call(*args) : block.call(*args)
      end
    end
    
    def call(function_name, *args)
      library = library_for_function(function_name)
      raise UnknownFunctionError.new(function_name) unless library
      library.send(function_name.to_sym, *args)
    end
    
    def include_library(library)
      @libraries << library
    end
    
    def constants
      @variables.keys.select { |variable_name| constant?(variable_name) }
    end
    
    def variables
      @variables.keys - constants
    end
    
    private
    
    def constant?(variable_name)
      variable_name.upcase == variable_name
    end
    
    def key(variable_name)
      return variable_name if @opts[:case_sensitive]
      result = @variables.keys.select { |key| key.downcase == variable_name.downcase }.first
      result || variable_name
    end
    
    def library_for_function(function_name)
      @libraries.detect { |l| l.respond_to?(function_name.to_sym) }
    end
  end
end