From 391b8d50a39e6c5c71114fc226c158bb5e610457 Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Thu, 16 Apr 2020 23:05:43 +0800 Subject: [PATCH 01/12] REPL: add variant option --- src/repl/repl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/repl/repl.ts b/src/repl/repl.ts index 9ba212fad..23f1aac54 100644 --- a/src/repl/repl.ts +++ b/src/repl/repl.ts @@ -61,6 +61,7 @@ function main() { const opt = require('node-getopt') .create([ ['c', 'chapter=CHAPTER', 'set the Source chapter number (i.e., 1-4)', '1'], + ['v', 'variant=VARIANT', 'set the Source variant (i.e., lazy, non-det, concurrent, wasm)', 'default'], ['s', 'use-subst', 'use substitution'], ['h', 'help', 'display this help'], ['i', 'interpreter', 'use the interpreter for execution'], @@ -72,7 +73,7 @@ function main() { .parseSystem() const executionMethod = opt.options.interpreter === true ? 'interpreter' : 'native' - const variant = opt.options.lazy === true ? 'lazy' : 'default' + const variant = opt.options.lazy === true ? 'lazy' : opt.options.variant const chapter = parseInt(opt.options.chapter, 10) const useSubst = opt.options.s const useRepl = !opt.options.e From 637bd1fcd80bfd502cbf68561e9f4e643a8b0f35 Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Thu, 16 Apr 2020 23:20:04 +0800 Subject: [PATCH 02/12] . From 876cf2f0b4162e77dafc0d55d1911f56d06cb412 Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Tue, 28 Apr 2020 13:14:50 +0800 Subject: [PATCH 03/12] Fix bug: yield new array (#40) * fix bug: yield new array for array expression --- src/interpreter/interpreter-non-det.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts index c4fa97285..d41f91238 100644 --- a/src/interpreter/interpreter-non-det.ts +++ b/src/interpreter/interpreter-non-det.ts @@ -301,7 +301,10 @@ export const evaluators: { [nodeType: string]: Evaluator } = { }, ArrayExpression: function*(node: es.ArrayExpression, context: Context) { - yield* cartesianProduct(context, node.elements as es.Expression[], []) + const arrayGenerator = cartesianProduct(context, node.elements as es.Expression[], []) + for (const array of arrayGenerator) { + yield array.slice() // yield a new array to avoid modifying previous ones + } }, Identifier: function*(node: es.Identifier, context: Context) { From 75c0fddb939850e743133814133e978439d83af4 Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Tue, 28 Apr 2020 18:03:22 +0800 Subject: [PATCH 04/12] Add user friendly REPL message (#43) * add user friendly REPL message --- src/repl/repl-non-det.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/repl/repl-non-det.ts b/src/repl/repl-non-det.ts index b5c46e51f..359cf8514 100644 --- a/src/repl/repl-non-det.ts +++ b/src/repl/repl-non-det.ts @@ -6,8 +6,10 @@ import { CUT, TRY_AGAIN } from '../constants' import { inspect } from 'util' import Closure from '../interpreter/closure' -// stores the result obtained when execution is suspended -let previousResult: Result +const NO_MORE_VALUES_MESSAGE: string = 'There are no more values of: ' +let previousInput: string | undefined // stores the input which is then shown when there are no more values for the program +let previousResult: Result // stores the result obtained when execution is suspended + function _handleResult( result: Result, context: Context, @@ -24,12 +26,24 @@ function _handleResult( } } +function _try_again_message(): string | undefined { + if (previousInput) { + const message: string = NO_MORE_VALUES_MESSAGE + previousInput + previousInput = undefined + + return message + } else { + return undefined + } +} + function _resume( toResume: SuspendedNonDet, context: Context, callback: (err: Error | null, result: any) => void ) { Promise.resolve(resume(toResume)).then((result: Result) => { + if (result.status === 'finished') result.value = _try_again_message() _handleResult(result, context, callback) }) } @@ -38,7 +52,7 @@ function _try_again(context: Context, callback: (err: Error | null, result: any) if (previousResult && previousResult.status === 'suspended-non-det') { _resume(previousResult, context, callback) } else { - callback(null, undefined) + callback(null, _try_again_message()) } } @@ -51,6 +65,7 @@ function _run( if (cmd.trim() === TRY_AGAIN) { _try_again(context, callback) } else { + previousInput = cmd.trim() runInContext(cmd, context, options).then(result => { _handleResult(result, context, callback) }) From 72836aa0d23e2114c8473700e0277aeab8303f44 Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Thu, 30 Apr 2020 01:15:12 +0800 Subject: [PATCH 05/12] Add randomized amb operator (#15) Adds the ambR operator which functions similarly to amb but makes choices randomly instead of sequentially. Also adds landing page and library documentation for the operator, and adds this operator into the specs. --- docs/lib/non-det.js | 14 +++++- docs/md/README_3_NON-DET.md | 7 +++ docs/specs/source_nondet.tex | 3 +- src/__tests__/non-det-interpreter.ts | 70 +++++++++++++++++++++----- src/interpreter/interpreter-non-det.ts | 31 ++++++++++-- yarn.lock | 19 +++++++ 6 files changed, 126 insertions(+), 18 deletions(-) diff --git a/docs/lib/non-det.js b/docs/lib/non-det.js index 955869ea8..e89a75759 100644 --- a/docs/lib/non-det.js +++ b/docs/lib/non-det.js @@ -6,10 +6,22 @@ * If n is 0, it forces the language processor to backtrack to * the most recent amb expression without returning a value. * @param {value} e1,e2,...en - given values - * @returns {value} a value from the given values + * @returns {value} a value from the given values chosen sequentially */ function amb(e1, e2, ...en) {} +/** + * Given n values, creates a choice point whose value is chosen, + * at run-time, randomly from the set e1, e2, ..., en.
+ * If n is 0, it forces the language processor to backtrack to + * the most recent amb expression without returning a value.
+ * Functions similarly to the amb operator but makes choices randomly + * instead of sequentially. + * @param {value} e1,e2,...en - given values + * @returns {value} a value from the given values chosen randomly + */ +function ambR(e1, e2, ...en) {} + /** * Prevents the language processor from backtracking any further * beyond the current statement. diff --git a/docs/md/README_3_NON-DET.md b/docs/md/README_3_NON-DET.md index 5d2b3d072..c3afe1c47 100644 --- a/docs/md/README_3_NON-DET.md +++ b/docs/md/README_3_NON-DET.md @@ -55,6 +55,13 @@ In the following example, we specify two possible choices for a value we want:'bye'
, enter the command try again in the playground REPL. This will give the result 'bye world!'. +### ambR operator +The ambR operator functions similarly to amb but makes choices randomly +instead of sequentially. + +Upon running the above example, we may randomly obtain the result 'hello world!' first and 'bye world! second
+or 'bye world! first and 'hello world!' second. + ### require function Requirements can be specified with the require function. diff --git a/docs/specs/source_nondet.tex b/docs/specs/source_nondet.tex index b9d2e9d95..7620d5293 100644 --- a/docs/specs/source_nondet.tex +++ b/docs/specs/source_nondet.tex @@ -4,8 +4,9 @@ \subsection*{Nondeterminism support} \begin{itemize} \item \lstinline{amb(e1, e2, ..., en)}: \textit{primitive}, creates a choice point whose value is chosen, at runtime, -from the set \lstinline{e1, e2, ..., en}. Note: although the usage of this primitive syntactically resembles a function application, \textit{amb} is not a function value. It is simply an operator, like the binary and unary operators. +from the set \lstinline{e1, e2, ..., en}. Note: although the usage of this primitive syntactically resembles a function application, \textit{amb} is not a function value. It is simply an operator, like the binary and unary operators. The values from the set \lstinline{(e1, e2, ..., en)} are chosen sequentially, from left-to-right. \item \lstinline{amb()}: applying \textit{amb} without any arguments forces the language processor to backtrack to the most recent \textit{amb expression}. +\item \lstinline{ambR(e1, e2, ..., en)}: \textit{primitive}, creates a choice point. This is similar to \lstinline{amb}, but the values from the set \lstinline{(e1, e2, ..., en)} are chosen randomly and not sequentially. \item \lstinline{cut()}: \textit{primitive}, prevents the language processor from backtracking any further beyond the current statement. Note: \textit{cut} is also not a function value. It is simply an operator, like the binary and unary operators. \item \lstinline{require(pred)}: \textit{builtin}, forces the language processor to backtrack to the most recent \textit{amb expression}, if and only if \lstinline{pred} is false. \item \lstinline{an_element_of(xs)}: \textit{builtin}, nondeterministically returns an element from the list \lstinline{xs}. diff --git a/src/__tests__/non-det-interpreter.ts b/src/__tests__/non-det-interpreter.ts index b8fa0ef8a..76e29feb6 100644 --- a/src/__tests__/non-det-interpreter.ts +++ b/src/__tests__/non-det-interpreter.ts @@ -1,7 +1,7 @@ /* tslint:disable:max-line-length */ import { runInContext, resume, IOptions, Result, parseError } from '../index' import { mockContext } from '../mocks/context' -import { SuspendedNonDet, Finished } from '../types' +import { SuspendedNonDet, Finished, Context } from '../types' test('Empty code returns undefined', async () => { await testDeterministicCode('', undefined) @@ -380,6 +380,19 @@ test('Block statements', async () => { ) }) +test('ambR application', async () => { + await testNonDeterministicCode('ambR();', [], false, true) + + await testNonDeterministicCode('ambR(1, 2, 3, 4, 5);', [1, 2, 3, 4, 5], false, true) + + await testNonDeterministicCode( + 'ambR(ambR(4, 5, 6, 7), ambR(3, 8));', + [4, 5, 6, 7, 3, 8], + false, + true + ) +}) + test('Deterministic arrays', async () => { await testDeterministicCode(`[];`, []) @@ -492,30 +505,63 @@ export async function testDeterministicCode( export async function testNonDeterministicCode( code: string, expectedValues: any[], - hasError: boolean = false + hasError: boolean = false, + random: boolean = false ) { - const context = makeNonDetContext() + const context: Context = makeNonDetContext() let result: Result = await runInContext(code, context, nonDetTestOptions) + const results: any[] = [] const numOfRuns = hasError ? expectedValues.length - 1 : expectedValues.length for (let i = 0; i < numOfRuns; i++) { - expect((result as SuspendedNonDet).value).toEqual(expectedValues[i]) - expect(result.status).toEqual('suspended-non-det') + if (random) { + results.push((result as SuspendedNonDet).value) + } else { + expect((result as SuspendedNonDet).value).toEqual(expectedValues[i]) + } + expect(result.status).toEqual('suspended-non-det') result = await resume(result) } - if (!hasError) { - // all non deterministic programs have a final result whose value is undefined - expect(result.status).toEqual('finished') - expect((result as Finished).value).toEqual(undefined) + if (random) { + verifyRandomizedTest(results, expectedValues) + } + + if (hasError) { + verifyError(result, expectedValues, context) } else { - expect(result.status).toEqual('error') - const message: string = parseError(context.errors) - expect(message).toEqual(expectedValues[expectedValues.length - 1]) + verifyFinalResult(result) } } +/* Checks the final result obtained for a test + * Assumes the test is not erroneous + */ +function verifyFinalResult(result: Result) { + // all non deterministic programs have a final result whose value is undefined + expect(result.status).toEqual('finished') + expect((result as Finished).value).toEqual(undefined) +} + +/* Checks the error obtained for an erroneous test + * The error message is captured as the test's final result + */ +function verifyError(result: Result, expectedValues: any[], context: Context) { + expect(result.status).toEqual('error') + const message: string = parseError(context.errors) + expect(message).toEqual(expectedValues[expectedValues.length - 1]) +} + +/* Compares expected and obtained results after a test is run + * Assumes the test involves randomization + */ +function verifyRandomizedTest(results: any[], expectedValues: any[]) { + results.sort() + expectedValues.sort() + expect(results).toEqual(expectedValues) +} + function makeNonDetContext() { const context = mockContext(3, 'non-det') context.executionMethod = 'interpreter' diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts index d41f91238..438a5ef03 100644 --- a/src/interpreter/interpreter-non-det.ts +++ b/src/interpreter/interpreter-non-det.ts @@ -175,6 +175,26 @@ const checkNumberOfArguments = ( return undefined } +/** + * Returns a random integer for a given interval (inclusive). + */ +function randomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1) + min) +} + +function* getAmbRArgs(context: Context, call: es.CallExpression) { + const originalContext = cloneDeep(context) + + const args: es.Node[] = cloneDeep(call.arguments) + while (args.length > 0) { + const r = randomInt(0, args.length - 1) + const arg: es.Node = args.splice(r, 1)[0] + + yield* evaluate(arg, context) + assignIn(context, cloneDeep(originalContext)) + } +} + function* getArgs(context: Context, call: es.CallExpression) { const args = cloneDeep(call.arguments) return yield* cartesianProduct(context, args as es.Expression[], []) @@ -314,10 +334,13 @@ export const evaluators: { [nodeType: string]: Evaluator } = { CallExpression: function*(node: es.CallExpression, context: Context) { const callee = node.callee; if (rttc.isIdentifier(callee)) { - if (callee.name === 'amb') { - return yield* getAmbArgs(context, node) - } else if (callee.name === 'cut') { - return yield CUT + switch (callee.name) { + case 'amb': + return yield* getAmbArgs(context, node) + case 'ambR': + return yield* getAmbRArgs(context, node) + case 'cut': + return yield CUT } } diff --git a/yarn.lock b/yarn.lock index 50a976b4d..acf708c4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -540,6 +540,25 @@ resolved "http://r.cnpmjs.org/@types/lodash/download/@types/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" integrity sha1-E0LWPZSMYGKDj7+WEBL3TU5jhEA= +"@types/lodash.assignin@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.assignin/-/lodash.assignin-4.2.6.tgz#fe94b7bbad78f97897a028e8f4e8945c03d84a6f" + integrity sha512-kO9C2Oq0X8yehLu0o689SwR+wy+m4IQZg2TxRBXNkmpd0WY/GYEV+tTqrWRu2jt69eDOaVMJxna6QnDQ/g1TSg== + dependencies: + "@types/lodash" "*" + +"@types/lodash.clonedeep@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b" + integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.149" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" + integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== + "@types/node@^13.7.0": version "13.11.1" resolved "http://r.cnpmjs.org/@types/node/download/@types/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" From b9f8a78edc9d3097e95251eb80b5543a6ac1913f Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Thu, 30 Apr 2020 19:44:55 +0800 Subject: [PATCH 06/12] Add while loops (#39) This PR adds support for while loops, including break, continue and return statements in the loop body. It also tweaks the scheduler so that stackoverflow errors are captured and displayed to the user. Co-authored-by: anubh-v --- src/__tests__/non-det-interpreter.ts | 196 +++++++++++++++++++++++++ src/interpreter/interpreter-non-det.ts | 47 +++++- src/schedulers.ts | 39 +++-- 3 files changed, 265 insertions(+), 17 deletions(-) diff --git a/src/__tests__/non-det-interpreter.ts b/src/__tests__/non-det-interpreter.ts index 76e29feb6..5415e44e1 100644 --- a/src/__tests__/non-det-interpreter.ts +++ b/src/__tests__/non-det-interpreter.ts @@ -485,6 +485,202 @@ test('Material Biconditional', async () => { await testDeterministicCode(`bi_implication(false, true);`, false) await testDeterministicCode(`bi_implication(false, false);`, true) }) + +test('While loops', async () => { + await testDeterministicCode( + ` + let i = 2; + while (false) { + i = i - 1; + } + i;`, + 2 + ) + + await testDeterministicCode( + ` + let i = 5; + while (i > 0) { + i = i - 1; + }`, + 0 + ) + + await testDeterministicCode( + ` + let i = 5; + let j = 0; + while (i > 0 && j < 5) { + i = i - 1; + j = j + 2; + }`, + 6 + ) + + await testDeterministicCode( + ` + let i = 2; + while (i) { + i = i - 1; + } + i;`, + 'Line 3: Expected boolean as condition, got number.', + true + ) +}) + +test('Let statement should be block scoped in body of while loop', async () => { + await testDeterministicCode( + ` + let i = 2; + let x = 5; + while (i > 0) { + i = i - 1; + let x = 10; + } + x;`, + 5 + ) + + await testDeterministicCode( + ` + let i = 2; + while (i > 0) { + i = i - 1; + let x = 5; + } + x;`, + 'Line -1: Name x not declared.', + true + ) +}) + +test('Nested while loops', async () => { + await testDeterministicCode( + ` + let count = 0; + let i = 1; + while (i > 0) { + let j = 2; + while (j > 0) { + let k = 4; + while (k > 0) { + count = count + 1; + k = k - 1; + } + j = j - 1; + } + i = i - 1; + } + count;`, + 8 + ) +}) + +test('Break statement in while loop body', async () => { + await testDeterministicCode( + ` + let i = 5; + while (i > 0) { + i = i - 1; + break; + } + i;`, + 4 + ) +}) + +test('Continue statement in while loop body', async () => { + await testDeterministicCode( + ` + let i = 5; + let j = 0; + while (i > 0 && j < 5) { + i = i - 1; + continue; + j = j + 2; + } + j;`, + 0 + ) +}) + +test('Return statement in while loop body', async () => { + await testDeterministicCode( + ` + function loopTest(i, j) { + while (i > 0 && j > i) { + return i * j; + i = i - 1; + j = j + i; + } + } + loopTest(5, 10);`, + 50 + ) +}) + +test('Non-deterministic while loop condition', async () => { + await testNonDeterministicCode( + ` + let i = amb(3, 4); + let j = 0; + while (i > 0) { + i = i - 1; + j = j + 1; + } + j;`, + [3, 4] + ) + + await testNonDeterministicCode( + ` + let i = 1; + let j = 2; + let count = 0; + while (amb(i, j) > 0) { + i = i - 1; + j = j - 1; + + count = count + 1; + } + count;`, + [1, 2, 2, 1, 2, 2] + ) // chosen variables: (i,i), (i,j,i), (i,j,j), (j,i), (j,j,i), (j,j,j) +}) + +test('Non-deterministic while loop body', async () => { + /* number of total program values = + (number of values from cartesian product of the statements in loop body)^ + (number of loop iterations) + */ + + await testNonDeterministicCode( + ` + let i = 3; + let count = 0; + while (i > 0) { + count = count + amb(0, 1); + i = i - 1; + } + count;`, + [0, 1, 1, 2, 1, 2, 2, 3] + ) + + await testNonDeterministicCode( + ` + let i = 2; + let count = 0; + while (i > 0) { + count = count + amb(0, 1); + count = count + amb(0, 1); + + i = i - 1; + } + count;`, + [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] + ) +}) // ---------------------------------- Helper functions ------------------------------------------- const nonDetTestOptions = { diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts index 438a5ef03..2e39f421f 100644 --- a/src/interpreter/interpreter-non-det.ts +++ b/src/interpreter/interpreter-non-det.ts @@ -11,6 +11,10 @@ import Closure from './closure' import { cloneDeep, assignIn } from 'lodash' import { CUT } from '../constants' +class BreakValue {} + +class ContinueValue {} + class ReturnValue { constructor(public value: Value) {} } @@ -273,7 +277,11 @@ function* evaluateSequence(context: Context, sequence: es.Statement[]): Iterable // prevent unshifting of cut operator shouldUnshift = sequenceValue !== CUT - if (sequenceValue instanceof ReturnValue) { + if ( + sequenceValue instanceof ReturnValue || + sequenceValue instanceof BreakValue || + sequenceValue instanceof ContinueValue + ) { yield sequenceValue continue } @@ -462,6 +470,43 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return yield* evaluate(node.expression, context) }, + ContinueStatement: function*(node: es.ContinueStatement, context: Context) { + yield new ContinueValue() + }, + + BreakStatement: function*(node: es.BreakStatement, context: Context) { + yield new BreakValue() + }, + + WhileStatement: function*(node: es.WhileStatement, context: Context) { + let value: Value // tslint:disable-line + function* loop(): Value { + const testGenerator = evaluate(node.test, context) + for (const test of testGenerator) { + const error = rttc.checkIfStatement(node.test, test) + if (error) return handleRuntimeError(context, error) + + if (test && + !(value instanceof ReturnValue) && + !(value instanceof BreakValue) + ) { + const iterationValueGenerator = evaluate(cloneDeep(node.body), context) + for (const iterationValue of iterationValueGenerator) { + value = iterationValue + yield* loop(); + } + } else { + if (value instanceof BreakValue || value instanceof ContinueValue) { + yield undefined + } else { + yield value + } + } + } + } + + yield* loop(); + }, ReturnStatement: function*(node: es.ReturnStatement, context: Context) { const returnExpression = node.argument! diff --git a/src/schedulers.ts b/src/schedulers.ts index 0fd44d7c6..3fd101825 100644 --- a/src/schedulers.ts +++ b/src/schedulers.ts @@ -52,6 +52,7 @@ export class NonDetScheduler implements Scheduler { } as Result) } } catch (e) { + checkForStackOverflow(e, context) resolve({ status: 'error' }) } finally { context.runtime.isRunning = false @@ -84,22 +85,7 @@ export class PreemptiveScheduler implements Scheduler { } saveState(context, it, this) } catch (e) { - if (/Maximum call stack/.test(e.toString())) { - const environments = context.runtime.environments - const stacks: any = [] - let counter = 0 - for ( - let i = 0; - counter < MaximumStackLimitExceeded.MAX_CALLS_TO_SHOW && i < environments.length; - i++ - ) { - if (environments[i].callExpression) { - stacks.unshift(environments[i].callExpression) - counter++ - } - } - context.errors.push(new MaximumStackLimitExceeded(context.runtime.nodes[0], stacks)) - } + checkForStackOverflow(e, context) context.runtime.isRunning = false clearInterval(interval) resolve({ status: 'error' }) @@ -122,3 +108,24 @@ export class PreemptiveScheduler implements Scheduler { }) } } + +/* Checks if the error is a stackoverflow error, and captures it in the + context if this is the case */ +function checkForStackOverflow(error: any, context: Context) { + if (/Maximum call stack/.test(error.toString())) { + const environments = context.runtime.environments + const stacks: any = [] + let counter = 0 + for ( + let i = 0; + counter < MaximumStackLimitExceeded.MAX_CALLS_TO_SHOW && i < environments.length; + i++ + ) { + if (environments[i].callExpression) { + stacks.unshift(environments[i].callExpression) + counter++ + } + } + context.errors.push(new MaximumStackLimitExceeded(context.runtime.nodes[0], stacks)) + } +} From da5901ec343b02cfce915ad1533d4518e70b035f Mon Sep 17 00:00:00 2001 From: Anubhav <35621759+anubh-v@users.noreply.github.com> Date: Fri, 1 May 2020 20:43:38 +0800 Subject: [PATCH 07/12] Clear the stack trace of any error created in REPL (#46) --- src/repl/repl-non-det.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/repl/repl-non-det.ts b/src/repl/repl-non-det.ts index 359cf8514..d72331c93 100644 --- a/src/repl/repl-non-det.ts +++ b/src/repl/repl-non-det.ts @@ -21,7 +21,12 @@ function _handleResult( if (result.value === CUT) result.value = undefined callback(null, result.value) } else { - callback(new Error(parseError(context.errors)), undefined) + const error = new Error(parseError(context.errors)) + // we do not display the stack trace, because the stack trace points to code within this REPL + // program, rather than the erroneous line in the user's program. Such a trace is too low level + // to be helpful. + error.stack = undefined + callback(error, undefined) return } } From 4c851c7523e60b5872d9a24820cdcb0d2d5ac75b Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Fri, 1 May 2020 21:10:00 +0800 Subject: [PATCH 08/12] Add for loops (#41) This PR adds support for for loops, including break, continue and return statements in the loop body. Co-authored-by: Anubhav --- src/__tests__/non-det-interpreter.ts | 235 ++++++++++++++++++++++++- src/interpreter/interpreter-non-det.ts | 65 +++++++ 2 files changed, 298 insertions(+), 2 deletions(-) diff --git a/src/__tests__/non-det-interpreter.ts b/src/__tests__/non-det-interpreter.ts index 5415e44e1..4fa3a8114 100644 --- a/src/__tests__/non-det-interpreter.ts +++ b/src/__tests__/non-det-interpreter.ts @@ -641,7 +641,6 @@ test('Non-deterministic while loop condition', async () => { while (amb(i, j) > 0) { i = i - 1; j = j - 1; - count = count + 1; } count;`, @@ -674,13 +673,245 @@ test('Non-deterministic while loop body', async () => { while (i > 0) { count = count + amb(0, 1); count = count + amb(0, 1); - i = i - 1; } count;`, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] ) }) + +test('For loops', async () => { + await testDeterministicCode( + ` + let i = 0; + for (i; i < 0; i = i + 1) { + } + i; + `, + 0 + ) + + await testDeterministicCode( + ` + for (let i = 0; i < 5; i = i + 1) { + i; + } + `, + 4 + ) + + await testDeterministicCode( + ` + let count = 0; + for (let i = 5; i > 0; i = i - 2) { + count = count + 1; + } + count; + `, + 3 + ) + + await testDeterministicCode( + ` + for (let i = 0; 2; i = i + 1) { + } + `, + 'Line 2: Expected boolean as condition, got number.', + true + ) +}) + +test('Let statement should be block scoped in head of for loop', async () => { + await testDeterministicCode( + ` + for (let i = 2; i > 0; i = i - 1) { + } + i; + `, + 'Line -1: Name i not declared.', + true + ) +}) + +test('Let statement should be block scoped in body of for loop', async () => { + await testDeterministicCode( + ` + let x = 0; + for (x; x < 10; x = x + 1) { + let x = 1; + } + x;`, + 10 + ) + + await testDeterministicCode( + ` + for (let i = 2; i > 0; i = i - 1) { + let x = 5; + } + x; + `, + 'Line -1: Name x not declared.', + true + ) +}) + +test('Loop control variable should be copied into for loop body', async () => { + await testDeterministicCode( + ` + let arr = []; + for (let i = 0; i < 5; i = i + 1) { + arr[i] = () => i; + } + arr[3]();`, + 3 + ) +}) + +test('Assignment to loop control variable', async () => { + await testDeterministicCode( + ` + for (let i = 0; i < 2; i = i + 1){ + i = i + 1; + } + `, + 'Line 3: Assignment to a for loop variable in the for loop is not allowed.', + true + ) + + await testDeterministicCode( + ` + let i = 0; + for (i; i < 2; i = i + 1){ + i = i + 1; + } + i;`, + 2 + ) +}) + +test('Nested for loops', async () => { + await testDeterministicCode( + ` + let count = 0; + for (let i = 0; i < 1; i = i + 1) { + for (let j = 0; j < 2; j = j + 1) { + for (let k = 0; k < 4; k = k + 1) { + count = count + 1; + } + } + } + count; + `, + 8 + ) +}) + +test('Break statement in for loop body', async () => { + await testDeterministicCode( + ` + let count = 0; + for (let i = 0; i < 5; i = i + 1) { + break; + count = count + 1; + } + count;`, + 0 + ) +}) + +test('Continue statement in for loop body', async () => { + await testDeterministicCode( + ` + let count = 0; + for (let i = 0; i < 5; i = i + 1) { + continue; + count = count + 1; + } + count;`, + 0 + ) +}) + +test('Return statement in for loop body', async () => { + await testDeterministicCode( + ` + let count = 0; + function loopTest(x) { + for (let i = 0; i < 5; i = i + 1) { + return x; + count = count + 1; + } + } + loopTest(10);`, + 10 + ) +}) + +test('Non-deterministic for loop initializer', async () => { + await testNonDeterministicCode( + ` + let j = 0; + for (let i = amb(3, 4); i > 0; i = i - 1) { + j = j + 1; + } + j;`, + [3, 4] + ) +}) + +test('Non-deterministic for loop condition', async () => { + await testNonDeterministicCode( + ` + let count = 0; + for (let i = 2; i > amb(0, 1); i = i - 1) { + count = count + 1; + } + count;`, + [2, 2, 1, 2, 2, 1] + ) // chosen conditions: (0, 0, 0), (0, 0, 1), (0, 1), (1, 0, 0), (1, 0, 1), (1, 1) +}) + +test('Non-deterministic for loop update', async () => { + await testNonDeterministicCode( + ` + let count = 0; + for (let i = 2; i > 0; i = i - amb(1, 2)) { + count = count + 1; + } + count;`, + [2, 2, 1] + ) // chosen updates: (1, 1), (1, 2), (2) +}) + +test('Non-deterministic for loop body', async () => { + /* number of total program values = + (number of values from cartesian product of the statements in loop body)^ + (number of loop iterations) + */ + + await testNonDeterministicCode( + ` + let count = 0; + for (let i = 3; i > 0; i = i - 1) { + count = count + amb(0, 1); + } + count;`, + [0, 1, 1, 2, 1, 2, 2, 3] + ) + + await testNonDeterministicCode( + ` + let count = 0; + for (let i = 2; i > 0; i = i - 1) { + count = count + amb(0, 1); + count = count + amb(0, 1); + } + count;`, + [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] + ) +}) + // ---------------------------------- Helper functions ------------------------------------------- const nonDetTestOptions = { diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts index 2e39f421f..fc6480e6b 100644 --- a/src/interpreter/interpreter-non-det.ts +++ b/src/interpreter/interpreter-non-det.ts @@ -508,6 +508,71 @@ export const evaluators: { [nodeType: string]: Evaluator } = { yield* loop(); }, + ForStatement: function*(node: es.ForStatement, context: Context) { + let value: Value + function* loop(): Value { + const testGenerator = evaluate(node.test!, context) + for (const test of testGenerator) { + const error = rttc.checkIfStatement(node.test!, test) + if (error) return handleRuntimeError(context, error) + + if (test && + !(value instanceof ReturnValue) && + !(value instanceof BreakValue) + ) { + const iterationEnvironment = createBlockEnvironment(context, 'forBlockEnvironment') + pushEnvironment(context, iterationEnvironment) + for (const name in loopEnvironment.head) { + if (loopEnvironment.head.hasOwnProperty(name)) { + declareIdentifier(context, name, node) + defineVariable(context, name, loopEnvironment.head[name], true) + } + } + + const iterationValueGenerator = evaluate(cloneDeep(node.body), context) + for (const iterationValue of iterationValueGenerator) { + value = iterationValue + popEnvironment(context) + const updateNode = evaluate(node.update!, context) + for (const _update of updateNode) { + yield* loop(); + } + + pushEnvironment(context, iterationEnvironment) + } + popEnvironment(context) + } else { + if (value instanceof BreakValue || value instanceof ContinueValue) { + yield undefined + } else { + yield value + } + } + } + } + + // Create a new block scope for the loop variables + const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment') + pushEnvironment(context, loopEnvironment) + + const initNode = node.init! + if (initNode.type === 'VariableDeclaration') { + declareVariables(context, initNode) + } + + const initNodeGenerator = evaluate(node.init!, context) + for (const _init of initNodeGenerator) { + const loopGenerator = loop() + for (const loopValue of loopGenerator) { + popEnvironment(context) + yield loopValue + pushEnvironment(context, loopEnvironment) + } + } + + popEnvironment(context) + }, + ReturnStatement: function*(node: es.ReturnStatement, context: Context) { const returnExpression = node.argument! const returnValueGenerator = evaluate(returnExpression, context) From 287adff225b361df4e791018e04268ebe31d3196 Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Sat, 2 May 2020 00:10:56 +0800 Subject: [PATCH 09/12] Add documentation for loops (#44) Co-authored-by: anubh-v --- docs/md/README_3_NON-DET.md | 2 +- docs/specs/source_3_nondet.tex | 4 +- docs/specs/source_3_nondet_bnf.tex | 100 ----------------------------- 3 files changed, 4 insertions(+), 102 deletions(-) delete mode 100644 docs/specs/source_3_nondet_bnf.tex diff --git a/docs/md/README_3_NON-DET.md b/docs/md/README_3_NON-DET.md index c3afe1c47..1b69afb59 100644 --- a/docs/md/README_3_NON-DET.md +++ b/docs/md/README_3_NON-DET.md @@ -39,7 +39,7 @@ order. Click on a name to see how it is used. ## What can you do in Source §3 Non-Det? You can use all features of -Source §3, with the exception of loops, and all +Source §3 and all features that are introduced in chapter 4.3 of the textbook. diff --git a/docs/specs/source_3_nondet.tex b/docs/specs/source_3_nondet.tex index 9af99b9ca..fa5df7c14 100644 --- a/docs/specs/source_3_nondet.tex +++ b/docs/specs/source_3_nondet.tex @@ -20,7 +20,7 @@ \section*{Changes} In the Source Academy implementation, the Source \S 3 Non-Det evaluator can only process one program at a time. If the evaluator is given a new program \textbf{before} all the outcomes of an existing program have been processed, then all remaining outcomes will be ignored. \newline -Unlike Source \S 3, Source \S 3 Non-Det currently does not support loops. +Unlike the implementations of Source \S 3, the implementation of Source \S 3 Non-Det does not use a constant amount of memory for loops. Instead, memory usage increases linearly with the number of iterations. This is because each iteration can be nondeterministic. Each iteration's state must be kept in memory so that the language processor can backtrack to every iteration. Similarly, no tail call optimization is done, because every function application's state must be kept in memory to allow backtracking. \input source_bnf.tex @@ -33,6 +33,8 @@ \section*{Changes} \input source_boolean_operators +\input source_loops + \input source_return_3 \input source_names diff --git a/docs/specs/source_3_nondet_bnf.tex b/docs/specs/source_3_nondet_bnf.tex deleted file mode 100644 index 4a34d6afc..000000000 --- a/docs/specs/source_3_nondet_bnf.tex +++ /dev/null @@ -1,100 +0,0 @@ -\begin{alignat*}{9} -&& \textit{program} &&\quad ::= &\quad && \textit{statement} \ \ldots - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/4}{statement sequence}} \\[1mm] -&& \textit{statement} &&\quad ::= &\quad && \textbf{\texttt{const}}\ \textit{name} \ - \textbf{\texttt{=}}\ \textit{expression} \ \textbf{\texttt{;}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/4}{constant declaration}} \\ -&& && | &\quad && \textit{let} \ \textbf{\texttt{;}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/49}{variable declaration}} \\ -&& && | &\quad && \textit{assignment} \ \textbf{\texttt{;}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/49}{variable assignment}} \\ -&& && | &\quad && \textit{expression} \textbf{\texttt{[}} - \textit{expression} \textbf{\texttt{]}} \ - \textbf{\texttt{=}}\ \textit{expression} \ \textbf{\texttt{;}} - && \textrm{array assignment} \\ -&& && | &\quad && \textbf{\texttt{function}}\ \textit{name} \ - \textbf{\texttt{(}}\ \textit{parameters} \ \textbf{\texttt{)}}\ \textit{block} \quad - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/6}{function declaration}}\\ -&& && | &\quad && \textbf{\texttt{return}}\ \textit{expression} \ \textbf{\texttt{;}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/6}{return statement}}\\ -&& && | &\quad && \textit{if-statement} \quad - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/20}{conditional statement}}\\ -&& && | &\quad && \textit{block} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/10}{block statement}}\\ -&& && | &\quad && \textit{expression} \ \textbf{\texttt{;}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/3}{expression statement}} \\[1mm] -&& \textit{parameters} && ::= &\quad && \epsilon\ | \ \textit{name} \ - (\ \textbf{\texttt{,}} \ \textit{name}\ )\ \ldots - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/6}{function parameters}} \\[1mm] -&& \textit{if-statement} && ::= &\quad && \textbf{\texttt{if}}\ - \textbf{\texttt{(}}\ \textit{expression} \ \textbf{\texttt{)}}\ - \textit{block} \\ -&& && & && \textbf{\texttt{else}}\ - (\ \textit{block} - \ | \ - \textit{\href{https://sicp.comp.nus.edu.sg/chapters/21\#footnote-1}{if-statement}} \ ) - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/20}{conditional statement}} \\[1mm] -&& \textit{block} && ::= & && \textbf{\texttt{\{}}\ \textit{program} \ \textbf{\texttt{\}}} \quad - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/10}{block statement}} \\[1mm] -&& \textit{let} && ::= &\quad && \textbf{\texttt{let}}\ \textit{name} \ - \textbf{\texttt{=}}\ \textit{expression} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/49}{variable declaration}} \\[1mm] -&& \textit{assignment} && ::= &\quad && \textit{name} \ - \textbf{\texttt{=}}\ \textit{expression} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/49}{variable assignment}} \\[1mm] -&& \textit{expression} && ::= &\quad && \textit{number} && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/3}{primitive number expression}}\\ -&& && | &\quad && \textbf{\texttt{true}}\ |\ \textbf{\texttt{false}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/8}{primitive boolean expression}}\\ -&& && | &\quad && \textbf{\texttt{null}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/30}{primitive list expression}} \\ -&& && | &\quad && \textit{string} && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/17}{primitive string expression}} \\ -&& && | &\quad && \textit{name} && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/4}{name expression}} \\ -&& && | &\quad && \textit{expression} \ \textit{binary-operator} \ - \textit{expression} \qquad - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/3}{binary operator combination}} \\ -&& && | &\quad && \textit{unary-operator} \ - \textit{expression} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/8}{unary operator combination}}\\ -&& && | &\quad && \textit{expression} \ - \textbf{\texttt{(}}\ \textit{expressions}\ - \textbf{\texttt{)}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/6}{function application}} \\ -&& && | &\quad && (\ \textit{name}\ | \ - \textbf{\texttt{(}}\ \textit{parameters}\ \textbf{\texttt{)}}\ - )\ - \texttt{\textbf{=>}}\ \textit{expression} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/20}{function definition (expr. body)}} \\ -&& && | &\quad && (\ \textit{name}\ | \ - \textbf{\texttt{(}}\ \textit{parameters}\ \textbf{\texttt{)}}\ - )\ - \texttt{\textbf{=>}}\ \textit{block} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/33\#footnote-link-7}{function definition (block body)}} \\ -&& && | &\quad && \textit{expression} \ \textbf{\texttt{?}}\ - \textit{expression} - \ \textbf{\texttt{:}}\ - \textit{expression}\ - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/8}{conditional expression}} \\ -&& && | &\quad && \textit{expression} \textbf{\texttt{[}} - \textit{expression} \textbf{\texttt{]}} - && \textrm{array access} \\ -&& && | &\quad && \textbf{\texttt{[}}\ - \textit{expressions}\ - \textbf{\texttt{]}} - && \textrm{literal array expression}\\ -&& && | &\quad && \textbf{\texttt{(}}\ \textit{expression} \ - \textbf{\texttt{)}} && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/3}{parenthesised expression}}\\[1mm] -&& \textit{binary-operator} \ - && ::= &\quad && \textbf{\texttt{+}}\ |\ \textbf{\texttt{-}}\ |\ \textbf{\texttt{*}}\ |\ \textbf{\texttt{/}}\ |\ \textbf{\texttt{\%}}\ |\ - \textbf{\texttt{===}}\ |\ \textbf{\texttt{!==}}\ \\ -&& && | &\quad && \texttt{\textbf{>}}\ |\ \texttt{\textbf{<}}\ |\ \texttt{\textbf{>=}}\ |\ \texttt{\textbf{<=}}\ - |\ \textbf{\texttt{\&\&}}\ |\ \texttt{\textbf{||}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/3}{binary operator}}\\[1mm] -&& \textit{unary-operator} - && ::= &\quad && \textbf{\texttt{!}}\ |\ \textbf{\texttt{-}} - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/8}{unary operator}}\\[1mm] -&& \textit{expressions} && ::= &\quad && \epsilon\ | \ \textit{expression}\ ( - \ \textbf{\texttt{,}} \ - \textit{expression} \ - )\ \ldots - && \textrm{\href{https://sicp.comp.nus.edu.sg/chapters/6}{argument expressions}} -\end{alignat*} From 4e643337ceaba42f9eb91fac5e0762a7c1bc5ae6 Mon Sep 17 00:00:00 2001 From: Anubhav <35621759+anubh-v@users.noreply.github.com> Date: Sat, 2 May 2020 00:34:56 +0800 Subject: [PATCH 10/12] Improve comments in non-det interpreter (#47) * Remove unhelpful comment * Fix spelling in comment --- src/interpreter/interpreter-non-det.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts index fc6480e6b..152ee6e6f 100644 --- a/src/interpreter/interpreter-non-det.ts +++ b/src/interpreter/interpreter-non-det.ts @@ -264,7 +264,7 @@ function* evaluateBlockSatement(context: Context, node: es.BlockStatement) { function* evaluateSequence(context: Context, sequence: es.Statement[]): IterableIterator { if (sequence.length === 0) { - return yield undefined // repl does not work unless we handle this case --> Why? + return yield undefined } const firstStatement = sequence[0] const sequenceValGenerator = evaluate(firstStatement, context) @@ -288,7 +288,7 @@ function* evaluateSequence(context: Context, sequence: es.Statement[]): Iterable const res = yield* evaluateSequence(context, sequence) if (res === CUT) { - // prevent unshifting of statenents before cut + // prevent unshifting of statements before cut shouldUnshift = false break } From 076d8ba47e456c6cbb66e42aac89f67ec931d3bb Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Sat, 2 May 2020 15:08:01 +0800 Subject: [PATCH 11/12] allow only supported chapter and variant combinations --- src/constants.ts | 14 ++++++++++++++ src/repl/repl.ts | 30 +++++++++++++++++++++++------- src/types.ts | 5 +++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 03350d874..f14f5758c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,5 @@ import * as es from 'estree' +import { SourceLanguage } from './types' export const CUT = 'cut' // cut operator for Source 4.3 export const TRY_AGAIN = 'try again' // command for Source 4.3 @@ -19,3 +20,16 @@ export const JSSLANG_PROPERTIES = { maxExecTime: 1000, factorToIncreaseBy: 10 } + +export const sourceLanguages: SourceLanguage[] = [ + { chapter: 1, variant: 'default' }, + { chapter: 1, variant: 'wasm' }, + { chapter: 1, variant: 'lazy' }, + { chapter: 2, variant: 'default' }, + { chapter: 2, variant: 'lazy' }, + { chapter: 3, variant: 'default' }, + { chapter: 3, variant: 'concurrent' }, + { chapter: 3, variant: 'non-det' }, + { chapter: 4, variant: 'default' }, + { chapter: 4, variant: 'gpu' } +]; diff --git a/src/repl/repl.ts b/src/repl/repl.ts index 841fde685..a648e3a08 100644 --- a/src/repl/repl.ts +++ b/src/repl/repl.ts @@ -4,6 +4,7 @@ import { inspect } from 'util' import { createContext, IOptions, parseError, runInContext } from '../index' import { Variant, ExecutionMethod } from '../types' import Closure from '../interpreter/closure' +import { sourceLanguages } from '../constants' function startRepl( chapter = 1, @@ -14,7 +15,7 @@ function startRepl( prelude = '' ) { // use defaults for everything - const context = createContext(chapter, undefined, undefined, undefined) + const context = createContext(chapter, variant, undefined, undefined) const options: Partial = { scheduler: 'preemptive', executionMethod, @@ -22,7 +23,7 @@ function startRepl( useSubst } runInContext(prelude, context, options).then(preludeResult => { - if (preludeResult.status === 'finished') { + if (preludeResult.status === 'finished' || preludeResult.status === 'suspended-non-det') { console.dir(preludeResult.value, { depth: null }) if (!useRepl) { return @@ -32,7 +33,7 @@ function startRepl( { eval: (cmd, unusedContext, unusedFilename, callback) => { runInContext(cmd, context, options).then(obj => { - if (obj.status === 'finished') { + if (obj.status === 'finished' || obj.status === 'suspended-non-det') { callback(null, obj.value) } else { callback(new Error(parseError(context.errors)), undefined) @@ -57,24 +58,39 @@ function startRepl( }) } +/** + * Returns true iff the given chapter and variant combination is supported. + */ +function validChapterVariant(chapter: any, variant: any) { + for (const lang of sourceLanguages) { + if (lang.chapter === chapter && lang.variant === variant) return true + } + + return false +} + function main() { const opt = require('node-getopt') .create([ ['c', 'chapter=CHAPTER', 'set the Source chapter number (i.e., 1-4)', '1'], - ['v', 'variant=VARIANT', 'set the Source variant (i.e., lazy, non-det, concurrent, wasm)', 'default'], + ['v', 'variant=VARIANT', 'set the Source variant (i.e., default, lazy, non-det, concurrent, wasm, gpu)', 'default'], ['s', 'use-subst', 'use substitution'], ['h', 'help', 'display this help'], ['i', 'interpreter', 'use the interpreter for execution'], - ['l', 'lazy', 'use lazy evaluation'], ['e', 'eval', "don't show REPL, only display output of evaluation"] ]) .bindHelp() .setHelp('Usage: js-slang [PROGRAM_STRING] [OPTION]\n\n[[OPTIONS]]') .parseSystem() - const executionMethod = opt.options.interpreter === true ? 'interpreter' : 'native' - const variant = opt.options.lazy === true ? 'lazy' : opt.options.variant + const variant = opt.options.variant const chapter = parseInt(opt.options.chapter, 10) + const areValidChapterVariant:boolean = validChapterVariant(chapter, variant); + if (!areValidChapterVariant) { + throw new Error("The chapter and variant combination provided is unsupported. Use the -h option to view valid chapters and variants.") + } + + const executionMethod = opt.options.interpreter === true ? 'interpreter' : 'native' const useSubst = opt.options.s const useRepl = !opt.options.e const prelude = opt.argv[0] ?? '' diff --git a/src/types.ts b/src/types.ts index 9dbdd6fd3..9ab9843cb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -59,6 +59,11 @@ export interface Comment { export type ExecutionMethod = 'native' | 'interpreter' | 'auto' export type Variant = 'wasm' | 'lazy' | 'non-det' | 'concurrent' | 'gpu' | 'default' // this might replace EvaluationMethod +export interface SourceLanguage { + chapter: number; + variant: Variant; +} + export interface Context { /** The source version used */ chapter: number From 63a4cedc8fb20b0b9c17444f47cc3358b423101a Mon Sep 17 00:00:00 2001 From: Arsalan Cheema Date: Sat, 2 May 2020 15:11:59 +0800 Subject: [PATCH 12/12] format --- src/constants.ts | 2 +- src/repl/repl.ts | 13 ++++++++++--- src/types.ts | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index f14f5758c..489428198 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -32,4 +32,4 @@ export const sourceLanguages: SourceLanguage[] = [ { chapter: 3, variant: 'non-det' }, { chapter: 4, variant: 'default' }, { chapter: 4, variant: 'gpu' } -]; +] diff --git a/src/repl/repl.ts b/src/repl/repl.ts index a648e3a08..1b9a72cad 100644 --- a/src/repl/repl.ts +++ b/src/repl/repl.ts @@ -73,7 +73,12 @@ function main() { const opt = require('node-getopt') .create([ ['c', 'chapter=CHAPTER', 'set the Source chapter number (i.e., 1-4)', '1'], - ['v', 'variant=VARIANT', 'set the Source variant (i.e., default, lazy, non-det, concurrent, wasm, gpu)', 'default'], + [ + 'v', + 'variant=VARIANT', + 'set the Source variant (i.e., default, lazy, non-det, concurrent, wasm, gpu)', + 'default' + ], ['s', 'use-subst', 'use substitution'], ['h', 'help', 'display this help'], ['i', 'interpreter', 'use the interpreter for execution'], @@ -85,9 +90,11 @@ function main() { const variant = opt.options.variant const chapter = parseInt(opt.options.chapter, 10) - const areValidChapterVariant:boolean = validChapterVariant(chapter, variant); + const areValidChapterVariant: boolean = validChapterVariant(chapter, variant) if (!areValidChapterVariant) { - throw new Error("The chapter and variant combination provided is unsupported. Use the -h option to view valid chapters and variants.") + throw new Error( + 'The chapter and variant combination provided is unsupported. Use the -h option to view valid chapters and variants.' + ) } const executionMethod = opt.options.interpreter === true ? 'interpreter' : 'native' diff --git a/src/types.ts b/src/types.ts index 9ab9843cb..1b5ece63c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,8 +60,8 @@ export type ExecutionMethod = 'native' | 'interpreter' | 'auto' export type Variant = 'wasm' | 'lazy' | 'non-det' | 'concurrent' | 'gpu' | 'default' // this might replace EvaluationMethod export interface SourceLanguage { - chapter: number; - variant: Variant; + chapter: number + variant: Variant } export interface Context {