Skip to content
Merged
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: 1 addition & 1 deletion reference/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Status of built-in methods and properties on GiavaScript runtime types.

#### Array callback argument behavior

- In array methods that accept callbacks, user-defined callback functions use JavaScript-compatible argument normalization.
- In array methods that accept callbacks, JavaScript-compatible argument normalization applies to user-defined function expressions and references to function declarations.
- Extra callback arguments are ignored.
- Missing callback arguments are passed as `undefined`.

Expand Down
2 changes: 1 addition & 1 deletion reference/Types.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Status of built-in methods and properties on GiavaScript runtime types.

### Array callback argument behavior

- In array methods that accept callbacks, user-defined callback functions use JavaScript-compatible argument normalization.
- In array methods that accept callbacks, JavaScript-compatible argument normalization applies to user-defined function expressions and references to function declarations.
- Extra callback arguments are ignored.
- Missing callback arguments are passed as `undefined`.

Expand Down
20 changes: 20 additions & 0 deletions spec/giavascript_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,26 @@ describe GiavaScript do
interpreter.eval("[1, 2, 3].reduce(function(acc, value) { return acc + value; }, 0);").should eq(["6"])
end

it "uses JS-compatible callback arity for function declaration references" do
interpreter = GiavaScript::Interpreter.new
interpreter.eval("function double(value) { return value * 2; }").should eq([] of String)
interpreter.eval("[1, 2, 3].map(double);").should eq(["[2, 4, 6]"])

interpreter.eval("function takeFourth(value, index, source, extra) { return extra; }").should eq([] of String)
interpreter.eval("[1, 2].map(takeFourth);").should eq(["[undefined, undefined]"])

interpreter.eval("function sum(acc, value) { return acc + value; }").should eq([] of String)
interpreter.eval("[1, 2, 3].reduce(sum, 0);").should eq(["6"])
end

it "keeps strict arity for non-callback function declaration calls" do
interpreter = GiavaScript::Interpreter.new
interpreter.eval("function addOne(x) { return x + 1; }").should eq([] of String)
interpreter.eval("addOne(1, 2);").should eq(["Error: function 'addOne' expects 1 arguments but got 2"])
interpreter.eval("var ref = addOne;").should eq([] of String)
interpreter.eval("ref(1, 2);").should eq(["Error: function 'addOne' expects 1 arguments but got 2"])
end

it "supports Array.reduce with and without an initial value" do
interpreter = GiavaScript::Interpreter.new
interpreter.eval("[1, 2, 3].reduce(function(acc, value, index, array) { return acc + value + index + array.length; }, 0);").should eq(["18"])
Expand Down
7 changes: 7 additions & 0 deletions src/giavascript/function_runtime.cr
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ module GiavaScript
@functions.has_key?(name)
end

def function_parameter_count(name : String) : Int32?
function = @functions[name]?
return nil unless function

function.parameters.size
end

def invoke_function(name : String, args : Array(Value), outer_env : Environment, &evaluate_statement : String, Environment, Bool, Bool -> String?) : Value
function = @functions[name]?
raise ExpressionError.new("Error: function '#{name}' does not exist") unless function
Expand Down
7 changes: 6 additions & 1 deletion src/giavascript/interpreter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@ module GiavaScript
private def resolve_function_reference(name : String, env : Environment) : BuiltinFunction?
return nil unless @function_runtime.function_defined?(name)

BuiltinFunction.new(name, ->(_receiver : Value, args : Array(Value)) { call_function(name, args, env).as(Value) })
callback_arity_resolver = -> { @function_runtime.function_parameter_count(name) }
BuiltinFunction.new(
name,
->(_receiver : Value, args : Array(Value)) { call_function(name, args, env).as(Value) },
callback_arity_resolver
)
end

private def parse_assignment_target(lhs : String) : Expr
Expand Down
24 changes: 21 additions & 3 deletions src/giavascript/runtime_types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ module GiavaScript
class BuiltinFunction
getter name : String

def initialize(@name : String, @body : BuiltinMethodBody)
def initialize(
@name : String,
@body : BuiltinMethodBody,
@callback_arity_resolver : Proc(Int32?)? = nil,
)
end

def call(receiver : Value, args : Array(Value)) : Value
Expand All @@ -18,6 +22,13 @@ module GiavaScript
def to_s(io : IO)
io << "[builtin " << @name << "]"
end

def callback_arity : Int32?
resolver = @callback_arity_resolver
return nil unless resolver

resolver.call
end
end

module RuntimeTypes
Expand Down Expand Up @@ -964,9 +975,9 @@ module GiavaScript
end

private def normalize_callback_args(callback : Value, args : Array(Value)) : Array(Value)
return args unless callback.is_a?(UserFunction)
expected_count = callback_expected_arity(callback)
return args unless expected_count

expected_count = callback.parameters.size
provided_count = args.size
return args if expected_count == provided_count

Expand All @@ -981,6 +992,13 @@ module GiavaScript
normalized
end

private def callback_expected_arity(callback : Value) : Int32?
return callback.parameters.size if callback.is_a?(UserFunction)
return callback.callback_arity if callback.is_a?(BuiltinFunction)

nil
end

private def string_argument(value : Value, method_name : String) : String
return value if value.is_a?(String)
raise ExpressionError.new("Error: #{method_name} expects a string argument")
Expand Down
Loading