Silence lint for main thread

pull/32/head
Piotr 3 months ago
parent d121e74c6f
commit f5619ce591

@ -0,0 +1,54 @@
import { LuaError } from './LuaError'
import { LuaType } from './utils'
export type ThreadStatus = 'running' | 'suspended' | 'dead'
type Gen = Generator<LuaType[], LuaType[], LuaType[]>
type GenFn = (...args: LuaType[]) => Gen
class Thread {
private readonly fn: GenFn
private gen?: Gen
public status: ThreadStatus = 'suspended'
public last: LuaType[] = []
public static current: Thread
public static main: Thread
public constructor(fn: GenFn) {
this.fn = fn
}
public resume(...args: LuaType[]): LuaType[] {
if (this.status === 'dead') {
throw new LuaError('cannot resume dead coroutine')
}
const prev = Thread.current
Thread.current = this
this.status = 'running'
try {
if (!this.gen) {
this.gen = this.fn(...args)
const r = this.gen.next()
this.status = r.done ? 'dead' : 'suspended'
this.last = r.value || []
return this.last
}
const r = this.gen.next(args)
this.status = r.done ? 'dead' : 'suspended'
this.last = r.value || []
return this.last
} finally {
Thread.current = prev
}
}
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
const mainThread = new Thread((function* () {}) as unknown as GenFn)
mainThread.status = 'running'
Thread.main = mainThread
Thread.current = mainThread
export { Thread }

@ -10,15 +10,27 @@ import { libTable } from './lib/table'
import { libString, metatable as stringMetatable } from './lib/string' import { libString, metatable as stringMetatable } from './lib/string'
import { getLibOS } from './lib/os' import { getLibOS } from './lib/os'
import { getLibPackage } from './lib/package' import { getLibPackage } from './lib/package'
import { libCoroutine } from './lib/coroutine'
import { LuaType, ensureArray, Config } from './utils' import { LuaType, ensureArray, Config } from './utils'
import { Thread } from './Thread'
import { parse as parseScript } from './parser' import { parse as parseScript } from './parser'
interface Script { interface Script {
exec: () => LuaType exec: () => LuaType
} }
const call = (f: Function | Table, ...args: LuaType[]): LuaType[] => { const call = (f: Function | Table | Thread, ...args: LuaType[]): LuaType[] => {
if (f instanceof Function) return ensureArray(f(...args)) if (f instanceof Thread) return f.resume(...args)
if (f instanceof Function) {
const res = f(...args)
if (res && typeof res.next === 'function') {
let r = res.next()
while (!r.done) r = res.next()
return ensureArray(r.value)
}
return ensureArray(res as LuaType)
}
const mm = f instanceof Table && f.getMetaMethod('__call') const mm = f instanceof Table && f.getMetaMethod('__call')
if (mm) return ensureArray(mm(f, ...args)) if (mm) return ensureArray(mm(f, ...args))
@ -37,17 +49,21 @@ const get = (t: Table | string, v: LuaType): LuaType => {
} }
const execChunk = (_G: Table, chunk: string, chunkName?: string): LuaType[] => { const execChunk = (_G: Table, chunk: string, chunkName?: string): LuaType[] => {
const exec = new Function('__lua', chunk) const exec = new Function(`return ${chunk}`)() as (lua: unknown) => Generator<LuaType[]>
const globalScope = new Scope(_G.strValues).extend() const globalScope = new Scope(_G.strValues).extend()
if (chunkName) globalScope.setVarargs([chunkName]) if (chunkName) globalScope.setVarargs([chunkName])
const res = exec({ const iterator = exec({
globalScope, globalScope,
...operators, ...operators,
Table, Table,
call, call,
get get
}) })
return res === undefined ? [undefined] : res let res = iterator.next()
while (!res.done) {
res = iterator.next()
}
return res.value === undefined ? [undefined] : res.value
} }
function createEnv( function createEnv(
@ -83,6 +99,7 @@ function createEnv(
loadLib('table', libTable) loadLib('table', libTable)
loadLib('string', libString) loadLib('string', libString)
loadLib('os', getLibOS(cfg)) loadLib('os', getLibOS(cfg))
loadLib('coroutine', libCoroutine)
_G.rawset('require', _require) _G.rawset('require', _require)

@ -0,0 +1,43 @@
import { Table } from '../Table'
import { LuaType, coerceArgToFunction, coerceArgToThread } from '../utils'
import { LuaError } from '../LuaError'
import { Thread } from '../Thread'
function create(fn: LuaType): Thread {
const F = coerceArgToFunction(fn, 'create', 1)
return new Thread(F as (...args: LuaType[]) => Generator<LuaType[]>)
}
function resume(thread: LuaType, ...args: LuaType[]): LuaType[] {
const THREAD = coerceArgToThread(thread, 'resume', 1)
try {
return [true, ...THREAD.resume(...args)]
} catch (e) {
if (e instanceof LuaError) return [false, e.message]
throw e
}
}
function status(thread: LuaType): string {
const THREAD = coerceArgToThread(thread, 'status', 1)
return THREAD.status
}
function wrap(fn: LuaType): Function {
const thread = create(fn)
return (...args: LuaType[]): LuaType[] => thread.resume(...args)
}
function running(): LuaType[] {
return [Thread.current, Thread.current === Thread.main]
}
const libCoroutine = new Table({
create,
resume,
wrap,
running,
status
})
export { libCoroutine }

@ -156,7 +156,7 @@ const generate = (node: luaparse.Node): string | MemExpr => {
const argsStr = params.length === 0 ? '' : '...args' const argsStr = params.length === 0 ? '' : '...args'
const returnStr = const returnStr =
node.body.findIndex(node => node.type === 'ReturnStatement') === -1 ? '\nreturn []' : '' node.body.findIndex(node => node.type === 'ReturnStatement') === -1 ? '\nreturn []' : ''
return `(${argsStr}) => {\n${body}${returnStr}\n}` return `function* (${argsStr}) {\n${body}${returnStr}\n}`
} }
const params = node.parameters.map(param => { const params = node.parameters.map(param => {
@ -212,7 +212,7 @@ const generate = (node: luaparse.Node): string | MemExpr => {
case 'Chunk': { case 'Chunk': {
const body = parseBody(node) const body = parseBody(node)
return `'use strict'\nconst $0 = __lua.globalScope\nlet vars\nlet vals\nlet label\n\n${body}` return `function* (__lua) {\n'use strict'\nconst $0 = __lua.globalScope\nlet vars\nlet vals\nlet label\n\n${body}\n}`
} }
case 'Identifier': { case 'Identifier': {
@ -324,6 +324,19 @@ const generate = (node: luaparse.Node): string | MemExpr => {
case 'CallExpression': case 'CallExpression':
case 'TableCallExpression': case 'TableCallExpression':
case 'StringCallExpression': { case 'StringCallExpression': {
if (
node.type === 'CallExpression' &&
node.base.type === 'MemberExpression' &&
node.base.base.type === 'Identifier' &&
node.base.base.name === 'coroutine' &&
node.base.identifier.type === 'Identifier' &&
node.base.identifier.name === 'yield' &&
node.base.indexer === '.'
) {
const args = parseExpressions(node.arguments)
return `yield ${args}`
}
const functionName = expression(node.base) const functionName = expression(node.base)
const args = const args =
node.type === 'CallExpression' node.type === 'CallExpression'

@ -1,7 +1,8 @@
import { LuaError } from './LuaError' import { LuaError } from './LuaError'
import { Table } from './Table' import { Table } from './Table'
import { Thread } from './Thread'
type LuaType = undefined | boolean | number | string | Function | Table // thread | userdata type LuaType = undefined | boolean | number | string | Function | Table | Thread // userdata
interface Config { interface Config {
LUA_PATH?: string LUA_PATH?: string
@ -18,7 +19,7 @@ const FLOATING_POINT_PATTERN = /^[-+]?[0-9]*\.?([0-9]+([eE][-+]?[0-9]+)?)?$/
/** Pattern to identify a hex string value that can validly be converted to a number in Lua */ /** Pattern to identify a hex string value that can validly be converted to a number in Lua */
const HEXIDECIMAL_CONSTANT_PATTERN = /^(-)?0x([0-9a-fA-F]*)\.?([0-9a-fA-F]*)$/ const HEXIDECIMAL_CONSTANT_PATTERN = /^(-)?0x([0-9a-fA-F]*)\.?([0-9a-fA-F]*)$/
function type(v: LuaType): 'string' | 'number' | 'boolean' | 'function' | 'nil' | 'table' { function type(v: LuaType): 'string' | 'number' | 'boolean' | 'function' | 'nil' | 'table' | 'thread' {
const t = typeof v const t = typeof v
switch (t) { switch (t) {
@ -34,6 +35,7 @@ function type(v: LuaType): 'string' | 'number' | 'boolean' | 'function' | 'nil'
case 'object': case 'object':
if (v instanceof Table) return 'table' if (v instanceof Table) return 'table'
if (v instanceof Function) return 'function' if (v instanceof Function) return 'function'
if (v instanceof Thread) return 'thread'
} }
} }
@ -49,6 +51,10 @@ function tostring(v: LuaType): string {
return valToStr(v, 'function: 0x') return valToStr(v, 'function: 0x')
} }
if (v instanceof Thread) {
return valToStr(v, 'thread: 0x')
}
return coerceToString(v) return coerceToString(v)
function valToStr(v: LuaType, prefix: string): string { function valToStr(v: LuaType, prefix: string): string {
@ -193,6 +199,14 @@ function coerceArgToFunction(value: LuaType, funcName: string, index: number): F
} }
} }
function coerceArgToThread(value: LuaType, funcName: string, index: number): Thread {
if (value instanceof Thread) {
return value
}
const typ = type(value)
throw new LuaError(`bad argument #${index} to '${funcName}' (thread expected, got ${typ})`)
}
const ensureArray = <T>(value: T | T[]): T[] => (value instanceof Array ? value : [value]) const ensureArray = <T>(value: T | T[]): T[] => (value instanceof Array ? value : [value])
const hasOwnProperty = (obj: Record<string, unknown> | unknown[], key: string | number): boolean => const hasOwnProperty = (obj: Record<string, unknown> | unknown[], key: string | number): boolean =>
@ -211,6 +225,7 @@ export {
coerceArgToString, coerceArgToString,
coerceArgToTable, coerceArgToTable,
coerceArgToFunction, coerceArgToFunction,
coerceArgToThread,
ensureArray, ensureArray,
hasOwnProperty hasOwnProperty
} }

@ -54,4 +54,56 @@ let exitCode = 0
} }
} }
{
const luaEnv = luainjs.createEnv()
const script = luaEnv.parse(`
local co = coroutine.create(function(a)
local x, y = coroutine.yield(a + 1, a + 2)
return x + y
end)
local r1 = {coroutine.resume(co, 3)}
if r1[1] ~= true or r1[2] ~= 4 or r1[3] ~= 5 then return 'fail1' end
local r2 = {coroutine.resume(co, 5, 6)}
if r2[1] ~= true or r2[2] ~= 11 then return 'fail2' end
return 'ok'
`)
if (script.exec() !== 'ok') throw Error('coroutine resume failed')
}
{
const luaEnv = luainjs.createEnv()
const script = luaEnv.parse(`
local f = coroutine.wrap(function(a)
local b = coroutine.yield(a + 1)
return a + b
end)
local r1 = {f(3)}
if r1[1] ~= 4 then return 'fail1' end
local r2 = {f(5)}
if r2[1] ~= 8 then return 'fail2' end
return 'ok'
`)
if (script.exec() !== 'ok') throw Error('coroutine wrap failed')
}
{
const luaEnv = luainjs.createEnv()
const script = luaEnv.parse(`
local main, isMain = coroutine.running()
if not isMain then return 'fail1' end
local co
co = coroutine.create(function()
if coroutine.status(co) ~= 'running' then return 'fail2' end
local t, m = coroutine.running()
if t ~= co or m then return 'fail3' end
end)
if coroutine.status(co) ~= 'suspended' then return 'fail4' end
local ok = coroutine.resume(co)
if not ok then return 'fail5' end
if coroutine.status(co) ~= 'dead' then return 'fail6' end
return 'ok'
`)
if (script.exec() !== 'ok') throw Error('coroutine running/status failed')
}
process.exit(exitCode) process.exit(exitCode)

Loading…
Cancel
Save