@@ -499,20 +499,53 @@ JavaScriptCompiler.prototype = {
499499 this . pushStackLiteral ( omitEmpty ? 'undefined' : '{}' ) ;
500500 } ,
501501 pushHash : function ( ) {
502- if ( this . hash ) {
503- this . hashes . push ( this . hash ) ;
502+ if ( this . hashPieces ) {
503+ this . hashes . push ( this . hashPieces ) ;
504504 }
505- this . hash = { values : [ ] , types : [ ] , contexts : [ ] , ids : [ ] } ;
505+ this . hashPieces = [ ] ;
506506 } ,
507- popHash : function ( splat ) {
508- let hash = this . hash ;
509- this . hash = this . hashes . pop ( ) ;
510-
511- this . push ( this . objectLiteral ( hash . values ) ) ;
512- if ( splat ) {
513- let splatIdentifier = 'depth' + splat . depth + '.' + splat . original ; // or parts[0] ? what's the different
514- this . push ( [ this . aliasable ( 'container.splat' ) , '(' , this . popStack ( ) , ', ' , splatIdentifier , ')' ] ) ;
507+ popHash : function ( ) {
508+ let hashPieces = this . hashPieces ;
509+ this . hashPieces = this . hashes . pop ( ) ;
510+ this . hashPiece = this . hashPieces && this . hashPieces [ this . hashPieces . length - 1 ] ;
511+
512+ let splatParams = [ ] ;
513+ for ( let i = 0 , l = hashPieces . length ; i < l ; ++ i ) {
514+ let piece = hashPieces [ i ] ;
515+ if ( piece . type === 'pairs' ) {
516+ splatParams . push ( this . objectLiteral ( piece . values ) ) ;
517+ } else {
518+ splatParams . push ( piece . value ) ;
519+ }
515520 }
521+
522+ if ( hashPieces . length === 1 ) {
523+ // don't splat(); just use single piece as hash
524+ this . push ( splatParams [ 0 ] ) ;
525+ } else {
526+ if ( hashPieces [ 0 ] . type === 'splat' ) {
527+ // we merge into an empty POJO so that we don't mutate first splat param
528+ splatParams . unshift ( '{}' ) ;
529+ }
530+
531+ this . push ( [ this . aliasable ( 'container.splat' ) , '(' ] . concat ( splatParams . join ( ',' ) , ')' ) ) ;
532+ }
533+ } ,
534+
535+ pushHashPiece : function ( ) {
536+ this . hashPiece = { type : 'pairs' , values : { } } ;
537+ this . hashPieces . push ( this . hashPiece ) ;
538+ } ,
539+
540+ // [pushSplatHashPiece]
541+ //
542+ // On stack, before: value, ..., hash, ...
543+ // On stack, after: ..., hash, ...
544+ //
545+ // Pops a splat value off the stack and pushes it to hashPieces
546+ pushSplatHashPiece : function ( ) {
547+ this . hashPiece = null ;
548+ this . hashPieces . push ( { type : 'splat' , value : this . popStack ( ) } ) ;
516549 } ,
517550
518551 // [pushString]
@@ -580,7 +613,7 @@ JavaScriptCompiler.prototype = {
580613 // and pushes the helper's return value onto the stack.
581614 //
582615 // If the helper is not found, `helperMissing` is called.
583- invokeHelper : function ( paramSize , name , isSimple ) {
616+ invokeHelper : function ( paramSize , name , isSimple , splatMap ) {
584617 let nonHelper = this . popStack ( ) ,
585618 helper = this . setupHelper ( paramSize , name ) ,
586619 simple = isSimple ? [ helper . name , ' || ' ] : '' ;
@@ -591,7 +624,7 @@ JavaScriptCompiler.prototype = {
591624 }
592625 lookup . push ( ')' ) ;
593626
594- this . push ( this . source . functionCall ( lookup , 'call' , helper . callParams ) ) ;
627+ this . push ( this . helperFunctionCall ( lookup , helper , splatMap ) ) ;
595628 } ,
596629
597630 // [invokeKnownHelper]
@@ -601,9 +634,9 @@ JavaScriptCompiler.prototype = {
601634 //
602635 // This operation is used when the helper is known to exist,
603636 // so a `helperMissing` fallback is not required.
604- invokeKnownHelper : function ( paramSize , name ) {
637+ invokeKnownHelper : function ( paramSize , name , splatMap ) {
605638 let helper = this . setupHelper ( paramSize , name ) ;
606- this . push ( this . source . functionCall ( helper . name , 'call' , helper . callParams ) ) ;
639+ this . push ( this . helperFunctionCall ( helper . name , helper , splatMap ) ) ;
607640 } ,
608641
609642 // [invokeAmbiguous]
@@ -641,10 +674,27 @@ JavaScriptCompiler.prototype = {
641674 '(' , lookup ,
642675 ( helper . paramsInit ? [ '),(' , helper . paramsInit ] : [ ] ) , '),' ,
643676 '(typeof helper === ' , this . aliasable ( '"function"' ) , ' ? ' ,
644- this . source . functionCall ( 'helper' , 'call' , helper . callParams ) , ' : helper))'
677+ this . helperFunctionCall ( 'helper' , helper , null ) , ' : helper))'
645678 ] ) ;
646679 } ,
647680
681+ helperFunctionCall : function ( helperName , helperOptions , splatMap ) {
682+ if ( splatMap ) {
683+ let splatMapObj = { } ;
684+ for ( let i = 0 , l = splatMap . length ; i < l ; ++ i ) {
685+ splatMapObj [ splatMap [ i ] ] = 1 ;
686+ }
687+
688+ let argsWithSplatMap = helperOptions . params . slice ( ) ;
689+ argsWithSplatMap . push ( this . objectLiteral ( splatMapObj ) ) ;
690+ let splattedArgs = this . source . functionCall ( this . aliasable ( 'container.splatArgs' ) , null , argsWithSplatMap ) ;
691+ return this . source . functionCall ( helperName , 'apply' , [ helperOptions . callContext , splattedArgs ] ) ;
692+ } else {
693+ let args = [ helperOptions . callContext ] . concat ( helperOptions . params ) ;
694+ return this . source . functionCall ( helperName , 'call' , args ) ;
695+ }
696+ } ,
697+
648698 // [invokePartial]
649699 //
650700 // On stack, before: context, ...
@@ -688,9 +738,9 @@ JavaScriptCompiler.prototype = {
688738 // On stack, before: value, ..., hash, ...
689739 // On stack, after: ..., hash, ...
690740 //
691- // Pops a value off the stack and assigns it to the current hash
741+ // Pops a value off the stack and assigns it to the current hash piece
692742 assignToHash : function ( key ) {
693- this . hash . values [ key ] = this . popStack ( ) ;
743+ this . hashPiece . values [ key ] = this . popStack ( ) ;
694744 } ,
695745
696746 // HELPERS
@@ -911,13 +961,14 @@ JavaScriptCompiler.prototype = {
911961 let params = [ ] ,
912962 paramsInit = this . setupHelperArgs ( name , paramSize , params , blockHelper ) ;
913963 let foundHelper = this . nameLookup ( 'helpers' , name , 'helper' ) ,
914- callContext = this . aliasable ( `${ this . contextName ( 0 ) } != null ? ${ this . contextName ( 0 ) } : {}` ) ;
964+ contextName = this . contextName ( 0 ) ,
965+ callContext = this . aliasable ( `${ contextName } != null ? ${ contextName } : {}` ) ;
915966
916967 return {
917- params : params ,
918- paramsInit : paramsInit ,
968+ params,
969+ paramsInit,
919970 name : foundHelper ,
920- callParams : [ callContext ] . concat ( params )
971+ callContext
921972 } ;
922973 } ,
923974
0 commit comments