Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/keisan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
require "keisan/ast/unary_inverse"
require "keisan/ast/unary_bitwise_not"
require "keisan/ast/unary_logical_not"
require "keisan/ast/factorial"
require "keisan/ast/arithmetic_operator"
require "keisan/ast/plus"
require "keisan/ast/times"
Expand Down Expand Up @@ -119,6 +120,7 @@
require "keisan/parsing/unary_operator"
require "keisan/parsing/unary_plus"
require "keisan/parsing/unary_minus"
require "keisan/parsing/factorial"

require "keisan/parsing/arithmetic_operator"
require "keisan/parsing/plus"
Expand Down
50 changes: 50 additions & 0 deletions lib/keisan/ast/factorial.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Keisan
module AST
class Factorial < UnaryOperator
def value(context = nil)
n = child.value(context)
(1..n).inject(1) { |res, i| res * i }
end

def evaluate(context = nil)
context ||= Context.new
node = child.evaluate(context).to_node
if node.is_a?(Number)
Number.new((1..node.value(context)).inject(1) { |res, i| res * i })
else
self.class.new(node)
end
end

def simplify(context = nil)
context ||= Context.new
node = child.simplify(context).to_node
if node.is_a?(Number)
Number.new((1..node.value(context)).inject(1) { |res, i| res * i })
else
self.class.new(node)
end
end

def to_s
"#{child.to_s}!"
end

def self.symbol
:"!"
end

def self.arity
ARITIES[:"!"]
end

def self.priority
PRIORITIES[:"!"]
end

def self.associativity
ASSOCIATIVITIES[:"!"]
end
end
end
end
6 changes: 4 additions & 2 deletions lib/keisan/ast/line_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ def apply_postfix_component_to_node(postfix_component, node)
},
postfix_component.name
)
when Keisan::Parsing::Factorial
postfix_component.node_class.new(node)
else
raise Keisan::Exceptions::ASTError.new("Invalid postfix component #{postfix_component}")
end
Expand All @@ -104,10 +106,10 @@ def apply_postfix_component_to_node(postfix_component, node)
# Returns an array of the form
# [node, postfix_operators]
# middle_node is the main node which will be modified by prefix and postfix operators
# postfix_operators is an array of Keisan::Parsing::Indexing, DotWord, and DotOperator objects
# postfix_operators is an array of Keisan::Parsing::Indexing, DotWord, DotOperator, and Factorial objects
def node_postfixes(components)
index_of_postfix_components = components.map.with_index {|c,i| [c,i]}.select {|c,i|
c.is_a?(Keisan::Parsing::Indexing) || c.is_a?(Keisan::Parsing::DotWord) || c.is_a?(Keisan::Parsing::DotOperator)
c.is_a?(Keisan::Parsing::Indexing) || c.is_a?(Keisan::Parsing::DotWord) || c.is_a?(Keisan::Parsing::DotOperator) || c.is_a?(Keisan::Parsing::Factorial)
}.map(&:last)
unless index_of_postfix_components.reverse.map.with_index.all? {|i,j| i + j == components.size - 1 }
raise Keisan::Exceptions::ASTError.new("postfix components must be in back")
Expand Down
1 change: 1 addition & 0 deletions lib/keisan/ast/operator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Operator < Parent
"u+": [1, 100, :right], # Unary plus
"**": [2, 95, :right], # Exponent
"u-": [1, 90, :right], # Unary minus
"!": [1, 100, :left], # Factorial
"*": [2, 85, :left], # Times
# "/": [2, 85, :left], # Divide
"%": [2, 85, :left], # Modulo
Expand Down
3 changes: 3 additions & 0 deletions lib/keisan/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ def add_operator_to_components!(token)
# Assignment
when :"="
add_assignment_to_components!(token)
when :"!"
@components << Parsing::Factorial.new
else
@components << operator_to_component(token.operator_type)
end
Expand All @@ -244,6 +246,7 @@ def add_operator_to_components!(token)
:"^" => Parsing::BitwiseXor,
:<< => Parsing::BitwiseLeftShift,
:>> => Parsing::BitwiseRightShift,
:"!" => Parsing::Factorial,
:"==" => Parsing::LogicalEqual,
:"!=" => Parsing::LogicalNotEqual,
:"&&" => Parsing::LogicalAnd,
Expand Down
9 changes: 9 additions & 0 deletions lib/keisan/parsing/factorial.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Keisan
module Parsing
class Factorial < Component
def node_class
AST::Factorial
end
end
end
end
2 changes: 2 additions & 0 deletions spec/keisan/ast/operator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
it_behaves_like "an operator object", Keisan::Parsing::Exponent.new, 2, 95, :right
it_behaves_like "an operator object", Keisan::AST::UnaryMinus, 1, 90, :right
it_behaves_like "an operator object", Keisan::Parsing::UnaryMinus.new, 1, 90, :right
it_behaves_like "an operator object", Keisan::AST::Factorial,
1, 100, :left
it_behaves_like "an operator object", Keisan::AST::Times, 2, 85, :left
it_behaves_like "an operator object", Keisan::Parsing::Times.new, 2, 85, :left
it_behaves_like "an operator object", Keisan::AST::Times, 2, 85, :left
Expand Down
10 changes: 10 additions & 0 deletions spec/keisan/factorial_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require "spec_helper"

RSpec.describe "Factorial operator" do
it "evaluates correctly" do
calculator = Keisan::Calculator.new
expect(calculator.evaluate("5!")).to eq 120
expect(calculator.evaluate("(3+2)!")).to eq 120
expect(calculator.evaluate("0!")).to eq 1
end
end
14 changes: 14 additions & 0 deletions spec/keisan/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -726,3 +726,17 @@
end
end
end


RSpec.describe Keisan::Parser do
context "factorial postfix" do
it "parses number factorial" do
parser = described_class.new(string: "5!")
expect(parser.components.map(&:class)).to match_array([
Keisan::Parsing::Number,
Keisan::Parsing::Factorial
])
expect(parser.components[0].value).to eq 5
end
end
end