diff --git a/README.md b/README.md index fd2a3e4..4b78d01 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,18 @@ console.log(helloStr) Check out the [math lib](./src/lib/math.ts) for a more extensive example. +### Extend a library + +Use `extendLib` to add new functions to an existing library without overwriting it. + +```js +const extraMath = new luainjs.Table({ tau: Math.PI * 2 }) +luaEnv.extendLib('math', extraMath) + +const tau = luaEnv.parse('return math.tau').exec() +console.log(tau) +``` + ## Example Check out the [test runner](./tests/test.js) for a concrete example. diff --git a/src/index.ts b/src/index.ts index 4d7b72f..5556d0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ import { libString, metatable as stringMetatable } from './lib/string' import { getLibOS } from './lib/os' import { getLibPackage } from './lib/package' import { libCoroutine } from './lib/coroutine' -import { LuaType, ensureArray, Config } from './utils' +import { LuaType, ensureArray, Config, hasOwnProperty } from './utils' import { Thread } from './Thread' import { parse as parseScript } from './parser' @@ -72,6 +72,7 @@ function createEnv( parse: (script: string) => Script parseFile: (path: string) => Script loadLib: (name: string, value: Table) => void + extendLib: (name: string, value: Table) => void } { const cfg: Config = { LUA_PATH: './?.lua', @@ -93,6 +94,24 @@ function createEnv( loaded.rawset(name, value) } + const extendLib = (name: string, value: Table): void => { + const lib = _G.rawget(name) + if (lib instanceof Table) { + for (let i = 1; i < value.numValues.length; i++) { + if (hasOwnProperty(value.numValues, i)) lib.rawset(i, value.numValues[i]) + } + for (const key in value.strValues) { + if (hasOwnProperty(value.strValues, key)) lib.rawset(key, value.strValues[key]) + } + for (let i = 0; i < value.keys.length; i++) { + lib.rawset(value.keys[i], value.values[i]) + } + return + } + + loadLib(name, value) + } + loadLib('_G', _G) loadLib('package', libPackage) loadLib('math', libMath) @@ -122,7 +141,8 @@ function createEnv( return { parse, parseFile, - loadLib + loadLib, + extendLib } } diff --git a/src/lib/string.ts b/src/lib/string.ts index d504ac1..3107dc6 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -1,7 +1,15 @@ import { sprintf } from 'printj' import { Table } from '../Table' import { LuaError } from '../LuaError' -import { tostring, posrelat, coerceArgToNumber, coerceArgToString, hasOwnProperty, LuaType } from '../utils' +import { + coerceArgToBoolean, + coerceArgToNumber, + coerceArgToString, + hasOwnProperty, + LuaType, + posrelat, + tostring +} from '../utils' const ROSETTA_STONE: Record = { '([^a-zA-Z0-9%(])-': '$1*?', @@ -117,7 +125,7 @@ function find(s: LuaType, pattern: LuaType, init: LuaType, plain: LuaType): (num const S = coerceArgToString(s, 'find', 1) const P = coerceArgToString(pattern, 'find', 2) const INIT = init === undefined ? 1 : coerceArgToNumber(init, 'find', 3) - const PLAIN = plain === undefined ? false : coerceArgToNumber(plain, 'find', 4) + const PLAIN = plain === undefined ? false : coerceArgToBoolean(plain, 'find', 4) // Regex if (!PLAIN) { diff --git a/src/utils.ts b/src/utils.ts index ee2a8e6..21ab97a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -166,7 +166,7 @@ function coerceToString(val: LuaType, errorMessage?: string): string { function coerceArg( value: LuaType, coerceFunc: (val: LuaType, errorMessage?: string) => T, - typ: 'number' | 'string', + typ: 'number' | 'string' | 'boolean', funcName: string, index: number ): T { @@ -181,6 +181,10 @@ function coerceArgToString(value: LuaType, funcName: string, index: number): str return coerceArg(value, coerceToString, 'string', funcName, index) } +function coerceArgToBoolean(value: LuaType, funcName: string, index: number): boolean { + return coerceArg(value, coerceToBoolean, 'boolean', funcName, index) +} + function coerceArgToTable(value: LuaType, funcName: string, index: number): Table { if (value instanceof Table) { return value @@ -221,10 +225,11 @@ export { coerceToBoolean, coerceToNumber, coerceToString, + coerceArgToBoolean, + coerceArgToFunction, coerceArgToNumber, coerceArgToString, coerceArgToTable, - coerceArgToFunction, coerceArgToThread, ensureArray, hasOwnProperty diff --git a/tests/starlight/lib-string.lua b/tests/starlight/lib-string.lua index 59f9749..a89e55f 100644 --- a/tests/starlight/lib-string.lua +++ b/tests/starlight/lib-string.lua @@ -101,6 +101,13 @@ b = string.find(a, 'The .* fox') assertTrue (b == 1, 'The dot pattern should match across lines in string.find()') +local ok, plainFind = pcall(function () return string.find('abc', 'a.c', 1, true) end) +assertTrue (ok and plainFind == nil, 'string.find() should accept a boolean plain argument and disable pattern matching') + +local literalFind = string.find('a.c', 'a.c', 1, true) +assertTrue (literalFind == 1, 'string.find() should find literal matches when plain is true') + + -- format diff --git a/tests/test.js b/tests/test.js index bc27821..f56f424 100644 --- a/tests/test.js +++ b/tests/test.js @@ -41,6 +41,16 @@ let exitCode = 0 } } +{ + const luaEnv = luainjs.createEnv() + const ext = new luainjs.Table({ foo: () => 'bar' }) + luaEnv.extendLib('math', ext) + const val = luaEnv.parse('return math.foo()').exec() + if (val !== 'bar') { + throw Error('extendLib failed!') + } +} + { const luaEnv = luainjs.createEnv() let str