Skip to content

Commit

Permalink
Merge efa385b into a4a9a1a
Browse files Browse the repository at this point in the history
  • Loading branch information
project-eutopia committed Nov 23, 2018
2 parents a4a9a1a + efa385b commit 511b360
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,16 @@ calculator.evaluate("2 + if(1 > 0, 10, 29)")
```

For looping, you can use the basic `while` loop, which has an expression that evaluates to a boolean as the first argument, and any expression in the second argument.
One can use the keywords `break` and `continue` to control loop flow as well.

```ruby
calculator = Keisan::Calculator.new
calculator.evaluate("my_sum(a) = {let i = 0; let total = 0; while(i < a.size, {total += a[i]; i += 1}); total}")
calculator.evaluate("my_sum([1,3,5,7,9])")
#=> 25
calculator.evaluate("has_element(a, x) = {let i=0; let found=false; while(i<a.size, if(a[i] == x, found = true; break); i+=1); found}")
calculator.evaluate("[2, 3, 7, 11].has_element(11)")
#=> true
```

##### Bitwise operations
Expand Down
3 changes: 3 additions & 0 deletions lib/keisan/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ class UnmodifiableError < StandardError; end
class InvalidExpression < StandardError; end
class TypeError < StandardError; end
class NonDifferentiableError < StandardError; end

class BreakError < StandardError; end
class ContinueError < StandardError; end
end
end
11 changes: 11 additions & 0 deletions lib/keisan/functions/break.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require_relative "loop_control_flow_function"

module Keisan
module Functions
class Break < LoopControlFlowFuntion
def initialize
super("break", Exceptions::BreakError)
end
end
end
end
11 changes: 11 additions & 0 deletions lib/keisan/functions/continue.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require_relative "loop_control_flow_function"

module Keisan
module Functions
class Continue < LoopControlFlowFuntion
def initialize
super("continue", Exceptions::ContinueError)
end
end
end
end
4 changes: 4 additions & 0 deletions lib/keisan/functions/default_registry.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require_relative "let"
require_relative "puts"
require_relative "break"
require_relative "continue"

require_relative "if"
require_relative "while"
Expand Down Expand Up @@ -49,6 +51,8 @@ def self.registry
def self.register_defaults!(registry)
registry.register!(:let, Let.new, force: true)
registry.register!(:puts, Puts.new, force: true)
registry.register!(:break, Break.new, force: true)
registry.register!(:continue, Continue.new, force: true)

registry.register!(:if, If.new, force: true)
registry.register!(:while, While.new, force: true)
Expand Down
22 changes: 22 additions & 0 deletions lib/keisan/functions/loop_control_flow_function.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Keisan
module Functions
class LoopControlFlowFuntion < Function
def initialize(name, exception_class)
super(name, 0)
@exception_class = exception_class
end

def value(ast_function, context = nil)
raise @exception_class.new
end

def evaluate(ast_function, context = nil)
raise @exception_class.new
end

def simplify(ast_function, context = nil)
raise @exception_class.new
end
end
end
end
8 changes: 7 additions & 1 deletion lib/keisan/functions/while.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ def while_loop(logical_node, body_node, context)
current = Keisan::AST::Null.new

while logical_node_evaluates_to_true(logical_node, context)
current = body_node.evaluated(context)
begin
current = body_node.evaluated(context)
rescue Exceptions::BreakError
break
rescue Exceptions::ContinueError
next
end
end

current
Expand Down
2 changes: 1 addition & 1 deletion lib/keisan/parser.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Keisan
class Parser
KEYWORDS = %w(let puts).freeze
KEYWORDS = %w(let puts break continue).freeze

attr_reader :tokens, :components

Expand Down
18 changes: 18 additions & 0 deletions spec/keisan/default_functions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,22 @@
expect(calculator.simplify("diff(cbrt(2*x), x)").to_s).to eq "(2/3)*((2*x)**(-2/3))"
end
end

context "loop control flow keywords" do
describe "break" do
it "immediately ends the loop" do
calculator = Keisan::Calculator.new
expect(calculator.evaluate("x = 0; while(true, x += 1; if(x >= 5, break)); x")).to eq 5
end
end

describe "continue" do
it "skips to the next iteration of the loop" do
calculator = Keisan::Calculator.new
expect(calculator.evaluate("x = 0; sum = 0; while(x < 10, x += 1; if (x % 2 == 0, continue); sum += x**2); sum")).to eq(
1 + 3**2 + 5**2 + 7**2 + 9**2
)
end
end
end
end
5 changes: 5 additions & 0 deletions spec/keisan/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -650,9 +650,14 @@
end

context "keyword" do
it "has these keywords" do
expect(described_class::KEYWORDS).to match_array(%w(let puts break continue))
end

it "parses the keyword into a function call" do
parser = described_class.new(string: "let x = 5")
expect(parser.components.map(&:class)).to eq([Keisan::Parsing::Function])
expect(parser.components.first.name).to eq "let"
expect(parser.components.first.arguments.count).to eq 1
expect(parser.components.first.arguments.first.components.map(&:class)).to eq([
Keisan::Parsing::Variable,
Expand Down
2 changes: 1 addition & 1 deletion spec/readme_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
digest = Digest::SHA256.hexdigest(content)

# cat README.md | sha256sum
expected_digest = "c430ca4bd926e553e7414c96f89b8f81ef8bf3c491c5484e9f9599f38e06e2f7"
expected_digest = "5cbc6ded4d07c2304325871a0a2181bfc66616b789bc63aa57fc8310592d789d"
if digest != expected_digest
raise "Invalid README file detected with SHA256 digest of #{digest}. Use command `cat README.md | sha256sum` to get correct digest if your changes to the README are safe. Aborting README test."
end
Expand Down

0 comments on commit 511b360

Please sign in to comment.