Skip to content

Commit 8fef11f

Browse files
committedMar 3, 2025
Relocate svmc repl prompt
1 parent e35223f commit 8fef11f

36 files changed

+463
-4205
lines changed
 

‎README.md

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ Currently, valid CHAPTER/VARIANT combinations are:
8181
- `--chapter=2 --variant=interpreter`
8282
- `--chapter=2 --variant=typed`
8383
- `--chapter=3 --variant=default`
84-
- `--chapter=3 --variant=concurrent`
8584
- `--chapter=3 --variant=interpreter`
8685
- `--chapter=3 --variant=typed`
8786
- `--chapter=4 --variant=default`

‎docs/lib/concurrency.js

-32
This file was deleted.

‎docs/md/README_3_CONCURRENT.md

-62
This file was deleted.

‎docs/md/README_CONCURRENCY.md

-10
This file was deleted.

‎docs/md/README_top.md

-4
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ the members of our learning community.
3131

3232
#### <a href="source_2_typed/">Source §2 Typed</a>
3333

34-
#### <a href="source_3_concurrent/">Source §3 Concurrent</a>
35-
3634
#### <a href="source_3_typed/">Source §3 Typed</a>
3735

3836
#### <a href="source_4_typed/">Source §4 Typed</a>
@@ -58,8 +56,6 @@ the Source Academy.
5856

5957
#### <a href="source_2_typed.pdf">Specification of Source §2 Typed</a>
6058

61-
#### <a href="source_3_concurrent.pdf">Specification of Source §3 Concurrent</a>
62-
6359
#### <a href="source_3_typed.pdf">Specification of Source §3 Typed</a>
6460

6561
#### <a href="source_4_typed.pdf">Specification of Source §4 Typed</a>

‎docs/specs/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PDFLATEX = latexmk -pdf
22

3-
SPECSNUMS = 1 1_wasm 1_type_inference 1_infinite_loop_detection 1_typed 2 2_typed 3_type_inference 3 3_concurrent 3_typed 4 4_explicitcontrol 4_typed styleguide 2_stepper studio_2 python_1
3+
SPECSNUMS = 1 1_wasm 1_type_inference 1_infinite_loop_detection 1_typed 2 2_typed 3_type_inference 3 3_typed 4 4_explicitcontrol 4_typed styleguide 2_stepper studio_2 python_1
44

55
SPECS = $(SPECSNUMS:%=source_%)
66

‎docs/specs/source_3_concurrent.tex

-106
This file was deleted.

‎docs/specs/source_concurrency.tex

-9
This file was deleted.

‎scripts/docs.mjs

-21
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,6 @@ const configs = {
7171
"pairmutator.js"
7272
]
7373
},
74-
"Source §3 Concurrent": {
75-
"readme": "README_3_CONCURRENT.md",
76-
"dst": "source_3_concurrent/",
77-
"libs": [
78-
"auxiliary.js",
79-
"misc.js",
80-
"math.js",
81-
"list.js",
82-
"stream.js",
83-
"array.js",
84-
"pairmutator.js",
85-
"concurrency.js"
86-
]
87-
},
8874
"Source §3 Typed": {
8975
"readme": "README_3_TYPED.md",
9076
"dst": "source_3_typed/",
@@ -190,13 +176,6 @@ const configs = {
190176
"pairmutator.js"
191177
]
192178
},
193-
"CONCURRENCY": {
194-
"readme": "README_CONCURRENCY.md",
195-
"dst": "CONCURRENCY/",
196-
"libs": [
197-
"concurrency.js"
198-
]
199-
},
200179
"MCE": {
201180
"readme": "README_MCE.md",
202181
"dst": "MCE/",

‎src/constants.ts

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ export const sourceLanguages: Language[] = [
3535
{ chapter: Chapter.SOURCE_2, variant: Variant.TYPED },
3636
{ chapter: Chapter.SOURCE_3, variant: Variant.DEFAULT },
3737
{ chapter: Chapter.SOURCE_3, variant: Variant.TYPED },
38-
{ chapter: Chapter.SOURCE_3, variant: Variant.CONCURRENT },
3938
{ chapter: Chapter.SOURCE_4, variant: Variant.DEFAULT },
4039
{ chapter: Chapter.SOURCE_4, variant: Variant.TYPED },
4140
{ chapter: Chapter.SOURCE_4, variant: Variant.EXPLICIT_CONTROL }

‎src/createContext.ts

-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
import { GLOBAL, JSSLANG_PROPERTIES } from './constants'
1212
import { call_with_current_continuation } from './cse-machine/continuations'
1313
import Heap from './cse-machine/heap'
14-
import { AsyncScheduler } from './schedulers'
1514
import * as list from './stdlib/list'
1615
import { list_to_vector } from './stdlib/list'
1716
import { listPrelude } from './stdlib/list.prelude'
@@ -121,7 +120,6 @@ const createEmptyDebugger = () => ({
121120
it: (function* (): any {
122121
return
123122
})(),
124-
scheduler: new AsyncScheduler()
125123
}
126124
})
127125

‎src/editors/ace/docTooltip/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as source_1_typed from './source_1_typed.json'
44
import * as source_2 from './source_2.json'
55
import * as source_2_typed from './source_2_typed.json'
66
import * as source_3 from './source_3.json'
7-
import * as source_3_concurrent from './source_3_concurrent.json'
87
import * as source_3_typed from './source_3_typed.json'
98
import * as source_4 from './source_4.json'
109
import * as source_4_typed from './source_4_typed.json'
@@ -45,7 +44,6 @@ export const SourceDocumentation = {
4544
'2': resolveImportInconsistency(source_2),
4645
'2_typed': resolveImportInconsistency(source_2_typed),
4746
'3': resolveImportInconsistency(source_3),
48-
'3_concurrent': resolveImportInconsistency(source_3_concurrent),
4947
'3_typed': resolveImportInconsistency(source_3_typed),
5048
'4': resolveImportInconsistency(source_4),
5149
'4_typed': resolveImportInconsistency(source_4_typed),

‎src/index.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,9 @@ export async function runFilesInContext(
253253
export function resume(result: Result): Finished | ResultError | Promise<Result> {
254254
if (result.status === 'finished' || result.status === 'error') {
255255
return result
256-
} else if (result.status === 'suspended-cse-eval') {
257-
const value = resumeEvaluate(result.context)
258-
return CSEResultPromise(result.context, value)
259-
} else {
260-
return result.scheduler.run(result.it, result.context)
261-
}
256+
}
257+
const value = resumeEvaluate(result.context)
258+
return CSEResultPromise(result.context, value)
262259
}
263260

264261
export function interrupt(context: Context) {

‎src/modules/preprocessor/__tests__/preprocessor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { mockContext } from '../../../mocks/context'
66
import { Chapter, type RecursivePartial } from '../../../types'
77
import { memoizedGetModuleDocsAsync } from '../../loader/loaders'
88
import preprocessFileImports from '..'
9-
import { sanitizeAST } from '../../../utils/ast/sanitizer'
9+
import { sanitizeAST } from '../../../utils/testing/sanitizer'
1010
import { parse } from '../../../parser/parser'
1111
import {
1212
accessExportFunctionName,

‎src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { mockContext } from '../../../../mocks/context'
22
import { parse } from '../../../../parser/parser'
33
import { Chapter } from '../../../../types'
44
import hoistAndMergeImports from '../../transformers/hoistAndMergeImports'
5-
import { sanitizeAST } from '../../../../utils/ast/sanitizer'
5+
import { sanitizeAST } from '../../../../utils/testing/sanitizer'
66

77
describe('hoistAndMergeImports', () => {
88
const assertASTsAreEqual = (actualCode: string, expectedCode: string) => {

‎src/modules/preprocessor/__tests__/transformers/removeExports.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { mockContext } from '../../../../mocks/context'
22
import { parse } from '../../../../parser/parser'
33
import { Chapter, type Context } from '../../../../types'
44
import removeExports from '../../transformers/removeExports'
5-
import { sanitizeAST } from '../../../../utils/ast/sanitizer'
5+
import { sanitizeAST } from '../../../../utils/testing/sanitizer'
66

77
type TestCase = [description: string, inputCode: string, expectedCode: string]
88

‎src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { parse } from '../../../../parser/parser'
33
import { defaultExportLookupName } from '../../../../stdlib/localImport.prelude'
44
import { Chapter } from '../../../../types'
55
import { transformProgramToFunctionDeclaration } from '../../transformers/transformProgramToFunctionDeclaration'
6-
import { sanitizeAST } from '../../../../utils/ast/sanitizer'
6+
import { sanitizeAST } from '../../../../utils/testing/sanitizer'
77

88
describe('transformImportedFile', () => {
99
const currentFileName = '/dir/a.js'

‎src/repl/__tests__/main.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Command } from 'commander'
2+
import { getMainCommand } from '../main'
3+
4+
jest.spyOn(process, 'exit').mockImplementation(code => {
5+
throw new Error(`process.exit called with ${code}`)
6+
})
7+
8+
jest.spyOn(process.stdout, 'write').mockImplementation(() => true)
9+
10+
describe('Make sure each subcommand can be run', () => {
11+
const mainCommand = getMainCommand()
12+
test.each(mainCommand.commands.map(cmd => [cmd.name(), cmd] as [string, Command]))(
13+
'Testing %s command',
14+
(_, cmd) => {
15+
return expect(cmd.parseAsync(['-h'], { from: 'user' })).rejects.toMatchInlineSnapshot(
16+
'[Error: process.exit called with 0]'
17+
)
18+
}
19+
)
20+
})

‎src/repl/__tests__/svmc.ts

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { asMockedFunc } from '../../utils/testing/misc'
2+
import { compileToChoices, getSVMCCommand } from '../svmc'
3+
import * as vm from '../../vm/svml-compiler'
4+
import * as fs from 'fs/promises'
5+
import { INTERNAL_FUNCTIONS } from '../../stdlib/vm.prelude'
6+
import { expectWritten, getCommandRunner } from './utils'
7+
8+
jest.mock('fs/promises', () => ({
9+
writeFile: jest.fn(),
10+
readFile: jest.fn()
11+
}))
12+
13+
const mockedReadFile = asMockedFunc(fs.readFile)
14+
const mockedWriteFile = asMockedFunc(fs.writeFile)
15+
16+
jest.spyOn(vm, 'compileToIns')
17+
18+
beforeEach(() => {
19+
jest.clearAllMocks()
20+
})
21+
22+
const { expectError: rawExpectError, expectSuccess: rawExpectSuccess } =
23+
getCommandRunner(getSVMCCommand)
24+
25+
async function expectSuccess(code: string, ...args: string[]) {
26+
mockedReadFile.mockResolvedValueOnce(code)
27+
28+
await rawExpectSuccess(...args)
29+
expect(fs.readFile).toHaveBeenCalledTimes(1)
30+
expect(fs.writeFile).toHaveBeenCalledTimes(1)
31+
}
32+
33+
function expectError(code: string, ...args: string[]) {
34+
mockedReadFile.mockResolvedValueOnce(code)
35+
return rawExpectError(...args)
36+
}
37+
38+
test('Running with defaults', async () => {
39+
await expectSuccess('1+1;', 'test.js')
40+
41+
const [[fileName]] = mockedWriteFile.mock.calls
42+
expect(fileName).toEqual('test.svm')
43+
})
44+
45+
it("won't run if the program has parsing errors", async () => {
46+
await expectError('1 + 1', '/test.js')
47+
expect(vm.compileToIns).toHaveBeenCalledTimes(0)
48+
expectWritten(process.stderr.write).toMatchInlineSnapshot(
49+
`"Line 1: Missing semicolon at the end of statement"`
50+
)
51+
})
52+
53+
it("won't perform compilation if the output type is 'ast'", async () => {
54+
await expectSuccess('1+1;', 'test.js', '-t', 'ast')
55+
expect(vm.compileToIns).toHaveBeenCalledTimes(0)
56+
})
57+
58+
describe('--internals option', () => {
59+
test('with valid values', async () => {
60+
await expectSuccess('1+1;', 'test.js', '--internals', '["func1", "func2"]')
61+
expect(vm.compileToIns).toHaveBeenCalledTimes(1)
62+
const [[, , internals]] = asMockedFunc(vm.compileToIns).mock.calls
63+
64+
expect(internals).toEqual(['func1', 'func2'])
65+
})
66+
67+
test('with non-string values in array', async () => {
68+
await expectError('1+1;', 'test.js', '--internals', '[1, 2]')
69+
expectWritten(process.stderr.write).toMatchInlineSnapshot(`
70+
"error: option '-i, --internals <names>' argument '[1, 2]' is invalid. Expected a JSON array of strings!
71+
"
72+
`)
73+
})
74+
75+
test('with a non-array', async () => {
76+
await expectError('1+1;', 'test.js', '--internals', '{ "a": 1, "b": 2}')
77+
expectWritten(process.stderr.write).toMatchInlineSnapshot(`
78+
"error: option '-i, --internals <names>' argument '{ \\"a\\": 1, \\"b\\": 2}' is invalid. Expected a JSON array of strings!
79+
"
80+
`)
81+
})
82+
83+
it('is ignored if variant is concurrent', async () => {
84+
await expectSuccess(
85+
'1+1;',
86+
'test.js',
87+
'--internals',
88+
'["func1", "func2"]',
89+
'--variant',
90+
'concurrent'
91+
)
92+
93+
expect(vm.compileToIns).toHaveBeenCalledTimes(1)
94+
const [[, , internals]] = asMockedFunc(vm.compileToIns).mock.calls
95+
const expectedNames = INTERNAL_FUNCTIONS.map(([name]) => name)
96+
expect(internals).toEqual(expectedNames)
97+
})
98+
})
99+
100+
describe('Test output options', () => {
101+
compileToChoices.forEach(choice => {
102+
test(choice, async () => {
103+
await expectSuccess('1 + 1;', 'test.js', '-t', choice)
104+
const [[fileName, contents]] = mockedWriteFile.mock.calls
105+
106+
expect((fileName as string).startsWith('test')).toEqual(true)
107+
expect(contents).toMatchSnapshot()
108+
})
109+
})
110+
})

‎src/repl/__tests__/transpiler.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { asMockedFunc } from '../../utils/testing/misc'
2+
import { getTranspilerCommand } from '../transpiler'
3+
import * as fs from 'fs/promises'
4+
import { expectWritten, getCommandRunner } from './utils'
5+
6+
jest.mock('fs/promises', () => ({
7+
readFile: jest.fn(),
8+
writeFile: jest.fn()
9+
}))
10+
11+
beforeEach(() => {
12+
jest.clearAllMocks()
13+
})
14+
15+
const mockedWriteFile = asMockedFunc(fs.writeFile)
16+
const mockedReadFile = asMockedFunc(fs.readFile)
17+
const { expectError, expectSuccess } = getCommandRunner(getTranspilerCommand)
18+
19+
test('Nothing should be written if the program has parser errors', async () => {
20+
mockedReadFile.mockResolvedValueOnce('1+1')
21+
await expectError('/test.js')
22+
expect(fs.writeFile).toHaveBeenCalledTimes(0)
23+
24+
expectWritten(process.stderr.write).toMatchInlineSnapshot(
25+
`"[/test.js] Line 1: Missing semicolon at the end of statement"`
26+
)
27+
})
28+
29+
test('Nothing should be written if the program has transpiler errors', async () => {
30+
mockedReadFile.mockResolvedValueOnce('a;')
31+
await expectError('/test.js')
32+
expect(fs.writeFile).toHaveBeenCalledTimes(0)
33+
34+
expectWritten(process.stderr.write).toMatchInlineSnapshot(
35+
`"[/test.js] Line 1: Name a not declared."`
36+
)
37+
})
38+
39+
test('Nothing should be written to disk if no output file was specified', async () => {
40+
mockedReadFile.mockResolvedValueOnce('1+1;')
41+
await expectSuccess('test.js')
42+
expect(fs.writeFile).toHaveBeenCalledTimes(0)
43+
44+
// Code should have been written to stdout
45+
expectWritten(process.stdout.write).toMatchSnapshot()
46+
})
47+
48+
test('Writing to file', async () => {
49+
mockedReadFile.mockResolvedValueOnce('1+1;')
50+
await expectSuccess('test.js', '-o', 'out.js')
51+
expect(fs.writeFile).toHaveBeenCalledTimes(1)
52+
53+
const [[fileName, contents]] = mockedWriteFile.mock.calls
54+
expect(fileName).toEqual('out.js')
55+
expect(contents).toMatchSnapshot()
56+
})
57+
58+
test('pretranspile option', async () => {
59+
mockedReadFile.mockResolvedValueOnce('1+1;')
60+
await expectSuccess('test.js', '-o', 'out.js', '-p')
61+
expect(fs.writeFile).toHaveBeenCalledTimes(1)
62+
63+
const [[fileName, contents]] = mockedWriteFile.mock.calls
64+
expect(fileName).toEqual('out.js')
65+
expect(contents).toEqual('1 + 1;\n')
66+
})

‎src/repl/__tests__/utils.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { Command } from '@commander-js/extra-typings'
2+
import { asMockedFunc } from '../../utils/testing/misc'
3+
4+
/**
5+
* Set up the environment for testing the given command. Returns
6+
* `expectSuccess` and `expectError` for use with making assertions
7+
* about the behaviour of the command
8+
*/
9+
export function getCommandRunner<T extends Command<any, any>>(getter: () => T) {
10+
jest.spyOn(process.stdout, 'write').mockImplementation(() => true)
11+
jest.spyOn(process.stderr, 'write').mockImplementation(() => true)
12+
jest.spyOn(process, 'exit').mockImplementation(code => {
13+
throw new Error(`process.exit called with ${code}`)
14+
})
15+
16+
async function runner(...args: string[]) {
17+
await getter().parseAsync(args, { from: 'user' })
18+
}
19+
20+
return {
21+
expectError(...args: string[]) {
22+
// Error conditions should always cause commands to call
23+
// process.exit(1)
24+
return expect(runner(...args)).rejects.toMatchInlineSnapshot(
25+
`[Error: process.exit called with 1]`
26+
)
27+
},
28+
expectSuccess(...args: string[]) {
29+
return expect(runner(...args)).resolves.toBeUndefined()
30+
}
31+
}
32+
}
33+
34+
export function expectWritten(f: (contents: string) => any) {
35+
expect(f).toHaveBeenCalledTimes(1)
36+
const [[contents]] = asMockedFunc(f).mock.calls
37+
return expect(contents)
38+
}

‎src/repl/index.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
#!/usr/bin/env node
1+
#!/bin/env/node
2+
import { getMainCommand } from './main'
23

3-
import { Command } from '@commander-js/extra-typings'
4-
5-
import { getReplCommand } from './repl'
6-
import { transpilerCommand } from './transpiler'
7-
8-
new Command()
9-
.addCommand(transpilerCommand)
10-
.addCommand(getReplCommand(), { isDefault: true })
11-
.parseAsync()
4+
getMainCommand().parseAsync()

‎src/repl/main.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Command } from '@commander-js/extra-typings'
2+
3+
import { getSVMCCommand } from './svmc'
4+
import { getReplCommand } from './repl'
5+
import { getTranspilerCommand } from './transpiler'
6+
7+
export const getMainCommand = () =>
8+
new Command()
9+
.addCommand(getSVMCCommand())
10+
.addCommand(getTranspilerCommand())
11+
.addCommand(getReplCommand(), { isDefault: true })

‎src/repl/svmc.ts

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type pathlib from 'path'
2+
import type fslib from 'fs/promises'
3+
4+
import { Command, InvalidArgumentError, Option } from '@commander-js/extra-typings'
5+
import { createEmptyContext } from '../createContext'
6+
import { parse } from '../parser/parser'
7+
import { Chapter, Variant } from '../types'
8+
import { stripIndent } from '../utils/formatters'
9+
import { parseError } from '..'
10+
import { assemble } from '../vm/svml-assembler'
11+
import { compileToIns } from '../vm/svml-compiler'
12+
import { stringifyProgram } from '../vm/util'
13+
import { chapterParser, getChapterOption } from './utils'
14+
15+
export const compileToChoices = ['ast', 'binary', 'debug', 'json'] as const
16+
17+
export const getSVMCCommand = () =>
18+
new Command('svmc')
19+
.argument('<inputFile>', 'File to read code from')
20+
.addOption(getChapterOption(Chapter.SOURCE_3, chapterParser))
21+
.addOption(
22+
new Option(
23+
'-t, --compileTo <compileOption>',
24+
stripIndent`
25+
json: Compile only, but don't assemble.
26+
binary: Compile and assemble.
27+
debug: Compile and pretty-print the compiler output. For debugging the compiler.
28+
ast: Parse and pretty-print the AST. For debugging the parser.`
29+
)
30+
.choices(compileToChoices)
31+
.default('binary' as (typeof compileToChoices)[number])
32+
)
33+
.option(
34+
'-o, --out <outFile>',
35+
stripIndent`
36+
Sets the output filename.
37+
Defaults to the input filename, minus any file extension, plus '.svm'.
38+
`
39+
)
40+
.addOption(
41+
new Option(
42+
'-i, --internals <names>',
43+
`Sets the list of VM-internal functions. The argument should be a JSON array of
44+
strings containing the names of the VM-internal functions.`
45+
)
46+
.argParser(value => {
47+
const parsed = JSON.parse(value)
48+
if (!Array.isArray(parsed)) {
49+
throw new InvalidArgumentError('Expected a JSON array of strings!')
50+
}
51+
52+
for (const each of parsed) {
53+
if (typeof each !== 'string') {
54+
throw new InvalidArgumentError('Expected a JSON array of strings!')
55+
}
56+
}
57+
return parsed as string[]
58+
})
59+
.default([] as string[])
60+
)
61+
.action(async (inputFile, opts) => {
62+
const fs: typeof fslib = require('fs/promises')
63+
const vmInternalFunctions = opts.internals || []
64+
65+
const source = await fs.readFile(inputFile, 'utf-8')
66+
const context = createEmptyContext(opts.chapter, Variant.DEFAULT, [], null)
67+
const program = parse(source, context)
68+
if (program === null) {
69+
process.stderr.write(parseError(context.errors))
70+
process.exit(1)
71+
}
72+
73+
let output: string | Uint8Array
74+
let ext: string
75+
76+
if (opts.compileTo === 'ast') {
77+
output = JSON.stringify(program, undefined, 2)
78+
ext = '.json'
79+
} else {
80+
const compiled = compileToIns(program, undefined, vmInternalFunctions)
81+
switch (opts.compileTo) {
82+
case 'debug': {
83+
output = stringifyProgram(compiled).trimEnd()
84+
ext = '.svm'
85+
break
86+
}
87+
case 'json': {
88+
output = JSON.stringify(compiled)
89+
ext = '.json'
90+
break
91+
}
92+
case 'binary': {
93+
output = assemble(compiled)
94+
ext = '.svm'
95+
break
96+
}
97+
}
98+
}
99+
100+
const { extname, basename }: typeof pathlib = require('path')
101+
const extToRemove = extname(inputFile)
102+
103+
const outputFileName = opts.out ?? `${basename(inputFile, extToRemove)}${ext}`
104+
await fs.writeFile(outputFileName, output)
105+
console.log(`Output written to ${outputFileName}`)
106+
})

‎src/repl/transpiler.ts

+54-54
Original file line numberDiff line numberDiff line change
@@ -16,63 +16,63 @@ import {
1616
validateChapterAndVariantCombo
1717
} from './utils'
1818

19-
export const transpilerCommand = new Command('transpiler')
20-
.addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.NATIVE]))
21-
.addOption(getChapterOption(Chapter.SOURCE_4, chapterParser))
22-
.option(
23-
'-p, --pretranspile',
24-
"only pretranspile (e.g. GPU -> Source) and don't perform Source -> JS transpilation"
25-
)
26-
.option('-o, --out <outFile>', 'Specify a file to write to')
27-
.argument('<filename>')
28-
.action(async (fileName, opts) => {
29-
if (!validateChapterAndVariantCombo(opts)) {
30-
console.log('Invalid language combination!')
31-
return
32-
}
33-
34-
const fs: typeof fslib = require('fs/promises')
35-
const context = createContext(opts.chapter, opts.variant)
36-
const entrypointFilePath = resolve(fileName)
37-
38-
const linkerResult = await parseProgramsAndConstructImportGraph(
39-
async p => {
40-
try {
41-
const text = await fs.readFile(p, 'utf-8')
42-
return text
43-
} catch (error) {
44-
if (error.code === 'ENOENT') return undefined
45-
throw error
46-
}
47-
},
48-
entrypointFilePath,
49-
context,
50-
{},
51-
true
19+
export const getTranspilerCommand = () =>
20+
new Command('transpiler')
21+
.addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.NATIVE]))
22+
.addOption(getChapterOption(Chapter.SOURCE_4, chapterParser))
23+
.option(
24+
'-p, --pretranspile',
25+
"only pretranspile and don't perform Source -> JS transpilation"
5226
)
27+
.option('-o, --out <outFile>', 'Specify a file to write to')
28+
.argument('<filename>')
29+
.action(async (fileName, opts) => {
30+
if (!validateChapterAndVariantCombo(opts)) {
31+
console.log('Invalid language combination!')
32+
return
33+
}
5334

54-
if (!linkerResult.ok) {
55-
console.log(parseError(context.errors, linkerResult.verboseErrors))
56-
return
57-
}
35+
const fs: typeof fslib = require('fs/promises')
36+
const context = createContext(opts.chapter, opts.variant)
37+
const entrypointFilePath = resolve(fileName)
5838

59-
const { programs, topoOrder } = linkerResult
60-
const bundledProgram = defaultBundler(programs, entrypointFilePath, topoOrder, context)
39+
const linkerResult = await parseProgramsAndConstructImportGraph(
40+
async p => {
41+
try {
42+
const text = await fs.readFile(p, 'utf-8')
43+
return text
44+
} catch (error) {
45+
if (error.code === 'ENOENT') return undefined
46+
throw error
47+
}
48+
},
49+
entrypointFilePath,
50+
context,
51+
{},
52+
true
53+
)
6154

62-
const transpiled = opts.pretranspile
63-
? generate(bundledProgram)
64-
: transpile(bundledProgram, context).transpiled
55+
if (!linkerResult.ok) {
56+
process.stderr.write(parseError(context.errors, linkerResult.verboseErrors))
57+
process.exit(1)
58+
}
6559

66-
if (context.errors.length > 0) {
67-
console.log(parseError(context.errors, linkerResult.verboseErrors))
68-
return
69-
}
60+
const { programs, topoOrder } = linkerResult
61+
const bundledProgram = defaultBundler(programs, entrypointFilePath, topoOrder, context)
7062

71-
if (opts.out) {
72-
const resolvedOut = resolve(opts.out)
73-
await fs.writeFile(resolvedOut, transpiled)
74-
console.log(`Code written to ${resolvedOut}`)
75-
} else {
76-
console.log(transpiled)
77-
}
78-
})
63+
try {
64+
const transpiled = opts.pretranspile
65+
? generate(bundledProgram)
66+
: transpile(bundledProgram, context).transpiled
67+
68+
if (opts.out) {
69+
await fs.writeFile(opts.out, transpiled)
70+
console.log(`Code written to ${opts.out}`)
71+
} else {
72+
process.stdout.write(transpiled)
73+
}
74+
} catch (error) {
75+
process.stderr.write(parseError([error], linkerResult.verboseErrors))
76+
process.exit(1)
77+
}
78+
})

‎src/runner/sourceRunner.ts

+6-50
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@ import * as _ from 'lodash'
33
import type { RawSourceMap } from 'source-map'
44

55
import { type IOptions, type Result } from '..'
6-
import { JSSLANG_PROPERTIES, UNKNOWN_LOCATION } from '../constants'
6+
import { JSSLANG_PROPERTIES } from '../constants'
77
import { CSEResultPromise, evaluate as CSEvaluate } from '../cse-machine/interpreter'
88
import { ExceptionError } from '../errors/errors'
99
import { RuntimeSourceError } from '../errors/runtimeSourceError'
1010
import { TimeoutError } from '../errors/timeoutErrors'
1111
import { isPotentialInfiniteLoop } from '../infiniteLoops/errors'
1212
import { testForInfiniteLoop } from '../infiniteLoops/runtime'
13-
import { evaluateProgram as evaluate } from '../interpreter/interpreter'
1413
import preprocessFileImports from '../modules/preprocessor'
1514
import { defaultAnalysisOptions } from '../modules/preprocessor/analyzer'
1615
import { defaultLinkerOptions } from '../modules/preprocessor/linker'
1716
import { parse } from '../parser/parser'
18-
import { AsyncScheduler, PreemptiveScheduler } from '../schedulers'
1917
import {
2018
callee,
2119
getEvaluationSteps,
@@ -25,12 +23,11 @@ import {
2523
} from '../stepper/stepper'
2624
import { sandboxedEval } from '../transpiler/evalContainer'
2725
import { transpile } from '../transpiler/transpiler'
28-
import { Chapter, type Context, type RecursivePartial, type Scheduler, Variant } from '../types'
26+
import { Chapter, type Context, type RecursivePartial, Variant } from '../types'
2927
import { validateAndAnnotate } from '../validator/validator'
30-
import { compileForConcurrent } from '../vm/svml-compiler'
31-
import { runWithProgram } from '../vm/svml-machine'
3228
import type { FileGetter } from '../modules/moduleTypes'
3329
import { mapResult } from '../alt-langs/mapper'
30+
import assert from '../utils/assert'
3431
import { toSourceError } from './errors'
3532
import { fullJSRunner } from './fullJSRunner'
3633
import { determineExecutionMethod, determineVariant, resolvedErrorPromise } from './utils'
@@ -60,29 +57,6 @@ let previousCode: {
6057
} | null = null
6158
let isPreviousCodeTimeoutError = false
6259

63-
function runConcurrent(program: es.Program, context: Context, options: IOptions): Promise<Result> {
64-
if (context.shouldIncreaseEvaluationTimeout) {
65-
context.nativeStorage.maxExecTime *= JSSLANG_PROPERTIES.factorToIncreaseBy
66-
} else {
67-
context.nativeStorage.maxExecTime = options.originalMaxExecTime
68-
}
69-
70-
try {
71-
return Promise.resolve({
72-
status: 'finished',
73-
context,
74-
value: runWithProgram(compileForConcurrent(program, context), context)
75-
})
76-
} catch (error) {
77-
if (error instanceof RuntimeSourceError || error instanceof ExceptionError) {
78-
context.errors.push(error) // use ExceptionErrors for non Source Errors
79-
return resolvedErrorPromise
80-
}
81-
context.errors.push(new ExceptionError(error, UNKNOWN_LOCATION))
82-
return resolvedErrorPromise
83-
}
84-
}
85-
8660
function runSubstitution(
8761
program: es.Program,
8862
context: Context,
@@ -110,17 +84,6 @@ function runSubstitution(
11084
})
11185
}
11286

113-
function runInterpreter(program: es.Program, context: Context, options: IOptions): Promise<Result> {
114-
let it = evaluate(program, context)
115-
let scheduler: Scheduler
116-
if (options.scheduler === 'async') {
117-
scheduler = new AsyncScheduler()
118-
} else {
119-
scheduler = new PreemptiveScheduler(options.steps)
120-
}
121-
return scheduler.run(it, context)
122-
}
123-
12487
async function runNative(
12588
program: es.Program,
12689
context: Context,
@@ -226,10 +189,6 @@ async function sourceRunner(
226189
return resolvedErrorPromise
227190
}
228191

229-
if (context.variant === Variant.CONCURRENT) {
230-
return runConcurrent(program, context, theOptions)
231-
}
232-
233192
if (theOptions.useSubst) {
234193
return runSubstitution(program, context, theOptions)
235194
}
@@ -238,7 +197,7 @@ async function sourceRunner(
238197

239198
// native, don't evaluate prelude
240199
if (context.executionMethod === 'native' && context.variant === Variant.NATIVE) {
241-
return await fullJSRunner(program, context, theOptions.importOptions)
200+
return fullJSRunner(program, context, theOptions.importOptions)
242201
}
243202

244203
// All runners after this point evaluate the prelude.
@@ -264,11 +223,8 @@ async function sourceRunner(
264223
return runCSEMachine(program, context, theOptions)
265224
}
266225

267-
if (context.executionMethod === 'native') {
268-
return runNative(program, context, theOptions)
269-
}
270-
271-
return runInterpreter(program, context, theOptions)
226+
assert(context.executionMethod !== 'auto', 'Execution method should have been properly determined!')
227+
return runNative(program, context, theOptions)
272228
}
273229

274230
/**

‎src/schedulers.ts

-105
This file was deleted.

‎src/stdlib/inspector.ts

+1-21
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,9 @@
1-
import { Context, Result } from '..'
2-
import { Node, Scheduler, Value } from '../types'
3-
4-
export const saveState = (
5-
context: Context,
6-
it: IterableIterator<Value>,
7-
scheduler: Scheduler
8-
): void => {
9-
context.debugger.state.it = it
10-
context.debugger.state.scheduler = scheduler
11-
}
1+
import type { Context, Node } from '../types'
122

133
export const setBreakpointAtLine = (lines: string[]): void => {
144
breakpoints = lines
155
}
166

17-
export const manualToggleDebugger = (context: Context): Result => {
18-
context.runtime.break = true
19-
return {
20-
status: 'suspended',
21-
scheduler: context.debugger.state.scheduler,
22-
it: context.debugger.state.it,
23-
context
24-
}
25-
}
26-
277
let breakpoints: string[] = []
288
let moved: boolean = true
299
let prevStoppedLine: number = -1

‎src/transpiler/__tests__/transpiled-code.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { mockContext } from '../../mocks/context'
22
import { parse } from '../../parser/parser'
33
import { Chapter } from '../../types'
44
import * as ast from '../../utils/ast/astCreator'
5-
import { sanitizeAST } from '../../utils/ast/sanitizer'
5+
import { sanitizeAST } from '../../utils/testing/sanitizer'
66
import { stripIndent } from '../../utils/formatters'
77
import { transformImportDeclarations, transpile } from '../transpiler'
88

‎src/types.ts

+4-15
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface Comment {
6464
loc: SourceLocation | undefined
6565
}
6666

67-
export type ExecutionMethod = 'native' | 'interpreter' | 'auto' | 'cse-machine'
67+
export type ExecutionMethod = 'native' | 'auto' | 'cse-machine'
6868

6969
export enum Chapter {
7070
SOURCE_1 = 1,
@@ -94,7 +94,6 @@ export enum Variant {
9494
TYPED = 'typed',
9595
NATIVE = 'native',
9696
WASM = 'wasm',
97-
CONCURRENT = 'concurrent',
9897
EXPLICIT_CONTROL = 'explicit-control'
9998
}
10099

@@ -168,7 +167,6 @@ export interface Context<T = any> {
168167
status: boolean
169168
state: {
170169
it: IterableIterator<T>
171-
scheduler: Scheduler
172170
}
173171
}
174172

@@ -274,23 +272,12 @@ export interface Finished {
274272
// field instead
275273
}
276274

277-
export interface Suspended {
278-
status: 'suspended'
279-
it: IterableIterator<Value>
280-
scheduler: Scheduler
281-
context: Context
282-
}
283-
284275
export interface SuspendedCseEval {
285276
status: 'suspended-cse-eval'
286277
context: Context
287278
}
288279

289-
export type Result = Suspended | Finished | Error | SuspendedCseEval
290-
291-
export interface Scheduler {
292-
run(it: IterableIterator<Value>, context: Context): Promise<Result>
293-
}
280+
export type Result = Finished | Error | SuspendedCseEval
294281

295282
/**
296283
* StatementSequence : A sequence of statements not surrounded by braces.
@@ -510,3 +497,5 @@ export type RecursivePartial<T> = T extends Array<any>
510497
[K in keyof T]: RecursivePartial<T[K]>
511498
}>
512499
: T
500+
501+
export type NodeTypeToNode<T extends Node['type']> = Extract<Node, { type: T }>

‎src/utils/testing.ts ‎src/utils/testing/index.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { MockedFunction } from 'jest-mock'
22

3-
import createContext, { defineBuiltin } from '../createContext'
4-
import { parseError, Result, runInContext } from '../index'
5-
import { mockContext } from '../mocks/context'
6-
import { ImportOptions } from '../modules/moduleTypes'
7-
import { parse } from '../parser/parser'
8-
import { transpile } from '../transpiler/transpiler'
3+
import createContext, { defineBuiltin } from '../../createContext'
4+
import { parseError, Result, runInContext } from '../../'
5+
import { mockContext } from '../../mocks/context'
6+
import type { ImportOptions } from '../../modules/moduleTypes'
7+
import { parse } from '../../parser/parser'
8+
import { transpile } from '../../transpiler/transpiler'
99
import {
1010
Chapter,
1111
Context,
@@ -14,8 +14,8 @@ import {
1414
Value,
1515
Variant,
1616
type Finished
17-
} from '../types'
18-
import { stringify } from './stringify'
17+
} from '../../types'
18+
import { stringify } from '../stringify'
1919

2020
export interface CodeSnippetTestCase {
2121
name: string

‎src/utils/testing/misc.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { MockedFunction } from 'jest-mock'
2+
import type { Result } from '../..'
3+
import type { Finished, Value, Node, NodeTypeToNode } from '../../types'
4+
5+
export function asMockedFunc<T extends (...args: any[]) => any>(func: T) {
6+
return func as MockedFunction<T>
7+
}
8+
9+
export function expectTrue(cond: boolean): asserts cond {
10+
expect(cond).toEqual(true)
11+
}
12+
13+
export function expectFinishedResult(result: Result): asserts result is Finished {
14+
expect(result.status).toEqual('finished')
15+
}
16+
17+
export function expectFinishedResultValue(result: Result, value: Value) {
18+
expectFinishedResult(result)
19+
expect(result.value).toEqual(value)
20+
}
21+
22+
export function expectNodeType<T extends Node['type']>(
23+
typeStr: T,
24+
node: Node
25+
): asserts node is NodeTypeToNode<T> {
26+
expect(node.type).toEqual(typeStr)
27+
}
File renamed without changes.

‎src/vm/__tests__/svml-machine.ts

-1,577
This file was deleted.

‎src/vm/svmc.ts

-231
This file was deleted.

‎src/vm/svml-machine.ts

-1,872
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.