fix lint errors

pull/32/head
Piotr Wilczynski 3 months ago
parent f5619ce591
commit 256d5db51a

3
.gitignore vendored

@ -1,3 +1,4 @@
node_modules node_modules
dist dist
package-lock.json package-lock.json
.idea

@ -1,8 +1,6 @@
import { LuaError } from './LuaError' import { LuaError } from './LuaError'
import { LuaType } from './utils' import { LuaType } from './utils'
export type ThreadStatus = 'running' | 'suspended' | 'dead'
type Gen = Generator<LuaType[], LuaType[], LuaType[]> type Gen = Generator<LuaType[], LuaType[], LuaType[]>
type GenFn = (...args: LuaType[]) => Gen type GenFn = (...args: LuaType[]) => Gen
@ -46,9 +44,10 @@ class Thread {
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
const mainThread = new Thread((function* () {}) as unknown as GenFn) const mainThread = new Thread((function*() {} as unknown) as GenFn)
mainThread.status = 'running' mainThread.status = 'running'
Thread.main = mainThread Thread.main = mainThread
Thread.current = mainThread Thread.current = mainThread
export { Thread } export { Thread }
export type ThreadStatus = 'running' | 'suspended' | 'dead'

@ -7,36 +7,36 @@ import { Table } from './Table'
import { LuaError } from './LuaError' import { LuaError } from './LuaError'
import { libMath } from './lib/math' import { libMath } from './lib/math'
import { libTable } from './lib/table' 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 { libCoroutine } from './lib/coroutine'
import { LuaType, ensureArray, Config } from './utils' import { LuaType, ensureArray, Config } from './utils'
import { Thread } from './Thread' 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 | Thread, ...args: LuaType[]): LuaType[] => { const call = (f: Function | Table | Thread, ...args: LuaType[]): LuaType[] => {
if (f instanceof Thread) return f.resume(...args) if (f instanceof Thread) return f.resume(...args)
if (f instanceof Function) { if (f instanceof Function) {
const res = f(...args) const res = f(...args)
if (res && typeof res.next === 'function') { if (res && typeof res.next === 'function') {
let r = res.next() let r = res.next()
while (!r.done) r = res.next() while (!r.done) r = res.next()
return ensureArray(r.value) return ensureArray(r.value)
} }
return ensureArray(res as LuaType) 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))
throw new LuaError(`attempt to call an uncallable type`) throw new LuaError(`attempt to call an uncallable type`)
} }
const stringTable = new Table() const stringTable = new Table()
stringTable.metatable = stringMetatable stringTable.metatable = stringMetatable
@ -49,22 +49,22 @@ 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(`return ${chunk}`)() as (lua: unknown) => Generator<LuaType[]> 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 iterator = exec({ const iterator = exec({
globalScope, globalScope,
...operators, ...operators,
Table, Table,
call, call,
get get
}) })
let res = iterator.next() let res = iterator.next()
while (!res.done) { while (!res.done) {
res = iterator.next() res = iterator.next()
} }
return res.value === undefined ? [undefined] : res.value return res.value === undefined ? [undefined] : res.value
} }
function createEnv( function createEnv(
config: Config = {} config: Config = {}
@ -97,9 +97,9 @@ function createEnv(
loadLib('package', libPackage) loadLib('package', libPackage)
loadLib('math', libMath) loadLib('math', libMath)
loadLib('table', libTable) loadLib('table', libTable)
loadLib('string', libString) loadLib('string', libString)
loadLib('os', getLibOS(cfg)) loadLib('os', getLibOS(cfg))
loadLib('coroutine', libCoroutine) loadLib('coroutine', libCoroutine)
_G.rawset('require', _require) _G.rawset('require', _require)

@ -1,407 +1,408 @@
import { parse } from '../parser' import { parse } from '../parser'
import { Table } from '../Table' import { Table } from '../Table'
import { LuaError } from '../LuaError' import { LuaError } from '../LuaError'
import { import {
LuaType, LuaType,
Config, Config,
type, type,
tostring, tostring,
posrelat, posrelat,
coerceToNumber, coerceToNumber,
coerceToString, coerceToString,
coerceToBoolean, coerceToBoolean,
coerceArgToNumber, coerceArgToNumber,
coerceArgToString, coerceArgToString,
coerceArgToTable, coerceArgToTable,
hasOwnProperty hasOwnProperty
} from '../utils' } from '../utils'
import { metatable as stringMetatable } from './string' import { metatable as stringMetatable } from './string'
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
function ipairsIterator(table: Table, index: number): LuaType[] { function ipairsIterator(table: Table, index: number): LuaType[] {
if (index === undefined) { if (index === undefined) {
throw new LuaError('Bad argument #2 to ipairs() iterator') throw new LuaError('Bad argument #2 to ipairs() iterator')
} }
const nextIndex = index + 1 const nextIndex = index + 1
const numValues = table.numValues const numValues = table.numValues
if (!numValues[nextIndex] || numValues[nextIndex] === undefined) return undefined if (!numValues[nextIndex] || numValues[nextIndex] === undefined) return undefined
return [nextIndex, numValues[nextIndex]] return [nextIndex, numValues[nextIndex]]
} }
const _VERSION = 'Lua 5.3' const _VERSION = 'Lua 5.3'
function assert(v: LuaType, m?: LuaType): [unknown, unknown] { function assert(v: LuaType, m?: LuaType): [unknown, unknown] {
if (coerceToBoolean(v)) return [v, m] if (coerceToBoolean(v)) return [v, m]
const msg = m === undefined ? 'Assertion failed!' : coerceArgToString(m, 'assert', 2) const msg = m === undefined ? 'Assertion failed!' : coerceArgToString(m, 'assert', 2)
throw new LuaError(msg) throw new LuaError(msg)
} }
function collectgarbage(): [] { function collectgarbage(): [] {
// noop // noop
return [] return []
} }
function error(message: LuaType): void { function error(message: LuaType): void {
const msg = coerceArgToString(message, 'error', 1) const msg = coerceArgToString(message, 'error', 1)
throw new LuaError(msg) throw new LuaError(msg)
} }
/** /**
* If object does not have a metatable, returns nil. * If object does not have a metatable, returns nil.
* Otherwise, if the object's metatable has a __metatable field, returns the associated value. * Otherwise, if the object's metatable has a __metatable field, returns the associated value.
* Otherwise, returns the metatable of the given object. * Otherwise, returns the metatable of the given object.
*/ */
function getmetatable(table: LuaType): Table { function getmetatable(table: LuaType): Table {
if (table instanceof Table && table.metatable) { if (table instanceof Table && table.metatable) {
const mm = table.metatable.rawget('__metatable') as Table const mm = table.metatable.rawget('__metatable') as Table
return mm ? mm : table.metatable return mm ? mm : table.metatable
} }
if (typeof table === 'string') { if (typeof table === 'string') {
return stringMetatable return stringMetatable
} }
} }
/** /**
* Returns three values (an iterator function, the table t, and 0) so that the construction * Returns three values (an iterator function, the table t, and 0) so that the construction
* *
* `for i,v in ipairs(t) do body end` * `for i,v in ipairs(t) do body end`
* *
* will iterate over the keyvalue pairs (1,t[1]), (2,t[2]), ..., up to the first nil value. * will iterate over the keyvalue pairs (1,t[1]), (2,t[2]), ..., up to the first nil value.
*/ */
function ipairs(t: LuaType): [Function, Table, number] { function ipairs(t: LuaType): [Function, Table, number] {
const table = coerceArgToTable(t, 'ipairs', 1) const table = coerceArgToTable(t, 'ipairs', 1)
const mm = table.getMetaMethod('__pairs') || table.getMetaMethod('__ipairs') const mm = table.getMetaMethod('__pairs') || table.getMetaMethod('__ipairs')
return mm ? mm(table).slice(0, 3) : [ipairsIterator, table, 0] return mm ? mm(table).slice(0, 3) : [ipairsIterator, table, 0]
} }
/** /**
* Allows a program to traverse all fields of a table. * Allows a program to traverse all fields of a table.
* Its first argument is a table and its second argument is an index in this table. * Its first argument is a table and its second argument is an index in this table.
* next returns the next index of the table and its associated value. * next returns the next index of the table and its associated value.
* When called with nil as its second argument, next returns an initial index and its associated value. * When called with nil as its second argument, next returns an initial index and its associated value.
* When called with the last index, or with nil in an empty table, next returns nil. * When called with the last index, or with nil in an empty table, next returns nil.
* If the second argument is absent, then it is interpreted as nil. * If the second argument is absent, then it is interpreted as nil.
* In particular, you can use next(t) to check whether a table is empty. * In particular, you can use next(t) to check whether a table is empty.
* *
* The order in which the indices are enumerated is not specified, even for numeric indices. * The order in which the indices are enumerated is not specified, even for numeric indices.
* (To traverse a table in numerical order, use a numerical for.) * (To traverse a table in numerical order, use a numerical for.)
* *
* The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table. * The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table.
* You may however modify existing fields. In particular, you may clear existing fields. * You may however modify existing fields. In particular, you may clear existing fields.
*/ */
function next(table: LuaType, index?: LuaType): [number | string, LuaType] { function next(table: LuaType, index?: LuaType): [number | string, LuaType] {
const TABLE = coerceArgToTable(table, 'next', 1) const TABLE = coerceArgToTable(table, 'next', 1)
// SLOOOOOOOW... // SLOOOOOOOW...
let found = index === undefined let found = index === undefined
if (found || (typeof index === 'number' && index > 0)) { if (found || (typeof index === 'number' && index > 0)) {
const numValues = TABLE.numValues const numValues = TABLE.numValues
const keys = Object.keys(numValues) const keys = Object.keys(numValues)
let i = 1 let i = 1
if (!found) { if (!found) {
const I = keys.indexOf(`${index}`) const I = keys.indexOf(`${index}`)
if (I >= 0) { if (I >= 0) {
found = true found = true
i += I i += I
} }
} }
if (found) { if (found) {
for (i; keys[i] !== undefined; i++) { for (i; keys[i] !== undefined; i++) {
const key = Number(keys[i]) const key = Number(keys[i])
const value = numValues[key] const value = numValues[key]
if (value !== undefined) return [key, value] if (value !== undefined) return [key, value]
} }
} }
} }
for (const i in TABLE.strValues) { for (const i in TABLE.strValues) {
if (hasOwnProperty(TABLE.strValues, i)) { if (hasOwnProperty(TABLE.strValues, i)) {
if (!found) { if (!found) {
if (i === index) found = true if (i === index) found = true
} else if (TABLE.strValues[i] !== undefined) { } else if (TABLE.strValues[i] !== undefined) {
return [i, TABLE.strValues[i]] return [i, TABLE.strValues[i]]
} }
} }
} }
for (const i in TABLE.keys) { for (const i in TABLE.keys) {
if (hasOwnProperty(TABLE.keys, i)) { if (hasOwnProperty(TABLE.keys, i)) {
const key = TABLE.keys[i] const key = TABLE.keys[i]
if (!found) { if (!found) {
if (key === index) found = true if (key === index) found = true
} else if (TABLE.values[i] !== undefined) { } else if (TABLE.values[i] !== undefined) {
return [key, TABLE.values[i]] return [key, TABLE.values[i]]
} }
} }
} }
} }
/** /**
* If t has a metamethod __pairs, calls it with t as argument and returns the first three results from the call. * If t has a metamethod __pairs, calls it with t as argument and returns the first three results from the call.
* *
* Otherwise, returns three values: the next function, the table t, and nil, so that the construction * Otherwise, returns three values: the next function, the table t, and nil, so that the construction
* *
* `for k,v in pairs(t) do body end` * `for k,v in pairs(t) do body end`
* *
* will iterate over all keyvalue pairs of table t. * will iterate over all keyvalue pairs of table t.
* *
* See function next for the caveats of modifying the table during its traversal. * See function next for the caveats of modifying the table during its traversal.
*/ */
function pairs(t: LuaType): [Function, Table, undefined] { function pairs(t: LuaType): [Function, Table, undefined] {
const table = coerceArgToTable(t, 'pairs', 1) const table = coerceArgToTable(t, 'pairs', 1)
const mm = table.getMetaMethod('__pairs') const mm = table.getMetaMethod('__pairs')
return mm ? mm(table).slice(0, 3) : [next, table, undefined] return mm ? mm(table).slice(0, 3) : [next, table, undefined]
} }
/** /**
* Calls function f with the given arguments in protected mode. * Calls function f with the given arguments in protected mode.
* This means that any error inside f is not propagated; * This means that any error inside f is not propagated;
* instead, pcall catches the error and returns a status code. * instead, pcall catches the error and returns a status code.
* Its first result is the status code (a boolean), which is true if the call succeeds without errors. * Its first result is the status code (a boolean), which is true if the call succeeds without errors.
* In such case, pcall also returns all results from the call, after this first result. * In such case, pcall also returns all results from the call, after this first result.
* In case of any error, pcall returns false plus the error message. * In case of any error, pcall returns false plus the error message.
*/ */
function pcall(f: LuaType, ...args: LuaType[]): [false, string] | [true, ...LuaType[]] { function pcall(f: LuaType, ...args: LuaType[]): [false, string] | [true, ...LuaType[]] {
if (typeof f !== 'function') { if (typeof f !== 'function') {
throw new LuaError('Attempt to call non-function') throw new LuaError('Attempt to call non-function')
} }
try { try {
return [true, ...f(...args)] return [true, ...f(...args)]
} catch (e) { } catch (e) {
return [false, e && e.toString()] return [false, e && e.toString()]
} }
} }
/** /**
* Checks whether v1 is equal to v2, without invoking the __eq metamethod. Returns a boolean. * Checks whether v1 is equal to v2, without invoking the __eq metamethod. Returns a boolean.
*/ */
function rawequal(v1: LuaType, v2: LuaType): boolean { function rawequal(v1: LuaType, v2: LuaType): boolean {
return v1 === v2 return v1 === v2
} }
/** /**
* Gets the real value of table[index], without invoking the __index metamethod. * Gets the real value of table[index], without invoking the __index metamethod.
* table must be a table; index may be any value. * table must be a table; index may be any value.
*/ */
function rawget(table: LuaType, index: LuaType): LuaType { function rawget(table: LuaType, index: LuaType): LuaType {
const TABLE = coerceArgToTable(table, 'rawget', 1) const TABLE = coerceArgToTable(table, 'rawget', 1)
return TABLE.rawget(index) return TABLE.rawget(index)
} }
/** /**
* Returns the length of the object v, which must be a table or a string, without invoking the __len metamethod. * Returns the length of the object v, which must be a table or a string, without invoking the __len metamethod.
* Returns an integer. * Returns an integer.
*/ */
function rawlen(v: LuaType): number { function rawlen(v: LuaType): number {
if (v instanceof Table) return v.getn() if (v instanceof Table) return v.getn()
if (typeof v === 'string') return v.length if (typeof v === 'string') return v.length
throw new LuaError('attempt to get length of an unsupported value') throw new LuaError('attempt to get length of an unsupported value')
} }
/** /**
* Sets the real value of table[index] to value, without invoking the __newindex metamethod. * Sets the real value of table[index] to value, without invoking the __newindex metamethod.
* table must be a table, index any value different from nil and NaN, and value any Lua value. * table must be a table, index any value different from nil and NaN, and value any Lua value.
* *
* This function returns table. * This function returns table.
*/ */
function rawset(table: LuaType, index: LuaType, value: LuaType): Table { function rawset(table: LuaType, index: LuaType, value: LuaType): Table {
const TABLE = coerceArgToTable(table, 'rawset', 1) const TABLE = coerceArgToTable(table, 'rawset', 1)
if (index === undefined) throw new LuaError('table index is nil') if (index === undefined) throw new LuaError('table index is nil')
TABLE.rawset(index, value) TABLE.rawset(index, value)
return TABLE return TABLE
} }
/** /**
* If index is a number, returns all arguments after argument number index; * If index is a number, returns all arguments after argument number index;
* a negative number indexes from the end (-1 is the last argument). * a negative number indexes from the end (-1 is the last argument).
* Otherwise, index must be the string "#", and select returns the total number of extra arguments it received. * Otherwise, index must be the string "#", and select returns the total number of extra arguments it received.
*/ */
function select(index: number | '#', ...args: LuaType[]): LuaType[] | number { function select(index: number | '#', ...args: LuaType[]): LuaType[] | number {
if (index === '#') { if (index === '#') {
return args.length return args.length
} }
if (typeof index === 'number') { if (typeof index === 'number') {
const pos = posrelat(Math.trunc(index), args.length) const pos = posrelat(Math.trunc(index), args.length)
return args.slice(pos - 1) return args.slice(pos - 1)
} }
throw new LuaError(`bad argument #1 to 'select' (number expected, got ${type(index)})`) throw new LuaError(`bad argument #1 to 'select' (number expected, got ${type(index)})`)
} }
/** /**
* Sets the metatable for the given table. * Sets the metatable for the given table.
* (To change the metatable of other types from Lua code,you must use the debug library (§6.10).) * (To change the metatable of other types from Lua code,you must use the debug library (§6.10).)
* If metatable is nil, removes the metatable of the given table. * If metatable is nil, removes the metatable of the given table.
* If the original metatable has a __metatable field, raises an error. * If the original metatable has a __metatable field, raises an error.
* *
* This function returns table. * This function returns table.
*/ */
function setmetatable(table: LuaType, metatable: LuaType): Table { function setmetatable(table: LuaType, metatable: LuaType): Table {
const TABLE = coerceArgToTable(table, 'setmetatable', 1) const TABLE = coerceArgToTable(table, 'setmetatable', 1)
if (TABLE.metatable && TABLE.metatable.rawget('__metatable')) { if (TABLE.metatable && TABLE.metatable.rawget('__metatable')) {
throw new LuaError('cannot change a protected metatable') throw new LuaError('cannot change a protected metatable')
} }
TABLE.metatable = metatable === null || metatable === undefined ? null : coerceArgToTable(metatable, 'setmetatable', 2) TABLE.metatable =
return TABLE metatable === null || metatable === undefined ? null : coerceArgToTable(metatable, 'setmetatable', 2)
} return TABLE
}
/**
* When called with no base, tonumber tries to convert its argument to a number. /**
* If the argument is already a number or a string convertible to a number, * When called with no base, tonumber tries to convert its argument to a number.
* then tonumber returns this number; otherwise, it returns nil. * If the argument is already a number or a string convertible to a number,
* * then tonumber returns this number; otherwise, it returns nil.
* The conversion of strings can result in integers or floats, *
* according to the lexical conventions of Lua (see §3.1). * The conversion of strings can result in integers or floats,
* (The string may have leading and trailing spaces and a sign.) * according to the lexical conventions of Lua (see §3.1).
* * (The string may have leading and trailing spaces and a sign.)
* When called with base, then e must be a string to be interpreted as an integer numeral in that base. *
* The base may be any integer between 2 and 36, inclusive. * When called with base, then e must be a string to be interpreted as an integer numeral in that base.
* In bases above 10, the letter 'A' (in either upper or lower case) represents 10, * The base may be any integer between 2 and 36, inclusive.
* 'B' represents 11, and so forth, with 'Z' representing 35. * In bases above 10, the letter 'A' (in either upper or lower case) represents 10,
* If the string e is not a valid numeral in the given base, the function returns nil. * 'B' represents 11, and so forth, with 'Z' representing 35.
*/ * If the string e is not a valid numeral in the given base, the function returns nil.
function tonumber(e: LuaType, base: LuaType): number { */
const E = coerceToString(e).trim() function tonumber(e: LuaType, base: LuaType): number {
const BASE = base === undefined ? 10 : coerceArgToNumber(base, 'tonumber', 2) const E = coerceToString(e).trim()
const BASE = base === undefined ? 10 : coerceArgToNumber(base, 'tonumber', 2)
if (BASE !== 10 && E === 'nil') {
throw new LuaError("bad argument #1 to 'tonumber' (string expected, got nil)") if (BASE !== 10 && E === 'nil') {
} throw new LuaError("bad argument #1 to 'tonumber' (string expected, got nil)")
}
if (BASE < 2 || BASE > 36) {
throw new LuaError(`bad argument #2 to 'tonumber' (base out of range)`) if (BASE < 2 || BASE > 36) {
} throw new LuaError(`bad argument #2 to 'tonumber' (base out of range)`)
}
if (E === '') return
if (BASE === 10) return coerceToNumber(E) if (E === '') return
if (BASE === 10) return coerceToNumber(E)
const pattern = new RegExp(`^${BASE === 16 ? '(0x)?' : ''}[${CHARS.substr(0, BASE)}]*$`, 'gi')
const pattern = new RegExp(`^${BASE === 16 ? '(0x)?' : ''}[${CHARS.substr(0, BASE)}]*$`, 'gi')
if (!pattern.test(E)) return // Invalid
return parseInt(E, BASE) if (!pattern.test(E)) return // Invalid
} return parseInt(E, BASE)
}
/**
* This function is similar to pcall, except that it sets a new message handler msgh. /**
*/ * This function is similar to pcall, except that it sets a new message handler msgh.
function xpcall(f: LuaType, msgh: LuaType, ...args: LuaType[]): [false, string] | [true, ...LuaType[]] { */
if (typeof f !== 'function' || typeof msgh !== 'function') { function xpcall(f: LuaType, msgh: LuaType, ...args: LuaType[]): [false, string] | [true, ...LuaType[]] {
throw new LuaError('Attempt to call non-function') if (typeof f !== 'function' || typeof msgh !== 'function') {
} throw new LuaError('Attempt to call non-function')
}
try {
return [true, ...f(...args)] try {
} catch (e) { return [true, ...f(...args)]
return [false, msgh(e)[0]] } catch (e) {
} return [false, msgh(e)[0]]
} }
}
function createG(cfg: Config, execChunk: (_G: Table, chunk: string) => LuaType[]): Table {
function print(...args: LuaType[]): void { function createG(cfg: Config, execChunk: (_G: Table, chunk: string) => LuaType[]): Table {
const output = args.map(arg => tostring(arg)).join('\t') function print(...args: LuaType[]): void {
cfg.stdout(output) const output = args.map(arg => tostring(arg)).join('\t')
} cfg.stdout(output)
}
function load(
chunk: LuaType, function load(
_chunkname?: string, chunk: LuaType,
_mode?: 'b' | 't' | 'bt', _chunkname?: string,
env?: Table _mode?: 'b' | 't' | 'bt',
): [undefined, string] | (() => LuaType[]) { env?: Table
let C = '' ): [undefined, string] | (() => LuaType[]) {
if (chunk instanceof Function) { let C = ''
let ret = ' ' if (chunk instanceof Function) {
while (ret !== '' && ret !== undefined) { let ret = ' '
C += ret while (ret !== '' && ret !== undefined) {
ret = chunk()[0] C += ret
} ret = chunk()[0]
} else { }
C = coerceArgToString(chunk, 'load', 1) } else {
} C = coerceArgToString(chunk, 'load', 1)
}
let parsed: string
try { let parsed: string
parsed = parse(C) try {
} catch (e) { parsed = parse(C)
return [undefined, e.message] } catch (e) {
} return [undefined, e.message]
}
return () => execChunk(env || _G, parsed)
} return () => execChunk(env || _G, parsed)
}
function dofile(filename?: LuaType): LuaType[] {
const res = loadfile(filename) function dofile(filename?: LuaType): LuaType[] {
const res = loadfile(filename)
if (Array.isArray(res) && res[0] === undefined) {
throw new LuaError(res[1]) if (Array.isArray(res) && res[0] === undefined) {
} throw new LuaError(res[1])
}
const exec = res as () => LuaType[]
return exec() const exec = res as () => LuaType[]
} return exec()
}
function loadfile(
filename?: LuaType, function loadfile(
mode?: 'b' | 't' | 'bt', filename?: LuaType,
env?: Table mode?: 'b' | 't' | 'bt',
): [undefined, string] | (() => LuaType[]) { env?: Table
const FILENAME = filename === undefined ? cfg.stdin : coerceArgToString(filename, 'loadfile', 1) ): [undefined, string] | (() => LuaType[]) {
const FILENAME = filename === undefined ? cfg.stdin : coerceArgToString(filename, 'loadfile', 1)
if (!cfg.fileExists) {
throw new LuaError('loadfile requires the config.fileExists function') if (!cfg.fileExists) {
} throw new LuaError('loadfile requires the config.fileExists function')
}
if (!cfg.fileExists(FILENAME)) return [undefined, 'file not found']
if (!cfg.fileExists(FILENAME)) return [undefined, 'file not found']
if (!cfg.loadFile) {
throw new LuaError('loadfile requires the config.loadFile function') if (!cfg.loadFile) {
} throw new LuaError('loadfile requires the config.loadFile function')
}
return load(cfg.loadFile(FILENAME), FILENAME, mode, env)
} return load(cfg.loadFile(FILENAME), FILENAME, mode, env)
}
const _G = new Table({
_VERSION, const _G = new Table({
assert, _VERSION,
dofile, assert,
collectgarbage, dofile,
error, collectgarbage,
getmetatable, error,
ipairs, getmetatable,
load, ipairs,
loadfile, load,
next, loadfile,
pairs, next,
pcall, pairs,
print, pcall,
rawequal, print,
rawget, rawequal,
rawlen, rawget,
rawset, rawlen,
select, rawset,
setmetatable, select,
tonumber, setmetatable,
tostring, tonumber,
type, tostring,
xpcall type,
}) xpcall
})
return _G
} return _G
}
export { tostring, createG }
export { tostring, createG }

@ -38,13 +38,13 @@ const bool = (value: LuaType): boolean => coerceToBoolean(value)
// logical // logical
const and = (l: () => LuaType, r: () => LuaType): LuaType => { const and = (l: () => LuaType, r: () => LuaType): LuaType => {
const lv = l() const lv = l()
return coerceToBoolean(lv) ? r() : lv return coerceToBoolean(lv) ? r() : lv
} }
const or = (l: () => LuaType, r: () => LuaType): LuaType => { const or = (l: () => LuaType, r: () => LuaType): LuaType => {
const lv = l() const lv = l()
return coerceToBoolean(lv) ? lv : r() return coerceToBoolean(lv) ? lv : r()
} }
// unary // unary
@ -191,7 +191,7 @@ const operators = {
lt, lt,
le, le,
gt, gt,
ge, ge
} }
export { operators } export { operators }

@ -149,15 +149,15 @@ const generate = (node: luaparse.Node): string | MemExpr => {
return generate(node.expression) return generate(node.expression)
} }
case 'FunctionDeclaration': { case 'FunctionDeclaration': {
const getFuncDef = (params: string[]): string => { const getFuncDef = (params: string[]): string => {
const paramStr = params.join(';\n') const paramStr = params.join(';\n')
const body = parseBody(node, paramStr) const body = parseBody(node, paramStr)
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 `function* (${argsStr}) {\n${body}${returnStr}\n}` return `function* (${argsStr}) {\n${body}${returnStr}\n}`
} }
const params = node.parameters.map(param => { const params = node.parameters.map(param => {
if (param.type === 'VarargLiteral') { if (param.type === 'VarargLiteral') {
@ -210,10 +210,10 @@ const generate = (node: luaparse.Node): string | MemExpr => {
return `for (let [iterator, table, next] = ${iterators}, res = __lua.call(iterator, table, next); res[0] !== undefined; res = __lua.call(iterator, table, res[0])) {\n${body}\n}` return `for (let [iterator, table, next] = ${iterators}, res = __lua.call(iterator, table, next); res[0] !== undefined; res = __lua.call(iterator, table, res[0])) {\n${body}\n}`
} }
case 'Chunk': { case 'Chunk': {
const body = parseBody(node) const body = parseBody(node)
return `function* (__lua) {\n'use strict'\nconst $0 = __lua.globalScope\nlet vars\nlet vals\nlet label\n\n${body}\n}` return `function* (__lua) {\n'use strict'\nconst $0 = __lua.globalScope\nlet vars\nlet vals\nlet label\n\n${body}\n}`
} }
case 'Identifier': { case 'Identifier': {
return `$${nodeToScope.get(node)}.get('${node.name}')` return `$${nodeToScope.get(node)}.get('${node.name}')`
@ -321,34 +321,34 @@ const generate = (node: luaparse.Node): string | MemExpr => {
return new MemExpr(base, index) return new MemExpr(base, index)
} }
case 'CallExpression': case 'CallExpression':
case 'TableCallExpression': case 'TableCallExpression':
case 'StringCallExpression': { case 'StringCallExpression': {
if ( if (
node.type === 'CallExpression' && node.type === 'CallExpression' &&
node.base.type === 'MemberExpression' && node.base.type === 'MemberExpression' &&
node.base.base.type === 'Identifier' && node.base.base.type === 'Identifier' &&
node.base.base.name === 'coroutine' && node.base.base.name === 'coroutine' &&
node.base.identifier.type === 'Identifier' && node.base.identifier.type === 'Identifier' &&
node.base.identifier.name === 'yield' && node.base.identifier.name === 'yield' &&
node.base.indexer === '.' node.base.indexer === '.'
) { ) {
const args = parseExpressions(node.arguments) const args = parseExpressions(node.arguments)
return `yield ${args}` return `yield ${args}`
} }
const functionName = expression(node.base) const functionName = expression(node.base)
const args = const args =
node.type === 'CallExpression' node.type === 'CallExpression'
? parseExpressionList(node.arguments).join(', ') ? parseExpressionList(node.arguments).join(', ')
: expression(node.type === 'TableCallExpression' ? node.arguments : node.argument) : expression(node.type === 'TableCallExpression' ? node.arguments : node.argument)
if (functionName instanceof MemExpr && node.base.type === 'MemberExpression' && node.base.indexer === ':') { if (functionName instanceof MemExpr && node.base.type === 'MemberExpression' && node.base.indexer === ':') {
return `__lua.call(${functionName}, ${functionName.base}, ${args})` return `__lua.call(${functionName}, ${functionName.base}, ${args})`
} }
return `__lua.call(${functionName}, ${args})` return `__lua.call(${functionName}, ${args})`
} }
default: default:
throw new Error(`No generator found for: ${node.type}`) throw new Error(`No generator found for: ${node.type}`)

@ -1,8 +1,8 @@
import { LuaError } from './LuaError' import { LuaError } from './LuaError'
import { Table } from './Table' import { Table } from './Table'
import { Thread } from './Thread' 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
@ -19,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' | 'thread' { function type(v: LuaType): 'string' | 'number' | 'boolean' | 'function' | 'nil' | 'table' | 'thread' {
const t = typeof v const t = typeof v
switch (t) { switch (t) {
@ -32,12 +32,12 @@ function type(v: LuaType): 'string' | 'number' | 'boolean' | 'function' | 'nil'
case 'function': case 'function':
return t return t
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' if (v instanceof Thread) return 'thread'
} }
} }
function tostring(v: LuaType): string { function tostring(v: LuaType): string {
if (v instanceof Table) { if (v instanceof Table) {
@ -47,13 +47,13 @@ function tostring(v: LuaType): string {
return valToStr(v, 'table: 0x') return valToStr(v, 'table: 0x')
} }
if (v instanceof Function) { if (v instanceof Function) {
return valToStr(v, 'function: 0x') return valToStr(v, 'function: 0x')
} }
if (v instanceof Thread) { if (v instanceof Thread) {
return valToStr(v, 'thread: 0x') return valToStr(v, 'thread: 0x')
} }
return coerceToString(v) return coerceToString(v)
@ -190,29 +190,29 @@ function coerceArgToTable(value: LuaType, funcName: string, index: number): Tabl
} }
} }
function coerceArgToFunction(value: LuaType, funcName: string, index: number): Function { function coerceArgToFunction(value: LuaType, funcName: string, index: number): Function {
if (value instanceof Function) { if (value instanceof Function) {
return value return value
} else { } else {
const typ = type(value) const typ = type(value)
throw new LuaError(`bad argument #${index} to '${funcName}' (function expected, got ${typ})`) throw new LuaError(`bad argument #${index} to '${funcName}' (function expected, got ${typ})`)
} }
} }
function coerceArgToThread(value: LuaType, funcName: string, index: number): Thread { function coerceArgToThread(value: LuaType, funcName: string, index: number): Thread {
if (value instanceof Thread) { if (value instanceof Thread) {
return value return value
} }
const typ = type(value) const typ = type(value)
throw new LuaError(`bad argument #${index} to '${funcName}' (thread expected, got ${typ})`) 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 =>
Object.prototype.hasOwnProperty.call(obj, key) Object.prototype.hasOwnProperty.call(obj, key)
export { export {
LuaType, LuaType,
Config, Config,
type, type,
@ -223,9 +223,9 @@ export {
coerceToString, coerceToString,
coerceArgToNumber, coerceArgToNumber,
coerceArgToString, coerceArgToString,
coerceArgToTable, coerceArgToTable,
coerceArgToFunction, coerceArgToFunction,
coerceArgToThread, coerceArgToThread,
ensureArray, ensureArray,
hasOwnProperty hasOwnProperty
} }

Loading…
Cancel
Save