@@ -65,6 +65,8 @@ namespace Rice::detail
6565 // Execute the function but make sure to catch any C++ exceptions!
6666 return cpp_protect ([&]()
6767 {
68+ std::map<std::string, VALUE> values = readRubyArgs (argc, argv);
69+
6870 const std::vector<std::unique_ptr<Native>>& natives = Registries::instance.natives .lookup (klass, methodId);
6971
7072 if (natives.size () == 1 )
@@ -86,7 +88,7 @@ namespace Rice::detail
8688 std::back_inserter (resolves),
8789 [&](const std::unique_ptr<Native>& native)
8890 {
89- return native->matches (argc, argv );
91+ return native->matches (values );
9092 });
9193
9294 // Now sort from best to worst
@@ -146,7 +148,7 @@ namespace Rice::detail
146148 }
147149
148150 // Call the C++ function
149- return (*native)(argc, argv , self);
151+ return (*native)(values , self);
150152 });
151153 }
152154
@@ -290,14 +292,8 @@ namespace Rice::detail
290292 // Fill in missing args
291293 for (size_t i = argsVector.size (); i < std::tuple_size_v<Parameter_Tuple>; i++)
292294 {
293- argsVector.emplace_back (std::make_unique<Arg>(" arg_" + std::to_string (i)));
294- }
295-
296- // TODO - there has to be a better way!
297- for (size_t i = 0 ; i < argsVector.size (); i++)
298- {
299- std::unique_ptr<Arg>& arg = argsVector[i];
300- arg->position = (int32_t )i;
295+ std::string argName = " arg_" + std::to_string (i);
296+ argsVector.emplace_back (std::make_unique<Arg>(argName));
301297 }
302298
303299 auto indices = std::make_index_sequence<std::tuple_size_v<Parameter_Tuple>>{};
@@ -328,81 +324,116 @@ namespace Rice::detail
328324 return result;
329325 }
330326
331- inline std::vector <std::optional< VALUE>> Native::getRubyValues (size_t argc, const VALUE* argv, bool validate )
327+ inline std::map <std::string, VALUE> Native::readRubyArgs (size_t argc, const VALUE* argv)
332328 {
333- #undef max
334- size_t size = std::max (this ->parameters_ .size (), argc);
335- std::vector<std::optional<VALUE>> result (size);
329+ std::map<std::string, VALUE> result;
336330
337331 // Keyword handling
338332 if (rb_keyword_given_p ())
339333 {
340334 // Keywords are stored in the last element in a hash
341335 size_t actualArgc = argc - 1 ;
342336
343- VALUE value = argv[actualArgc];
344- Hash keywords (value);
345-
346337 // Copy over leading non-keyword arguments
347338 for (size_t i = 0 ; i < actualArgc; i++)
348339 {
349- result[i] = argv[i];
340+ std::string key = " arg_" + std::to_string (i);
341+ result[key] = argv[i];
350342 }
351343
344+ VALUE value = argv[actualArgc];
345+ Hash keywords (value);
346+
352347 // Copy over keyword arguments
353348 for (auto pair : keywords)
354349 {
355- Symbol key (pair.first );
356- ParameterAbstract* parameter = this ->getParameterByName (key.str ());
357- if (!parameter && validate)
358- {
359- throw std::invalid_argument (" Unknown keyword: " + key.str ());
360- }
361- else if (!parameter)
362- {
363- continue ;
364- }
365-
366- const Arg* arg = parameter->arg ();
367-
368- result[arg->position ] = pair.second .value ();
350+ result[pair.first .to_s ().str ()] = pair.second .value ();
369351 }
370352 }
371353 else
372354 {
373- std::copy (argv, argv + argc, result.begin ());
355+ // Copy over leading non-keyword arguments
356+ for (size_t i = 0 ; i < argc; i++)
357+ {
358+ std::string key = " arg_" + std::to_string (i);
359+ result[key] = argv[i];
360+ }
374361 }
375362
376363 // Block handling. If we find a block and the last parameter is missing then
377364 // set it to the block
378- if (rb_block_given_p () && result.size () > 0 && !result.back ().has_value ())
365+ if (rb_block_given_p ()) // FIXME && result.size() > 0 && !result.back().second .has_value())
379366 {
380367 VALUE proc = rb_block_proc ();
381- result.back () = proc;
368+ std::string key = " arg_" + std::to_string (result.size ());
369+ result[key] = proc;
382370 }
383371
384- if (validate)
372+ return result;
373+ }
374+
375+ inline std::vector<std::optional<VALUE>> Native::getRubyValues (std::map<std::string, VALUE> values, bool validate)
376+ {
377+ // !!!NOTE!!! We copied the values parameter because we are going to modify it!
378+
379+ // Protect against user sending too many arguments
380+ if (values.size () > this ->parameters_ .size ())
381+ {
382+ std::string message = " wrong number of arguments (given " +
383+ std::to_string (values.size ()) + " , expected " + std::to_string (this ->parameters_ .size ()) + " )" ;
384+ throw std::invalid_argument (message);
385+ }
386+
387+ std::vector<std::optional<VALUE>> result (this ->parameters_ .size ());
388+
389+ for (size_t i=0 ; i< this ->parameters_ .size (); i++)
385390 {
386- // Protect against user sending too many arguments
387- if (argc > this ->parameters_ .size ())
391+ std::unique_ptr<ParameterAbstract>& parameter = this ->parameters_ [i];
392+ Arg* arg = parameter->arg ();
393+
394+ // If using keywords arguments, then the value key will be arg->name(). If using positional
395+ // arguments then they key will be "arg_<position>"
396+ std::string keywordKey = arg->name ;
397+ std::string positionKey = " arg_" + std::to_string (i);
398+
399+ auto iter = values.find (keywordKey);
400+ if (iter == values.end () && keywordKey != positionKey)
388401 {
389- std::string message = " wrong number of arguments (given " +
390- std::to_string (argc) + " , expected " + std::to_string (this ->parameters_ .size ()) + " )" ;
391- throw std::invalid_argument (message);
402+ iter = values.find (positionKey);
392403 }
393404
394- for (size_t i = 0 ; i < result.size (); i++)
405+ if (iter != values.end ())
406+ {
407+ result[i] = iter->second ;
408+ // Remove the value
409+ values.erase (iter);
410+ }
411+ else if (arg->hasDefaultValue ())
412+ {
413+ result[i] = parameter->defaultValueRuby ();
414+ }
415+ else if (validate)
395416 {
396- std::optional<VALUE> value = result[i];
397- ParameterAbstract* parameter = this ->parameters_ [i].get ();
417+ std::string message = " Missing argument. Name: " + arg->name + " . Index: " + std::to_string (i) + " ." ;
418+ throw std::invalid_argument (message);
419+ }
420+ }
398421
399- if (!parameter->arg ()->hasDefaultValue () && !value.has_value ())
400- {
401- std::string message;
402- message = " Missing argument. Name: " + parameter->arg ()->name + " . Index: " + std::to_string (parameter->arg ()->position ) + " ." ;
403- throw std::invalid_argument (message);
404- }
422+ // Check for unknown arguments
423+ if (validate && values.size () > 0 )
424+ {
425+ // There are unknown arguments
426+ std::ostringstream message;
427+ message << " Unknown argument(s): " ;
428+ size_t count = 0 ;
429+ for (const std::pair<const std::string, VALUE>& pair : values)
430+ {
431+ if (count > 0 )
432+ message << " , " ;
433+ message << pair.first ;
434+ count++;
405435 }
436+ throw std::invalid_argument (message.str ());
406437 }
407438
408439 return result;
@@ -420,25 +451,20 @@ namespace Rice::detail
420451 return result;
421452 }
422453
423- inline Resolved Native::matches (size_t argc, const VALUE* argv )
454+ inline Resolved Native::matches (std::map<std::string, VALUE>& values )
424455 {
425456 // Return false if Ruby provided more arguments than the C++ method takes
426- if (argc > this ->parameters_ .size ())
457+ if (values. size () > this ->parameters_ .size ())
427458 return Resolved{ Convertible::None, 0 , this };
428459
429460 Resolved result{ Convertible::Exact, 1 , this };
430461
431- std::vector<std::optional<VALUE>> rubyValues = this ->getRubyValues (argc, argv , false );
462+ std::vector<std::optional<VALUE>> rubyValues = this ->getRubyValues (values , false );
432463 result.convertible = this ->matchParameters (rubyValues);
433464
434465 if (this ->parameters_ .size () > 0 )
435466 {
436- size_t providedValues = std::count_if (rubyValues.begin (), rubyValues.end (), [](std::optional<VALUE>& value)
437- {
438- return value.has_value ();
439- });
440-
441- result.parameterMatch = providedValues / (double )this ->parameters_ .size ();
467+ result.parameterMatch = values.size () / (double )this ->parameters_ .size ();
442468 }
443469 return result;
444470 }
0 commit comments