diff --git a/README.md b/README.md index 97f106c..1ed6887 100644 --- a/README.md +++ b/README.md @@ -425,6 +425,14 @@ calculator.evaluate("replace(diff(f(2*t, t+1), t), t, 3)") #=> 1+8*3 ``` +There is also a `puts` function that can be used to output the result of an expression to STDOUT. + +```ruby +calculator = Keisan::Calculator.new +calculator.evaluate("x = 5") +calculator.evaluate("puts x**2") # prints "25\n" to STDOUT +``` + ### Adding custom variables and functions The `Keisan::Calculator` class has a single `Keisan::Context` object in its `context` attribute. This class is used to store local variables and functions. These can be stored using either the `define_variable!` or `define_function!` methods, or by using the assignment operator `=` in an expression that is evaluated. As an example of pre-defining some variables and functions, see the following diff --git a/lib/keisan/functions/default_registry.rb b/lib/keisan/functions/default_registry.rb index 8707c11..d8d3af6 100644 --- a/lib/keisan/functions/default_registry.rb +++ b/lib/keisan/functions/default_registry.rb @@ -1,4 +1,5 @@ require_relative "let" +require_relative "puts" require_relative "if" require_relative "while" @@ -46,6 +47,7 @@ def self.registry def self.register_defaults!(registry) registry.register!(:let, Let.new, force: true) + registry.register!(:puts, Puts.new, force: true) registry.register!(:if, If.new, force: true) registry.register!(:while, While.new, force: true) diff --git a/lib/keisan/functions/puts.rb b/lib/keisan/functions/puts.rb new file mode 100644 index 0000000..3e3b525 --- /dev/null +++ b/lib/keisan/functions/puts.rb @@ -0,0 +1,23 @@ +module Keisan + module Functions + class Puts < Function + def initialize + super("puts", 1) + end + + def value(ast_function, context = nil) + evaluate(ast_function, context) + end + + def evaluate(ast_function, context = nil) + validate_arguments!(ast_function.children.count) + puts ast_function.children.first.evaluate(context).to_s + Keisan::AST::Null.new + end + + def simplify(ast_function, context = nil) + evaluate(ast_function, context) + end + end + end +end diff --git a/lib/keisan/parser.rb b/lib/keisan/parser.rb index e46d0b0..e18e78b 100644 --- a/lib/keisan/parser.rb +++ b/lib/keisan/parser.rb @@ -1,6 +1,6 @@ module Keisan class Parser - KEYWORDS = %w(let).freeze + KEYWORDS = %w(let puts).freeze attr_reader :tokens, :components diff --git a/spec/keisan/puts_spec.rb b/spec/keisan/puts_spec.rb new file mode 100644 index 0000000..c95a2b7 --- /dev/null +++ b/spec/keisan/puts_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" + +RSpec.describe Keisan::Functions::Puts do + let(:calculator) { Keisan::Calculator.new } + + it "outputs to STDOUT" do + expect { calculator.evaluate("puts 123") }.to output("123\n").to_stdout + expect { calculator.evaluate("puts(x**2 + 1)") }.to output("(x**2)+1\n").to_stdout + calculator.evaluate("x = 2") + expect { calculator.evaluate("puts(x**2 + 1)") }.to output("5\n").to_stdout + end + + describe "evaluate" do + it "returns null" do + expect { + ast = calculator.ast("puts x = 5") + expect(ast.evaluate).to eq Keisan::AST::Null.new + }.to output("5\n").to_stdout + end + + it "does evaluation of arguments" do + expect { calculator.evaluate("puts(x = 12)") }.to output("12\n").to_stdout + expect(calculator.evaluate("x")).to eq 12 + end + end + + describe "#value and #simplify call evaluate" do + it "value calls evaluate" do + expect_any_instance_of(Keisan::Functions::Puts).to receive(:evaluate).and_return(Keisan::AST::Null.new) + ast = calculator.ast("puts x = 5") + expect(ast.value).to eq Keisan::AST::Null.new + end + + it "simplify calls evaluate" do + expect_any_instance_of(Keisan::Functions::Puts).to receive(:evaluate).and_return(Keisan::AST::Null.new) + ast = calculator.ast("puts x = 5") + expect(ast.simplify).to eq Keisan::AST::Null.new + end + end +end diff --git a/spec/readme_spec.rb b/spec/readme_spec.rb index 52da946..2c758f6 100644 --- a/spec/readme_spec.rb +++ b/spec/readme_spec.rb @@ -13,7 +13,7 @@ digest = Digest::SHA256.hexdigest(content) # cat README.md | sha256sum - expected_digest = "ceca07925099af54db28079b7ae408bbf1d478ebe96c26c0deb1c1b0a458e194" + expected_digest = "ec824b6fb3574ec4c5ffab47e4c39c86573fad0f3fd7c73a8c24e55c32fee9fc" 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 @@ -25,7 +25,11 @@ outputs = code_block.map do |line| begin - eval(line, b) + # Capture output of any `puts` statements + $stdout = StringIO.new + result = eval(line, b) + $stdout = STDOUT + result rescue Keisan::Exceptions::BaseError => e e end