-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
evaluate-function-declaration.ts
139 lines (112 loc) · 5.54 KB
/
evaluate-function-declaration.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import type {EvaluatorOptions} from "./evaluator-options.js";
import type {LexicalEnvironment} from "../lexical-environment/lexical-environment.js";
import {getFromLexicalEnvironment, pathInLexicalEnvironmentEquals, setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js";
import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment.js";
import type {Literal} from "../literal/literal.js";
import {evaluateParameterDeclarations} from "./evaluate-parameter-declarations.js";
import {THIS_SYMBOL} from "../util/this/this-symbol.js";
import {RETURN_SYMBOL} from "../util/return/return-symbol.js";
import {getImplementationForDeclarationWithinDeclarationFile} from "../util/module/get-implementation-for-declaration-within-declaration-file.js";
import {hasModifier} from "../util/modifier/has-modifier.js";
import type {TS} from "../../type/ts.js";
/**
* Evaluates, or attempts to evaluate, a FunctionDeclaration
*/
export function evaluateFunctionDeclaration(options: EvaluatorOptions<TS.FunctionDeclaration>): void {
const {node, environment, evaluate, stack, typescript, getCurrentError} = options;
const nameResult = node.name == null ? undefined : node.name.text;
const _functionDeclaration = hasModifier(node, typescript.SyntaxKind.AsyncKeyword)
? async function functionDeclaration(this: Literal, ...args: Literal[]) {
// Prepare a lexical environment for the function context
const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node);
const nextOptions = {...options, environment: localLexicalEnvironment};
// Define a new binding for a return symbol within the environment
setInLexicalEnvironment({...nextOptions, path: RETURN_SYMBOL, value: false, newBinding: true});
// Define a new binding for the arguments given to the function
// eslint-disable-next-line prefer-rest-params
setInLexicalEnvironment({...nextOptions, path: "arguments", value: arguments, newBinding: true});
if (this != null) {
setInLexicalEnvironment({...nextOptions, path: THIS_SYMBOL, value: this, newBinding: true});
}
// Evaluate the parameters based on the given arguments
evaluateParameterDeclarations(
{
...options,
node: node.parameters,
environment: localLexicalEnvironment
},
args
);
if (getCurrentError() != null) {
return;
}
const sourceFile = node.getSourceFile();
if (nameResult != null && sourceFile.isDeclarationFile) {
const implementation = getImplementationForDeclarationWithinDeclarationFile(options);
return (implementation as CallableFunction)(...args);
}
// If the body is a block, evaluate it as a statement
if (node.body == null) return;
evaluate.statement(node.body, nextOptions);
if (getCurrentError() != null) {
return;
}
// If a 'return' has occurred within the block, pop the Stack and return that value
if (pathInLexicalEnvironmentEquals(node, localLexicalEnvironment, true, RETURN_SYMBOL)) {
return stack.pop();
}
// Otherwise, return 'undefined'. Nothing is returned from the function
else {
return undefined;
}
}
: function functionDeclaration(this: Literal, ...args: Literal[]) {
// Prepare a lexical environment for the function context
const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node);
const nextOptions = {...options, environment: localLexicalEnvironment};
// Define a new binding for a return symbol within the environment
setInLexicalEnvironment({...nextOptions, path: RETURN_SYMBOL, value: false, newBinding: true});
// Define a new binding for the arguments given to the function
// eslint-disable-next-line prefer-rest-params
setInLexicalEnvironment({...nextOptions, path: "arguments", value: arguments, newBinding: true});
if (this != null) {
setInLexicalEnvironment({...nextOptions, path: THIS_SYMBOL, value: this, newBinding: true});
}
// Evaluate the parameters based on the given arguments
evaluateParameterDeclarations(
{
...nextOptions,
node: node.parameters
},
args
);
if (getCurrentError() != null) {
return;
}
const sourceFile = node.getSourceFile();
if (nameResult != null && sourceFile.isDeclarationFile) {
const implementation = getImplementationForDeclarationWithinDeclarationFile(options);
return (implementation as CallableFunction)(...args);
}
// If the body is a block, evaluate it as a statement
if (node.body == null) return;
evaluate.statement(node.body, nextOptions);
if (getCurrentError() != null) {
return;
}
// If a 'return' has occurred within the block, pop the Stack and return that value
if (pathInLexicalEnvironmentEquals(node, localLexicalEnvironment, true, RETURN_SYMBOL)) {
return stack.pop();
}
// Otherwise, return 'undefined'. Nothing is returned from the function
else return undefined;
};
if (nameResult != null) {
setInLexicalEnvironment({...options, path: nameResult, value: _functionDeclaration.bind(_functionDeclaration)});
}
_functionDeclaration.toString = () => `[Function${nameResult == null ? "" : `: ${nameResult}`}]`;
// Make sure to use the Function that is contained within the Realm. Otherwise, 'instanceof' checks may fail
// since this particular function comes from the executing context.
Object.setPrototypeOf(_functionDeclaration, getFromLexicalEnvironment(node, environment, "Function")!.literal as CallableFunction);
stack.push(_functionDeclaration);
}