import { ArrayPattern , ArrowFunctionExpression , BlockStatement , CallExpression , CatchClause , DoWhileStatement , Expression , ForInStatement , ForOfStatement , ForStatement , FunctionDeclaration , FunctionExpression , Identifier , NewExpression , ObjectPattern , PatternLike , RestElement , Statement , SwitchCase , TemplateLiteral , VariableDeclaration , WhileStatement , } from "@babel/types" ; import { SimpleFunction } from "@next-core/brick-types" ; import { ApplyStringOrNumericAssignment , CreateListIteratorRecord , ApplyStringOrNumericBinaryOperator , GetV , GetValue , InitializeReferencedBinding , IsPropertyReference , LoopContinues , PutValue , RequireObjectCoercible , ToPropertyKey , UpdateEmpty , ApplyUnaryOperator , GetIdentifierReference , ForDeclarationBindingInstantiation , CopyDataProperties , } from "./context-free" ; import { CompletionRecord , DeclarativeEnvironment , ECMAScriptCode , Empty , Environment , EnvironmentRecord , ExecutionContext , FormalParameters , FunctionEnvironment , FunctionObject , IsConstructor , NormalCompletion , OptionalChainRef , ReferenceRecord , SourceNode , } from "./ExecutionContext" ; import { EstreeLVal , EstreeNode , EstreeObjectExpression , EstreeObjectPattern , EstreeProperty , CookRules , } from "./interfaces" ; import { sanitize , isAllowedConstructor } from "./sanitize" ; import { collectBoundNames , collectScopedDeclarations , containsExpression , } from "./traverse" ; export interface CookOptions { rules ? : CookRules ; globalVariables ? : Record < string , unknown > ; hooks ? : CookHooks ; } export interface CookHooks { beforeEvaluate ? ( node ) : void ; beforeCall ? ( node ) : void ; beforeBranch ? ( node , branch ) : void ; } /** For next-core internal usage only. */ export function cook ( rootAst , codeSource , { rules , globalVariables = { } , hooks = { } } = { } ) { const expressionOnly = rootAst . type !== "FunctionDeclaration" ; const rootEnv = new DeclarativeEnvironment ( null ) ; const rootContext = new ExecutionContext ( ) ; rootContext . VariableEnvironment = rootEnv ; rootContext . LexicalEnvironment = rootEnv ; const executionContextStack = [ rootContext ] ; for ( const [ key , value ] of Object . entries ( globalVariables ) ) { rootEnv . CreateImmutableBinding ( key , true ) ; rootEnv . InitializeBinding ( key , value ) ; } const TemplateMap = new WeakMap < TemplateLiteral , string [ ] > ( ) ; function GetTemplateObject ( templateLiteral ) { const memo = TemplateMap . get ( templateLiteral ) ; if ( memo ) { return memo ; } const rawObj = templateLiteral . quasis . map ( ( quasi ) => quasi . value . raw ) ; const template = templateLiteral . quasis . map ( ( quasi ) => quasi . value . cooked ) ; Object . freeze ( rawObj ) ; Object . defineProperty ( template , "raw" , { value : rawObj , writable : false , enumerable : false , configurable : false , } ) ; Object . freeze ( template ) ; TemplateMap . set ( templateLiteral , template ) ; return template ; } function Evaluate ( node , optionalChainRef ? ) { hooks . beforeEvaluate ?. ( node ) ; switch ( node . type ) { case "ArrayExpression" : { const array = [ ] ; for ( const element of node . elements ) { if ( ! element ) { array . length += 1 ; } else if ( element . type === "SpreadElement" ) { const spreadValues = GetValue ( Evaluate ( element . argument ) ) as unknown [ ] ; array . push ( ... spreadValues ) ; } else { array . push ( GetValue ( Evaluate ( element ) ) ) ; } } return NormalCompletion ( array ) ; } case "ArrowFunctionExpression" : { ThrowIfFunctionIsInvalid ( node ) ; const closure = InstantiateArrowFunctionExpression ( node ) ; return NormalCompletion ( closure ) ; } case "BinaryExpression" : { const leftRef = Evaluate ( node . left ) ; const leftValue = GetValue ( leftRef ) ; const rightRef = Evaluate ( node . right ) . Value ; const rightValue = GetValue ( rightRef ) ; if ( expressionOnly && ( node . operator as unknown ) === "|>" ) { if ( typeof rightValue !== "function" ) { const funcName = codeSource . substring ( node . right . start , node . right . end ) ; throw new TypeError ( `${ funcName } is not a function` ) ; } let thisValue ; if ( rightRef instanceof ReferenceRecord ) { if ( IsPropertyReference ( rightRef ) ) { thisValue = rightRef . Base ; } } return NormalCompletion ( ( rightValue as unknown as SimpleFunction ) . call ( thisValue , leftValue ) ) ; } const result = ApplyStringOrNumericBinaryOperator ( leftValue as number , node . operator , rightValue as number ) ; return NormalCompletion ( result ) ; } case "CallExpression" : { const ref = Evaluate ( node . callee , optionalChainRef ) . Value as ReferenceRecord ; const func = GetValue ( ref ) as SimpleFunction ; if ( ( func === undefined || func === null ) && ( node . optional || optionalChainRef ?. skipped ) ) { optionalChainRef . skipped = true ; return NormalCompletion ( undefined ) ; } sanitize ( func ) ; return EvaluateCall ( func , ref , node . arguments , node . callee ) ; } case "ChainExpression" : return Evaluate ( node . expression , { } ) ; case "ConditionalExpression" : return NormalCompletion ( GetValue ( Evaluate ( GetValue ( Evaluate ( node . test ) ) ? node . consequent : node . alternate ) ) ) ; case "Identifier" : return NormalCompletion ( ResolveBinding ( node . name ) ) ; case "Literal" : { if ( node . regex ) { if ( node . value === null ) { throw new SyntaxError ( `Invalid regular expression: ${ node . raw }` ) ; } if ( node . regex . flags . includes ( "u" ) ) { throw new SyntaxError ( `Unsupported unicode flag in regular expression: ${ node . raw }` ) ; } } return NormalCompletion ( node . value ) ; } case "LogicalExpression" : { const leftValue = GetValue ( Evaluate ( node . left ) ) ; switch ( node . operator ) { case "&&" : return NormalCompletion ( leftValue && GetValue ( Evaluate ( node . right ) ) ) ; case "||" : return NormalCompletion ( leftValue || GetValue ( Evaluate ( node . right ) ) ) ; case "??" : return NormalCompletion ( leftValue ?? GetValue ( Evaluate ( node . right ) ) ) ; default : throw new SyntaxError ( `Unsupported logical operator '${ node . operator }'` ) ; } } case "MemberExpression" : { const baseReference = Evaluate ( node . object , optionalChainRef ) . Value as ReferenceRecord ; const baseValue = GetValue ( baseReference ) as Record < PropertyKey , unknown > ; if ( ( baseValue === undefined || baseValue === null ) && ( node . optional || optionalChainRef ?. skipped ) ) { optionalChainRef . skipped = true ; return NormalCompletion ( undefined ) ; } sanitize ( baseValue ) ; const result = node . computed ? EvaluatePropertyAccessWithExpressionKey ( baseValue , node . property as Expression , true ) : EvaluatePropertyAccessWithIdentifierKey ( baseValue , node . property as Identifier , true ) ; sanitize ( result ) ; return NormalCompletion ( result ) ; } case "NewExpression" : return EvaluateNew ( node . callee , node . arguments ) ; case "ObjectExpression" : { const object = { } ; for ( const prop of ( node as EstreeObjectExpression ) . properties ) { if ( prop . type === "SpreadElement" ) { const fromValue = GetValue ( Evaluate ( prop . argument ) ) ; CopyDataProperties ( object , fromValue , new Set ( ) ) ; } else { if ( prop . kind !== "init" ) { throw new SyntaxError ( "Unsupported object getter/setter" ) ; } const propName = ! prop . computed && prop . key . type === "Identifier" ? prop . key . name : EvaluateComputedPropertyName ( prop . key ) ; if ( propName === "__proto__" ) { throw new TypeError ( "Setting '__proto__' property is not allowed" ) ; } object [ propName ] = GetValue ( Evaluate ( prop . value ) ) ; } } return NormalCompletion ( object ) ; } case "SequenceExpression" : { let result ; for ( const expr of node . expressions ) { result = NormalCompletion ( GetValue ( Evaluate ( expr ) ) ) ; } return result ; } case "TemplateLiteral" : { const chunks = [ node . quasis [ 0 ] . value . cooked ] ; let index = 0 ; for ( const expr of node . expressions ) { const val = GetValue ( Evaluate ( expr ) ) ; chunks . push ( String ( val ) ) ; chunks . push ( node . quasis [ ( index += 1 ) ] . value . cooked ) ; } return NormalCompletion ( chunks . join ( "" ) ) ; } case "TaggedTemplateExpression" : { const tagRef = Evaluate ( node . tag ) . Value as ReferenceRecord ; const tagFunc = GetValue ( tagRef ) as SimpleFunction ; sanitize ( tagFunc ) ; return EvaluateCall ( tagFunc , tagRef , node . quasi , node . tag ) ; } case "UnaryExpression" : { const ref = Evaluate ( node . argument ) . Value as ReferenceRecord ; if ( ! expressionOnly && node . operator === "delete" ) { if ( ! ( ref instanceof ReferenceRecord ) ) { return NormalCompletion ( true ) ; } if ( IsPropertyReference ( ref ) ) { const deleteStatus = delete ( ref . Base as Record < PropertyKey , unknown > ) [ ref . ReferenceName ] ; return NormalCompletion ( deleteStatus ) ; } } if ( node . operator === "typeof" ) { if ( ref instanceof ReferenceRecord && ref . Base === "unresolvable" ) { return NormalCompletion ( "undefined" ) ; } return NormalCompletion ( typeof GetValue ( ref ) ) ; } return NormalCompletion ( ApplyUnaryOperator ( GetValue ( ref ) , node . operator ) ) ; } } if ( ! expressionOnly ) { switch ( node . type ) { case "AssignmentExpression" : { if ( node . operator === "=" ) { if ( ! ( node . left . type === "ArrayPattern" || node . left . type === "ObjectPattern" ) ) { const lref = Evaluate ( node . left ) . Value as ReferenceRecord ; const rref = Evaluate ( node . right ) ; const rval = GetValue ( rref ) ; PutValue ( lref , rval ) ; return NormalCompletion ( rval ) ; } const rref = Evaluate ( node . right ) ; const rval = GetValue ( rref ) as string | number ; DestructuringAssignmentEvaluation ( node . left , rval ) ; return NormalCompletion ( rval ) ; } const lref = Evaluate ( node . left ) . Value as ReferenceRecord ; const lval = GetValue ( lref ) as string | number ; const rref = Evaluate ( node . right ) ; const rval = GetValue ( rref ) as string | number ; const r = ApplyStringOrNumericAssignment ( lval , node . operator , rval ) ; PutValue ( lref , r ) ; return NormalCompletion ( r ) ; } case "BlockStatement" : { if ( ! node . body . length ) { return NormalCompletion ( Empty ) ; } const oldEnv = getRunningContext ( ) . LexicalEnvironment ; const blockEnv = new DeclarativeEnvironment ( oldEnv ) ; BlockDeclarationInstantiation ( node . body , blockEnv ) ; getRunningContext ( ) . LexicalEnvironment = blockEnv ; const blockValue = EvaluateStatementList ( node . body ) ; getRunningContext ( ) . LexicalEnvironment = oldEnv ; return blockValue ; } case "BreakStatement" : return new CompletionRecord ( "break" , Empty ) ; case "ContinueStatement" : return new CompletionRecord ( "continue" , Empty ) ; case "EmptyStatement" : return NormalCompletion ( Empty ) ; case "DoWhileStatement" : return EvaluateBreakableStatement ( DoWhileLoopEvaluation ( node ) ) ; case "ExpressionStatement" : case "TSAsExpression" : return Evaluate ( node . expression ) ; case "ForInStatement" : case "ForOfStatement" : return EvaluateBreakableStatement ( ForInOfLoopEvaluation ( node ) ) ; case "ForStatement" : return EvaluateBreakableStatement ( ForLoopEvaluation ( node ) ) ; case "FunctionDeclaration" : return NormalCompletion ( Empty ) ; case "FunctionExpression" : ThrowIfFunctionIsInvalid ( node ) ; return NormalCompletion ( InstantiateOrdinaryFunctionExpression ( node ) ) ; case "IfStatement" : return GetValue ( Evaluate ( node . test ) ) ? ( hooks . beforeBranch ?. ( node , "if" ) , UpdateEmpty ( Evaluate ( node . consequent ) , undefined ) ) : ( hooks . beforeBranch ?. ( node , "else" ) , node . alternate ) ? UpdateEmpty ( Evaluate ( node . alternate ) , undefined ) : NormalCompletion ( undefined ) ; case "ReturnStatement" : { let v ; if ( node . argument ) { const exprRef = Evaluate ( node . argument ) ; v = GetValue ( exprRef ) ; } return new CompletionRecord ( "return" , v ) ; } case "ThrowStatement" : throw GetValue ( Evaluate ( node . argument ) ) ; case "UpdateExpression" : { const lhs = Evaluate ( node . argument ) . Value as ReferenceRecord ; const oldValue = Number ( GetValue ( lhs ) ) ; const newValue = node . operator === "++" ? oldValue + 1 : oldValue - 1 ; PutValue ( lhs , newValue ) ; return NormalCompletion ( node . prefix ? newValue : oldValue ) ; } case "SwitchCase" : return EvaluateStatementList ( node . consequent ) ; case "SwitchStatement" : { const exprRef = Evaluate ( node . discriminant ) ; const switchValue = GetValue ( exprRef ) ; const oldEnv = getRunningContext ( ) . LexicalEnvironment ; const blockEnv = new DeclarativeEnvironment ( oldEnv ) ; BlockDeclarationInstantiation ( node . cases , blockEnv ) ; getRunningContext ( ) . LexicalEnvironment = blockEnv ; const R = CaseBlockEvaluation ( node . cases , switchValue ) ; getRunningContext ( ) . LexicalEnvironment = oldEnv ; return EvaluateBreakableStatement ( R ) ; } case "TryStatement" : { let R ; try { R = Evaluate ( node . block ) ; } catch ( error ) { if ( node . handler ) { hooks . beforeEvaluate ?. ( node . handler ) ; R = CatchClauseEvaluation ( node . handler , error ) ; } else { throw error ; } } finally { if ( node . finalizer ) { const F = Evaluate ( node . finalizer ) ; if ( F . Type !== "normal" ) { R = F ; } } } return R ; } case "VariableDeclaration" : { let result ; for ( const declarator of node . declarations ) { if ( ! declarator . init ) { if ( node . kind === "var" ) { result = NormalCompletion ( Empty ) ; } else { const lhs = ResolveBinding ( ( declarator . id as Identifier ) . name ) ; result = InitializeReferencedBinding ( lhs , undefined ) ; } } else if ( declarator . id . type === "Identifier" ) { const bindingId = declarator . id . name ; const lhs = ResolveBinding ( bindingId ) ; const rhs = Evaluate ( declarator . init ) ; const value = GetValue ( rhs ) ; result = node . kind === "var" ? PutValue ( lhs , value ) : InitializeReferencedBinding ( lhs , value ) ; } else { const rhs = Evaluate ( declarator . init ) ; const rval = GetValue ( rhs ) ; result = BindingInitialization ( declarator . id , rval , node . kind === "var" ? undefined : getRunningContext ( ) . LexicalEnvironment ) ; } } return result ; } case "WhileStatement" : return EvaluateBreakableStatement ( WhileLoopEvaluation ( node ) ) ; } } throw new SyntaxError ( `Unsupported node type \`${ node . type }\`` ) ; } function getRunningContext ( ) { return executionContextStack [ executionContextStack . length - 1 ] ; } function ResolveBinding ( name , env ? ) { if ( ! env ) { env = getRunningContext ( ) . LexicalEnvironment ; } return GetIdentifierReference ( env , name , true ) ; } function CatchClauseEvaluation ( node , thrownValue ) { const oldEnv = getRunningContext ( ) . LexicalEnvironment ; const catchEnv = new DeclarativeEnvironment ( oldEnv ) ; for ( const argName of collectBoundNames ( node . param ) ) { catchEnv . CreateMutableBinding ( argName , false ) ; } getRunningContext ( ) . LexicalEnvironment = catchEnv ; BindingInitialization ( node . param , thrownValue , catchEnv ) ; const B = Evaluate ( node . body ) ; getRunningContext ( ) . LexicalEnvironment = oldEnv ; return B ; } function EvaluateBreakableStatement ( stmtResult ) { return stmtResult . Type === "break" ? stmtResult . Value === Empty ? NormalCompletion ( undefined ) : NormalCompletion ( stmtResult . Value ) : stmtResult ; } function CaseBlockEvaluation ( cases , input ) { let V ; const defaultCaseIndex = cases . findIndex ( ( switchCase ) => ! switchCase . test ) ; const hasDefaultCase = defaultCaseIndex >= 0 ; const A = hasDefaultCase ? cases . slice ( 0 , defaultCaseIndex ) : cases ; let found = false ; for ( const C of A ) { if ( ! found ) { found = CaseClauseIsSelected ( C , input ) ; } if ( found ) { const R = Evaluate ( C ) ; if ( R . Value !== Empty ) { V = R . Value ; } if ( R . Type !== "normal" ) { return UpdateEmpty ( R , V ) ; } } } if ( ! hasDefaultCase ) { return NormalCompletion ( V ) ; } let foundInB = false ; const B = cases . slice ( defaultCaseIndex + 1 ) ; if ( ! found ) { for ( const C of B ) { if ( ! foundInB ) { foundInB = CaseClauseIsSelected ( C , input ) ; } if ( foundInB ) { const R = Evaluate ( C ) ; if ( R . Value !== Empty ) { V = R . Value ; } if ( R . Type !== "normal" ) { return UpdateEmpty ( R , V ) ; } } } } if ( foundInB ) { return NormalCompletion ( V ) ; } const R = Evaluate ( cases [ defaultCaseIndex ] ) ; if ( R . Value !== Empty ) { V = R . Value ; } if ( R . Type !== "normal" ) { return UpdateEmpty ( R , V ) ; } for ( const C of B ) { const R = Evaluate ( C ) ; if ( R . Value !== Empty ) { V = R . Value ; } if ( R . Type !== "normal" ) { return UpdateEmpty ( R , V ) ; } } return NormalCompletion ( V ) ; } function CaseClauseIsSelected ( C , input ) { const clauseSelector = GetValue ( Evaluate ( C . test ) ) ; return input === clauseSelector ; } function WhileLoopEvaluation ( node ) { let V ; while ( true ) { const exprValue = GetValue ( Evaluate ( node . test ) ) ; if ( ! exprValue ) { return NormalCompletion ( V ) ; } const stmtResult = Evaluate ( node . body ) ; if ( ! LoopContinues ( stmtResult ) ) { return UpdateEmpty ( stmtResult , V ) ; } if ( stmtResult . Value !== Empty ) { V = stmtResult . Value ; } } } function DoWhileLoopEvaluation ( node ) { let V ; while ( true ) { const stmtResult = Evaluate ( node . body ) ; if ( ! LoopContinues ( stmtResult ) ) { return UpdateEmpty ( stmtResult , V ) ; } if ( stmtResult . Value !== Empty ) { V = stmtResult . Value ; } const exprValue = GetValue ( Evaluate ( node . test ) ) ; if ( ! exprValue ) { return NormalCompletion ( V ) ; } } } function ForInOfLoopEvaluation ( node ) { const lhs = node . left ; const isVariableDeclaration = lhs . type === "VariableDeclaration" ; const lhsKind = isVariableDeclaration ? lhs . kind === "var" ? "varBinding" : "lexicalBinding" : "assignment" ; const uninitializedBoundNames = lhsKind === "lexicalBinding" ? collectBoundNames ( lhs ) : [ ] ; const iterationKind = node . type === "ForInStatement" ? "enumerate" : "iterate" ; const keyResult = ForInOfHeadEvaluation ( uninitializedBoundNames , node . right , iterationKind ) ; if ( keyResult . Type !== "normal" ) { return keyResult ; } return ForInOfBodyEvaluation ( lhs , node . body , keyResult . Value as Iterator < unknown > , iterationKind , lhsKind ) ; } function ForInOfHeadEvaluation ( uninitializedBoundNames , expr , iterationKind ) { const runningContext = getRunningContext ( ) ; const oldEnv = runningContext . LexicalEnvironment ; if ( uninitializedBoundNames . length > 0 ) { const newEnv = new DeclarativeEnvironment ( oldEnv ) ; for ( const name of uninitializedBoundNames ) { newEnv . CreateMutableBinding ( name , false ) ; } runningContext . LexicalEnvironment = newEnv ; } const exprRef = Evaluate ( expr ) ; runningContext . LexicalEnvironment = oldEnv ; const exprValue = GetValue ( exprRef ) ; if ( iterationKind === "enumerate" ) { if ( exprValue === null || exprValue === undefined ) { return new CompletionRecord ( "break" , Empty ) ; } const iterator = EnumerateObjectProperties ( exprValue ) ; return NormalCompletion ( iterator ) ; } const iterator = CreateListIteratorRecord ( exprValue as Iterable < unknown > ) ; return NormalCompletion ( iterator ) ; } function ForInOfBodyEvaluation ( node , stmt , iteratorRecord , iterationKind , lhsKind ) { const lhs = lhsKind === "assignment" ? ( node as EstreeLVal ) : ( node as VariableDeclaration ) . declarations [ 0 ] . id ; const oldEnv = getRunningContext ( ) . LexicalEnvironment ; let V ; const destructuring = lhs . type === "ObjectPattern" || lhs . type === "ArrayPattern" ; while ( true ) { const { done , value : nextValue } = iteratorRecord . next ( ) ; if ( done ) { return NormalCompletion ( V ) ; } let lhsRef ; let iterationEnv ; if ( lhsKind === "lexicalBinding" ) { iterationEnv = new DeclarativeEnvironment ( oldEnv ) ; ForDeclarationBindingInstantiation ( node as VariableDeclaration , iterationEnv ) ; getRunningContext ( ) . LexicalEnvironment = iterationEnv ; if ( ! destructuring ) { const [ lhsName ] = collectBoundNames ( lhs ) ; lhsRef = ResolveBinding ( lhsName ) ; } } else if ( ! destructuring ) { lhsRef = Evaluate ( lhs ) . Value as ReferenceRecord ; } destructuring ? lhsKind === "assignment" ? DestructuringAssignmentEvaluation ( lhs , nextValue ) : lhsKind === "varBinding" ? BindingInitialization ( lhs , nextValue , undefined ) : BindingInitialization ( lhs , nextValue , iterationEnv ) : lhsKind === "lexicalBinding" ? InitializeReferencedBinding ( lhsRef , nextValue ) : PutValue ( lhsRef , nextValue ) ; const result = Evaluate ( stmt ) ; getRunningContext ( ) . LexicalEnvironment = oldEnv ; if ( ! LoopContinues ( result ) ) { const status = UpdateEmpty ( result , V ) ; if ( ! ( iterationKind === "enumerate" || iteratorRecord . return === undefined ) ) { const innerResult = iteratorRecord . return ( ) ; if ( ! innerResult || ! [ "object" , "function" ] . includes ( typeof innerResult ) ) { throw new TypeError ( `Iterator result is not an object` ) ; } } return status ; } if ( result . Value !== Empty ) { V = result . Value ; } } } function * EnumerateObjectProperties ( value ) { for ( const key in value ) { yield key ; } } function ForLoopEvaluation ( node ) { if ( node . init ?. type === "VariableDeclaration" ) { if ( node . init . kind === "var" ) { Evaluate ( node . init ) ; return ForBodyEvaluation ( node . test , node . update , node . body , [ ] ) ; } const oldEnv = getRunningContext ( ) . LexicalEnvironment ; const loopEnv = new DeclarativeEnvironment ( oldEnv ) ; const isConst = node . init . kind === "const" ; const boundNames = collectBoundNames ( node . init ) ; for ( const dn of boundNames ) { if ( isConst ) { loopEnv . CreateImmutableBinding ( dn , true ) ; } else { loopEnv . CreateMutableBinding ( dn , false ) ; } } getRunningContext ( ) . LexicalEnvironment = loopEnv ; Evaluate ( node . init ) ; const perIterationLets = isConst ? [ ] : Array . from ( boundNames ) ; const bodyResult = ForBodyEvaluation ( node . test , node . update , node . body , perIterationLets ) ; getRunningContext ( ) . LexicalEnvironment = oldEnv ; return bodyResult ; } if ( node . init ) { const exprRef = Evaluate ( node . init ) ; GetValue ( exprRef ) ; } return ForBodyEvaluation ( node . test , node . update , node . body , [ ] ) ; } function ForBodyEvaluation ( test , increment , stmt , perIterationBindings ) { CreatePerIterationEnvironment ( perIterationBindings ) ; let V ; while ( true ) { if ( test ) { const testRef = Evaluate ( test ) ; const testValue = GetValue ( testRef ) ; if ( ! testValue ) { return NormalCompletion ( V ) ; } } const result = Evaluate ( stmt ) as CompletionRecord ; if ( ! LoopContinues ( result ) ) { return UpdateEmpty ( result , V ) ; } if ( result . Value ) { V = result . Value ; } CreatePerIterationEnvironment ( perIterationBindings ) ; if ( increment ) { const incRef = Evaluate ( increment ) ; GetValue ( incRef ) ; } } } function CreatePerIterationEnvironment ( perIterationBindings ) { if ( perIterationBindings . length === 0 ) { return ; } const lastIterationEnv = getRunningContext ( ) . LexicalEnvironment ; const outer = lastIterationEnv . OuterEnv ; const thisIterationEnv = new DeclarativeEnvironment ( outer ) ; for ( const bn of perIterationBindings ) { thisIterationEnv . CreateMutableBinding ( bn , false ) ; const lastValue = lastIterationEnv . GetBindingValue ( bn , false ) ; thisIterationEnv . InitializeBinding ( bn , lastValue ) ; } getRunningContext ( ) . LexicalEnvironment = thisIterationEnv ; } function DestructuringAssignmentEvaluation ( pattern , value ) { if ( pattern . type === "ObjectPattern" ) { RequireObjectCoercible ( value ) ; if ( pattern . properties . length > 0 ) { PropertyDestructuringAssignmentEvaluation ( ( pattern as EstreeObjectPattern ) . properties , value ) ; } return NormalCompletion ( Empty ) ; } const iteratorRecord = CreateListIteratorRecord ( value as Iterable < unknown > ) ; return IteratorDestructuringAssignmentEvaluation ( pattern . elements , iteratorRecord ) ; } function PropertyDestructuringAssignmentEvaluation ( properties , value ) { const excludedNames = new Set < PropertyKey > ( ) ; for ( const prop of properties ) { if ( prop . type === "Property" ) { const propName = ! prop . computed && prop . key . type === "Identifier" ? prop . key . name : ( EvaluateComputedPropertyName ( prop . key ) as string ) ; const valueTarget = prop . value . type === "AssignmentPattern" ? prop . value . left : prop . value ; if ( valueTarget . type === "Identifier" ) { const lref = ResolveBinding ( valueTarget . name ) ; let v = GetV ( value , propName ) ; if ( prop . value . type === "AssignmentPattern" && v === undefined ) { const defaultValue = Evaluate ( prop . value . right ) ; v = GetValue ( defaultValue ) ; } PutValue ( lref , v ) ; excludedNames . add ( propName ) ; } else { KeyedDestructuringAssignmentEvaluation ( prop . value , value , propName ) ; excludedNames . add ( propName ) ; } } else { RestDestructuringAssignmentEvaluation ( prop , value , excludedNames ) ; } } } function KeyedDestructuringAssignmentEvaluation ( node , value , propertyName ) { const assignmentTarget = node . type === "AssignmentPattern" ? node . left : node ; const isObjectOrArray = assignmentTarget . type === "ArrayPattern" || assignmentTarget . type === "ObjectPattern" ; let lref ; if ( ! isObjectOrArray ) { lref = Evaluate ( assignmentTarget ) . Value as ReferenceRecord ; } const v = GetV ( value , propertyName ) ; let rhsValue ; if ( node . type === "AssignmentPattern" && v === undefined ) { const defaultValue = Evaluate ( node . right ) ; rhsValue = GetValue ( defaultValue ) ; } else { rhsValue = v ; } if ( isObjectOrArray ) { return DestructuringAssignmentEvaluation ( assignmentTarget , rhsValue ) ; } return PutValue ( lref , rhsValue ) ; } function RestDestructuringAssignmentEvaluation ( restProperty , value , excludedNames ) { const lref = Evaluate ( restProperty . argument ) . Value as ReferenceRecord ; const restObj = CopyDataProperties ( { } , value , excludedNames ) ; return PutValue ( lref , restObj ) ; } function IteratorDestructuringAssignmentEvaluation ( elements , iteratorRecord ) { let status = NormalCompletion ( Empty ) ; for ( const element of elements ) { if ( ! element ) { iteratorRecord . next ( ) ; status = NormalCompletion ( Empty ) ; continue ; } const assignmentTarget = element . type === "RestElement" ? element . argument : element . type === "AssignmentPattern" ? element . left : element ; const isObjectOrArray = assignmentTarget . type === "ArrayPattern" || assignmentTarget . type === "ObjectPattern" ; let lref ; if ( ! isObjectOrArray ) { lref = Evaluate ( assignmentTarget ) . Value as ReferenceRecord ; } let v ; if ( element . type !== "RestElement" ) { const { done , value : nextValue } = iteratorRecord . next ( ) ; const value = done ? undefined : nextValue ; if ( element . type === "AssignmentPattern" && value === undefined ) { const defaultValue = Evaluate ( element . right ) ; v = GetValue ( defaultValue ) ; } else { v = value ; } } else { v = [ ] ; let n = 0 ; while ( true ) { const { done , value : nextValue } = iteratorRecord . next ( ) ; if ( done ) { break ; } ( v as unknown [ ] ) [ n ] = nextValue ; n ++ ; } } if ( isObjectOrArray ) { status = DestructuringAssignmentEvaluation ( assignmentTarget , v ) ; } else { status = PutValue ( lref , v ) ; } } return status ; } function EvaluatePropertyAccessWithExpressionKey ( baseValue , expression , strict ) { const propertyNameReference = Evaluate ( expression ) ; const propertyNameValue = GetValue ( propertyNameReference ) ; const propertyKey = ToPropertyKey ( propertyNameValue ) ; return new ReferenceRecord ( baseValue , propertyKey , strict ) ; } function EvaluatePropertyAccessWithIdentifierKey ( baseValue , identifier , strict ) { const propertyNameString = identifier . name ; return new ReferenceRecord ( baseValue , propertyNameString , strict ) ; } function BlockDeclarationInstantiation ( code , env ) { const declarations = collectScopedDeclarations ( code , { var : false , topLevel : false , } ) ; for ( const d of declarations ) { const IsConstantDeclaration = d . type === "VariableDeclaration" && d . kind === "const" ; for ( const dn of collectBoundNames ( d ) ) { if ( IsConstantDeclaration ) { env . CreateImmutableBinding ( dn , true ) ; } else { env . CreateMutableBinding ( dn , false ) ; } } if ( d . type === "FunctionDeclaration" ) { const [ fn ] = collectBoundNames ( d ) ; const fo = InstantiateFunctionObject ( d , env ) ; env . InitializeBinding ( fn , fo ) ; } } } function EvaluateCall ( func , ref , args , callee ) { let thisValue ; if ( ref instanceof ReferenceRecord ) { if ( IsPropertyReference ( ref ) ) { thisValue = ref . Base ; } } const argList = ArgumentListEvaluation ( args ) ; if ( typeof func !== "function" ) { const funcName = codeSource . substring ( callee . start , callee . end ) ; throw new TypeError ( `${ funcName } is not a function` ) ; } const result = func . apply ( thisValue , argList ) ; sanitize ( result ) ; return NormalCompletion ( result ) ; } function EvaluateNew ( constructExpr , args ) { const ref = Evaluate ( constructExpr ) ; const constructor = GetValue ( ref ) as new ( ... args ) => unknown ; const argList = ArgumentListEvaluation ( args ) ; if ( typeof constructor !== "function" || ( constructor as unknown as FunctionObject ) [ IsConstructor ] === false ) { const constructorName = codeSource . substring ( constructExpr . start , constructExpr . end ) ; throw new TypeError ( `${ constructorName } is not a constructor` ) ; } if ( ! isAllowedConstructor ( constructor ) ) { const constructorName = codeSource . substring ( constructExpr . start , constructExpr . end ) ; throw new TypeError ( `${ constructorName } is not an allowed constructor` ) ; } return NormalCompletion ( new constructor ( ... argList ) ) ; } function ArgumentListEvaluation ( args ) { const array = [ ] ; if ( Array . isArray ( args ) ) { for ( const arg of args ) { if ( arg . type === "SpreadElement" ) { const spreadValues = GetValue ( Evaluate ( arg . argument ) ) as unknown [ ] ; array . push ( ... spreadValues ) ; } else { array . push ( GetValue ( Evaluate ( arg ) ) ) ; } } } else { array . push ( GetTemplateObject ( args ) ) ; for ( const expr of args . expressions ) { array . push ( GetValue ( Evaluate ( expr ) ) ) ; } } return array ; } function CallFunction ( closure , args ) { hooks . beforeCall ?. ( closure [ SourceNode ] ) ; PrepareForOrdinaryCall ( closure ) ; const result = OrdinaryCallEvaluateBody ( closure , args ) ; executionContextStack . pop ( ) ; if ( result . Type === "return" ) { return result . Value ; } return undefined ; } function PrepareForOrdinaryCall ( F ) { const calleeContext = new ExecutionContext ( ) ; calleeContext . Function = F ; const localEnv = new FunctionEnvironment ( F [ Environment ] ) ; calleeContext . VariableEnvironment = localEnv ; calleeContext . LexicalEnvironment = localEnv ; executionContextStack . push ( calleeContext ) ; return calleeContext ; } function OrdinaryCallEvaluateBody ( F , args ) { return EvaluateFunctionBody ( F [ ECMAScriptCode ] , F , args ) ; } function EvaluateFunctionBody ( body , F , args ) { FunctionDeclarationInstantiation ( F , args ) ; if ( Array . isArray ( body ) ) { return EvaluateStatementList ( body ) ; } return new CompletionRecord ( "return" , GetValue ( Evaluate ( body ) ) ) ; } function EvaluateStatementList ( statements ) { let result = NormalCompletion ( Empty ) ; for ( const stmt of statements ) { const s = Evaluate ( stmt ) ; if ( s . Type !== "normal" ) { return s ; } result = UpdateEmpty ( result , s . Value ) ; } return result ; } function FunctionDeclarationInstantiation ( func , args ) { const calleeContext = getRunningContext ( ) ; const code = func [ ECMAScriptCode ] as BlockStatement | Expression ; const formals = func [ FormalParameters ] as FunctionDeclaration [ "params" ] ; const parameterNames = collectBoundNames ( formals ) ; const hasParameterExpressions = containsExpression ( formals ) ; const varDeclarations = collectScopedDeclarations ( code , { var : true , topLevel : true , } ) ; const varNames = collectBoundNames ( varDeclarations ) ; const functionNames = [ ] ; const functionsToInitialize = [ ] ; for ( let i = varDeclarations . length - 1 ; i >= 0 ; i -- ) { const d = varDeclarations [ i ] ; if ( d . type === "FunctionDeclaration" ) { ThrowIfFunctionIsInvalid ( d ) ; const [ fn ] = collectBoundNames ( d ) ; if ( ! functionNames . includes ( fn ) ) { functionNames . unshift ( fn ) ; functionsToInitialize . unshift ( d ) ; } } else if ( rules ?. noVar ) { throw new SyntaxError ( "Var declaration is not recommended, use `let` or `const` instead" ) ; } } const env = calleeContext . LexicalEnvironment ; for ( const paramName of parameterNames ) { env . CreateMutableBinding ( paramName , false ) ; } const iteratorRecord = CreateListIteratorRecord ( args ) ; IteratorBindingInitialization ( formals , iteratorRecord , env ) ; let varEnv ; if ( ! hasParameterExpressions ) { for ( const n of varNames ) { if ( ! parameterNames . includes ( n ) ) { env . CreateMutableBinding ( n , false ) ; env . InitializeBinding ( n , undefined ) ; } } varEnv = env ; } else { varEnv = new DeclarativeEnvironment ( env ) ; calleeContext . VariableEnvironment = varEnv ; for ( const n of varNames ) { varEnv . CreateMutableBinding ( n , false ) ; let initialValue ; if ( parameterNames . includes ( n ) && ! functionNames . includes ( n ) ) { initialValue = env . GetBindingValue ( n , false ) ; } varEnv . InitializeBinding ( n , initialValue ) ; } } const lexEnv = varEnv ; calleeContext . LexicalEnvironment = lexEnv ; const lexDeclarations = collectScopedDeclarations ( code , { var : false , topLevel : true , } ) ; for ( const d of lexDeclarations ) { for ( const dn of collectBoundNames ( d ) ) { if ( ( d as VariableDeclaration ) . kind === "const" ) { lexEnv . CreateImmutableBinding ( dn , true ) ; } else { lexEnv . CreateMutableBinding ( dn , false ) ; } } } for ( const f of functionsToInitialize ) { const [ fn ] = collectBoundNames ( f ) ; const fo = InstantiateFunctionObject ( f , lexEnv ) ; varEnv . SetMutableBinding ( fn , fo , false ) ; } } function InstantiateFunctionObject ( func , scope ) { return OrdinaryFunctionCreate ( func , scope , true ) ; } function InstantiateOrdinaryFunctionExpression ( functionExpression ) { const scope = getRunningContext ( ) . LexicalEnvironment ; if ( functionExpression . id ) { const name = functionExpression . id . name ; const funcEnv = new DeclarativeEnvironment ( scope ) ; funcEnv . CreateImmutableBinding ( name , false ) ; const closure = OrdinaryFunctionCreate ( functionExpression , funcEnv , true ) ; funcEnv . InitializeBinding ( name , closure ) ; return closure ; } else { const closure = OrdinaryFunctionCreate ( functionExpression , scope , true ) ; return closure ; } } function InstantiateArrowFunctionExpression ( arrowFunction ) { const scope = getRunningContext ( ) . LexicalEnvironment ; const closure = OrdinaryFunctionCreate ( arrowFunction , scope , false ) ; return closure ; } function OrdinaryFunctionCreate ( sourceNode , scope , isConstructor ) { const F = function ( ) { return CallFunction ( F , arguments ) ; } as FunctionObject ; Object . defineProperties ( F , { [ SourceNode ] : { value : sourceNode , } , [ FormalParameters ] : { value : sourceNode . params , } , [ ECMAScriptCode ] : { value : sourceNode . body . type === "BlockStatement" ? sourceNode . body . body : sourceNode . body , } , [ Environment ] : { value : scope , } , [ IsConstructor ] : { value : isConstructor , } , } ) ; return F ; } function BindingInitialization ( node , value , environment ) { switch ( node . type ) { case "Identifier" : return InitializeBoundName ( node . name , value , environment ) ; case "ObjectPattern" : RequireObjectCoercible ( value ) ; return PropertyBindingInitialization ( ( node as EstreeObjectPattern ) . properties , value , environment ) ; case "ArrayPattern" : { const iteratorRecord = CreateListIteratorRecord ( value as Iterable < unknown > ) ; return IteratorBindingInitialization ( node . elements , iteratorRecord , environment ) ; } } } function PropertyBindingInitialization ( properties , value , environment ) { const excludedNames = new Set < PropertyKey > ( ) ; for ( const prop of properties ) { if ( prop . type === "RestElement" ) { return RestBindingInitialization ( prop , value , environment , excludedNames ) ; } if ( ! prop . computed && prop . key . type === "Identifier" ) { KeyedBindingInitialization ( prop . value as EstreeLVal , value , environment , prop . key . name ) ; excludedNames . add ( prop . key . name ) ; } else { const P = EvaluateComputedPropertyName ( prop . key ) ; KeyedBindingInitialization ( prop . value as EstreeLVal , value , environment , P ) ; excludedNames . add ( P ) ; } } return NormalCompletion ( Empty ) ; } function EvaluateComputedPropertyName ( node ) { const propName = GetValue ( Evaluate ( node ) ) ; return ToPropertyKey ( propName ) ; } function RestBindingInitialization ( restProperty , value , environment , excludedNames ) { const lhs = ResolveBinding ( ( restProperty . argument as Identifier ) . name , environment ) ; const restObj = CopyDataProperties ( { } , value , excludedNames ) ; if ( ! environment ) { return PutValue ( lhs , restObj ) ; } return InitializeReferencedBinding ( lhs , restObj ) ; } function IteratorBindingInitialization ( elements , iteratorRecord , environment ) { if ( elements . length === 0 ) { return NormalCompletion ( Empty ) ; } let result ; for ( const node of elements ) { if ( ! node ) { iteratorRecord . next ( ) ; result = NormalCompletion ( Empty ) ; } else if ( node . type === "RestElement" ) { if ( node . argument . type === "Identifier" ) { const lhs = ResolveBinding ( node . argument . name , environment ) ; const A = [ ] ; let n = 0 ; while ( true ) { const { done , value } = iteratorRecord . next ( ) ; if ( done ) { result = environment ? InitializeReferencedBinding ( lhs , A ) : PutValue ( lhs , A ) ; break ; } A [ n ] = value ; n ++ ; } } else { const A = [ ] ; let n = 0 ; while ( true ) { const { done , value } = iteratorRecord . next ( ) ; if ( done ) { result = BindingInitialization ( node . argument , A , environment ) ; break ; } A [ n ] = value ; n ++ ; } } } else { const bindingElement = node . type === "AssignmentPattern" ? node . left : node ; switch ( bindingElement . type ) { case "ObjectPattern" : case "ArrayPattern" : { let v ; const { done , value } = iteratorRecord . next ( ) ; if ( ! done ) { v = value ; } if ( node . type === "AssignmentPattern" && v === undefined ) { const defaultValue = Evaluate ( node . right ) ; v = GetValue ( defaultValue ) ; } result = BindingInitialization ( bindingElement , v , environment ) ; break ; } case "Identifier" : { const bindingId = bindingElement . name ; const lhs = ResolveBinding ( bindingId , environment ) ; let v ; const { done , value } = iteratorRecord . next ( ) ; if ( ! done ) { v = value ; } if ( node . type === "AssignmentPattern" && v === undefined ) { const defaultValue = Evaluate ( node . right ) ; v = GetValue ( defaultValue ) ; } result = environment ? InitializeReferencedBinding ( lhs , v ) : PutValue ( lhs , v ) ; break ; } } } } return result ; } function KeyedBindingInitialization ( node , value , environment , propertyName ) { const isIdentifier = node . type === "Identifier" || ( node . type === "AssignmentPattern" && node . left . type === "Identifier" ) ; if ( isIdentifier ) { const bindingId = node . type === "Identifier" ? node . name : ( node . left as Identifier ) . name ; const lhs = ResolveBinding ( bindingId , environment ) ; let v = GetV ( value , propertyName ) ; if ( node . type === "AssignmentPattern" && v === undefined ) { const defaultValue = Evaluate ( node . right ) ; v = GetValue ( defaultValue ) ; } if ( ! environment ) { return PutValue ( lhs , v ) ; } return InitializeReferencedBinding ( lhs , v ) ; } let v = GetV ( value , propertyName ) ; if ( node . type === "AssignmentPattern" && v === undefined ) { const defaultValue = Evaluate ( node . right ) ; v = GetValue ( defaultValue ) ; } return BindingInitialization ( node . type === "AssignmentPattern" ? node . left : node , v , environment ) ; } function InitializeBoundName ( name , value , environment ? ) { environment . InitializeBinding ( name , value ) ; return NormalCompletion ( Empty ) ; } function ThrowIfFunctionIsInvalid ( func ) { if ( func . async || func . generator ) { throw new SyntaxError ( `${ func . async ? "Async" : "Generator" } function is not allowed` ) ; } if ( expressionOnly && ! ( func as ArrowFunctionExpression ) . expression ) { throw new SyntaxError ( "Only an `Expression` is allowed in `ArrowFunctionExpression`'s body" ) ; } } if ( expressionOnly ) { return GetValue ( Evaluate ( rootAst ) ) ; } hooks . beforeEvaluate ?. ( rootAst ) ; ThrowIfFunctionIsInvalid ( rootAst ) ; const [ fn ] = collectBoundNames ( rootAst ) ; rootEnv . CreateImmutableBinding ( fn , true ) ; const fo = InstantiateFunctionObject ( rootAst , rootEnv ) ; rootEnv . InitializeBinding ( fn , fo ) ; return fo ; }