@@ -81,6 +81,11 @@ class TypedInput {
8181 return `(+${ this . source } || 0)` ;
8282 }
8383
84+ asInt ( ) {
85+ if ( this . type === TYPE_NUMBER || this . type === TYPE_NUMBER_NAN ) return `(${ this . source } | 0)` ;
86+ return `(+${ this . source } | 0)` ;
87+ }
88+
8489 asNumberOrNaN ( ) {
8590 if ( this . type === TYPE_NUMBER || this . type === TYPE_NUMBER_NAN ) return this . source ;
8691 return `(+${ this . source } )` ;
@@ -150,6 +155,17 @@ class ConstantInput {
150155 return '0' ;
151156 }
152157
158+ asInt ( ) {
159+ const numberValue = + this . constantValue ;
160+ if ( numberValue ) {
161+ return ( numberValue | 0 ) . toString ( ) ;
162+ }
163+ if ( Object . is ( numberValue , - 0 ) ) {
164+ return '-0' ;
165+ }
166+ return '0' ;
167+ }
168+
153169 asNumberOrNaN ( ) {
154170 return this . asNumber ( ) ;
155171 }
@@ -261,6 +277,12 @@ class VariableInput {
261277 return `(+${ this . source } || 0)` ;
262278 }
263279
280+ asInt ( ) {
281+ if ( this . type === TYPE_NUMBER ||
282+ this . type === TYPE_NUMBER_NAN ) return `(${ this . source } | 0)` ;
283+ return `(+${ this . source } | 0)` ;
284+ }
285+
264286 asNumberOrNaN ( ) {
265287 if ( this . type === TYPE_NUMBER || this . type === TYPE_NUMBER_NAN ) return this . source ;
266288 return `(+${ this . source } )` ;
@@ -394,7 +416,8 @@ const MATH_CACHE = {
394416 LN10 : 'const LN10=Math.LN10;' ,
395417 pow : 'const pow=Math.pow;' ,
396418 max : 'const max=Math.max;' ,
397- min : 'const min=Math.min;'
419+ min : 'const min=Math.min;' ,
420+ random : 'const random=runtime.ext_scratch3_operators._random;'
398421} ;
399422
400423class JSGenerator {
@@ -505,12 +528,14 @@ class JSGenerator {
505528 case 'list.get' : {
506529 const index = this . descendInput ( node . index ) ;
507530 if ( environment . supportsNullishCoalescing ) {
531+ if ( index instanceof ConstantInput && index . isAlwaysNumber ( ) ) {
532+ const indexValue = + index . constantValue ;
533+ if ( ! isNaN ( indexValue ) ) return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[${ ( indexValue | 0 ) - 1 } ] ?? "")` , TYPE_UNKNOWN ) ;
534+ return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[(${ index . asInt ( ) } ) - 1] ?? "")` , TYPE_UNKNOWN ) ;
535+ }
508536 if ( index . isAlwaysNumberOrNaN ( ) ) {
509- if ( ! isNaN ( index ) ) {
510- return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[${ index - 1 } ] ?? "")` , TYPE_UNKNOWN ) ;
511- }
512- return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[${ index . asNumber ( ) } - 1] ?? "")` , TYPE_UNKNOWN ) ;
513-
537+ if ( ! isNaN ( index ) ) return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[${ ( index - 1 ) | 0 } ] ?? "")` , TYPE_UNKNOWN ) ;
538+ return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[${ index . asInt ( ) } - 1] ?? "")` , TYPE_UNKNOWN ) ;
514539 }
515540 if ( index instanceof ConstantInput && index . constantValue === 'last' ) {
516541 return new TypedInput ( `(${ this . referenceVariable ( node . list ) } .value[${ this . referenceVariable ( node . list ) } .value.length - 1] ?? "")` , TYPE_UNKNOWN ) ;
@@ -564,9 +589,58 @@ class JSGenerator {
564589 this . usedMathFunctions . add ( 'acos' ) ;
565590 this . usedMathFunctions . add ( 'RAD_TO_DEG' ) ;
566591 return new TypedInput ( `(acos(${ this . descendInput ( node . value ) . asNumber ( ) } )*RAD_TO_DEG)` , TYPE_NUMBER_NAN ) ;
567- case 'op.add' :
592+ case 'op.add' : {
568593 // Needs to be marked as NaN because Infinity + -Infinity === NaN
569- return new TypedInput ( `(${ this . descendInput ( node . left ) . asNumber ( ) } + ${ this . descendInput ( node . right ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
594+ const left = this . descendInput ( node . left ) ;
595+ const right = this . descendInput ( node . right ) ;
596+ if ( left instanceof ConstantInput && right instanceof ConstantInput && left . isAlwaysNumber ( ) && right . isAlwaysNumber ( ) ) {
597+ const value = + left . constantValue + + right . constantValue ;
598+ if ( ! isNaN ( value ) ) return new ConstantInput ( value . toString ( ) , TYPE_NUMBER ) ;
599+
600+ return new TypedInput ( `(${ left . asNumber ( ) } + ${ right . asNumber ( ) } )` , TYPE_NUMBER ) ;
601+ }
602+ return new TypedInput ( `(${ left . asNumber ( ) } + ${ right . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
603+ }
604+ case 'op.subtract' : {
605+ const left = this . descendInput ( node . left ) ;
606+ const right = this . descendInput ( node . right ) ;
607+ if ( left instanceof ConstantInput && right instanceof ConstantInput && left . isAlwaysNumber ( ) && right . isAlwaysNumber ( ) ) {
608+ const value = + left . constantValue - + right . constantValue ;
609+ if ( ! isNaN ( value ) ) return new ConstantInput ( value . toString ( ) , TYPE_NUMBER ) ;
610+
611+ return new TypedInput ( `(${ left . asNumber ( ) } - ${ right . asNumber ( ) } )` , TYPE_NUMBER ) ;
612+ }
613+ // Needs to be marked as NaN because Infinity - Infinity === NaN
614+ return new TypedInput ( `(${ left . asNumber ( ) } - ${ right . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
615+ }
616+ case 'op.multiply' : {
617+ const left = this . descendInput ( node . left ) ;
618+ const right = this . descendInput ( node . right ) ;
619+ if ( right instanceof ConstantInput && right . isAlwaysNumber ( ) && + right . constantValue !== 0 ) {
620+ if ( left instanceof ConstantInput && left . isAlwaysNumber ( ) ) {
621+ const value = + left . constantValue * + right . constantValue ;
622+ if ( ! isNaN ( value ) ) return new ConstantInput ( value . toString ( ) , TYPE_NUMBER ) ;
623+
624+ return new TypedInput ( `(${ left . asNumber ( ) } * ${ right . asNumber ( ) } )` , TYPE_NUMBER ) ;
625+ }
626+ }
627+ // Needs to be marked as NaN because Infinity * 0 === NaN
628+ return new TypedInput ( `(${ left . asNumber ( ) } * ${ right . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
629+ }
630+ case 'op.divide' : {
631+ // Needs to be marked as NaN because 0 / 0 === NaN
632+ const left = this . descendInput ( node . left ) ;
633+ const right = this . descendInput ( node . right ) ;
634+ if ( right instanceof ConstantInput && right . isAlwaysNumber ( ) && + right . constantValue !== 0 ) {
635+ if ( left instanceof ConstantInput && left . isAlwaysNumber ( ) ) {
636+ const value = + left . constantValue / + right . constantValue ;
637+ // at this point we know it cant be NaN because right is nonzero
638+ return new ConstantInput ( value . toString ( ) , TYPE_NUMBER ) ;
639+ }
640+ return new TypedInput ( `(${ left . asNumber ( ) } / ${ right . asNumber ( ) } )` , TYPE_NUMBER ) ;
641+ }
642+ return new TypedInput ( `(${ left . asNumber ( ) } / ${ right . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
643+ }
570644 case 'op.and' :
571645 return new TypedInput ( `(${ this . descendInput ( node . left ) . asBoolean ( ) } && ${ this . descendInput ( node . right ) . asBoolean ( ) } )` , TYPE_BOOLEAN ) ;
572646 case 'op.asin' :
@@ -588,9 +662,6 @@ class JSGenerator {
588662 this . usedMathFunctions . add ( 'round' ) ;
589663 this . usedMathFunctions . add ( 'PI' ) ;
590664 return new TypedInput ( `(round(cos((PI * ${ this . descendInput ( node . value ) . asNumber ( ) } ) / 180) * 1e10) / 1e10)` , TYPE_NUMBER_NAN ) ;
591- case 'op.divide' :
592- // Needs to be marked as NaN because 0 / 0 === NaN
593- return new TypedInput ( `(${ this . descendInput ( node . left ) . asNumber ( ) } / ${ this . descendInput ( node . right ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
594665 case 'op.equals' : {
595666 let left = this . descendInput ( node . left ) ;
596667 let right = this . descendInput ( node . right ) ;
@@ -604,6 +675,10 @@ class JSGenerator {
604675 const rightAlwaysNumber = right . isAlwaysNumber ( ) ;
605676 // When both operands are known to be numbers, we can use ===
606677 if ( leftAlwaysNumber && rightAlwaysNumber ) {
678+ if ( left instanceof ConstantInput && right instanceof ConstantInput ) {
679+ const value = + left . constantValue === + right . constantValue ;
680+ if ( ! isNaN ( value ) ) return new TypedInput ( value . toString ( ) , TYPE_BOOLEAN ) ;
681+ }
607682 return new TypedInput ( `(${ left . asNumber ( ) } === ${ right . asNumber ( ) } )` , TYPE_BOOLEAN ) ;
608683 }
609684 // In certain conditions, we can use === when one of the operands is known to be a safe number.
@@ -688,9 +763,6 @@ class JSGenerator {
688763 return new TypedInput ( 'PI' , TYPE_NUMBER ) ;
689764 case 'op.newline' :
690765 return new TypedInput ( '"\n"' , TYPE_STRING ) ;
691- case 'op.multiply' :
692- // Needs to be marked as NaN because Infinity * 0 === NaN
693- return new TypedInput ( `(${ this . descendInput ( node . left ) . asNumber ( ) } * ${ this . descendInput ( node . right ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
694766 case 'op.not' :
695767 return new TypedInput ( `!${ this . descendInput ( node . operand ) . asBoolean ( ) } ` , TYPE_BOOLEAN ) ;
696768 case 'op.or' :
@@ -703,7 +775,8 @@ class JSGenerator {
703775 if ( node . useFloats ) {
704776 return new TypedInput ( `randomFloat(${ this . descendInput ( node . low ) . asNumber ( ) } , ${ this . descendInput ( node . high ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
705777 }
706- return new TypedInput ( `runtime.ext_scratch3_operators._random(${ this . descendInput ( node . low ) . asUnknown ( ) } , ${ this . descendInput ( node . high ) . asUnknown ( ) } )` , TYPE_NUMBER_NAN ) ;
778+ this . usedMathFunctions . add ( 'random' ) ;
779+ return new TypedInput ( `random(${ this . descendInput ( node . low ) . asUnknown ( ) } , ${ this . descendInput ( node . high ) . asUnknown ( ) } )` , TYPE_NUMBER_NAN ) ;
707780 case 'op.round' :
708781 this . usedMathFunctions . add ( 'round' ) ;
709782 return new TypedInput ( `round(${ this . descendInput ( node . value ) . asNumber ( ) } )` , TYPE_NUMBER ) ;
@@ -715,10 +788,11 @@ class JSGenerator {
715788 case 'op.sqrt' :
716789 // Needs to be marked as NaN because Math.sqrt(-1) === NaN
717790 this . usedMathFunctions . add ( 'sqrt' ) ;
791+ if ( node . value instanceof ConstantInput && node . value . isAlwaysNumber ( ) ) {
792+ if ( + node . value . constantValue < 0 ) return new TypedInput ( '0' , TYPE_NUMBER ) ;
793+ return new TypedInput ( Math . sqrt ( + node . value . constantValue ) . toString ( ) , TYPE_NUMBER ) ;
794+ }
718795 return new TypedInput ( `sqrt(${ this . descendInput ( node . value ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
719- case 'op.subtract' :
720- // Needs to be marked as NaN because Infinity - Infinity === NaN
721- return new TypedInput ( `(${ this . descendInput ( node . left ) . asNumber ( ) } - ${ this . descendInput ( node . right ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
722796 case 'op.tan' :
723797 return new TypedInput ( `tan(${ this . descendInput ( node . value ) . asNumber ( ) } )` , TYPE_NUMBER_NAN ) ;
724798 case 'op.10^' :
0 commit comments