1
- import type es from 'estree'
2
1
import * as _ from 'lodash'
3
2
import type { RawSourceMap } from 'source-map'
4
3
5
- import { type IOptions , type Result } from '..'
6
4
import { JSSLANG_PROPERTIES } from '../constants'
7
5
import { CSEResultPromise , evaluate as CSEvaluate } from '../cse-machine/interpreter'
8
6
import { ExceptionError } from '../errors/errors'
9
7
import { RuntimeSourceError } from '../errors/runtimeSourceError'
10
8
import { TimeoutError } from '../errors/timeoutErrors'
11
9
import { isPotentialInfiniteLoop } from '../infiniteLoops/errors'
12
10
import { testForInfiniteLoop } from '../infiniteLoops/runtime'
13
- import preprocessFileImports from '../modules/preprocessor'
14
- import { defaultAnalysisOptions } from '../modules/preprocessor/analyzer'
15
- import { defaultLinkerOptions } from '../modules/preprocessor/linker'
16
- import { parse } from '../parser/parser'
17
11
import {
18
12
callee ,
19
13
getEvaluationSteps ,
@@ -23,288 +17,114 @@ import {
23
17
} from '../stepper/stepper'
24
18
import { sandboxedEval } from '../transpiler/evalContainer'
25
19
import { transpile } from '../transpiler/transpiler'
26
- import { Chapter , type Context , type RecursivePartial , Variant } from '../types'
27
- import { validateAndAnnotate } from '../validator/validator'
28
- import type { FileGetter } from '../modules/moduleTypes'
29
- import { mapResult } from '../alt-langs/mapper'
30
- import assert from '../utils/assert'
20
+ import { Variant } from '../types'
31
21
import { toSourceError } from './errors'
32
- import { fullJSRunner } from './fullJSRunner'
33
- import { determineExecutionMethod , determineVariant , resolvedErrorPromise } from './utils'
22
+ import { resolvedErrorPromise } from './utils'
23
+ import type { Runner } from './types'
24
+ import fullJSRunner from './fullJSRunner'
34
25
35
- export const DEFAULT_SOURCE_OPTIONS : Readonly < IOptions > = {
36
- steps : 1000 ,
37
- stepLimit : - 1 ,
38
- executionMethod : 'auto' ,
39
- variant : Variant . DEFAULT ,
40
- originalMaxExecTime : 1000 ,
41
- useSubst : false ,
42
- isPrelude : false ,
43
- throwInfiniteLoops : true ,
44
- envSteps : - 1 ,
45
- importOptions : {
46
- ...defaultAnalysisOptions ,
47
- ...defaultLinkerOptions ,
48
- loadTabs : true
49
- } ,
50
- shouldAddFileName : null
51
- }
52
-
53
- let previousCode : {
54
- files : Partial < Record < string , string > >
55
- entrypointFilePath : string
56
- } | null = null
57
26
let isPreviousCodeTimeoutError = false
58
-
59
- function runSubstitution (
60
- program : es . Program ,
61
- context : Context ,
62
- options : IOptions
63
- ) : Promise < Result > {
64
- const steps = getEvaluationSteps ( program , context , options )
65
- if ( context . errors . length > 0 ) {
66
- return resolvedErrorPromise
67
- }
68
- const redexedSteps : IStepperPropContents [ ] = [ ]
69
- for ( const step of steps ) {
70
- const redex = getRedex ( step [ 0 ] , step [ 1 ] )
71
- const redexed = redexify ( step [ 0 ] , step [ 1 ] )
72
- redexedSteps . push ( {
73
- code : redexed [ 0 ] ,
74
- redex : redexed [ 1 ] ,
75
- explanation : step [ 2 ] ,
76
- function : callee ( redex , context )
77
- } )
78
- }
79
- return Promise . resolve ( {
80
- status : 'finished' ,
81
- context,
82
- value : redexedSteps
83
- } )
84
- }
85
-
86
- async function runNative (
87
- program : es . Program ,
88
- context : Context ,
89
- options : IOptions
90
- ) : Promise < Result > {
91
- if ( ! options . isPrelude ) {
92
- if ( context . shouldIncreaseEvaluationTimeout && isPreviousCodeTimeoutError ) {
93
- context . nativeStorage . maxExecTime *= JSSLANG_PROPERTIES . factorToIncreaseBy
94
- } else {
95
- context . nativeStorage . maxExecTime = options . originalMaxExecTime
27
+ const runners = {
28
+ fulljs : fullJSRunner ,
29
+ 'cse-machine' : ( program , context , options ) => {
30
+ const value = CSEvaluate ( program , context , options )
31
+ return CSEResultPromise ( context , value )
32
+ } ,
33
+ substitution : ( program , context , options ) => {
34
+ const steps = getEvaluationSteps ( program , context , options )
35
+ if ( context . errors . length > 0 ) {
36
+ return resolvedErrorPromise
96
37
}
97
- }
98
-
99
- // For whatever reason, the transpiler mutates the state of the AST as it is transpiling and inserts
100
- // a bunch of global identifiers to it. Once that happens, the infinite loop detection instrumentation
101
- // ends up generating code that has syntax errors. As such, we need to make a deep copy here to preserve
102
- // the original AST for future use, such as with the infinite loop detector.
103
- const transpiledProgram = _ . cloneDeep ( program )
104
- let transpiled
105
- let sourceMapJson : RawSourceMap | undefined
106
- try {
107
- ; ( { transpiled, sourceMapJson } = transpile ( transpiledProgram , context ) )
108
- let value = sandboxedEval ( transpiled , context . nativeStorage )
109
-
110
- if ( ! options . isPrelude ) {
111
- isPreviousCodeTimeoutError = false
38
+ const redexedSteps : IStepperPropContents [ ] = [ ]
39
+ for ( const step of steps ) {
40
+ const redex = getRedex ( step [ 0 ] , step [ 1 ] )
41
+ const redexed = redexify ( step [ 0 ] , step [ 1 ] )
42
+ redexedSteps . push ( {
43
+ code : redexed [ 0 ] ,
44
+ redex : redexed [ 1 ] ,
45
+ explanation : step [ 2 ] ,
46
+ function : callee ( redex , context )
47
+ } )
112
48
}
113
-
114
- return {
49
+ return Promise . resolve ( {
115
50
status : 'finished' ,
116
51
context,
117
- value
52
+ value : redexedSteps
53
+ } )
54
+ } ,
55
+ native : async ( program , context , options ) => {
56
+ if ( ! options . isPrelude ) {
57
+ if ( context . shouldIncreaseEvaluationTimeout && isPreviousCodeTimeoutError ) {
58
+ context . nativeStorage . maxExecTime *= JSSLANG_PROPERTIES . factorToIncreaseBy
59
+ } else {
60
+ context . nativeStorage . maxExecTime = options . originalMaxExecTime
61
+ }
118
62
}
119
- } catch ( error ) {
120
- const isDefaultVariant = options . variant === undefined || options . variant === Variant . DEFAULT
121
- if ( isDefaultVariant && isPotentialInfiniteLoop ( error ) ) {
122
- const detectedInfiniteLoop = testForInfiniteLoop (
123
- program ,
124
- context . previousPrograms . slice ( 1 ) ,
125
- context . nativeStorage . loadedModules
126
- )
127
- if ( detectedInfiniteLoop !== undefined ) {
128
- if ( options . throwInfiniteLoops ) {
129
- context . errors . push ( detectedInfiniteLoop )
130
- return resolvedErrorPromise
131
- } else {
132
- error . infiniteLoopError = detectedInfiniteLoop
133
- if ( error instanceof ExceptionError ) {
134
- ; ( error . error as any ) . infiniteLoopError = detectedInfiniteLoop
63
+
64
+ // For whatever reason, the transpiler mutates the state of the AST as it is transpiling and inserts
65
+ // a bunch of global identifiers to it. Once that happens, the infinite loop detection instrumentation
66
+ // ends up generating code that has syntax errors. As such, we need to make a deep copy here to preserve
67
+ // the original AST for future use, such as with the infinite loop detector.
68
+ const transpiledProgram = _ . cloneDeep ( program )
69
+ let transpiled
70
+ let sourceMapJson : RawSourceMap | undefined
71
+ try {
72
+ ; ( { transpiled, sourceMapJson } = transpile ( transpiledProgram , context ) )
73
+ let value = sandboxedEval ( transpiled , context . nativeStorage )
74
+
75
+ if ( ! options . isPrelude ) {
76
+ isPreviousCodeTimeoutError = false
77
+ }
78
+
79
+ return {
80
+ status : 'finished' ,
81
+ context,
82
+ value
83
+ }
84
+ } catch ( error ) {
85
+ const isDefaultVariant = options . variant === undefined || options . variant === Variant . DEFAULT
86
+ if ( isDefaultVariant && isPotentialInfiniteLoop ( error ) ) {
87
+ const detectedInfiniteLoop = testForInfiniteLoop (
88
+ program ,
89
+ context . previousPrograms . slice ( 1 ) ,
90
+ context . nativeStorage . loadedModules
91
+ )
92
+ if ( detectedInfiniteLoop !== undefined ) {
93
+ if ( options . throwInfiniteLoops ) {
94
+ context . errors . push ( detectedInfiniteLoop )
95
+ return resolvedErrorPromise
96
+ } else {
97
+ error . infiniteLoopError = detectedInfiniteLoop
98
+ if ( error instanceof ExceptionError ) {
99
+ ; ( error . error as any ) . infiniteLoopError = detectedInfiniteLoop
100
+ }
135
101
}
136
102
}
137
103
}
138
- }
139
- if ( error instanceof RuntimeSourceError ) {
140
- context . errors . push ( error )
141
- if ( error instanceof TimeoutError ) {
142
- isPreviousCodeTimeoutError = true
143
- }
144
- return resolvedErrorPromise
145
- }
146
- if ( error instanceof ExceptionError ) {
147
- // if we know the location of the error, just throw it
148
- if ( error . location . start . line !== - 1 ) {
104
+ if ( error instanceof RuntimeSourceError ) {
149
105
context . errors . push ( error )
106
+ if ( error instanceof TimeoutError ) {
107
+ isPreviousCodeTimeoutError = true
108
+ }
150
109
return resolvedErrorPromise
151
- } else {
152
- error = error . error // else we try to get the location from source map
153
110
}
154
- }
155
-
156
- const sourceError = await toSourceError ( error , sourceMapJson )
157
- context . errors . push ( sourceError )
158
- return resolvedErrorPromise
159
- }
160
- }
161
-
162
- function runCSEMachine ( program : es . Program , context : Context , options : IOptions ) : Promise < Result > {
163
- const value = CSEvaluate ( program , context , options )
164
- return CSEResultPromise ( context , value )
165
- }
166
-
167
- async function sourceRunner (
168
- program : es . Program ,
169
- context : Context ,
170
- isVerboseErrorsEnabled : boolean ,
171
- options : RecursivePartial < IOptions > = { }
172
- ) : Promise < Result > {
173
- // It is necessary to make a copy of the DEFAULT_SOURCE_OPTIONS object because merge()
174
- // will modify it rather than create a new object
175
- const theOptions = _ . merge ( { ...DEFAULT_SOURCE_OPTIONS } , options )
176
- context . variant = determineVariant ( context , options )
177
-
178
- if (
179
- context . chapter === Chapter . FULL_JS ||
180
- context . chapter === Chapter . FULL_TS ||
181
- context . chapter === Chapter . PYTHON_1
182
- ) {
183
- return fullJSRunner ( program , context , theOptions . importOptions )
184
- }
185
-
186
- validateAndAnnotate ( program , context )
187
- if ( context . errors . length > 0 ) {
188
- return resolvedErrorPromise
189
- }
190
-
191
- if ( theOptions . useSubst ) {
192
- return runSubstitution ( program , context , theOptions )
193
- }
194
-
195
- determineExecutionMethod ( theOptions , context , program , isVerboseErrorsEnabled )
196
-
197
- // native, don't evaluate prelude
198
- if ( context . executionMethod === 'native' && context . variant === Variant . NATIVE ) {
199
- return fullJSRunner ( program , context , theOptions . importOptions )
200
- }
111
+ if ( error instanceof ExceptionError ) {
112
+ // if we know the location of the error, just throw it
113
+ if ( error . location . start . line !== - 1 ) {
114
+ context . errors . push ( error )
115
+ return resolvedErrorPromise
116
+ } else {
117
+ error = error . error // else we try to get the location from source map
118
+ }
119
+ }
201
120
202
- // All runners after this point evaluate the prelude.
203
- if ( context . prelude !== null ) {
204
- context . unTypecheckedCode . push ( context . prelude )
205
- const prelude = parse ( context . prelude , context )
206
- if ( prelude === null ) {
121
+ const sourceError = await toSourceError ( error , sourceMapJson )
122
+ context . errors . push ( sourceError )
207
123
return resolvedErrorPromise
208
124
}
209
- context . prelude = null
210
- await sourceRunner ( prelude , context , isVerboseErrorsEnabled , { ...options , isPrelude : true } )
211
- return sourceRunner ( program , context , isVerboseErrorsEnabled , options )
212
- }
213
-
214
- if ( context . variant === Variant . EXPLICIT_CONTROL || context . executionMethod === 'cse-machine' ) {
215
- if ( options . isPrelude ) {
216
- const preludeContext = { ...context , runtime : { ...context . runtime , debuggerOn : false } }
217
- const result = await runCSEMachine ( program , preludeContext , theOptions )
218
- // Update object count in main program context after prelude is run
219
- context . runtime . objectCount = preludeContext . runtime . objectCount
220
- return result
221
- }
222
- return runCSEMachine ( program , context , theOptions )
223
- }
224
-
225
- assert (
226
- context . executionMethod !== 'auto' ,
227
- 'Execution method should have been properly determined!'
228
- )
229
- return runNative ( program , context , theOptions )
230
- }
231
-
232
- /**
233
- * Returns both the Result of the evaluated program, as well as
234
- * `verboseErrors`.
235
- */
236
- export async function sourceFilesRunner (
237
- filesInput : FileGetter ,
238
- entrypointFilePath : string ,
239
- context : Context ,
240
- options : RecursivePartial < IOptions > = { }
241
- ) : Promise < {
242
- result : Result
243
- verboseErrors : boolean
244
- } > {
245
- const preprocessResult = await preprocessFileImports (
246
- filesInput ,
247
- entrypointFilePath ,
248
- context ,
249
- options
250
- )
251
-
252
- if ( ! preprocessResult . ok ) {
253
- return {
254
- result : { status : 'error' } ,
255
- verboseErrors : preprocessResult . verboseErrors
256
- }
257
- }
258
-
259
- const { files, verboseErrors, program : preprocessedProgram } = preprocessResult
260
-
261
- context . variant = determineVariant ( context , options )
262
- // FIXME: The type checker does not support the typing of multiple files, so
263
- // we only push the code in the entrypoint file. Ideally, all files
264
- // involved in the program evaluation should be type-checked. Either way,
265
- // the type checker is currently not used at all so this is not very
266
- // urgent.
267
- context . unTypecheckedCode . push ( files [ entrypointFilePath ] )
268
-
269
- const currentCode = {
270
- files,
271
- entrypointFilePath
272
125
}
273
- context . shouldIncreaseEvaluationTimeout = _ . isEqual ( previousCode , currentCode )
274
- previousCode = currentCode
275
-
276
- context . previousPrograms . unshift ( preprocessedProgram )
126
+ } satisfies Record < string , Runner >
277
127
278
- const result = await sourceRunner ( preprocessedProgram , context , verboseErrors , options )
279
- const resultMapper = mapResult ( context )
280
-
281
- return {
282
- result : resultMapper ( result ) ,
283
- verboseErrors
284
- }
285
- }
128
+ export default runners
286
129
287
- /**
288
- * Useful for just running a single line of code with the given context
289
- * However, if this single line of code is an import statement,
290
- * then the FileGetter is necessary, otherwise all local imports will
291
- * fail with ModuleNotFoundError
292
- */
293
- export function runCodeInSource (
294
- code : string ,
295
- context : Context ,
296
- options : RecursivePartial < IOptions > = { } ,
297
- defaultFilePath : string = '/default.js' ,
298
- fileGetter ?: FileGetter
299
- ) {
300
- return sourceFilesRunner (
301
- path => {
302
- if ( path === defaultFilePath ) return Promise . resolve ( code )
303
- if ( ! fileGetter ) return Promise . resolve ( undefined )
304
- return fileGetter ( path )
305
- } ,
306
- defaultFilePath ,
307
- context ,
308
- options
309
- )
310
- }
130
+ export type RunnerTypes = keyof typeof runners
0 commit comments