-- -- KERNEL.LUA Copyright (c) 2002-04, Asko Kauppi -- -- This is the backbone of the LuaX system, loading in modules and governing their usage. -- This script is automatically loaded by 'luax' at startup (if found at the current -- directory, or in the same dir with the executable). -- -- To-do: -- - Testing with PocketPC -- - ... -- if not (lib and lib.static) then error "This script requires LuaX." end local m= lib.static -- our namespace local _ ASSUME= assert -- Stdout capture for luax_embed: -- if rawget(_G,"STDOUT_CAPTURE") then -- "" if enabled --local old_write= io.write io.write= function(...) local str= arg[1] for i=2,table.getn(arg) do str= (str or "")..'\t'..arg[i] end STDOUT_CAPTURE= STDOUT_CAPTURE..str --old_write( unpack(arg) ) end --local old_print= print print= function(...) io.write( unpack(arg) ) STDOUT_CAPTURE= STDOUT_CAPTURE..'\n' --old_print( unpack(arg) ) end end -- Make sure debug stuff really gets on screen: -- function _PRINT(...) print( unpack(arg) ); io.flush(); end function _WRITE(...) io.write( unpack(arg) ); io.flush(); end function _WARN(str) _PRINT( "\t\nWARNING: "..str ) end -- -- License info etc. (these apply to the LuaX system itself, each -- module has its own similar fields): -- ASSUME( m._info ) -- overriding the static sys module's fields m._info= { MODULE= "LuaX", AUTHOR= "asko.kauppi@sci.fi", LICENSE= "LGPL", PLATFORM= "Win32, Linux, NetBSD/FreeBSD, OS X", RELEASE= 20041017, -- release date DEPENDENCIES= nil, NOTES= nil } local _lookup= { } -- These are just samples, actual mapping happens later on: -- -- ["lib.sdl"]= { "sdl_module" }, -- ["lib.sys"]= { "sys_module", "sys_script" }, -- ["lib.tools"]= { "tools.lua", "tools_script" } local _mappath= { } -- Full pathname search paths for 'USES'. ----- -- The following static entries are depended upon: -- ASSUME( lib.static.gluahost ) ASSUME( lib.static.chdir ) local Loc_FileExists= ASSUME( lib.static.fileinfo ) local function Loc_DirExists( name ) -- local tbl= lib.static.fileinfo(name) -- 'tbl.type' contains "d" if its a directory -- return tbl and string.find( ASSUME(tbl.type), "d") end -- We present the OSes as tables, so we can place additional info -- (s.a. version numbers, processor platform..) within there later. -- WIN32= os.getenv("WINDIR") and { msys= os.getenv("OSTYPE")=="msys" } or false -- Win95..XP (not CE) WINCE= false local f= lib.static.wince_GetSystemInfo if f then local family, level, rev= f() ASSUME( family=="x86" or family=="arm" ) WINCE= { [family]=level } -- :) end LINUX= false BSD= false DARWIN= false QNX= false WIN32_MSYS= WIN32 and WIN32.msys local function dump_string(str) -- debugging! -- for n=1,string.len(str) do local c= string.sub(str,n,n) print( c, string.byte(c) ) end end if not (WIN32 or WINCE) then -- -- Any LuaX compilation will have 'io.popen()' enabled: -- local f= io.popen "uname" uname= f:read() -- first line f:close() if uname=="Linux" then LINUX= { x86=true } elseif string.find( uname, "BSD" ) then -- "NetBSD"/"FreeBSD" BSD= { x86=true } elseif uname=="Darwin" then DARWIN= { ppc=true } elseif uname=="QNX" then local cpu= ASSUME( os.getenv("PROCESSOR") ) QNX= { x86= (cpu=="x86") } else error( "Unknown OS: '"..uname.."'" ) end end if WIN32 and WIN32.msys then -- Win32/MSYS won't print nicely without this: -- -- Note: even with this, some printout (s.a. using 'io.stderr:write' -- explicitly will flush differently). Fix it if you can! -- local _print= print local _write= io.write function print(...) _print( unpack(arg) ); io.flush() end function io.write(...) _write( unpack(arg) ); io.flush() end end local DLL_SUFFIX= (WIN32 and ".dll") or (DARWIN and ".dylib") or ".so" -- -- Let's make it easier for others to know where we're installed. -- -- Note: we cannot use 'arg[0]' (which would normally be the name of -- this script) since we're loaded with "-l" as a library. -- -- If started as an exe, we have 'lib.static.exepath' set up for us. -- If started as a dll, the host must have set 'LUAX_ROOT' already. -- if not rawget(_G,"LUAX_ROOT") then -- running as exe -- local path= ASSUME(lib.static.exepath) if not Loc_FileExists( path.."kernel.lua" ) then -- same dir _,_,path= string.find( path, "(.+[/\\]).+[/\\]" ) -- parent if not path then -- must be "out_xxx/luax" (development phase) path= lib.static.chdir() -- current dir end end rawset( _G, "LUAX_ROOT", path ) -- global, for all to see end ASSUME( Loc_FileExists(LUAX_ROOT.."kernel.lua"), "LUAX_ROOT must lead to 'kernel.lua' (not found!)" ) --==( Lua 5.0 modifications )==-- -- These fixes should make it to the Lua core some day..? -- ----- -- Modify 'dofile()' so that it takes parameters and can also -- pass on (as 'arg[0]') the name of the file itself. -- -- Also, must be able to RETURN several values at once.. -- local old_dofile= ASSUME(dofile) function dofile( fn, ... ) -- local safe= rawget(_G,"arg") -- protect the 'arg' the caller sees local ret, ret2 arg[0]= fn rawset( _G, "arg", arg ) -- set the global 'arg' table ret, ret2= old_dofile(fn) rawset( _G, "arg", safe ) -- original 'arg' back return ret, ret2 end ----- -- Debug dump - should be part of any Lua programmer's toolkit.. -- -- Note: This is only for our own need, see 'Scripts/dump.lua' for the -- real version. -- -- Note2: Crafted such that it works even in disabled sandboxes (setfenv) -- local _write= assert(io.write) local _print= assert(print) local _flush= assert(io.flush) local _pairs= assert(pairs) local function _dump( tbl, lev ) -- lev= lev or 0 if not tbl then _print "(nil)" else for key,val in _pairs(tbl) do -- BUG!!: This don't work in a sandbox -- _write(string.rep(" ", lev)) _write(key, " : ") if type(val)=='table' then _print "table" if lev >= 0 then -- recurse _dump(val,lev+1) end elseif type(val)=='string' then _print( "\t'"..val.."'" ) else _print( "",val ) end end end _flush() -- make sure dump always gets to screen end dump= _dump --==( Local functions )==-- ----- -- str= Loc_FullPath( fn_str ) -- -- Returns the full pathname of 'fn'. -- local function Loc_FullPath( fn ) -- local c1= string.sub(fn,1,1) -- first char local c2= string.sub(fn,2,2) -- 2nd char -- Absolute pathnames can be: -- -- '/some/path..." (Linux, Unix etc.) -- 'C:\some\path..." (Win32) -- '\some\path.." (Win32, using current drive) -- '\\some\path.." (Win32) -- if (c1=='/') or (c2==':') or (c1..c2=='\\\\') then -- return fn -- Already an absolute path end if WINCE then -- WinCE only has absolute paths (no drive names) if c1=='\\' then return fn else return LUAX_ROOT..fn -- think LuaX root is always current end else local cwd= lib.static.chdir() if c1=='\\' then -- Absolute path on current drive return string.sub( cwd, 1, 3 ) .. fn else return cwd..fn end end end ----- -- Return a table (s.a. "lib.tools.misc") based on a variable name. -- If such a table (and/or its parents) don't exist, create them. -- local function Loc_EnsureTableExists( key ) -- str -- local tbl, parent -- Easy way if doing a global (first level) table: -- if not string.find( key, "%." ) then -- Note: Use 'rawset' so we won't get caught by access checking. -- tbl= rawget( _G, key ) if type(tbl)~='table' then tbl= {} rawset( _G, key, tbl ) end else -- Make sure parents exist first (possibly NOT the optimal way?!?) -- _,_,parent= string.find( key, "(.+)%." ) if parent and parent~="lib" then Loc_EnsureTableExists( parent ) end -- Now also we can be born. -- local cmd= key.."= (type("..key..")=='table') ".."and ".. key.." or { }; return "..key -- What was wrong with Lua 4.0's "dostring()".. nothing?!? -- tbl= assert( loadstring(cmd) ) () end return tbl end ----- -- [suffix_str]= Loc_Suffix( filename_str ) -- -- Returns: The filename suffix (if any), in lowercase (e.g. ".dll"). -- local function Loc_Suffix( fn_str ) -- local i,_,sfx= string.find( fn_str, ".+(%.%w+)$" ); if i then return string.lower(sfx) else return nil end end ----- -- str= Loc_AddTerminatingSlash( fname_str ) -- -- Returns: A string guaranteed to have '/' (or '\') at its end -- local function Loc_AddTerminatingSlash( str ) -- local c= string.sub( str,-1 ) if (c=='/') or (c=='\\') then return str -- already ends with a slash else return str..'/' end end ----- -- [found_fn_str | found_table]= Loc_FindModule( filename_str, suffix_str ) -- -- Tries to look for a module in given libraries (_mappath) -- and returns the filename of the found module (or 'nil' for none found). -- local function Loc_FindModule2( fname_str, ext ) -- --print( "trying:", fname_str ) if Loc_FileExists( fname_str ) then return fname_str end if Loc_FileExists( fname_str..ext ) then return fname_str..ext end return nil -- No such file! end -- local function Loc_FindModule( filename_str ) -- local ret= nil -- Try without path first: -- ret= Loc_FindModule2( filename_str, DLL_SUFFIX ) if (not ret) and _mappath then -- for i,v in ipairs(_mappath) do -- ret= Loc_FindModule2( v..filename_str, DLL_SUFFIX ) if ret then break; end end end return ret end -- local function Loc_PrintMap( key ) -- local fn_list= _lookup[key] if not fn_list then _PRINT( "No mapping for '"..key.."'." ) return end local str for _,fn in fn_list do str= str and (str.."\n\t\t\t"..fn) or fn end local spaces= 16- string.len(key) _PRINT( " '"..key.."' ".. string.rep(' ',spaces).. "-> "..str ) end --==( Public functions )==-- ----- -- This helps us ship unmodified luaSocket files: -- local _require= ASSUME( require ) -- original one function require( str ) -- local lookup= { ltn12= "lib.ltn12", mime= "lib.mime", socket= "lib.socket", ftp= "lib.socket.ftp", http= "lib.socket.http", url= "lib.socket.url", tp= "lib.socket.tp", } if lookup[str] then return USES( lookup[str], "quiet" ) else return _require( str ) end end ----- -- [int]= Gluahost( filename ) -- -- This function is called by wrapper scripts that try to initialize the -- gluax module they need. Without us, they would reach the gluahost code -- itself (so the wrappers can also be used bare-boned). -- -- Note: Gluahost() normally returns a number (0 for success, <0 for error). -- We return 'nil' if the binary module cannot be found: this allows -- the wrapper to step back gracefully, without generating error -- messages for non-compiled modules. -- function Gluahost( fname, namespace_tbl ) -- ASSUME( type(namespace_tbl)=="table" ) local fn= Loc_FindModule(fname) or fname if not Loc_FileExists(fn) then return nil -- elseif true then -- Set C code globals table to the namespace, causing any global -- access to be wrapped in namespace (for luaSocket 2.0, mainly). -- local OLD_G= getfenv(0) local m= namespace_tbl -- Set metatables so that real globals ('string.*' etc.) are seen through -- (but changes will be made in 'm'). -- local mt= getmetatable(m) if mt then ASSUME( not mt.__index ) mt.__index= _G else setmetatable(m, { __index=_G } ) end setfenv( 0, m ) -- "global global" (the thing C code uses) -- local ret= lib.static.gluahost( fn, m ) setfenv( 0, OLD_G ) if not mt then setmetatable(m,nil) else mt.__index= nil end return ret else -- The old way.. return lib.static.gluahost( fn, namespace_tbl ) end end ----- -- 'MAP_PATH( path_str/tbl [, ...] )' -- 'MAP_PATH()' <-- prints existing map paths -- -- Add the given directory (directories) into the search list of modules. -- function MAP_PATH( ... ) -- if not arg[1] then -- print existing map paths for _,v in ipairs(_mappath) do -- in order print( '\t'..v ) end end for _,v in ipairs(arg) do -- if type(v)=="table" then MAP_PATH( unpack(v) ) -- expand table parameters -- elseif type(v)=="string" then if v~="" then v= Loc_AddTerminatingSlash(v) end table.insert( _mappath, Loc_FullPath(v) ) -- elseif not v then -- hole in the table, just skip. -- else ASSUME( false, "Unknown param for 'MAP_PATH()': "..v ) end end end ----- -- 'MAP( key_str, filename_str [,filename2 [,...]] )' -- -- Called by a configuration file that maps library id's to actual files -- implementing them. -- -- Note: A single key may be mapped to several modules, so that they all -- together form the necessary behaviour. The mapped files are remembered -- in order. -- -- Note: No initialization happens at this stage, not even the checking that -- the file exists. That is done on demand at first 'USES()' needing -- the library. -- -- Note: The _full_ pathname of the file is stored, for a purpose. This allows -- the applications to change directories and the mappings won't be -- affected (even if they are relative). -- function MAP( key, ... ) -- if not arg[1] then -- if key then Loc_PrintMap( key ) else for k in _lookup do Loc_PrintMap(k) end end return end -- 'arg' given, do a mapping -- _lookup[key]= _lookup[key] or {} -- Append filenames to the lookup: -- for _,fn in ipairs(arg) do -- go in order table.insert( _lookup[key], fn ) end end ----- -- tbl= USES( key_str [,"quiet"] [,"global"] [,"try"] [,release_num] [,args_tbl] ) -- -- Purpose: Makes sure that a certain module (or rather, its API) is available. -- Used by application scripts (or other modules) to tell their -- dependencies. -- -- Params: The 'key' is a key into the library lookup table (above), which -- allows the implementation behind a certain API to be freely exchanged -- (as long as the API behaviour remains untouched). -- -- "quiet" flag prevents any informational printouts. -- "global" flag copies the contents to the global namespace. -- "try" does not care if the module's not found - just returns 'nil'. -- -- Returns: A reference to the created module table (also placed under global -- 'key' name) or 'nil' if the module couldn't be loaded. -- function USES( key, ... ) -- local cmd, map_tbl, file, target_tbl, err, tmp, skip_infocheck local args, release= nil local quiet= false local global= false local try= false local globalize_func= nil -- void= func( namespace_tbl ) if type(key) ~= "string" then error( "USES(): Place hyphens around the module name." ) end for i,v in ipairs(arg) do -- skip ".n" field -- if type(v)=="number" then release= v elseif type(v)=="table" then args= v elseif v=="quiet" then quiet= true elseif v=="global" then global= true elseif v=="try" then try= true else error( "Unknown param: "..v ) end end if not quiet then _WRITE( "USES '"..key.."'" ..(global and " (global)" or "") ..": " ) end ----- -- Make sure the namespace exists before passing it to the modules. -- if string.find( key, "%." ) then target_tbl= Loc_EnsureTableExists( key ) -- "lib.*" else -- "global", "debug", "libext" .. -- -- Without this, we'd collide at least with 'debug' namespace and possibly -- others in the future as well? -- target_tbl= Loc_EnsureTableExists( "lib.others."..key ) -- pushes them inside the 'lib' realm end ASSUME( target_tbl ) if target_tbl._name and (not global) then -- if not quiet then _PRINT( "already loaded." ); end return target_tbl end target_tbl._name= key ----- -- Resolve mapping ("do we know this guy..?!?") -- map_tbl= _lookup[key] if not map_tbl then error( "No module mapping for key '".. key .."'!" ) end ----- -- Find the files and link to them.. -- for i,v in ipairs(map_tbl) do -- file= Loc_FindModule(v) if not file then if not quiet then _PRINT "not found!"; end return nil end if type(file)=="table" then -- -- It's not a file but a library alias. -- -- _Copy_ the contents, don't assign! -- (we want to allow for cascading loading, right..) -- --for k,v in file do -- target_tbl[k]= v --end ASSUME( false ) else ASSUME( type(file)=="string" ) -- Print actually used filenames: -- if not quiet then _PRINT( file ); end -- Do the actual loading (into 'target_tbl') -- if Loc_Suffix(v) == ".lua" then local ret -- -- Lua scripts: -- -- The script is expected to return a 'namespace table', which -- contains its functions and other data. -- ret, globalize_func= dofile(file, unpack(args or {})) if not ret then if try then return nil; end error( "Error loading '"..file.."'." ) end if globalize_func then ASSUME( type(globalize_func) == "function" ) end if type(ret)=="function" then ASSUME( false ) -- Not implemented yet! -- elseif type(ret)=="table" then -- Copy contents of received table to those already in target -- (allows several modules to be loaded into same namespace) -- for k,v in ret do target_tbl[k]= v end -- What should we do if the module uses metatables..? -- local mt= getmetatable(ret) if mt then if getmetatable(target_tbl) then error( "Not ready to merge metatables, yet.." ) -- ..but could be in the future (fix this if it hurts!) else setmetatable( target_tbl, mt ) -- okay, we'll use it end end elseif type(ret)=="boolean" then -- okay, it didn't provide any namespace -- skip_infocheck= true else error( "Unknown return value (not table or function) ".. "from '"..file.."'." ) end else -- Binary modules: (currently, only gluax supported) -- if try and (not Loc_FileExists(file)) then return nil -- avoid giving error messages end local old_arg= arg arg= args or {} arg[0]= file local err= lib.static.gluahost( arg[0], target_tbl ) arg= old_arg if err<0 then error( "Error '"..err.."' loading '"..file.."'." ) end -- Are there baked-in wrappers in the module? (1..N) -- if target_tbl._wrap then -- local wraps= target_tbl._wrap target_tbl._wrap= nil -- no longer required ASSUME( type(wraps)=="table" ) local low_api= target_tbl -- C level API local public_api= nil target_tbl= nil for kk,vv in wraps do -- -- HACK: 'loadstring()' block does not get parameters -- through the normal way (at least, Lua 5.0.1). -- --local tbl= assert( loadstring(vv)( low_api ) ) -- local old_arg= rawget( _G, "arg" ) rawset( _G, "arg", { low_api } ) local tbl= assert( loadstring(vv) )() rawset( _G, "arg", old_arg ) if not public_api then -- 1st wrapper public_api= tbl else -- 2..N for kkk,vvv in tbl do public_api[kkk]= vvv end end local mt= getmetatable(tbl) if mt then local mt2= getmetatable(public_api) if not mt2 then setmetatable(public_api,mt) else for kkk,vvv in mt do mt2[kkk]= vvv -- append methods end end end end target_tbl= ASSUME( public_api ) end end end end -- Check for compliance with the 'info' fields (nasty, aren't we!) -- if type(target_tbl._info) ~= "table" then if not skip_infocheck then _WARN( "Module '"..key.."': missing info fields!" ) end else -- 1= must-have -- 2= optional -- local tags= { MODULE=1, AUTHOR=1, LICENSE=1, PLATFORM=2, COPYRIGHT=2, DEPENDENCIES=2, NOTES=2, CREDITS=2, LINKS=2, RELEASE=2, } for k,_ in target_tbl._info do if not tags[k] then _WARN( "Unknown info field: '"..k.."'." ) else tags[k]= nil -- we've had this! end end -- Check that we got all must-haves: -- for k,v in tags do if v==1 then _WARN( "Must-have info field '"..k.."' is missing!" ) end end end -- Check that the caller gets a fresh module: -- if release then local tmp= target_tbl._info.RELEASE if tmp and (tmp>=release) then -- all is okay :) else error( "Module "..key.." must be updated!".. (tmp and (" ("..tmp.." < "..release..")" ) or "") ) end end -- All done, module loaded and available at 'lib.*' -- if global then -- if globalize_func then globalize_func( target_tbl ) else for k,v in target_tbl do rawset(_G,k,v) -- tolerant to access checking end end end return target_tbl end ----- -- Prepare for arg override: -- -- When 'kernel.lua' is loaded, 'arg' is nil. It gets set up later, -- and 'gluahost.c' gladly intercepts that call for us. :) -- -- The reason? Just because we care.. and we modify the "flag=value" -- syntax params for tha apps, placing them in "arg.flag" etc. -- for easy access. -- -- Note: This introduces an incompatibility with the way Lua 5.0 -- parses cmdline args. If that matters, just rename this -- func and things will be back to normal. -- function _SetArgs(tbl) -- called by 'gluahost.c' ----- -- tbl[-5].. are the exe filename etc. that's not of interest -- to us (let alone the application scripts!) -- -- tbl[0] is the name of the application script -- -- tbl[1..N] are the params to the app -- arg= { [0]= tbl[0] } for i,v in ipairs(tbl) do -- 1..N -- local _,_, flag,val= string.find( v, "(.+)=(.+)" ) if flag then arg[flag]= val else table.insert( arg, v ) end end -- Once this is called we can detach ourselves -- _SetArgs= nil end ----- -- Debugging features (write out to console): -- function _MyHook( type ) -- 'type'= "call"/"return"/"line"/"count" -- local info= debug.getinfo( 2, "n" ) local name name= (info and info.name) or "(no name)" print( "Debug: " ..type.. " '" ..name.. "'" ) end --debug.sethook( _MyHook, "cl" ) --==( Main level )==-- function help( issue ) -- if lib.others.globals then lib.others.globals.off() -- interactive mode end print( [[ Short help: USES 'lib.sys' to load a module MAP('my.module', filename) to add a module MAP() to see the current (default) mappings dump(lib.sys) to see the contents of a module help() to reprint this message ]].."\n".. --Global namecheck is on - can be switched off by 'globals.off()'. -- ( (WIN32 and (not WIN32_MSYS)) and "Ctrl-Z" or "Ctrl-D") .." to exit." ) -- -- Note: For MSYS, neither Ctrl-Z or Ctrl-D works.. :) end -- Give a welcome message: -- print( [[ ============================= == Welcome to LuaX! == == LuaX is released under the LGPL Open Source license, see 'license.txt'. == Individual modules may use other licenses. == == Copyright (c) 2003-04, asko.kauppi@sci.fi =============================]] ) -- Autoload 'mappings.lua' if we can find it. -- local fn= LUAX_ROOT.."mappings.lua" ASSUME( Loc_FileExists(fn), "Mappings file '"..fn.."' not found!" ) ASSUME( dofile(fn) ) -- return true