--[[ Cratera Bootstrap Copyright (C) 2024 Soni L. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --]] --[[ Parts of this file are based on PUC-Rio Lua, available under the following license: * Copyright (C) 1994-2018 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] -- The Cratera Bootstrap library sets up Cratera "proper", not just the -- compiler. However, it also expects a vanilla(-compatible) Lua environment. -- In particular, it uses the io library to access the filesystem. -- It also expects _G to be the default environment. -- NOTE: file:read() has the a-bit-annoying property of resetting EOF and other -- errors every time it's called, so we must pass (n, 0) if we want a reliable -- EOF indicator (which we do). local cratera = require "cratera" local cratera_env = setmetatable({lua=_G}, {__index=_G}) cratera_env._G = cratera_env -- cratera.lib actually injects itself into *us*! package.loaded[...] = cratera_env local io_open = io.open local stdin = io.stdin -- only use setfenv if load doesn't support env argument local setfenv = (loadstring or load)("return setfenv", nil, nil, {})() -- we don't know the real BUFSIZ, use a common memory page size (16K, as on M1) local BUFSIZ = 16384 local cratera_loader_base = cratera.load -- it is convenient to set cratera_env.load -- it will be replaced with the proper function in cratera.lib cratera_env.load = cratera_loader_base -- duplicated with init.lua local LUA_SIGNATURE if string.dump then LUA_SIGNATURE = string.dump(function() end):sub(1,1) else LUA_SIGNATURE = string.char(27) end -- based on lauxlib.c, PUC Rio local function skip_interpreter_line(file) -- lua skips UTF-8 BOM, we do not. -- join us, reject the BOM. local initial_buf = "" -- see NOTE at the start of this file local cdata, err_or_more_data = file:read(1, 0) if cdata == "#" and err_or_more_data then -- skip interpreter line repeat cdata, err_or_more_data = file:read(1, 0) until cdata == "\n" or not cdata or not err_or_more_data -- align line numbers so error messages are correct! initial_buf = initial_buf .. "\n" if cdata and err_or_more_data then cdata, err_or_more_data = file:read(1, 0) end end return initial_buf, cdata, err_or_more_data end local function cratera_loadfile(filename, mode, env) if env == nil then -- load default env - if it doesn't exist, load lua env env = cratera_env end local file, err if filename ~= nil then file, err = io_open(filename, "r") if not file then return nil, ("cannot open %s: %s"):format(filename, err) end else file = stdin end local initial_buf, cdata, err_or_more_data = skip_interpreter_line(file) if cdata == LUA_SIGNATURE and err_or_more_data and filename then -- binary file file:close() file, err = io_open(filename, "rb") if not file then return nil, ("cannot reopen %s: %s"):format(filename, err) end initial_buf, cdata, err_or_more_data = skip_interpreter_line(file) end if cdata then initial_buf = initial_buf .. cdata end -- must declare and set separately -- this is a bit involved. importantly: the chunk (passed to load()) always -- tail calls into the "reader", however we redefine "reader" based on -- various condtions (on first call, on eof, on error) local reader reader = function() reader = function() local buf buf, err_or_more_data = file:read(BUFSIZ, 0) if not buf or not err_or_more_data then reader = function() end end if buf == "" then return reader() end return buf end if not err_or_more_data then reader = function() end end if initial_buf == "" then return reader() end return initial_buf end local chunkname if not filename then chunkname = "=stdin" else chunkname = "@" .. filename end local func, loader_err = cratera_loader_base(function() return reader() end, filename, mode, env) local closeok, closeerr = file:close() -- at this point, err_or_more_data *should* be nil. if err_or_more_data then -- unless it's an error return nil, ("cannot read %s: %s"):format(filename, err_or_more_data) end -- lua doesn't quite check this properly but it's possible for an error to -- be delayed until close if not closeok and closeerr then return nil, ("cannot read %s: %s"):format(filename, closeerr) end if setfenv and func and env then setfenv(func, env) end -- arity adjustments if func then return func else return func, loader_err end end cratera_env.loadfile = cratera_loadfile local searchers = package.searchers or package.loaders local dirsep, pathsep, pathmark = package.config:match("^([^\n]+)\n([^\n]+)\n([^\n]+)") local CRATERA_SEP = ".cratera.d" .. dirsep local tconcat = table.concat do -- discover the cratera path local default_path = require "cratera.prebuilt.path" local cratera_path_env = os.getenv("CRATERA_PATH") if cratera_path_env then local default_mark = cratera_path_env:find(pathsep..pathsep, 1, true) if not default_mark then cratera_env.craterapath = cratera_path_env else local left = cratera_path_env:sub(1, default_mark - 1) local right = cratera_path_env:sub(default_mark + 2) if #left > 0 and #right > 0 then cratera_env.craterapath = left .. ";" .. default_path .. ";" .. right elseif #left > 0 then cratera_env.craterapath = left .. ";" .. default_path elseif #right > 0 then cratera_env.craterapath = default_path .. ";" .. right else cratera_env.craterapath = default_path end end else cratera_env.craterapath = default_path end end local error = error local type = type local assert = assert local psearchpath = package.searchpath or (function() assert(#pathsep == 1, "unsupported lua configuration") assert(#pathmark == 1, "unsupported lua configuration") local pathfinder = "[^"..pathsep.."]*" return function(name, path, sep, rep) assert(sep == ".") -- table trick is safe to use with "untrusted" input (e.g. dirsep) local filename = name:gsub("%.", {["."]=rep}) -- this is a bit inefficient but it's the best we can do (safely)... local filepaths = path:gsub(".", {[pathmark]=filename}) -- this is safe given the previous assertions for filepath in filepaths:gmatch(pathfinder) do local file = io_open(filepath) if file then file:close() return filepath end end return nil, "no file '" .. filepaths:gsub(".", {[pathsep]="\n\tno file'"}) .. "'" end end)() -- put cratera searcher first, before lua searcher table.insert(searchers, 2, function(modname) if type(cratera_env.craterapath) ~= "string" then error("craterapath must be a string") end local filename, err = psearchpath(modname, cratera_env.craterapath, ".", CRATERA_SEP) if not filename then return err end local mod, err = cratera_loadfile(filename) if mod then return mod, filename else error(("error loading module '%s' from file '%s':\n\t%s"):format(modname, filename, err)) end end) -- load the cratera lib. require "cratera.lib"