From 4791b3997c6a63644f03e6c381c3b226f454d229 Mon Sep 17 00:00:00 2001 From: Christopher Locke Date: Thu, 22 Nov 2018 23:45:30 -0800 Subject: [PATCH] evaluate for enumerable function fully evaluates --- README.md | 3 +++ lib/keisan/functions/enumerable_function.rb | 12 ++++++++++-- lib/keisan/functions/filter.rb | 10 ++++------ lib/keisan/functions/map.rb | 10 ++++------ lib/keisan/functions/reduce.rb | 12 ++++-------- spec/keisan/ast/function_assignment_spec.rb | 3 +++ spec/readme_spec.rb | 2 +- 7 files changed, 29 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 66750ac..fb2f6b6 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,9 @@ calculator.evaluate("f(0-a)", a: 2) #=> -20 calculator.evaluate("n") # n only exists in the definition of f(x) #=> Keisan::Exceptions::UndefinedVariableError: n +calculator.evaluate("includes(a, element) = a.reduce(false, found, x, found || (x == element))") +calculator.evaluate("[3, 9].map(x, [1, 3, 5].includes(x))").value +#=> [true, false] ``` This form even supports recursion, but you must explicitly allow it. diff --git a/lib/keisan/functions/enumerable_function.rb b/lib/keisan/functions/enumerable_function.rb index dfdb2b7..a0996cb 100644 --- a/lib/keisan/functions/enumerable_function.rb +++ b/lib/keisan/functions/enumerable_function.rb @@ -12,6 +12,10 @@ def value(ast_function, context = nil) evaluate(ast_function, context) end + def unbound_variables(children, context) + super - Set.new(shadowing_variable_names(children).map(&:name)) + end + def evaluate(ast_function, context = nil) validate_arguments!(ast_function.children.count) context ||= Context.new @@ -20,9 +24,9 @@ def evaluate(ast_function, context = nil) case operand when AST::List - evaluate_list(operand, arguments, expression, context) + evaluate_list(operand, arguments, expression, context).evaluate(context) when AST::Hash - evaluate_hash(operand, arguments, expression, context) + evaluate_hash(operand, arguments, expression, context).evaluate(context) else raise Exceptions::InvalidFunctionError.new("Unhandled first argument to #{name}: #{operand}") end @@ -34,6 +38,10 @@ def simplify(ast_function, context = nil) protected + def shadowing_variable_names(children) + raise Exceptions::NotImplementedError.new + end + def verify_arguments!(arguments) unless arguments.all? {|argument| argument.is_a?(AST::Variable)} raise Exceptions::InvalidFunctionError.new("Middle arguments to #{name} must be variables") diff --git a/lib/keisan/functions/filter.rb b/lib/keisan/functions/filter.rb index ef85527..4acd427 100644 --- a/lib/keisan/functions/filter.rb +++ b/lib/keisan/functions/filter.rb @@ -10,12 +10,10 @@ def initialize super("filter") end - def unbound_variables(children, context) - if children.size == 3 - super - Set[children[1].name] - else - super - Set[children[1].name, children[2].name] - end + protected + + def shadowing_variable_names(children) + children.size == 3 ? children[1..1] : children[1..2] end private diff --git a/lib/keisan/functions/map.rb b/lib/keisan/functions/map.rb index 0a0a796..5f69be2 100644 --- a/lib/keisan/functions/map.rb +++ b/lib/keisan/functions/map.rb @@ -10,12 +10,10 @@ def initialize super("map") end - def unbound_variables(children, context) - if children.size == 3 - super - Set[children[1].name] - else - super - Set[children[1].name, children[2].name] - end + protected + + def shadowing_variable_names(children) + children.size == 3 ? children[1..1] : children[1..2] end private diff --git a/lib/keisan/functions/reduce.rb b/lib/keisan/functions/reduce.rb index 72f3733..189ed38 100644 --- a/lib/keisan/functions/reduce.rb +++ b/lib/keisan/functions/reduce.rb @@ -11,16 +11,12 @@ def initialize super("reduce") end - def unbound_variables(children, context) - if children.size == 5 - super - Set[children[2].name, children[3].name] - else - super - Set[children[2].name, children[3].name, children[4].name] - end - end - protected + def shadowing_variable_names(children) + children.size == 5 ? children[2..3] : children[2..4] + end + def verify_arguments!(arguments) unless arguments[1..-1].all? {|argument| argument.is_a?(AST::Variable)} raise Exceptions::InvalidFunctionError.new("Middle arguments to #{name} must be variables") diff --git a/spec/keisan/ast/function_assignment_spec.rb b/spec/keisan/ast/function_assignment_spec.rb index 32c3fa7..762b971 100644 --- a/spec/keisan/ast/function_assignment_spec.rb +++ b/spec/keisan/ast/function_assignment_spec.rb @@ -27,5 +27,8 @@ calculator = Keisan::Calculator.new calculator.evaluate("minimum(a) = a.reduce(INF, current_min, element, if (element < current_min, element, current_min))") expect(calculator.evaluate("minimum([5,1,3])")).to eq 1 + + calculator.evaluate("includes(a, element) = a.reduce(false, found, x, found || (x == element))") + expect(calculator.evaluate("[3, 9].map(x, [1, 3, 5].includes(x))")).to eq([true, false]) end end diff --git a/spec/readme_spec.rb b/spec/readme_spec.rb index bd808ca..06cce98 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 = "399be39e65ec8aaace1f9ce266415bf49627572bcd2ee6f8e05d9f4a62cc241c" + expected_digest = "c430ca4bd926e553e7414c96f89b8f81ef8bf3c491c5484e9f9599f38e06e2f7" 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