Skip to content

Commit

Permalink
Use Effection for Ursa async (WIP)
Browse files Browse the repository at this point in the history
Make launch an Exp, not a Statement, as we need to be able to await
it.

ArkPromise becomes ArkOperation.

Add NativeOperation, and bind Effection's sleep() using it.
  • Loading branch information
rrthomas committed Jul 27, 2024
1 parent 16223d9 commit d4061ef
Show file tree
Hide file tree
Showing 11 changed files with 63 additions and 44 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@prettier/sync": "^0.5.2",
"@sc3d/stacktracey": "^2.1.9",
"argparse": "^2.0.1",
"effection": "^3.0.3",
"env-paths": "^3.0.0",
"fs-extra": "^11.2.0",
"get-source": "^2.0.12",
Expand Down
6 changes: 3 additions & 3 deletions src/ark/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
globals, jsGlobals,
ArkBoolean, ArkBooleanVal, ArkList, ArkMap, ArkNull,
ArkNumber, ArkNullVal, ArkNumberVal, ArkObject, ArkString,
ArkStringVal, ArkUndefined, ArkVal, NativeFn, ArkPromise,
ArkStringVal, ArkUndefined, ArkVal, NativeFn, ArkOperation,
} from '../data.js'
import {ArkExp} from '../code.js'
import {debug} from '../util.js'
Expand Down Expand Up @@ -70,7 +70,7 @@ const runtimeContext: Record<string, unknown> = {
ArkObject,
ArkList,
ArkMap,
ArkPromise,
ArkOperation,
NativeFn,
jsGlobals,
}
Expand Down Expand Up @@ -142,7 +142,7 @@ export function flatToJs(insts: ArkInsts, file: string | null = null): CodeWithS
} else if (inst instanceof ArkLoopBlockOpenInst) {
return sourceNode([letAssign(inst.matchingClose.id, 'ArkNull()'), 'for (;;) {\n'])
} else if (inst instanceof ArkLaunchBlockOpenInst) {
return sourceNode([letAssign(inst.matchingClose.id, 'new ArkPromise((async function() {')])
return sourceNode([letAssign(inst.matchingClose.id, 'new ArkOperation((function* () {')])
} else if (inst instanceof ArkGeneratorBlockOpenInst) {
env = env.pushFrame(
new Frame(inst.params.map((p) => new Location(p, false)), [], inst.name),
Expand Down
34 changes: 21 additions & 13 deletions src/ark/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import assert from 'assert'

import {Operation, sleep} from 'effection'

import {FsMap} from './fsmap.js'
import programVersion from '../version.js'
import {debug} from './util.js'
Expand Down Expand Up @@ -147,8 +149,8 @@ export function ArkString(s: string) {
return ConcreteInterned.value<ArkStringVal, string>(ArkStringVal, s)
}

export class ArkPromise extends ArkVal {
constructor(public promise: Promise<ArkVal>) {
export class ArkOperation extends ArkVal {
constructor(public operation: Operation<ArkVal>) {
super()
}
}
Expand All @@ -175,6 +177,12 @@ export class NativeFn extends ArkCallable {
}
}

export class NativeOperation extends ArkCallable {
constructor(params: string[], public body: (...args: ArkVal[]) => Operation<void>) {
super(params)
}
}

// ts-unused-exports:disable-next-line
export class NativeAsyncFn extends ArkCallable {
constructor(params: string[], public body: (...args: ArkVal[]) => Promise<ArkVal>) {
Expand Down Expand Up @@ -366,17 +374,17 @@ export const globals = new ArkObject(new Map<string, ArkVal>([
debug(obj)
return ArkNull()
})],
['fs', new NativeFn(['path'], (path: ArkVal) => new NativeObject(new FsMap(toJs(path) as string)))],

['Promise', new NativeAsyncFn(
['resolve', 'reject'],
(fn: ArkVal) => Promise.resolve(new ArkPromise(
new Promise(
toJs(fn) as
(resolve: (value: unknown) => void, reject: (reason?: unknown) => void) => void,
).then((x) => fromJs(x)),
)),
)],
['fs', new NativeFn(['path'], (path: ArkVal) => new NativeObject(new FsMap((path as ArkStringVal).val)))],
['sleep', new NativeOperation(['ms'], (ms: ArkVal) => sleep((ms as ArkNumberVal).val))],
// ['Promise', new NativeAsyncFn(
// ['resolve', 'reject'],
// (fn: ArkVal) => Promise.resolve(new ArkOperation(
// new Operation(
// toJs(fn) as
// (resolve: (value: unknown) => void, reject: (reason?: unknown) => void) => void,
// ).then((x) => fromJs(x)),
// )),
// )],
['fetch', new NativeAsyncFn(
['url', 'options'],
async (url: ArkVal, options: ArkVal) => new NativeObject(
Expand Down
24 changes: 17 additions & 7 deletions src/ark/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// © Reuben Thomas 2023-2024
// Released under the MIT license.

import {Operation, run, spawn} from 'effection'
import {Interval} from 'ohm-js'

import {
Expand All @@ -15,8 +16,8 @@ import {
} from './flatten.js'
import {
ArkAbstractObjectBase, ArkBoolean, ArkList, ArkMap, ArkNull, ArkNullVal,
ArkObject, ArkPromise, ArkUndefined, ArkVal, NativeAsyncFn, NativeFn,
ArkRef, ArkValRef,
ArkObject, ArkOperation, ArkUndefined, ArkVal, NativeAsyncFn, NativeFn,
NativeOperation, ArkRef, ArkValRef,
} from './data.js'
import {
ArkCapture, ArkContinuation, ArkFlatClosure, ArkFlatGeneratorClosure,
Expand Down Expand Up @@ -101,6 +102,10 @@ function makeLocals(names: string[], vals: ArkVal[]): ArkRef[] {
}

async function evalFlat(outerArk: ArkState): Promise<ArkVal> {
return run(() => doEvalFlat(outerArk))
}

function* doEvalFlat(outerArk: ArkState): Operation<ArkVal> {
let ark: ArkState | undefined = outerArk
let inst = ark.inst
let prevInst
Expand Down Expand Up @@ -157,9 +162,10 @@ async function evalFlat(outerArk: ArkState): Promise<ArkVal> {
),
ark.outerState,
)
const result = Promise.resolve(new ArkPromise(evalFlat(innerArk)))
const operation = yield* spawn(() => doEvalFlat(innerArk))
const result = new ArkOperation(operation)
mem.set(inst.id, result)
// The Promise becomes the result of the entire block.
// The ArkOperation becomes the result of the entire block.
mem.set(inst.matchingClose.id, result)
inst = inst.matchingClose.next
} else if (inst instanceof ArkCallableBlockOpenInst) {
Expand All @@ -184,8 +190,9 @@ async function evalFlat(outerArk: ArkState): Promise<ArkVal> {
} else if (inst instanceof ArkBlockOpenInst) {
inst = inst.next
} else if (inst instanceof ArkAwaitInst) {
const promise = (mem.get(inst.argId)! as ArkPromise).promise
mem.set(inst.id, await promise)
const operation = (mem.get(inst.argId)! as ArkOperation).operation
const result = yield* operation
mem.set(inst.id, result)
inst = inst.next
} else if (inst instanceof ArkBreakInst) {
const result = mem.get(inst.argId)!
Expand Down Expand Up @@ -277,7 +284,10 @@ async function evalFlat(outerArk: ArkState): Promise<ArkVal> {
mem.set(inst.id, callable.body(...args))
inst = inst.next
} else if (callable instanceof NativeAsyncFn) {
mem.set(inst.id, await callable.body(...args))
mem.set(inst.id, callable.body(...args))
inst = inst.next
} else if (callable instanceof NativeOperation) {
yield* callable.body(...args)
inst = inst.next
} else {
throw new ArkRuntimeError(ark, 'Invalid call', inst.sourceLoc)
Expand Down
4 changes: 2 additions & 2 deletions src/ark/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import {
globals, ArkVal,
ArkConcreteVal, ArkNull, ArkPromise, ArkList, ArkMap, ArkObject,
ArkConcreteVal, ArkNull, ArkOperation, ArkList, ArkMap, ArkObject,
ArkUndefined, NativeObject,
} from './data.js'
import {
Expand Down Expand Up @@ -87,7 +87,7 @@ export function valToJs(val: ArkVal | ArkExp, externalSyms = globals) {
return ['yield', doValToJs(val.exp)]
} else if (val instanceof ArkReturn) {
return ['return', doValToJs(val.exp)]
} else if (val instanceof ArkPromise) {
} else if (val instanceof ArkOperation) {
// FIXME: Can we properly serialize a promise?
return ['promise']
} else if (val === ArkNull()) {
Expand Down
2 changes: 1 addition & 1 deletion src/grammar/ursa.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Ursa {
| For
| await Exp -- await
| yield Exp? -- yield
| launch Exp -- launch
| LogicExp

Assignment
Expand All @@ -119,7 +120,6 @@ Ursa {
| Use
| break Exp? -- break
| continue -- continue
| launch Exp -- launch
| return Exp? -- return
| Exp

Expand Down
4 changes: 2 additions & 2 deletions src/grammar/ursa.ohm-bundle.d.ts.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--- src/grammar/ursa.ohm-bundle.d.ts 2024-07-25 19:26:08.992907449 +0100
+++ src/grammar/ursa.ohm-bundle.d.part-patched.ts 2024-07-25 19:26:09.128908027 +0100
--- src/grammar/ursa.ohm-bundle.d.ts 2024-07-27 22:31:25.130617483 +0100
+++ src/grammar/ursa.ohm-bundle.d.part-patched.ts 2024-07-27 22:31:25.270617985 +0100
@@ -4,14 +4,33 @@
import {
BaseActionDict,
Expand Down
7 changes: 4 additions & 3 deletions src/ursa/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,10 @@ semantics.addOperation<ArkExp>('toExp(a)', {
return addLoc(new ArkYield(maybeVal(this.args.a, exp)), this)
},

Exp_launch(_launch, exp) {
return addLoc(new ArkLaunch(exp.toExp(this.args.a)), this)
},

Statement_break(_break, exp) {
if (!this.args.a.inLoop) {
throw new UrsaCompilerError(_break.source, 'break used outside a loop')
Expand All @@ -561,9 +565,6 @@ semantics.addOperation<ArkExp>('toExp(a)', {
}
return addLoc(new ArkContinue(), this)
},
Statement_launch(_await, exp) {
return addLoc(new ArkLaunch(exp.toExp(this.args.a)), this)
},
Statement_return(return_, exp) {
if (!this.args.a.inFn) {
throw new UrsaCompilerError(return_.source, 'return used outside a function')
Expand Down
15 changes: 8 additions & 7 deletions src/ursa/fmt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,14 @@ semantics.addOperation<Span>('fmt(a)', {
)
},

Exp_launch(_launch, exp) {
return tryFormats(
this.args.a,
(a) => hSpan(['launch', exp.fmt(a)]),
[(a) => hSpan(['launch', exp.fmt(a)])],
)
},

Statement_break(_break, exp) {
return tryFormats(
this.args.a,
Expand All @@ -481,13 +489,6 @@ semantics.addOperation<Span>('fmt(a)', {
Statement_continue(_continue) {
return hSpan(['continue'])
},
Statement_launch(_await, exp) {
return tryFormats(
this.args.a,
(a) => hSpan(['launch', exp.fmt(a)]),
[(a) => hSpan(['launch', exp.fmt(a)])],
)
},
Statement_return(_return, exp) {
return tryFormats(
this.args.a,
Expand Down
4 changes: 0 additions & 4 deletions test/launch.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,3 @@
10 4
5 4
10 5
10 6
10 7
10 8
10 9
6 changes: 4 additions & 2 deletions test/launch.ursa
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
let f = fn(n) {
for i of range(n) {
print(n, i)
sleep(0)
}
}

launch f(10)
launch f(5)
let fTask = launch f(10)
let gTask = launch f(5)
await gTask

0 comments on commit d4061ef

Please sign in to comment.