Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 7 additions & 4 deletions continuations-playground/ts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import MonacoEditor from 'react-monaco-editor';
import { Hook, Console, Decode } from 'console-feed';
import { compile } from 'stopify-continuations-compiler';
import { compile } from '@stopify/continuations';

function appendLog(method: 'log' | 'error', msg: any) {
return function(prevState: { logs: any[] }) {
Expand All @@ -18,7 +18,7 @@ function appendLog(method: 'log' | 'error', msg: any) {
type CodeLoaderState =
{ kind: 'loading' } |
{ kind: 'ok', code: string } |
{ kind: 'error', message: string }
{ kind: 'error', message: string };

class CodeLoader extends React.Component<{}, CodeLoaderState> {

Expand Down Expand Up @@ -97,8 +97,11 @@ class ContinuationsPlayground extends React.Component<{ initialCode: string }, {
console: {
log: (msg: any) => this.setState(appendLog('log', msg))
},
control: function(f: any) {
return runner.control(f);
shift: function(f: any) {
return runner.shift(f);
},
reset: function(f: any) {
return runner.reset(f);
}
};
runner.g = globals;
Expand Down
10 changes: 7 additions & 3 deletions stopify-continuations-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const reserved = [
'$top',
'$S'
];

const visitor: babel.Visitor = {
Program(path, state) {
const opts: types.CompilerOpts = {
Expand Down Expand Up @@ -73,7 +73,7 @@ const visitor: babel.Visitor = {
[ [ callcc.default, opts ] ]);
path.stop();
}
}
};

/**
* Compiles a program to support callCC.
Expand Down Expand Up @@ -138,12 +138,16 @@ class RunnerImpl implements types.Runner {
return eval(this.code);
}

control(f: (k: (v: any) => void) => void): void {
shift(f: (k: (v: any) => void) => void): void {
return this.rts.captureCC(k => {
return f((x: any) => k({ type: 'normal', value: x }));
});
}

reset(f: () => any): any {
return this.rts.reset(f);
}


processEvent(body: () => any, receiver: (x: Result) => void): void {
this.rts.runtime(body, receiver);
Expand Down
4 changes: 3 additions & 1 deletion stopify-continuations-compiler/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as t from 'babel-types';
import { Result } from '@stopify/continuations-runtime';
import { reset } from '@stopify/hygiene';

export interface LineMapping {
getLine: (line: number, column: number) => number | null
Expand Down Expand Up @@ -27,6 +28,7 @@ export interface CompilerOpts {
export type Runner = {
run: (onDone: (result: Result) => void) => void,
processEvent: (body: () => any, receiver: (x: Result) => void) => void,
control: (f: (k: (v: any) => void) => void) => void,
shift: (f: (k: (v: any) => void) => void) => void,
reset: (f: () => any) => any,
g: { [key: string]: any}
};
153 changes: 147 additions & 6 deletions stopify-continuations-compiler/test/callcc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import { compile } from '../src/index';

function compileForTest(code: string) {
let runner = compile(code).unwrap();
runner.g.callCC = function(f: any) {
return continuationRTS.newRTS('lazy').captureCC(k =>
f((x: any) => k({ type: 'normal', value: x })));
};
runner.g.callCC = (f: any) => runner.shift(f);
runner.g.shift = runner.g.callCC;
runner.g.reset = (f: any) => runner.reset(f);
runner.g.assert = assert;

// NOTE(arjun): For testing, we are relying on the fact that runner.run
Expand Down Expand Up @@ -197,8 +196,6 @@ test('fringe generator rest', () => {
});
});

test

test.skip('resume with an exception', () => {
// TODO(arjun): We do not have the right API to resume with an exception.

Expand Down Expand Up @@ -233,4 +230,148 @@ test.skip('resume with an exception', () => {
// console.log("Result of restart:", result);

// assert(result == "throw-this");
});

test('shift/reset: discard context within reset', () => {
let { run, globals } = compileForTest(`
let r = 20 + reset(function() {
return 300 + shift(function(k) { return 1; })
});
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r: 21
});
});

test('shift/reset: restore context within reset', () => {
let { run, globals } = compileForTest(`
let r = 20 + reset(function() {
return 300 + shift(function(k) { return k(1); })
});
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r: 321
});
});

test('shift/reset: return to context within shift', () => {
let { run, globals } = compileForTest(`
let r = 20 + reset(function() {
return 300 + shift(function(k) { return k(1) + 4000; })
});
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r: 4321
});
});

test('shift/reset: nested reset, discard inner context', () => {
let { run, globals } = compileForTest(`
let r = 1 + reset(function() {
return 20 + reset(function() {
return 300 + shift(function(k) {
return 4000;
});
});
});
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r: 4021
});
});

test('shift/reset: save continuation to global variable', () => {
let { run, globals } = compileForTest(`
let saved = false;
let r1 = 1 + reset(function() {
return 20 + shift(function(k) {
saved = k;
return 300;
});
});
let r2 = saved(4000);
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r1: 1 + 300,
r2: 20 + 4000
});
});

test('shift/reset: invoke continuation twice', () => {
let { run, globals } = compileForTest(`
r = reset(function() {
return 100 + shift(function(k) {
return k(1) + k(2);
});
});
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r: 100 + 1 + 100 + 2
});
});

test('shift/reset: two shifts', () => {
let { run, globals } = compileForTest(`
r = reset(function() {
return 100 + shift(function(k) {
return k(1) + k(2);
}) + shift(function(k) {
return k(3) + k(4);
});
});
`);
expect(run()).toMatchObject({ type: 'normal' });
expect(globals).toMatchObject({
r: (100 + 1 + 3) + (100 + 1 + 4) + (100 + 2 + 3) + (100 + 2 + 4)
});
});


test('shift/reset: non-determinism', () => {
let { run, globals } = compileForTest(`
function choose(args) {
return shift(function(k) {
let results = [ ];
for (let i = 0; i < args.length; i++) {
results = results.concat(k(args[i]));
}
return results;
});
}

function fail() {
return shift(function(k) {
return [];
});
}

function driver(body) {
return reset(function() {
return [body()];
});
}

function F() {
return choose([1,2,3]);
}

results = driver(function() {
let x = F();
let y = F();
if ((x + y) % 2 !== 0) {
fail();
}
return x + y;
});;
`);
expect(run()).toMatchObject({ type: 'normal' });
// Prelude> [ x + y | x <- [ 1 .. 3 ], y <- [ 1 .. 3], (x + y) `mod` 2 == 0 ]
// [2,4,4,4,6]
expect(globals.results).toMatchObject([2,4,4,4,6]);
});
2 changes: 2 additions & 0 deletions stopify-continuations/src/runtime/abstractRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,6 @@ export abstract class RuntimeImpl implements Runtime {
abstract abstractRun(body: () => any): RunResult;

abstract endTurn(callback: (onDone: (x: Result) => any) => any): never;

abstract reset(f: () => any): any;
}
4 changes: 4 additions & 0 deletions stopify-continuations/src/runtime/eagerRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export class EagerRuntime extends common.RuntimeImpl {
this.eagerStack = [];
}

reset(f: () => any): any {
throw 'Not implemented';
}

captureCC(f: (k: (x: Result) => any) => any): any {
this.capturing = true;
throw new common.Capture(f, [...this.eagerStack]);
Expand Down
4 changes: 4 additions & 0 deletions stopify-continuations/src/runtime/fudgeRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class FudgeRuntime extends common.RuntimeImpl {
super(Infinity, Infinity);
}

reset(f: () => any): any {
throw 'Not implemented';
}

captureCC(f: (k: any) => any): void {
throw new common.Capture(f, []);
}
Expand Down
33 changes: 33 additions & 0 deletions stopify-continuations/src/runtime/lazyRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,39 @@ export class LazyRuntime extends common.RuntimeImpl {
super(stackSize, restoreFrames);
}

reset(f: () => any): any {
try {
return f();
}
catch (exn) {
if (exn instanceof common.Capture) {
if (this.mode !== true) {
throw new Error('capture while restoring');
}
this.capturing = false;
let f = exn.f;
let k = this.makeCont(exn.stack);
return this.reset(() => f((v) => this.reset(() => k(v))));
}
else if (exn instanceof common.Restore) {
if (exn.savedStack.length > 0) {
throw new Error('savedStack found within shift/reset');
}
let stack = exn.stack;
this.stack = stack;
this.mode = false;
let frame = stack[stack.length - 1] as types.KFrameRest;
return this.reset(() => {
return frame.f.apply(frame.this || global, (frame.params || []) as any);
});

}
else {
throw exn;
}
}
}

captureCC(f: (k: (x: Result) => any) => any): any {
this.capturing = true;
throw new common.Capture(f, []);
Expand Down
4 changes: 4 additions & 0 deletions stopify-continuations/src/runtime/retvalRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export class RetvalRuntime extends common.RuntimeImpl {
super(stackSize, restoreFrames);
}

reset(f: () => any): any {
throw 'Not implemented';
}

captureCC(f: (k: (x: Result) => any) => any): any {
this.capturing = true;
return new common.Capture(f, []);
Expand Down
2 changes: 2 additions & 0 deletions stopify-continuations/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ export interface Runtime {

// Called when the stack needs to be captured.
captureCC(f: (k: (x: Result) => any) => any): void;

reset(f: () => any): any;
}