From 5eecbd6ef771a54b455d96f4033212062d7c3f8f Mon Sep 17 00:00:00 2001 From: SoniEx2 Date: Wed, 29 May 2024 23:00:53 -0300 Subject: Implement cratera bootstrap --- src/cratera/bootstrap.lua | 258 ++++++++++++++++++++++++++++++++++++++++++++++ src/cratera/compiler.lua | 8 +- src/cratera/init.lua | 9 +- 3 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 src/cratera/bootstrap.lua (limited to 'src/cratera') diff --git a/src/cratera/bootstrap.lua b/src/cratera/bootstrap.lua new file mode 100644 index 0000000..88cef17 --- /dev/null +++ b/src/cratera/bootstrap.lua @@ -0,0 +1,258 @@ +--[[ + 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" diff --git a/src/cratera/compiler.lua b/src/cratera/compiler.lua index 2f4a998..cb0681f 100644 --- a/src/cratera/compiler.lua +++ b/src/cratera/compiler.lua @@ -1,6 +1,6 @@ --[[ This file is part of Cratera Compiler - Copyright (C) 2019 Soni L. + Copyright (C) 2019, 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 @@ -42,7 +42,9 @@ local END_OF_STMT = {} local END_OF_CRATERA = {} -- implementation of myobj:[mytrait].myfunction(...) -local CRATERA_FUNCTION = "function(myobj, mytrait, myfunction, ...) return myobj[mytrait][myfunction](myobj, ...) end" +-- TODO benchmark under luajit? +local CRATERA_FUNCTION = "_CRATERA_INVOKE_TRAIT" +local CRATERA_INIT = "local function "..CRATERA_FUNCTION.."(myobj, mytrait, myfunction, ...) return myobj[mytrait][myfunction](myobj, ...) end " local EXTRA_DATA = {[TK.NAME] = true, [TK.INT] = true, [TK.FLT] = true, [TK.STRING] = true, [END_OF_STMT] = true} @@ -134,7 +136,7 @@ defs[parser.EOZ] = function(state, token) return FINISH end defs[parser.FALLBACK] = function(state, token) - local results = state.results or (function() state.results = {} return state.results end)() + local results = state.results or (function() state.results = {CRATERA_INIT, n=1} return state.results end)() do -- handle newlines. this allows error messages to correctly map between lua and cratera local oldline = state.oldline or 1 diff --git a/src/cratera/init.lua b/src/cratera/init.lua index bac3f53..a354d56 100644 --- a/src/cratera/init.lua +++ b/src/cratera/init.lua @@ -16,13 +16,16 @@ along with this program. If not, see . --]] --- This code is highly experimental and not very good - local parser = require "cratera.parser" local luatokens = require "cratera.luatokens" local compiler = require "cratera.compiler" -local LUA_SIGNATURE = string.dump(function() end):sub(1,1) +local LUA_SIGNATURE +if string.dump then + LUA_SIGNATURE = string.dump(function() end):sub(1,1) +else + LUA_SIGNATURE = string.char(27) +end local function cratera_load(reader, ...) if type(reader) == "string" and reader:sub(1,1) == LUA_SIGNATURE then -- cgit 1.4.1