Skip to content

Commit

Permalink
[Update] Support generator and async function
Browse files Browse the repository at this point in the history
  • Loading branch information
Siubaak committed Nov 17, 2019
1 parent e07264d commit 9d3a1d8
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 121 deletions.
13 changes: 12 additions & 1 deletion src/compile/helper.ts
Expand Up @@ -172,6 +172,17 @@ export function compileForXStatement(node: estree.ForInStatement | estree.ForOfS
throw new TypeError('Assignment to constant variable')
}
opCodes.push({ op: OP.STORE, val: symbol.pointer })
} else if (left.type === 'VariableDeclaration') {
state.symbols.type = left.kind
for (let i = 0; i < left.declarations.length; i++) {
const declr = left.declarations[i]
if (declr.id.type === 'Identifier') {
state.opCodes.push({ op: OP.ALLOC, val: state.symbols.set(declr.id.name).pointer })
} else { // declr.id.type === 'Pattern'
compilePattern(declr.id, state)
}
}
state.symbols.type = null
} else if (left.type === 'MemberExpression') {
compile(left.object, state)
if (left.object.type === 'Super') {
Expand All @@ -185,7 +196,7 @@ export function compileForXStatement(node: estree.ForInStatement | estree.ForOfS
}
state.opCodes.push({ op: OP.MSET, val: left.object.type === 'Super' })
} else {
compile(left, state)
compilePattern(left, state)
}

compile(node.body, state)
Expand Down
186 changes: 115 additions & 71 deletions src/jsvm/index.ts
@@ -1,16 +1,19 @@
import State from '../state'
import { OP, SIGNAL } from '../share/const'
import { getDptor, define, inherits, getGetter, getSetter } from '../share/utils'
import { OP, SIGNAL, Signal } from '../share/const'
import { getDptor, define, inherits, getGetter, getSetter, runAsync, AWAIT, createSymbol, runAsyncOptions } from '../share/utils'

const ES6_CLASS = createSymbol('class')
const SUPER_CALLED = createSymbol('super')

function step(state: State) {
const stack = state.stack
const code = state.opCodes[state.pc]
let signal = SIGNAL.NONE
let signal: Signal = { type: SIGNAL.NONE }
switch (code.op) {
case OP.LOADK: stack[state.esp++] = code.val; break
case OP.LOADV: {
const value = state.context[code.val] && state.context[code.val].store
if (code.this && value['#sval_super_called#'] === false) {
if (code.this && value[SUPER_CALLED] === false) {
throw new ReferenceError("Must call super constructor in derived class before accessing 'this'")
}
stack[state.esp++] = value
Expand Down Expand Up @@ -218,11 +221,11 @@ function step(state: State) {
state.pc = beginPc // offset pc to the function op codes
state.context = lexicalCtx // set the context as the lexical context of function

let signal = SIGNAL.NONE
let sig: Signal = { type: SIGNAL.NONE }
let ret: any
while (state.pc < endPc) {
signal = step(state)
if (signal === SIGNAL.RET) {
sig = step(state)
if (sig.type === SIGNAL.RET) {
ret = stack[--state.esp]
break
}
Expand All @@ -233,6 +236,7 @@ function step(state: State) {

return ret
}

define(func, 'name', {
value: code.val,
configurable: true
Expand All @@ -241,62 +245,102 @@ function step(state: State) {
value: code.length,
configurable: true
})

stack[state.esp++] = func
} else {
// const tmpFunc = function* () {
// for (let i = arguments.length - 1; i > -1; i--) {
// stack[state.esp++] = arguments[i] // load arguments
// }
// if (!code.arrow) {
// stack[state.esp++] = arguments // load argument array itself
// stack[state.esp++] = this // load this
// }

// const resetPc = state.pc // reserve the current pc
// const resetCtx = state.context // reserve the current context
// state.pc = beginPc // offset pc to the function op codes
// state.context = lexicalCtx // set the context as the lexical context of function

// while (state.pc < endPc) {
// const s = step(state)
// if (s === SIGNAL.RET) break
// else if (s === SIGNAL.YIELD) yield
// }

// state.pc = resetPc // reset to the current pc
// state.context = resetCtx // reset to the current context
// }
// if (code.async && code.generator) {
// stack[state.esp++] = function (: AsyncIterator<any> {
// const iterator = tmpFunc.apply(void 0, arguments)
// let last: Promise<any> = Promise.resolve()
// let hasCatch = false
// const run = (opts: runAsyncOptions) =>
// last = last
// .then(() => runAsync(iterator, assign({ fullRet: true }, opts)))
// .catch(err => {
// if (!hasCatch) {
// hasCatch = true
// return Promise.reject(err)
// }
// })
// const asyncIterator: AsyncIterator<any> = {
// next: (res?: any) => run({ res }),
// throw: (err?: any) => run({ err }),
// return: (ret?: any) => run({ ret })
// }
// if (typeof Symbol === 'function') {
// (asyncIterator as any)[Symbol.iterator] = function () { return this }
// }
// return asyncIterator
// })
// } else if (code.async) {
// stack[state.esp++] = function ( {
// return runAsync(tmpFunc.apply(void 0, arguments))
// })
// } else {
// stack[state.esp++] = tmpFunc
// }
const tmpFunc = function* () {
for (let i = arguments.length - 1; i > -1; i--) {
stack[state.esp++] = arguments[i] // load arguments
}
if (!code.arrow) {
stack[state.esp++] = arguments // load argument array itself
stack[state.esp++] = this // load this
}

let resetPc = state.pc // reserve the current pc
let resetCtx = state.context // reserve the current context
state.pc = beginPc // offset pc to the function op codes
state.context = lexicalCtx // set the context as the lexical context of function

let sig: Signal = { type: SIGNAL.NONE }
let ret: any
while (state.pc < endPc) {
sig = step(state)
if (sig.type === SIGNAL.RET) {
ret = stack[--state.esp]
break
} else if (sig.type === SIGNAL.YIELD) {
// resume caller pc and context, and save generator pc and context
;[state.pc, resetPc] = [resetPc, state.pc]
;[state.context, resetCtx] = [resetCtx, state.context]
// yield generator
const resumeVal = sig.val ? yield* stack[--state.esp] : yield stack[--state.esp]
stack[state.esp++] = resumeVal
// resume generator pc and context, and save next caller pc and context
;[state.pc, resetPc] = [resetPc, state.pc]
;[state.context, resetCtx] = [resetCtx, state.context]
} else if (sig.type === SIGNAL.AWAIT ) {
// resume caller pc and context, and save async function pc and context
;[state.pc, resetPc] = [resetPc, state.pc]
;[state.context, resetCtx] = [resetCtx, state.context]
// await the result
AWAIT.RES = stack[--state.esp]
const resumeVal = yield AWAIT
stack[state.esp++] = resumeVal
// get result, and resume async function pc and context
// and save current pc and context
;[state.pc, resetPc] = [resetPc, state.pc]
;[state.context, resetCtx] = [resetCtx, state.context]
}
}

state.pc = resetPc // reset to the current pc
state.context = resetCtx // reset to the current context

return ret
}

let func: any = tmpFunc
if (code.async && code.generator) {
func = function (): AsyncIterator<any> {
const iterator = tmpFunc.apply(this, arguments)
let last: Promise<any> = Promise.resolve()
let hasCatch = false
const run = (opts: runAsyncOptions) =>
last = last
.then(() => runAsync(iterator, Object.assign({ fullRet: true }, opts)))
.catch(err => {
if (!hasCatch) {
hasCatch = true
return Promise.reject(err)
}
})
const asyncIterator: AsyncIterator<any> = {
next: (res?: any) => run({ res }),
throw: (err?: any) => run({ err }),
return: (ret?: any) => run({ ret })
}
if (typeof Symbol === 'function') {
(asyncIterator as any)[Symbol.iterator] = function () { return this }
}
return asyncIterator
}
} else if (code.async) {
func = function () {
return runAsync(tmpFunc.apply(this, arguments))
}
}

define(func, 'name', {
value: code.val,
configurable: true
})
define(func, 'length', {
value: code.length,
configurable: true
})

stack[state.esp++] = func
}
state.pc = endPc - 1
break
Expand All @@ -313,14 +357,14 @@ function step(state: State) {
let ctor = function () {
if (classCtor) {
if (superClass) {
define(this, '#sval_super_called#', { value: false, configurable: true })
define(this, SUPER_CALLED, { value: false, configurable: true })
}
const result = classCtor.apply(this, arguments)
if (superClass) {
if (!this['#sval_super_called#']) {
if (!this[SUPER_CALLED]) {
throw new ReferenceError('Must call super constructor in derived class before returning from derived constructor')
}
delete this['#sval_super_called#']
delete this[SUPER_CALLED]
}
return result
} else if (superClass) {
Expand All @@ -334,7 +378,7 @@ function step(state: State) {
value: code.val,
configurable: true
})
define(ctor, '#sval_es6_class#', { value: true })
define(ctor, ES6_CLASS, { value: true })
stack[state.esp++] = ctor
break
}
Expand Down Expand Up @@ -373,17 +417,17 @@ function step(state: State) {
case OP.CALL: {
const func = stack[--state.esp]

if (!code.super && func['#sval_es6_class#']) {
if (!code.super && func[ES6_CLASS]) {
throw new TypeError(`Class constructor ${func.name} cannot be invoked without 'new'`)
}

const obj = stack[--state.esp]

if (code.super) {
if (obj['#sval_super_called#']) {
if (obj[SUPER_CALLED]) {
throw new ReferenceError('Super constructor may only be called once')
}
define(obj, '#sval_super_called#', { value: true, configurable: true })
define(obj, SUPER_CALLED, { value: true, configurable: true })
}

const spread = code.val.concat()
Expand Down Expand Up @@ -452,9 +496,9 @@ function step(state: State) {
stack[state.esp++] = result
break
}
case OP.RET: signal = SIGNAL.RET; break
case OP.YIELD: signal = SIGNAL.YIELD; break
case OP.AWAIT: signal = SIGNAL.AWAIT; break
case OP.RET: signal = { type: SIGNAL.RET }; break
case OP.YIELD: signal = { type: SIGNAL.YIELD, val: code.val }; break
case OP.AWAIT: signal = { type: SIGNAL.AWAIT }; break
case OP.COPY: {
const top = stack[state.esp - 1]
stack[state.esp++] = top
Expand Down
5 changes: 5 additions & 0 deletions src/share/const.ts
Expand Up @@ -59,4 +59,9 @@ export interface OpCode {
op: OP
val?: any
[more: string]: any
}

export interface Signal {
type: number,
val?: any
}

0 comments on commit 9d3a1d8

Please sign in to comment.