Skip to content

Instantly share code, notes, and snippets.

@machty

machty/sexprs.md Secret

Created December 26, 2013 02:57
Show Gist options
  • Save machty/2091198e5cba9fe0163f to your computer and use it in GitHub Desktop.
Save machty/2091198e5cba9fe0163f to your computer and use it in GitHub Desktop.
subexpressions r hrad

Subexpressions trouble

Here's why things do not Just Work reuse the same machinery for mustaches/sexprs recursively to handle subexpressions:

Duplication of recursive trees

Every helper invocation (for helpers not explicitly listed in knownHelpers) must include a ternary else invocation of helperMissing in case the helper isn't found in Handlebars.helpers. Unless we fundamentally change the way these strings of code are built up, this results in long strings of logic for evaluating internal subexpressions being duplicated between the found helper invocation and helper missing invocation. Example:

Pseudo-code for evaluating {{equal (equal true true) true}}

bigassStringToCalculateInnerEqual = 
  if helpers.equal
    helpers.equal(true, true, options)
  else
    helperMissing("equal", true, true, options)

if helpers.equal
  helpers.equal(bigassStringToCalculateInnerEqual, true, options)
else
  helperMissing("equal", bigassStringToCalculateInnerEqual, true, options)

When this is all stringified, you have megalong duplication between the various branches of code. Multiple nesting results in insanely long compiled teplate code.

options hash preservation

The same options object needs to be provided to either the helper or helperMissing, so its saved to the options var, and then both the helper and helperMissing invocation receive options as the last parameter.

But because the original code wasn't written to support subexpressions, the resulting compiled template code produced something like this whenever compiling subexpressions

options = {hash:{},data:data};
options = {hash:{},data:data};
options = {hash:{},data:data};

deeplyNestedRecursiveInvocationsOfHelperOrHelperMissing(...)

So each nested invocation added an options = ... that clobbered the previous. I already took a stab at moving the options = to within the giant deeply nested string of invocations, but the problem is that, unlike stack1 (which temporarily stores the helper being invoked before getting replaced by an inner helper), the options var doesn't seem like it can be used multiple time for nested invocations.

Consider {{equal (doSomething) true}}:

  1. options = { stuff for equal }
  2. invoke equal
  3. options = { stuff for doSomething }
  4. invoke doSomething

Jesus this is hard to explain. But press on I will.

(stack1 = helpers.equal), options = {options4equal}, stack1 ?  stack1.call(d, (internalStuff), options) : helperMissing('doSomething', (internalStuff), options)

internalStuff ->
  (stack1 = helpers.doSomething), options = {options4doSomething}, stack1 ? stack1.call(d, options) : helperMissing('doSomething', options)

Basically, we have this neato way for reusing the stack1 variable that's incompatible with storing the options in a single variable when we have nested subexpressions, because the first options will be set to

options = {options4Equal}

and then on the inner nested expression evaluation it sets

options = {options4doSomething}

Which overwrites the previous options, so the options that ends up getting passed to equal is options4doSomething, which is obviously bad.

Solution?

I don't know. I need to think on it. This seems like some elementary shit for anyone who's implemented a Lisp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment