Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further Enhancements to the Module System #1428

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
16ba5ad
Update configuration files
leeyi45 Jun 22, 2023
c1a51dd
Add async module loading code and tests
leeyi45 Jun 22, 2023
f8e3311
Add general utils
leeyi45 Jun 22, 2023
40b1eb3
Add helper types
leeyi45 Jun 22, 2023
d639512
Move preprocessor to modules folder
leeyi45 Jun 22, 2023
6d81ad4
Relocate ast creator to new utils folder
leeyi45 Jun 22, 2023
8acfd79
Add AST utils and relocate walkers
leeyi45 Jun 22, 2023
b00153e
Clean version of async-imports3
leeyi45 Jun 22, 2023
a1f47f4
Ran format
leeyi45 Jun 22, 2023
ef8d903
Update error tests
leeyi45 Jun 22, 2023
150e94e
Ran format
leeyi45 Jun 22, 2023
509be3b
Update snapshot
leeyi45 Jun 22, 2023
d565099
Remove unnecessary code
leeyi45 Jun 22, 2023
92d7b48
Clean up ec-evaluator implementation for imports
leeyi45 Jun 23, 2023
d493ffa
Fix accumulate working in the wrong direction
leeyi45 Jun 23, 2023
3ba38ef
Ran format
leeyi45 Jun 23, 2023
813cceb
Update list test to take initial argument into account
leeyi45 Jun 23, 2023
2a2ac1f
Add missing tests
leeyi45 Jun 23, 2023
ba1cc1b
Ran format
leeyi45 Jun 23, 2023
532a944
Ran format
leeyi45 Jun 23, 2023
fbbb690
Add tests for length()
leeyi45 Jun 28, 2023
24ec58b
Merge commit '8618e26e47595bdeb609857d7d3265cde09beb37' into async-im…
leeyi45 Jun 28, 2023
43481bb
Ensure that moduleLoaderAsync properly handles fetch type errors
leeyi45 Jun 28, 2023
9ed1e54
Fix broken error handling
leeyi45 Jun 28, 2023
0380f5e
Add test to ensure that TypeErrors thrown by fetch are caught
leeyi45 Jun 28, 2023
d67af01
Fix fetch not timing out when modules server is unreachable
leeyi45 Jul 14, 2023
bc6a53f
Ran format
leeyi45 Jul 14, 2023
fb49b56
fix(deps): update all non-major dependencies
renovate[bot] Jul 19, 2023
bd4b132
Merge from master
leeyi45 Jul 19, 2023
1e05b13
Update @types/node
leeyi45 Jul 19, 2023
d1e5800
Ran format
leeyi45 Jul 19, 2023
937bae9
Update stepper
leeyi45 Jul 29, 2023
d330c39
Move module mocking to individual tests
leeyi45 Jul 29, 2023
d06a2ca
Refactor ast utils
leeyi45 Jul 30, 2023
f66472d
Fix broken path
leeyi45 Jul 30, 2023
157cd3b
Add no rexport declaration rule
leeyi45 Jul 30, 2023
545f558
fix(deps): update all non-major dependencies
renovate[bot] Jul 31, 2023
fd901b9
Add no reexport declaration rule
leeyi45 Aug 1, 2023
8553c76
Ensure reexport rule is not enforced for FullJS
leeyi45 Aug 1, 2023
f4fc2e2
Update code to use type guards
leeyi45 Aug 1, 2023
2b7eb11
Ran format
leeyi45 Aug 1, 2023
cdbac82
Revert node types change temporarily
leeyi45 Aug 1, 2023
a3d9c4d
Merge commit '545f558853f44e377aca8822074ea1730f42921e' into async-im…
leeyi45 Aug 1, 2023
59dbf48
Ran format
leeyi45 Aug 1, 2023
dfb5882
Update stepper and tests for async
leeyi45 Aug 2, 2023
0dc35cc
Add tests for ArrayMap
leeyi45 Aug 2, 2023
5f8f68e
Fix broken tests
leeyi45 Aug 2, 2023
f264219
Update code to use ArrayMap
leeyi45 Aug 2, 2023
b05f072
Update node types
leeyi45 Aug 2, 2023
a8f3951
Update node types
leeyi45 Aug 2, 2023
657ab8c
Remove global mocks entirely
leeyi45 Aug 2, 2023
9913230
Fix tests not working with wrap-ansi
leeyi45 Aug 2, 2023
288af5d
Ran format
leeyi45 Aug 2, 2023
703da37
Merge branch 'master' into async-imports-clean
martin-henz Aug 7, 2023
1d75fa2
Merge branch 'master' into async-imports-clean
martin-henz Aug 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"presets": ["es2015", "stage-2"]
"presets": ["@babel/preset-env"]
}
5 changes: 5 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier"
],
"ignorePatterns": [
"**/__tests__/**",
"**/__mocks__/**",
"jest.setup.ts"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules
dist/
.idea/
coverage/
yarn-error.log

# emacs backup files
*~
Expand Down
16 changes: 16 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SourceMapConsumer } from "source-map"

jest.mock('lodash', () => ({
...jest.requireActual('lodash'),
memoize: jest.fn((x: any) => x),
}))

jest.mock('./src/modules/moduleLoaderAsync')
jest.mock('./src/modules/moduleLoader')

// @ts-ignore
SourceMapConsumer.initialize({
'lib/mappings.wasm': 'https://unpkg.com/[email protected]/lib/mappings.wasm'
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this is needed for the source-map library to work in browsers (aa58f3d). Is this really needed for the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running tests on my system, without this code in jest.setup.ts causes TypeError: Cannot read properties of undefined (reading 'then') when running certain tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I can't replicate this locally. Logically, this shouldn't be needed in a Node.js environment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I just don't really know where the issue is, or how to resolve it at the moment


global.fetch = jest.fn()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to mock only the functions/classes that each test suite needs within the test suite itself for the same reasons that we always try to reduce the scope of variables. If we were to declare all of our mocks here, you can imagine that it would very quickly become hard to keep track of which mocks are used in which test suite as new test cases are added/updated over time.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"dependencies": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump @types/node (dev dep) to 20.4.4 to solve CI error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my system, updating @types/node produces this error when running yarn test:

Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/leeyi45/source/source_academy/js-slang/node_modules/wrap-ansi/index.js from /Users/leeyi45/source/source_academy/js-slang/node_modules/cliui/build/index.cjs not supported.
Instead change the require of index.js in /Users/leeyi45/source/source_academy/js-slang/node_modules/cliui/build/index.cjs to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/Users/leeyi45/source/source_academy/js-slang/node_modules/cliui/build/index.cjs:293:14)
    at Object.<anonymous> (/Users/leeyi45/source/source_academy/js-slang/node_modules/yargs/build/index.cjs:1:60678)
    at Object.<anonymous> (/Users/leeyi45/source/source_academy/js-slang/node_modules/yargs/index.cjs:5:30)
    at _yargs (/Users/leeyi45/source/source_academy/js-slang/node_modules/jest-cli/build/run.js:30:39)
    at buildArgv (/Users/leeyi45/source/source_academy/js-slang/node_modules/jest-cli/build/run.js:143:26)
    at Object.run (/Users/leeyi45/source/source_academy/js-slang/node_modules/jest-cli/build/run.js:118:24)
    at Object.<anonymous> (/Users/leeyi45/source/source_academy/js-slang/node_modules/jest-cli/bin/jest.js:16:17)
    at Object.<anonymous> (/Users/leeyi45/source/source_academy/js-slang/node_modules/jest/bin/jest.js:12:3)

"@babel/parser": "^7.19.4",
"@joeychenofficial/alt-ergo-modified": "^2.4.0",
"@ts-morph/bootstrap": "^0.18.0",
"@ts-morph/bootstrap": "^0.20.0",
"@types/estree": "0.0.52",
"acorn": "^8.8.2",
"acorn-class-fields": "^1.0.0",
Expand All @@ -44,7 +44,7 @@
"js-base64": "^3.7.5",
"lodash": "^4.17.20",
"node-getopt": "^0.3.2",
"source-map": "0.7.3",
"source-map": "0.7.4",
"xmlhttprequest-ts": "^1.0.1"
},
"scripts": {
Expand Down Expand Up @@ -115,6 +115,9 @@
"/src/scm-slang",
"/src/py-slang/"
],
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.ts"
],
"reporters": [
"default",
[
Expand Down
1,097 changes: 593 additions & 504 deletions sicp_publish/yarn.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/__tests__/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ test('Function params and body identifiers are in different environment', () =>
const context = mockContext(Chapter.SOURCE_4)
context.prelude = null // hide the unneeded prelude
const parsed = parse(code, context)
const it = evaluate(parsed as any as Program, context, false, false)
const it = evaluate(parsed as any as Program, context, {
loadTabs: false,
wrapModules: false
})
const stepsToComment = 13 // manually counted magic number
for (let i = 0; i < stepsToComment; i += 1) {
it.next()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,19 +792,6 @@ Object {
}
`;

exports[`Importing unknown variables throws UndefinedImport error: expectParsedError 1`] = `
Object {
"alertResult": Array [],
"code": "import { foo1 } from 'one_module';",
"displayResult": Array [],
"numErrors": 1,
"parsedErrors": "'one_module' does not contain a definition for 'foo1'",
"result": undefined,
"resultStatus": "error",
"visualiseListResult": Array [],
}
`;

exports[`In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.: expectParsedError 1`] = `
Object {
"alertResult": Array [],
Expand Down
48 changes: 0 additions & 48 deletions src/ec-evaluator/__tests__/ec-evaluator-errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/* tslint:disable:max-line-length */
import * as _ from 'lodash'

import { Chapter, Variant } from '../../types'
import { stripIndent } from '../../utils/formatters'
import {
Expand All @@ -10,20 +8,6 @@ import {
expectResult
} from '../../utils/testing'

jest.spyOn(_, 'memoize').mockImplementation(func => func as any)

const mockXMLHttpRequest = (xhr: Partial<XMLHttpRequest> = {}) => {
const xhrMock: Partial<XMLHttpRequest> = {
open: jest.fn(() => {}),
send: jest.fn(() => {}),
status: 200,
responseText: 'Hello World!',
...xhr
}
jest.spyOn(window, 'XMLHttpRequest').mockImplementationOnce(() => xhrMock as XMLHttpRequest)
return xhrMock
}

const undefinedVariable = stripIndent`
im_undefined;
`
Expand Down Expand Up @@ -1016,35 +1000,3 @@ test('Shadowed variables may not be assigned to until declared in the current sc
optionEC3
).toMatchInlineSnapshot(`"Line 3: Name variable not declared."`)
})

test('Importing unknown variables throws UndefinedImport error', () => {
// for getModuleFile
mockXMLHttpRequest({
responseText: `{
"one_module": {
"tabs": []
},
"another_module": {
"tabs": []
}
}`
})

// for bundle body
mockXMLHttpRequest({
responseText: `
require => {
return {
foo: () => 'foo',
}
}
`
})

return expectParsedError(
stripIndent`
import { foo1 } from 'one_module';
`,
optionEC
).toMatchInlineSnapshot("\"'one_module' does not contain a definition for 'foo1'\"")
})
51 changes: 26 additions & 25 deletions src/ec-evaluator/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
*/

/* tslint:disable:max-classes-per-file */
import * as es from 'estree'
import { uniqueId } from 'lodash'

import { IOptions } from '..'
import { UNKNOWN_LOCATION } from '../constants'
import * as errors from '../errors/errors'
import { RuntimeSourceError } from '../errors/runtimeSourceError'
import Closure from '../interpreter/closure'
import { UndefinedImportError } from '../modules/errors'
import { loadModuleBundle, loadModuleTabs } from '../modules/moduleLoader'
import type { ImportTransformOptions } from '../modules/moduleTypes'
import { checkEditorBreakpoints } from '../stdlib/inspector'
import { Context, ContiguousArrayElements, Result, Value } from '../types'
import * as ast from '../utils/astCreator'
import * as ast from '../utils/ast/astCreator'
import type * as es from '../utils/ast/types'
import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators'
import * as rttc from '../utils/rttc'
import * as instr from './instrCreator'
Expand Down Expand Up @@ -99,7 +99,11 @@ export class Stash extends Stack<Value> {
* @param context The context to evaluate the program in.
* @returns The result of running the ECE machine.
*/
export function evaluate(program: es.Program, context: Context, options: IOptions): Value {
export async function evaluate(
program: es.Program,
context: Context,
options: IOptions
): Promise<Value> {
try {
context.runtime.isRunning = true
context.runtime.agenda = new Agenda(program)
Expand Down Expand Up @@ -135,8 +139,7 @@ export function resumeEvaluate(context: Context) {
function evaluateImports(
node: es.ImportDeclaration,
context: Context,
loadTabs: boolean,
checkImports: boolean
{ loadTabs, wrapModules }: ImportTransformOptions
) {
try {
const moduleName = node.source.value
Expand All @@ -153,22 +156,20 @@ function evaluateImports(
context.moduleContexts[moduleName].tabs = loadModuleTabs(moduleName)
}

const functions = loadModuleBundle(moduleName, context, node)
const functions = loadModuleBundle(moduleName, context, wrapModules, node)
const environment = currentEnvironment(context)
for (const spec of node.specifiers) {
if (spec.type !== 'ImportSpecifier') {
throw new Error(`Only ImportSpecifiers are supported, got: ${spec.type}`)
if (spec.type === 'ImportNamespaceSpecifier') {
throw new Error('ImportNamespaceSpecifiers are not supported!')
}

if (checkImports && !(spec.imported.name in functions)) {
throw new UndefinedImportError(spec.imported.name, moduleName, node)
}
const importedName = spec.type === 'ImportDefaultSpecifier' ? 'default' : spec.imported.name

declareIdentifier(context, spec.local.name, node, environment)
defineVariable(context, spec.local.name, functions[spec.imported.name], true, node)
defineVariable(context, spec.local.name, functions[importedName], true, node)
}
} catch (error) {
// console.log(error)
// console.error(error)
handleRuntimeError(context, error)
}
}
Expand All @@ -180,16 +181,15 @@ function evaluateImports(
* @param value The value of ec evaluating the program.
* @returns The corresponding promise.
*/
export function ECEResultPromise(context: Context, value: Value): Promise<Result> {
return new Promise((resolve, reject) => {
if (value instanceof ECEBreak) {
resolve({ status: 'suspended-ec-eval', context })
} else if (value instanceof ECError) {
resolve({ status: 'error' })
} else {
resolve({ status: 'finished', context, value })
}
})
export async function ECEResultPromise(context: Context, promise: Promise<Value>): Promise<Result> {
const value = await promise
if (value instanceof ECEBreak) {
return { status: 'suspended-ec-eval', context }
} else if (value instanceof ECError) {
return { status: 'error' }
} else {
return { status: 'finished', context, value }
}
}

/**
Expand Down Expand Up @@ -475,7 +475,8 @@ const cmdEvaluators: { [type: string]: CmdEvaluator } = {
},

ImportDeclaration: function (command: es.ImportDeclaration, context: Context, agenda: Agenda) {
evaluateImports(command, context, true, true)
// TODO: Don't hardcode options
evaluateImports(command, context, { wrapModules: true, loadTabs: true })
},

/**
Expand Down
2 changes: 1 addition & 1 deletion src/ec-evaluator/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as errors from '../errors/errors'
import { RuntimeSourceError } from '../errors/runtimeSourceError'
import Closure from '../interpreter/closure'
import { Environment, Frame, Value } from '../types'
import * as ast from '../utils/astCreator'
import * as ast from '../utils/ast/astCreator'
import * as instr from './instrCreator'
import { Agenda } from './interpreter'
import { AgendaItem, AppInstr, AssmtInstr, Instr, InstrType } from './types'
Expand Down
76 changes: 0 additions & 76 deletions src/errors/localImportErrors.ts

This file was deleted.

Loading
Loading