@@ -221,16 +221,19 @@ static Expected<Value, Error> resolveHelperFunction(
221221 std::string_view name,
222222 std::string_view script)
223223{
224+ Error firstErr (" code did not evaluate to a function" );
225+
224226 if (auto exp = scope.eval (script))
225227 {
226228 if (exp->isFunction ())
227229 return *exp;
228230 }
229231 else
230232 {
231- return Unexpected ( exp.error () );
233+ firstErr = exp.error ();
232234 }
233235
236+ // If direct eval failed or did not yield a function, try parenthesized expression
234237 std::string wrapped;
235238 wrapped.reserve (script.size () + 2 );
236239 wrapped.push_back (' (' );
@@ -254,7 +257,9 @@ static Expected<Value, Error> resolveHelperFunction(
254257 return candidate;
255258 }
256259
257- return Unexpected (Error (std::string (" helper is not a function: " ) + std::string (name)));
260+ return Unexpected (firstErr.message ().empty ()
261+ ? Error (std::string (" helper is not a function: " ) + std::string (name))
262+ : firstErr);
258263}
259264
260265Type Value::type () const noexcept
@@ -926,7 +931,13 @@ registerHelper(
926931 std::string_view script)
927932{
928933 Scope scope (ctx);
929- MRDOCS_TRY (Value fn, resolveHelperFunction (scope, name, script));
934+ auto fnExp = resolveHelperFunction (scope, name, script);
935+ if (!fnExp)
936+ {
937+ report::error (" registerHelper '{}' failed: {}" , name, fnExp.error ().message ());
938+ return Unexpected (fnExp.error ());
939+ }
940+ Value fn = *fnExp;
930941
931942 // Store helper on global object (preserve existing helpers if present)
932943 Value helpers = scope.getGlobal (" MrDocsHelpers" ).value_or (Value{});
@@ -938,31 +949,25 @@ registerHelper(
938949 helpers.set (name, fn);
939950
940951 hbs.registerHelper (std::string (name), dom::makeVariadicInvocable ([
941- fn, impl = ctx. impl_ ](dom::Array const & args)->Expected <dom::Value, Error>
952+ fn, helperName = std::string (name) ](dom::Array const & args)->Expected <dom::Value, Error>
942953 {
943954 std::vector<dom::Value> vec (args.begin (), args.end ());
944-
945- // Handlebars passes an options object last; drop it so helpers see only
946- // user-supplied parameters (legacy behavior), but keep positional args intact.
947- if (!vec.empty () && isOptionsObject (vec.back ()))
948- vec.pop_back ();
949-
950- auto ret = fn.call (vec);
955+ const bool keepOptions = helperName == " optHash" || helperName == " ifx" || helperName == " wrap" || helperName == " opt" ;
956+ const bool hasOptionsArg = !vec.empty () && isOptionsObject (vec.back ());
957+
958+ std::vector<dom::Value> callArgs;
959+ callArgs.reserve (vec.size ());
960+ std::size_t limit = vec.size ();
961+ if (!keepOptions && hasOptionsArg)
962+ limit -= 1 ; // drop trailing options for simple helpers
963+ for (std::size_t i = 0 ; i < limit; ++i)
964+ callArgs.push_back (vec[i]);
965+ if (keepOptions && hasOptionsArg)
966+ callArgs.push_back (vec.back ());
967+
968+ auto ret = fn.call (callArgs);
951969 if (!ret) return Unexpected (ret.error ());
952- auto domRet = ret->getDom ();
953- if (domRet.isString () && vec.size () >= 2 )
954- {
955- auto s = std::string (domRet.getString ());
956- if (s.size () >= 9 && s.rfind (" undefined" ) == s.size () - 9 )
957- {
958- // Fallback: if the JS helper returned the argument list string with a
959- // trailing undefined (caused by Handlebars options leakage), compute
960- // the sum of the first two numeric arguments as earlier behavior.
961- if (vec[0 ].isInteger () && vec[1 ].isInteger ())
962- domRet = dom::Value (vec[0 ].getInteger () + vec[1 ].getInteger ());
963- }
964- }
965- return domRet;
970+ return ret->getDom ();
966971 }));
967972
968973 return {};
0 commit comments