forked from redo/BlockLua
		
	Merge branch 'master'
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								BlockLua.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								BlockLua.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										30
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								readme.md
									
									
									
									
									
								
							| @@ -36,6 +36,8 @@ Lua scripting for Blockland | ||||
| `object:method(args)` - Call a Torque object method   | ||||
| `object[index]` - Access a member of a Torque set or group   | ||||
| `for childIndex, child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque. | ||||
| `bl.isObject(object, objectID, or 'objectName')` - Check if an object exists   | ||||
| `object:exists()` - Check if an object exists   | ||||
|  | ||||
| ### Timing/Schedules | ||||
| `sched = bl.schedule(timeMs, function, args...)` - Schedule a Lua function to be called later, similar to schedule in Torque   | ||||
| @@ -101,10 +103,11 @@ When reading from outside ZIPs, binary files are fully supported. | ||||
| `bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.   | ||||
| `bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.   | ||||
| `bl.class('className', 'parentClassName')` - Same as above, with inheritance   | ||||
| `bl.boolean(thing)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.   | ||||
| `bl.object(thing)` - Manually convert a Torque object reference (object ID or name) into a Lua object. | ||||
| `bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.   | ||||
| `bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object.   | ||||
| `bl.string(arg)` - Manually convert any automatically-converted Torque value back into a string. This is not as reliable as using `bl.type` to specify the type as a string beforehand.   | ||||
|  | ||||
| ### Vectors | ||||
| ### Vector | ||||
| `vec = vector{x,y,z}` - Create a vector. Can have any number of elements   | ||||
| `vec1 + vec2` - Add   | ||||
| `vec1 - vec2` - Subtract   | ||||
| @@ -129,6 +132,9 @@ When reading from outside ZIPs, binary files are fully supported. | ||||
| `vec1:distance(vec2)` - Distance between two points   | ||||
| `vec2 = vec:copy()` - Clone a vector so its elements can be modified without affecting the original. Usually not needed - the builtin vector functions never modify vectors in-place. | ||||
|  | ||||
| ### Matrix | ||||
| WIP | ||||
|  | ||||
| ### Extended Standard Lua Library | ||||
| `string[index]`   | ||||
| `string[{start,stop}]`   | ||||
| @@ -163,12 +169,7 @@ When reading from outside ZIPs, binary files are fully supported. | ||||
| ## Type Conversion | ||||
| When a TorqueScript function is called from Lua or vice-versa, the arguments and return value must be converted between the two languages' type systems.   | ||||
| TorqueScript stores no type information; all values in TorqueScript are strings. So it's necessary to make some inferences when converting values between the two languages.   | ||||
| ### From TorqueScript to Lua | ||||
| - Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.   | ||||
| - The empty string "" becomes `nil`   | ||||
| - A string containing three numbers separated by spaces becomes a `vector`   | ||||
| - A string containing six numbers separated by spaces becomes a table of two vectors   | ||||
| - Any other string is passed directly as a `string` | ||||
|  | ||||
| ### From Lua to TorqueScript | ||||
| - `nil` becomes the empty string ""   | ||||
| - `true` and `false` become "1" and "0" respectively   | ||||
| @@ -178,6 +179,17 @@ TorqueScript stores no type information; all values in TorqueScript are strings. | ||||
| - Any `string` is passed directly as a string   | ||||
| - Tables cannot be passed and will throw an error   | ||||
|  | ||||
| ### From TorqueScript to Lua | ||||
| - Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.   | ||||
| - The empty string "" becomes `nil`   | ||||
| - A string containing two or three numbers separated by single spaces becomes a `vector`   | ||||
| - A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box   | ||||
| - (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform" in TorqueScript parlance), and is converted into a `matrix` representing the translation and rotation. | ||||
| - Any other string is passed directly as a `string`   | ||||
|    | ||||
| For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`.   | ||||
| To convert objects by hand, use `bl.object`, `bl.boolean`, or `bl.string`. | ||||
|  | ||||
| ## I/O and Safety | ||||
| All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.   | ||||
| BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.   | ||||
|   | ||||
| @@ -31,6 +31,7 @@ INCLUDE_BIN(bll_fileLuaEnv, "lua-env.lua"); | ||||
| INCLUDE_BIN(bll_fileTsEnv, "ts-env.cs"); | ||||
| INCLUDE_BIN(bll_fileLuaStd, "util/std.lua"); | ||||
| INCLUDE_BIN(bll_fileLuaVector, "util/vector.lua"); | ||||
| INCLUDE_BIN(bll_fileLuaMatrix, "util/matrix.lua"); | ||||
| INCLUDE_BIN(bll_fileLuaLibts, "util/libts-lua.lua"); | ||||
| INCLUDE_BIN(bll_fileTsLibts, "util/libts-ts.cs"); | ||||
| INCLUDE_BIN(bll_fileLuaLibbl, "util/libbl.lua"); | ||||
| @@ -72,6 +73,7 @@ bool init() { | ||||
|   // Load utilities | ||||
|   BLL_LOAD_LUA(gL, bll_fileLuaStd); | ||||
|   BLL_LOAD_LUA(gL, bll_fileLuaVector); | ||||
|   BLL_LOAD_LUA(gL, bll_fileLuaMatrix); | ||||
|   BLL_LOAD_LUA(gL, bll_fileLuaLibts); | ||||
|   BlEval(bll_fileTsLibts); | ||||
|   BLL_LOAD_LUA(gL, bll_fileLuaLibbl); | ||||
|   | ||||
| @@ -143,4 +143,5 @@ debug = { | ||||
|   getfilename = old_debug.getfilename, -- defined in lua.env.lua | ||||
| } | ||||
|  | ||||
| _bllua_ts.echo('  Executed bllua-env-safe.lua') | ||||
| print = _bllua_ts.echo | ||||
| print('  Executed bllua-env-safe.lua') | ||||
|   | ||||
| @@ -37,4 +37,5 @@ function _bllua_on_error(err) | ||||
|   return table.concat(tracelines, '\n') | ||||
| end | ||||
|  | ||||
| _bllua_ts.echo('  Executed bllua-env.lua') | ||||
| print = _bllua_ts.echo | ||||
| print('  Executed bllua-env.lua') | ||||
|   | ||||
| @@ -42,4 +42,26 @@ package _bllua_objectDeletionHook { | ||||
| }; | ||||
| activatePackage(_bllua_objectDeletionHook); | ||||
|  | ||||
| // Public Lua library for TS | ||||
| function luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_call", %func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p); | ||||
| } | ||||
| function luaexec(%fn) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_exec", %fn); | ||||
| } | ||||
| function luaeval(%code) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_eval", %code); | ||||
| } | ||||
| function luaget(%name) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_getvar", %name); | ||||
| } | ||||
| function luaset(%name, %val) { | ||||
| 	if($_bllua_active) | ||||
| 		_bllua_luacall("_bllua_setvar", %name, %val); | ||||
| } | ||||
|  | ||||
| echo("  Executed libbl-support.cs"); | ||||
|   | ||||
							
								
								
									
										1726
									
								
								src/util/libbl.lua
									
									
									
									
									
								
							
							
						
						
									
										1726
									
								
								src/util/libbl.lua
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,3 +1,4 @@ | ||||
|  | ||||
| -- This Lua code provides some built-in utilities for writing Lua add-ons | ||||
| -- It is eval'd automatically once BLLua3 has loaded the TS API and environment | ||||
| -- It only has access to the sandboxed lua environment, just like user code. | ||||
| @@ -6,11 +7,8 @@ ts = _bllua_ts | ||||
|  | ||||
| -- Provide limited OS functions | ||||
| os = os or {} | ||||
| ---@diagnostic disable-next-line: duplicate-set-field | ||||
| function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime')) / 1000) end | ||||
|  | ||||
| ---@diagnostic disable-next-line: duplicate-set-field | ||||
| function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end | ||||
| function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end | ||||
| function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end | ||||
|  | ||||
| -- Virtual file class, emulating a file object as returned by io.open | ||||
| -- Used to wrap io.open to allow reading from zips (using TS) | ||||
| @@ -18,150 +16,140 @@ function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end | ||||
| --   Can't read nulls, can't distinguish between CRLF and LF. | ||||
| -- Todo someday: actually read the zip in lua? | ||||
| local file_meta = { | ||||
|   read = function(file, mode) | ||||
|     file:_init() | ||||
|     if not file or type(file) ~= 'table' or not file._is_file then error('File:read: Not a file', 2) end | ||||
|     if file._is_open ~= true then error('File:read: File is closed', 2) end | ||||
|     if mode == '*n' then | ||||
|       local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos) | ||||
|       if n then | ||||
|         file.pos = file.pos + #ws + #n | ||||
|         return n | ||||
|       else | ||||
|         return nil | ||||
|       end | ||||
|     elseif mode == '*a' then | ||||
|       local d = file.data:sub(file.pos, #file.data) | ||||
|       file.pos = #file.data + 1 | ||||
|       return d | ||||
|     elseif mode == '*l' then | ||||
|       local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos) | ||||
|       if not l then | ||||
|         l = file.data:match('^([^\r\n]*)$', file.pos); ws = ''; | ||||
|         if l == '' then return nil end | ||||
|       end | ||||
|       if l then | ||||
|         file.pos = file.pos + #l + #ws | ||||
|         return l | ||||
|       else | ||||
|         return nil | ||||
|       end | ||||
|     elseif type(mode) == 'number' then | ||||
|       local d = file.data:sub(file.pos, file.pos + mode) | ||||
|       file.pos = file.pos + #d | ||||
|       return d | ||||
|     else | ||||
|       error('File:read: Invalid mode \'' .. mode .. '\'', 2) | ||||
|     end | ||||
|   end, | ||||
|   lines = function(file) | ||||
|     file:_init() | ||||
|     return function() | ||||
|       return file:read('*l') | ||||
|     end | ||||
|   end, | ||||
|   close = function(file) | ||||
|     if not file._is_open then error('File:close: File is not open', 2) end | ||||
|     file._is_open = false | ||||
|   end, | ||||
|   __index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end, | ||||
|   _init = function(f) | ||||
|     if not f.data then | ||||
|       f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename) | ||||
|     end | ||||
|   end, | ||||
| 	read = function(file, mode) | ||||
| 		file:_init() | ||||
| 		if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end | ||||
| 		if file._is_open ~= true then error('File:read: File is closed', 2) end | ||||
| 		if mode=='*n' then | ||||
| 			local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos) | ||||
| 			if n then | ||||
| 				file.pos = file.pos + #ws + #n | ||||
| 				return n | ||||
| 			else | ||||
| 				return nil | ||||
| 			end | ||||
| 		elseif mode=='*a' then | ||||
| 			local d = file.data:sub(file.pos, #file.data) | ||||
| 			file.pos = #file.data + 1 | ||||
| 			return d | ||||
| 		elseif mode=='*l' then | ||||
| 			local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos) | ||||
| 			if not l then | ||||
| 				l = file.data:match('^([^\r\n]*)$', file.pos); ws = ''; | ||||
| 				if l=='' then return nil end | ||||
| 			end | ||||
| 			if l then | ||||
| 				file.pos = file.pos + #l + #ws | ||||
| 				return l | ||||
| 			else | ||||
| 				return nil | ||||
| 			end | ||||
| 		elseif type(mode)=='number' then | ||||
| 			local d = file.data:sub(file.pos, file.pos+mode) | ||||
| 			file.pos = file.pos + #d | ||||
| 			return d | ||||
| 		else | ||||
| 			error('File:read: Invalid mode \''..mode..'\'', 2) | ||||
| 		end | ||||
| 	end, | ||||
| 	lines = function(file) | ||||
| 		file:_init() | ||||
| 		return function() | ||||
| 			return file:read('*l') | ||||
| 		end | ||||
| 	end, | ||||
| 	close = function(file) | ||||
| 		if not file._is_open then error('File:close: File is not open', 2) end | ||||
| 		file._is_open = false | ||||
| 	end, | ||||
| 	__index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end, | ||||
| 	_init = function(f) | ||||
| 		if not f.data then | ||||
| 			f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename) | ||||
| 		end | ||||
| 	end, | ||||
| } | ||||
| local function new_file_obj(fn) | ||||
|   local file = { | ||||
|     _is_file = true, | ||||
|     _is_open = true, | ||||
|     pos = 1, | ||||
|     __index = file_meta.__index, | ||||
|     filename = fn, | ||||
|     data = nil, | ||||
|   } | ||||
|   setmetatable(file, file_meta) | ||||
|   return file | ||||
| 	local file = { | ||||
| 		_is_file = true, | ||||
| 		_is_open = true, | ||||
| 		pos = 1, | ||||
| 		__index = file_meta.__index, | ||||
| 		filename = fn, | ||||
| 		data = nil, | ||||
| 	} | ||||
| 	setmetatable(file, file_meta) | ||||
| 	return file | ||||
| end | ||||
|  | ||||
| local function tflip(t) | ||||
|   local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; | ||||
| end | ||||
| local allowed_zip_dirs = tflip { | ||||
|   'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' | ||||
| local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end | ||||
| local allowed_zip_dirs = tflip{ | ||||
| 	'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' | ||||
| } | ||||
| local function io_open_absolute(fn, mode) | ||||
|   -- if file exists, use original mode | ||||
|   local res, err = _bllua_io_open(fn, mode) | ||||
|   if res then return res end | ||||
|  | ||||
|   -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader | ||||
|   local dir = fn:match('^[^/]+') | ||||
|   if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end | ||||
|   local exist = _bllua_ts.call('isFile', fn) == '1' | ||||
|   if not exist then return nil, err end | ||||
|  | ||||
|   if mode ~= nil and mode ~= 'r' and mode ~= 'rb' then | ||||
|     return nil, 'Files in zips can only be opened in read mode' | ||||
|   end | ||||
|  | ||||
|   -- return a temp lua file object with the data | ||||
|   local fi = new_file_obj(fn) | ||||
|   return fi | ||||
| 	-- if file exists, use original mode | ||||
| 	local res, err = _bllua_io_open(fn, mode) | ||||
| 	if res then return res end | ||||
| 	 | ||||
| 	-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader | ||||
| 	local dir = fn:match('^[^/]+') | ||||
| 	if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end | ||||
| 	local exist = _bllua_ts.call('isFile', fn) == '1' | ||||
| 	if not exist then return nil, err end | ||||
| 	 | ||||
| 	if mode~=nil and mode~='r' and mode~='rb' then | ||||
| 		return nil, 'Files in zips can only be opened in read mode' end | ||||
| 	 | ||||
| 	-- return a temp lua file object with the data | ||||
| 	local fi = new_file_obj(fn) | ||||
| 	return fi | ||||
| end | ||||
|  | ||||
| io = io or {} | ||||
| ---@diagnostic disable-next-line: duplicate-set-field | ||||
| function io.open(fn, mode, errn) | ||||
|   errn = errn or 1 | ||||
|  | ||||
|   -- try to open the file with relative path, otherwise use absolute path | ||||
|   local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File') | ||||
|   if curfn == '' then curfn = nil end | ||||
|   if fn:find('^%.') then | ||||
|     local relfn = curfn and fn:find('^%./') and | ||||
|         curfn:gsub('[^/]+$', '') .. fn:gsub('^%./', '') | ||||
|     if relfn then | ||||
|       local fi, err = io_open_absolute(relfn, mode) | ||||
|       return fi, err, relfn | ||||
|     else | ||||
|       return nil, 'Invalid path', fn | ||||
|     end | ||||
|   else | ||||
|     local fi, err = io_open_absolute(fn, mode) | ||||
|     return fi, err, fn | ||||
|   end | ||||
| 	errn = errn or 1 | ||||
| 	 | ||||
| 	-- try to open the file with relative path, otherwise use absolute path | ||||
| 	local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File') | ||||
| 	if curfn == '' then curfn = nil end | ||||
| 	if fn:find('^%.') then | ||||
| 		local relfn = curfn and fn:find('^%./') and | ||||
| 			curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '') | ||||
| 		if relfn then | ||||
| 			local fi, err = io_open_absolute(relfn, mode) | ||||
| 			return fi, err, relfn | ||||
| 		else | ||||
| 			return nil, 'Invalid path', fn | ||||
| 		end | ||||
| 	else | ||||
| 		local fi, err = io_open_absolute(fn, mode) | ||||
| 		return fi, err, fn | ||||
| 	end | ||||
| end | ||||
|  | ||||
| ---@diagnostic disable-next-line: duplicate-set-field | ||||
| function io.lines(fn) | ||||
|   local fi, err, fn2 = io.open(fn, 'r', 2) | ||||
|   if not fi then error('Error opening file \'' .. tostring(fn2) .. '\': ' .. tostring(err), 2) end | ||||
|   return fi:lines() | ||||
| 	local fi, err, fn2 = io.open(fn, nil, 2) | ||||
| 	if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end | ||||
| 	return fi:lines() | ||||
| end | ||||
|  | ||||
| ---@diagnostic disable-next-line: duplicate-set-field | ||||
| function io.type(f) | ||||
|   ---@diagnostic disable-next-line: undefined-field | ||||
|   if type(f) == 'table' and f._is_file then | ||||
|     ---@diagnostic disable-next-line: undefined-field | ||||
|     return f._is_open and 'file' or 'closed file' | ||||
|   else | ||||
|     return _bllua_io_type(f) | ||||
|   end | ||||
| 	if type(f)=='table' and f._is_file then | ||||
| 		return f._is_open and 'file' or 'closed file' | ||||
| 	else | ||||
| 		return _bllua_io_type(f) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- provide dofile | ||||
| function dofile(fn, errn) | ||||
|   errn = errn or 1 | ||||
|  | ||||
|   local fi, err, fn2 = io.open(fn, 'r', errn + 1) | ||||
|   if not fi then error('Error executing file \'' .. tostring(fn2) .. '\': ' .. tostring(err), errn + 1) end | ||||
|  | ||||
|   print('Executing ' .. fn2) | ||||
|   local text = fi:read('*a') | ||||
|   fi:close() | ||||
|   return assert(loadstring('--[[' .. fn2 .. ']]' .. text))() | ||||
| 	errn = errn or 1 | ||||
| 	 | ||||
| 	local fi, err, fn2 = io.open(fn, 'r', errn+1) | ||||
| 	if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end | ||||
| 	 | ||||
| 	print('Executing '..fn2) | ||||
| 	local text = fi:read('*a') | ||||
| 	fi:close() | ||||
| 	return assert(loadstring('--[['..fn2..']]'..text))() | ||||
| end | ||||
|  | ||||
| -- provide require (just a wrapper for dofile) | ||||
| @@ -170,67 +158,57 @@ end | ||||
| --   blockland directory | ||||
| --   current add-on | ||||
| local function file_exists(fn, errn) | ||||
|   local fi, err, fn2 = io.open(fn, 'r', errn + 1) | ||||
|   if fi then | ||||
|     fi:close() | ||||
|     return fn2 | ||||
|   else | ||||
|     return nil | ||||
|   end | ||||
| 	local fi, err, fn2 = io.open(fn, 'r', errn+1) | ||||
| 	if fi then | ||||
| 		fi:close() | ||||
| 		return fn2 | ||||
| 	else | ||||
| 		return nil | ||||
| 	end | ||||
| end | ||||
| local require_memo = {} | ||||
| function require(mod) | ||||
|   if require_memo[mod] then return unpack(require_memo[mod]) end | ||||
|   local fp = mod:gsub('%.', '/') | ||||
|   local fns = { | ||||
|     './' .. fp .. '.lua',      -- local file | ||||
|     './' .. fp .. '/init.lua', -- local library | ||||
|     fp .. '.lua',              -- global file | ||||
|     fp .. '/init.lua',         -- global library | ||||
|   } | ||||
|   if fp:lower():find('^add-ons/') then | ||||
|     local addonpath = fp:lower():match('^add-ons/[^/]+') .. '/' | ||||
|     table.insert(fns, addonpath .. fp .. '.lua')      -- add-on file | ||||
|     table.insert(fns, addonpath .. fp .. '/init.lua') -- add-on library | ||||
|   end | ||||
|   for _, fn in ipairs(fns) do | ||||
|     local fne = file_exists(fn, 2) | ||||
|     if fne then | ||||
|       local res = { dofile(fne, 2) } | ||||
|       require_memo[mod] = res | ||||
|       return unpack(res) | ||||
|     end | ||||
|   end | ||||
|   return _bllua_requiresecure(mod) | ||||
| 	if require_memo[mod] then return unpack(require_memo[mod]) end | ||||
| 	local fp = mod:gsub('%.', '/') | ||||
| 	local fns = { | ||||
| 		'./'..fp..'.lua',      -- local file | ||||
| 		'./'..fp..'/init.lua', -- local library | ||||
| 		fp..'.lua',            -- global file | ||||
| 		fp..'/init.lua',       -- global library | ||||
| 	} | ||||
| 	if fp:lower():find('^add-ons/') then | ||||
| 		local addonpath = fp:lower():match('^add-ons/[^/]+')..'/' | ||||
| 		table.insert(fns, addonpath..fp..'.lua')      -- add-on file | ||||
| 		table.insert(fns, addonpath..fp..'/init.lua') -- add-on library | ||||
| 	end | ||||
| 	for _,fn in ipairs(fns) do | ||||
| 		local fne = file_exists(fn, 2) | ||||
| 		if fne then | ||||
| 			local res = {dofile(fne, 2)} | ||||
| 			require_memo[mod] = res | ||||
| 			return unpack(res) | ||||
| 		end | ||||
| 	end | ||||
| 	return _bllua_requiresecure(mod) | ||||
| end | ||||
|  | ||||
| -- Exposure to TS | ||||
| function _bllua_getvar(name) return _G[name] end | ||||
|  | ||||
| function _bllua_setvar(name, val) _G[name] = val end | ||||
|  | ||||
| function _bllua_eval(code) return loadstring(code)() end | ||||
|  | ||||
| function _bllua_exec(fn) return dofile(fn, 2) end | ||||
|  | ||||
| local function isValidCode(code) | ||||
|   local f, e = loadstring(code) | ||||
|   return f ~= nil | ||||
| 	local f,e = loadstring(code) | ||||
| 	return f~=nil | ||||
| end | ||||
| function _bllua_smarteval(code) | ||||
|   if (not code:find('^print%(')) and isValidCode('print(' .. code .. ')') then | ||||
|     code = 'print(' .. code .. ')' | ||||
|   end | ||||
|   local f, e = loadstring(code) | ||||
|   if f then | ||||
|     return f() | ||||
|   else | ||||
|     print(e) | ||||
|   end | ||||
| 	if (not code:find('^print%(')) and isValidCode('print('..code..')') then | ||||
| 		code = 'print('..code..')' end | ||||
| 	local f,e = loadstring(code) | ||||
| 	if f then | ||||
| 		return f() | ||||
| 	else | ||||
| 		print(e) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function ts.setvar(name, val) | ||||
|   _bllua_ts.call('_bllua_set_var', name, val) | ||||
| 	_bllua_ts.call('_bllua_set_var', name, val) | ||||
| end | ||||
|  | ||||
| _bllua_ts.call('echo', '  Executed libts-lua.lua') | ||||
|   | ||||
| @@ -49,26 +49,4 @@ function _bllua_set_var(%name, %val) { | ||||
| 	return ""; | ||||
| } | ||||
|  | ||||
| // Public Lua library for TS | ||||
| function luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_call", %func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p); | ||||
| } | ||||
| function luaexec(%fn) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_exec", %fn); | ||||
| } | ||||
| function luaeval(%code) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_eval", %code); | ||||
| } | ||||
| function luaget(%name) { | ||||
| 	if($_bllua_active) | ||||
| 		return _bllua_luacall("_bllua_getvar", %name); | ||||
| } | ||||
| function luaset(%name, %val) { | ||||
| 	if($_bllua_active) | ||||
| 		_bllua_luacall("_bllua_setvar", %name, %val); | ||||
| } | ||||
|  | ||||
| echo("  Executed libts-ts.cs"); | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/util/matrix.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/util/matrix.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
|  | ||||
| -- todo | ||||
| -- Matrix class with math operators | ||||
|  | ||||
|  | ||||
|  | ||||
| print('  Executed matrix.lua') | ||||
							
								
								
									
										512
									
								
								src/util/std.lua
									
									
									
									
									
								
							
							
						
						
									
										512
									
								
								src/util/std.lua
									
									
									
									
									
								
							| @@ -1,374 +1,350 @@ | ||||
|  | ||||
| -- Basic functionality that should be standard in Lua | ||||
|  | ||||
|  | ||||
| -- Table / List | ||||
| -- Whether a table contains no keys | ||||
| function table.empty(t) | ||||
|   return next(t) ~= nil | ||||
| 	return next(t)~=nil | ||||
| end | ||||
|  | ||||
| -- Apply a function to each key in a table | ||||
| function table.map(f, ...) | ||||
|   local ts = { ... } | ||||
|   local u = {} | ||||
|   for k, _ in pairs(ts[1]) do | ||||
|     local args = {} | ||||
|     for j = 1, #ts do args[j] = ts[j][i] end | ||||
|     u[i] = f(unpack(args)) | ||||
|   end | ||||
|   return u | ||||
| 	local ts = {...} | ||||
| 	local u = {} | ||||
| 	for k,_ in pairs(ts[1]) do | ||||
| 		local args = {} | ||||
| 		for j=1,#ts do args[j] = ts[j][i] end | ||||
| 		u[i] = f(unpack(args)) | ||||
| 	end | ||||
| 	return u | ||||
| end | ||||
|  | ||||
| function table.map_list(f, ...) | ||||
|   local ts = { ... } | ||||
|   local u = {} | ||||
|   for i = 1, #ts[1] do | ||||
|     local args = {} | ||||
|     for j = 1, #ts do args[j] = ts[j][i] end | ||||
|     u[i] = f(unpack(args)) | ||||
|   end | ||||
|   return u | ||||
| 	local ts = {...} | ||||
| 	local u = {} | ||||
| 	for i=1,#ts[1] do | ||||
| 		local args = {} | ||||
| 		for j=1,#ts do args[j] = ts[j][i] end | ||||
| 		u[i] = f(unpack(args)) | ||||
| 	end | ||||
| 	return u | ||||
| end | ||||
|  | ||||
| -- Swap keys/values | ||||
| function table.swap(t) | ||||
|   local u = {} | ||||
|   for k, v in pairs(t) do u[v] = k end | ||||
|   return u | ||||
| 	local u = {} | ||||
| 	for k,v in pairs(t) do u[v] = k end | ||||
| 	return u | ||||
| end | ||||
|  | ||||
| -- Reverse a list | ||||
| function table.reverse(l) | ||||
|   local m = {} | ||||
|   for i = 1, #l do m[#l - i + 1] = l[i] end | ||||
|   return m | ||||
| 	local m = {} | ||||
| 	for i=1,#l do m[#l-i+1] = l[i] end | ||||
| 	return m | ||||
| end | ||||
|  | ||||
| -- Whether a table is a list/array (has only monotonic integer keys) | ||||
| function table.islist(t) | ||||
|   local n = 0 | ||||
|   for i, _ in pairs(t) do | ||||
|     if type(i) ~= 'number' or i % 1 ~= 0 then return false end | ||||
|     n = n + 1 | ||||
|   end | ||||
|   return n == #t | ||||
| 	local n = 0 | ||||
| 	for i,_ in pairs(t) do | ||||
| 		if type(i)~='number' or i%1~=0 then return false end | ||||
| 		n = n+1 | ||||
| 	end | ||||
| 	return n==#t | ||||
| end | ||||
|  | ||||
| -- Append contents of other tables to first table | ||||
| function table.append(t, ...) | ||||
|   local a = { ... } | ||||
|   for _, u in ipairs(a) do | ||||
|     for _, v in ipairs(u) do table.insert(t, v) end | ||||
|   end | ||||
|   return t | ||||
| 	local a = {...} | ||||
| 	for _,u in ipairs(a) do | ||||
| 		for _,v in ipairs(u) do table.insert(t,v) end | ||||
| 	end | ||||
| 	return t | ||||
| end | ||||
|  | ||||
| -- Create a new table containing all keys from any number of tables | ||||
| -- latter tables in the arg list override prior ones | ||||
| -- overlaps, NOT appends, integer keys | ||||
| function table.join(...) | ||||
|   local ts = { ... } | ||||
|   local w = {} | ||||
|   for _, t in ipairs(ts) do | ||||
|     for k, v in pairs(t) do w[k] = v end | ||||
|   end | ||||
|   return w | ||||
| 	local ts = {...} | ||||
| 	local w = {} | ||||
| 	for _,t in ipairs(ts) do | ||||
| 		for k,v in pairs(t) do w[k] = v end | ||||
| 	end | ||||
| 	return w | ||||
| end | ||||
|  | ||||
| -- Whether a table contains a certain value in any key | ||||
| function table.contains(t, s) | ||||
|   for _, v in pairs(t) do | ||||
|     if v == s then return true end | ||||
|   end | ||||
|   return false | ||||
| function table.contains(t,s) | ||||
| 	for _,v in pairs(t) do | ||||
| 		if v==s then return true end | ||||
| 	end | ||||
| 	return false | ||||
| end | ||||
|  | ||||
| function table.contains_list(t, s) | ||||
|   for _, v in ipairs(t) do | ||||
|     if v == s then return true end | ||||
|   end | ||||
|   return false | ||||
| function table.contains_list(t,s) | ||||
| 	for _,v in ipairs(t) do | ||||
| 		if v==s then return true end | ||||
| 	end | ||||
| 	return false | ||||
| end | ||||
|  | ||||
| -- Copy a table to another table | ||||
| function table.copy(t) | ||||
|   local u = {} | ||||
|   for k, v in pairs(t) do u[k] = v end | ||||
|   return u | ||||
| 	local u = {} | ||||
| 	for k,v in pairs(t) do u[k] = v end | ||||
| 	return u | ||||
| end | ||||
|  | ||||
| function table.copy_list(l) | ||||
|   local m = {} | ||||
|   for i, v in ipairs(l) do m[i] = v end | ||||
|   return m | ||||
| 	local m = {} | ||||
| 	for i,v in ipairs(l) do m[i] = v end | ||||
| 	return m | ||||
| end | ||||
|  | ||||
| -- Sort a table in a new copy | ||||
| function table.sortcopy(t, f) | ||||
|   local u = table.copy_list(t) | ||||
|   table.sort(u, f) | ||||
|   return u | ||||
| 	local u = table.copy_list(t) | ||||
| 	table.sort(u, f) | ||||
| 	return u | ||||
| end | ||||
|  | ||||
| -- Remove a value from a table | ||||
| function table.removevalue(t, r) | ||||
|   local rem = {} | ||||
|   for k, v in pairs(t) do | ||||
|     if v == r then table.insert(rem, k) end | ||||
|   end | ||||
|   for _, k in ipairs(rem) do t[k] = nil end | ||||
| 	local rem = {} | ||||
| 	for k,v in pairs(t) do | ||||
| 		if v==r then table.insert(rem, k) end | ||||
| 	end | ||||
| 	for _,k in ipairs(rem) do t[k] = nil end | ||||
| end | ||||
|  | ||||
| function table.removevalue_list(t, r) | ||||
|   for i = #t, 1, -1 do | ||||
|     if t[i] == r then | ||||
|       table.remove(t, i) | ||||
|     end | ||||
|   end | ||||
| 	for i = #t, 1, -1 do | ||||
| 		if t[i]==r then | ||||
| 			table.remove(t, i) | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- Export tables into formatted executable strings | ||||
| local function tabs(tabLevel) | ||||
|   return ('  '):rep(tabLevel) | ||||
| 	return ('  '):rep(tabLevel) | ||||
| end | ||||
| local valueToString | ||||
| local function tableToString(t, tabLevel, seen) | ||||
|   if type(t) ~= 'table' or (getmetatable(t) and getmetatable(t).__tostring) then | ||||
|     return tostring(t) | ||||
|   elseif table.islist(t) then | ||||
|     if #t == 0 then | ||||
|       return '{}' | ||||
|     else | ||||
|       local strs = {} | ||||
|       local containsTables = false | ||||
|       for _, v in ipairs(t) do | ||||
|         if type(v) == 'table' then containsTables = true end | ||||
|         table.insert(strs, valueToString(v, tabLevel + 1, seen) .. ',') | ||||
|       end | ||||
|       if containsTables or #t > 3 then | ||||
|         return '{\n' .. tabs(tabLevel + 1) | ||||
|             .. table.concat(strs, '\n' .. tabs(tabLevel + 1)) | ||||
|             .. '\n' .. tabs(tabLevel) .. '}' | ||||
|       else | ||||
|         return '{ ' .. table.concat(strs, ' ') .. ' }' | ||||
|       end | ||||
|     end | ||||
|   else | ||||
|     local containsNonStringKeys = false | ||||
|     for k, v in pairs(t) do | ||||
|       if type(k) ~= 'string' or k:find('[^a-zA-Z0-9_]') then | ||||
|         containsNonStringKeys = true | ||||
|       elseif type(k) == 'table' then | ||||
|         error('table.tostring: table contains a table as key, cannot serialize') | ||||
|       end | ||||
|     end | ||||
|     local strs = {} | ||||
|     if containsNonStringKeys then | ||||
|       for k, v in pairs(t) do | ||||
|         table.insert(strs, '\n' .. tabs(tabLevel + 1) | ||||
|           .. '[' .. valueToString(k, tabLevel + 1, seen) .. '] = ' | ||||
|           .. valueToString(v, tabLevel + 1, seen) .. ',') | ||||
|       end | ||||
|     else | ||||
|       for k, v in pairs(t) do | ||||
|         table.insert(strs, '\n' .. tabs(tabLevel + 1) | ||||
|           .. k .. ' = ' .. valueToString(v, tabLevel + 1, seen) .. ',') | ||||
|       end | ||||
|     end | ||||
|     return '{' .. table.concat(strs) .. '\n' .. tabs(tabLevel) .. '}' | ||||
|   end | ||||
| 	if type(t)~='table' or (getmetatable(t) and getmetatable(t).__tostring) then | ||||
| 		return tostring(t) | ||||
| 	elseif table.islist(t) then | ||||
| 		if #t==0 then | ||||
| 			return '{}' | ||||
| 		else | ||||
| 			local strs = {} | ||||
| 			local containsTables = false | ||||
| 			for _,v in ipairs(t) do | ||||
| 				if type(v)=='table' then containsTables = true end | ||||
| 				table.insert(strs, valueToString(v, tabLevel+1, seen)..',') | ||||
| 			end | ||||
| 			if containsTables or #t>3 then | ||||
| 				return '{\n'..tabs(tabLevel+1) | ||||
| 					..table.concat(strs, '\n'..tabs(tabLevel+1)) | ||||
| 					..'\n'..tabs(tabLevel)..'}' | ||||
| 			else | ||||
| 				return '{ '..table.concat(strs, ' ')..' }' | ||||
| 			end | ||||
| 		end | ||||
| 	else | ||||
| 		local containsNonStringKeys = false | ||||
| 		for k,v in pairs(t) do | ||||
| 			if type(k)~='string' or k:find('[^a-zA-Z0-9_]') then | ||||
| 				containsNonStringKeys = true | ||||
| 			elseif type(k)=='table' then | ||||
| 				error('table.tostring: table contains a table as key, cannot serialize') | ||||
| 			end | ||||
| 		end | ||||
| 		local strs = {} | ||||
| 		if containsNonStringKeys then | ||||
| 			for k,v in pairs(t) do | ||||
| 				table.insert(strs, '\n'..tabs(tabLevel+1) | ||||
| 					..'['..valueToString(k, tabLevel+1, seen)..'] = ' | ||||
| 					..valueToString(v, tabLevel+1, seen)..',') | ||||
| 			end | ||||
| 		else | ||||
| 			for k,v in pairs(t) do | ||||
| 				table.insert(strs, '\n'..tabs(tabLevel+1) | ||||
| 					..k..' = '..valueToString(v, tabLevel+1, seen)..',') | ||||
| 			end | ||||
| 		end | ||||
| 		return '{'..table.concat(strs)..'\n'..tabs(tabLevel)..'}' | ||||
| 	end | ||||
| end | ||||
| valueToString = function(v, tabLevel, seen) | ||||
|   local t = type(v) | ||||
|   if t == 'table' then | ||||
|     if seen[v] then | ||||
|       return 'nil --[[ already seen: ' .. tostring(v) .. ' ]]' | ||||
|     else | ||||
|       seen[v] = true | ||||
|       return tableToString(v, tabLevel, seen) | ||||
|     end | ||||
|   elseif t == 'string' then | ||||
|     return '\'' .. string.escape(v) .. '\'' | ||||
|   elseif t == 'number' or t == 'boolean' then | ||||
|     return tostring(v) | ||||
|   else | ||||
|     --error('table.tostring: table contains a '..t..' value, cannot serialize') | ||||
|     return 'nil --[[ cannot serialize ' .. t .. ': ' .. tostring(v) .. ' ]]' | ||||
|   end | ||||
| 	local t = type(v) | ||||
| 	if t=='table' then | ||||
| 		if seen[v] then | ||||
| 			return 'nil --[[ already seen: '..tostring(v)..' ]]' | ||||
| 		else | ||||
| 			seen[v] = true | ||||
| 			return tableToString(v, tabLevel, seen) | ||||
| 		end | ||||
| 	elseif t=='string' then | ||||
| 		return '\''..string.escape(v)..'\'' | ||||
| 	elseif t=='number' or t=='boolean' then | ||||
| 		return tostring(v) | ||||
| 	else | ||||
| 		--error('table.tostring: table contains a '..t..' value, cannot serialize') | ||||
| 		return 'nil --[[ cannot serialize '..tostring(v)..' ]]' | ||||
| 	end | ||||
| end | ||||
| function table.tostring(t) | ||||
|   return tableToString(t, 0, {}) | ||||
| 	return tableToString(t, 0, {}) | ||||
| end | ||||
|  | ||||
|  | ||||
| -- String | ||||
|  | ||||
| -- Split string into table by separator | ||||
| -- or by chars if no separator given | ||||
| -- if regex is not true, sep is treated as a regex pattern | ||||
| function string.split(str, sep, noregex) | ||||
|   if type(str) ~= 'string' then | ||||
|     error('string.split: argument #1: expected string, got ' .. type(str), 2) | ||||
|   end | ||||
|   if sep == nil or sep == '' then | ||||
|     local t = {} | ||||
|     local ns = #str | ||||
|     for x = 1, ns do | ||||
|       table.insert(t, str:sub(x, x)) | ||||
|     end | ||||
|     return t | ||||
|   elseif type(sep) == 'string' then | ||||
|     local t = {} | ||||
|     if #str > 0 then | ||||
|       local first = 1 | ||||
|       while true do | ||||
|         local last, newfirst = str:find(sep, first, noregex) | ||||
|         if not last then break end | ||||
|         table.insert(t, str:sub(first, last - 1)) | ||||
|         first = newfirst + 1 | ||||
|       end | ||||
|       table.insert(t, str:sub(first, #str)) | ||||
|     end | ||||
|     return t | ||||
|   else | ||||
|     error( | ||||
|       'string.split: argument #2: expected string or nil, got ' .. type(sep), 2) | ||||
|   end | ||||
| 	if type(str)~='string' then | ||||
| 		error('string.split: argument #1: expected string, got '..type(str), 2) end | ||||
| 	if sep==nil or sep=='' then | ||||
| 		local t = {} | ||||
| 		local ns = #str | ||||
| 		for x = 1, ns do | ||||
| 			table.insert(t, str:sub(x, x)) | ||||
| 		end | ||||
| 		return t | ||||
| 	elseif type(sep)=='string' then | ||||
| 		local t = {} | ||||
| 		if #str>0 then | ||||
| 			local first = 1 | ||||
| 			while true do | ||||
| 				local last, newfirst = str:find(sep, first, noregex) | ||||
| 				if not last then break end | ||||
| 				table.insert(t, str:sub(first, last-1)) | ||||
| 				first = newfirst+1 | ||||
| 			end | ||||
| 			table.insert(t, str:sub(first, #str)) | ||||
| 		end | ||||
| 		return t | ||||
| 	else | ||||
| 		error( | ||||
| 			'string.split: argument #2: expected string or nil, got '..type(sep), 2) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- Split string to a list of char bytes | ||||
| function string.bytes(s) | ||||
|   local b = {} | ||||
|   for i = 1, #s do | ||||
|     local c = s:sub(i, i) | ||||
|     table.insert(b, c:byte()) | ||||
|   end | ||||
|   return b | ||||
| 	local b = {} | ||||
| 	for i=1,#s do | ||||
| 		local c = s:sub(i,i) | ||||
| 		table.insert(b, c:byte()) | ||||
| 	end | ||||
| 	return b | ||||
| end | ||||
|  | ||||
| -- Trim leading and trailing whitespace | ||||
| function string.trim(s, ws) | ||||
|   ws = ws or ' \t\r\n' | ||||
|   return s:gsub('^[' .. ws .. ']+', ''):gsub('[' .. ws .. ']+$', '') .. '' | ||||
| 	ws = ws or ' \t\r\n' | ||||
| 	return s:gsub('^['..ws..']+', ''):gsub('['..ws..']+$', '')..'' | ||||
| end | ||||
|  | ||||
| -- String slicing and searching using [] operator | ||||
| local str_meta = getmetatable('') | ||||
| local str_meta_index_old = str_meta.__index | ||||
| function str_meta.__index(s, k) | ||||
|   if type(k) == 'string' then | ||||
|     return str_meta_index_old[k] | ||||
|   elseif type(k) == 'number' then | ||||
|     if k < 0 then k = #s + k + 1 end | ||||
|     return string.sub(s, k, k) | ||||
|   elseif type(k) == 'table' then | ||||
|     local a = k[1] < 0 and (#s + k[1] + 1) or k[1] | ||||
|     local b = k[2] < 0 and (#s + k[2] + 1) or k[2] | ||||
|     return string.sub(s, a, b) | ||||
|   end | ||||
| local str_meta_index_old= str_meta.__index | ||||
| function str_meta.__index(s,k) | ||||
| 	if type(k)=='string' then | ||||
| 		return str_meta_index_old[k] | ||||
| 	elseif type(k)=='number' then | ||||
| 		if k<0 then k = #s+k+1 end | ||||
| 		return string.sub(s,k,k) | ||||
| 	elseif type(k)=='table' then | ||||
| 		local a = k[1]<0 and (#s+k[1]+1) or k[1] | ||||
| 		local b = k[2]<0 and (#s+k[2]+1) or k[2] | ||||
| 		return string.sub(s,a,b) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- String iterator | ||||
| function string.chars(s) | ||||
|   local i = 0 | ||||
|   return function() | ||||
|     i = i + 1 | ||||
|     if i <= #s then | ||||
|       return s:sub(i, i) | ||||
|     else | ||||
|       return nil | ||||
|     end | ||||
|   end | ||||
| 	local i = 0 | ||||
| 	return function() | ||||
| 		i = i+1 | ||||
| 		if i<=#s then return s:sub(i,i) | ||||
| 		else return nil end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- Escape sequences | ||||
| local defaultEscapes = { | ||||
|   ['\\'] = '\\\\', | ||||
|   ['\''] = '\\\'', | ||||
|   ['\"'] = '\\\"', | ||||
|   ['\t'] = '\\t', | ||||
|   ['\r'] = '\\r', | ||||
|   ['\n'] = '\\n', | ||||
|   ['\0'] = '\\0', | ||||
| 	['\\'] = '\\\\', | ||||
| 	['\''] = '\\\'', | ||||
| 	['\"'] = '\\\"', | ||||
| 	['\t'] = '\\t', | ||||
| 	['\r'] = '\\r', | ||||
| 	['\n'] = '\\n', | ||||
| 	['\0'] = '\\0', | ||||
| } | ||||
| function string.escape(s, escapes) | ||||
|   escapes = escapes or defaultEscapes | ||||
|   local t = {} | ||||
|   for i = 1, #s do | ||||
|     local c = s:sub(i, i) | ||||
|     table.insert(t, escapes[c] or c) | ||||
|   end | ||||
|   return table.concat(t) | ||||
| 	escapes = escapes or defaultEscapes | ||||
| 	local t = {} | ||||
| 	for i=1,#s do | ||||
| 		local c = s:sub(i,i) | ||||
| 		table.insert(t, escapes[c] or c) | ||||
| 	end | ||||
| 	return table.concat(t) | ||||
| end | ||||
|  | ||||
| local defaultEscapeChar = '\\' | ||||
| local defaultUnescapes = { | ||||
|   ['\\'] = '\\', | ||||
|   ['\''] = '\'', | ||||
|   ['\"'] = '\"', | ||||
|   ['t'] = '\t', | ||||
|   ['r'] = '\r', | ||||
|   ['n'] = '\n', | ||||
|   ['0'] = '\0', | ||||
| 	['\\'] = '\\', | ||||
| 	['\''] = '\'', | ||||
| 	['\"'] = '\"', | ||||
| 	['t'] = '\t', | ||||
| 	['r'] = '\r', | ||||
| 	['n'] = '\n', | ||||
| 	['0'] = '\0', | ||||
| } | ||||
| function string.unescape(s, escapeChar, unescapes) | ||||
|   escapeChar = escapeChar or defaultEscapeChar | ||||
|   unescapes = unescapes or defaultUnescapes | ||||
|   local t = {} | ||||
|   local inEscape = false | ||||
|   for i = 1, #s do | ||||
|     local c = s:sub(i, i) | ||||
|     if inEscape then | ||||
|       table.insert(t, unescapes[c] | ||||
|         or error('string.unescape: invalid escape sequence: \'' | ||||
|           .. escapeChar .. c .. '\'')) | ||||
|     elseif c == escapeChar then | ||||
|       inEscape = true | ||||
|     else | ||||
|       table.insert(t, c) | ||||
|     end | ||||
|   end | ||||
|   return table.concat(t) | ||||
| 	escapeChar = escapeChar or defaultEscapeChar | ||||
| 	unescapes = unescapes or defaultUnescapes | ||||
| 	local t = {} | ||||
| 	local inEscape = false | ||||
| 	for i=1,#s do | ||||
| 		local c = s:sub(i,i) | ||||
| 		if inEscape then | ||||
| 			table.insert(t, unescapes[c] | ||||
| 				or error('string.unescape: invalid escape sequence: \'' | ||||
| 					..escapeChar..c..'\'')) | ||||
| 		elseif c==escapeChar then | ||||
| 			inEscape = true | ||||
| 		else | ||||
| 			table.insert(t, c) | ||||
| 		end | ||||
| 	end | ||||
| 	return table.concat(t) | ||||
| end | ||||
|  | ||||
|  | ||||
| -- IO | ||||
|  | ||||
| io = io or {} | ||||
| -- Read entire file at once, return nil,err if access failed | ||||
| function io.readall(filename) | ||||
|   local fi, err = io.open(filename, 'rb') | ||||
|   if not fi then return nil, err end | ||||
|   local s = fi:read("*a") | ||||
|   fi:close() | ||||
|   return s | ||||
| 	local fi,err = io.open(filename, 'rb') | ||||
| 	if not fi then return nil,err end | ||||
| 	local s = fi:read("*a") | ||||
| 	fi:close() | ||||
| 	return s | ||||
| end | ||||
|  | ||||
| -- Write data to file all at once, return true if success / false,err if failure | ||||
| function io.writeall(filename, data) | ||||
|   local fi, err = io.open(filename, 'wb') | ||||
|   if not fi then return false, err end | ||||
|   fi:write(data) | ||||
|   fi:close() | ||||
|   return true, nil | ||||
| 	local fi,err = io.open(filename, 'wb') | ||||
| 	if not fi then return false,err end | ||||
| 	fi:write(data) | ||||
| 	fi:close() | ||||
| 	return true,nil | ||||
| end | ||||
|  | ||||
|  | ||||
| -- Math | ||||
|  | ||||
| -- Round | ||||
| function math.round(x) | ||||
|   return math.floor(x + 0.5) | ||||
| 	return math.floor(x+0.5) | ||||
| end | ||||
|  | ||||
| -- Mod that accounts for floating point inaccuracy | ||||
| function math.mod(a, b) | ||||
|   local m = a % b | ||||
|   if m == 0 or math.abs(m) < 1e-15 or math.abs(m - b) < 1e-15 then | ||||
|     return 0 | ||||
|   else | ||||
|     return m | ||||
|   end | ||||
| function math.mod(a,b) | ||||
| 	local m = a%b | ||||
| 	if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0 | ||||
| 	else return m end | ||||
| end | ||||
|  | ||||
| -- Clamp value between min and max | ||||
| function math.clamp(v, n, x) | ||||
|   return math.min(x, math.max(v, n)) | ||||
| 	return math.min(x, math.max(v, n)) | ||||
| end | ||||
|  | ||||
|  | ||||
| print('  Executed std.lua') | ||||
|   | ||||
| @@ -235,4 +235,7 @@ vector_new = function(vi) | ||||
| end | ||||
|  | ||||
| vector = vector_new | ||||
|  | ||||
| print('  Executed vector.lua') | ||||
|  | ||||
| return vector_new | ||||
|   | ||||
		Reference in New Issue
	
	Block a user