summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cratera.cratera.d/lib.cratera40
-rw-r--r--src/cratera/bootstrap.lua258
-rw-r--r--src/cratera/compiler.lua8
-rw-r--r--src/cratera/init.lua9
-rw-r--r--src/host/genpath.lua25
5 files changed, 334 insertions, 6 deletions
diff --git a/src/cratera.cratera.d/lib.cratera b/src/cratera.cratera.d/lib.cratera
new file mode 100644
index 0000000..701de31
--- /dev/null
+++ b/src/cratera.cratera.d/lib.cratera
@@ -0,0 +1,40 @@
+--[[
+    Cratera Library
+    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 <https://www.gnu.org/licenses/>.
+--]]
+
+local cratera_env = _G
+local cratera_loader_base = cratera_env.load
+
+-- only use setfenv if load doesn't support env argument
+local setfenv = (lua.loadstring or lua.load)("return setfenv", nil, nil, {})()
+
+function cratera_env.load(chunk, chunkname, mode, env)
+    if env == nil then
+        env = cratera_env
+    end
+    local func, err = cratera_loader_base(chunk, chunkname, mode, env)
+    if setfenv and func and env then
+        setfenv(func, env)
+    end
+    -- arity adjustments
+    if func then
+        return func
+    else
+        return func, err
+    end
+end
+
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 <https://www.gnu.org/licenses/>.
+--]]
+
+--[[
+    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 <https://www.gnu.org/licenses/>.
 --]]
 
--- 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
diff --git a/src/host/genpath.lua b/src/host/genpath.lua
new file mode 100644
index 0000000..e12e9a8
--- /dev/null
+++ b/src/host/genpath.lua
@@ -0,0 +1,25 @@
+-- generate default cratera path
+-- package.path, but every .lua is replaced with .cratera
+-- or use an explicit CRATERA_PATH
+
+if os.getenv("DEFAULT_CRATERA_PATH") then
+    print(string.format("return %q", os.getenv("DEFAULT_CRATERA_PATH")))
+    return
+end
+
+local pathsep = package.config:match("^[^\n]+\n([^\n]+)")
+local pathfinder = "[^"..pathsep.."]*"
+-- not gonna worry about ".lua.lua" tbh...
+local possible_cratera_path = package.path:gsub("%.lua(.)", {[pathsep] = ".cratera" .. pathsep}):gsub("%.lua$",".cratera")
+local path_parts = {}
+local i = 1
+-- path entries to skip based on filename
+local INIT_FILE = "init.cratera"
+for path_part in possible_cratera_path:gmatch(pathfinder) do
+    if not path_part:find(INIT_FILE, -#INIT_FILE, true) then
+        path_parts[i] = path_part
+        i = i + 1
+    end
+end
+
+print(string.format("return %q", table.concat(path_parts, pathsep)))