diff options
Diffstat (limited to 'src/bin')
-rw-r--r-- | src/bin/cratera.lua | 400 |
1 files changed, 397 insertions, 3 deletions
diff --git a/src/bin/cratera.lua b/src/bin/cratera.lua index fa94e96..d86f500 100644 --- a/src/bin/cratera.lua +++ b/src/bin/cratera.lua @@ -1,5 +1,7 @@ +#!/usr/bin/env lua +-- previous line gets replaced by build script (for line number correctness) --[[ - Cratera Interpreter + Cratera Interpreter/REPL Copyright (C) 2024 Soni L. This program is free software: you can redistribute it and/or modify @@ -16,6 +18,398 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. --]] -local cratera = require "cratera" +--[[ + 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. +--]] + +local cratera = require "cratera.bootstrap" +local lua = _G + +local arg = _G.arg +local progname = arg[0] or "cratera" +local ssub = string.sub +local exit = os.exit +local pcall = pcall +local xpcall = xpcall +local assert = assert +--local assert = function(...) return ... end +local traceback = debug.traceback +local type = type +local tostring = tostring +local dtraceback = debug.traceback +local dgetmetatable = debug.getmetatable +local rawget = rawget +local tunpack = table.unpack or unpack +local tconcat = table.concat +local warn = _G.warn +local cratera_load = cratera.load +local cratera_loadfile = cratera.loadfile +local cratera_VERSION = cratera._VERSION +local getenv = os.getenv +local error = error +local sfind = string.find +local collectgarbage = collectgarbage +local select = select + +local stdin, stdout, stderr = io.stdin, io.stdout, io.stderr + +local _ENV = nil + +-- parse arguments +local i = 1 +local erropt = 1 +local interactive, version, exec, warnings, opterror, script +while arg[i] do + erropt = i + local argv = arg[i] + if argv == "--" or argv == "-" then + break + elseif argv == "-i" then + interactive = true + version = true + elseif argv == "-v" then + version = true + elseif warn and argv == "-W" then + warnings = true + elseif ssub(argv, 1, 1) == "-" then + local argkey = ssub(argv, 2, 2) + if argkey == "e" then + exec = true + end + if argkey == "e" or argkey == "l" then + if #argv == 2 then i = i + 1 end + if not arg[i] or ssub(arg[i], 1, 1) == "-" then + opterror = true + break + end + else + opterror = true + break + end + else + script = i + break + end + i = i + 1 +end + +-- print usage +if opterror then + stderr:write(progname .. ": ") + local optkey = ssub(arg[erropt], 2, 2) + if optkey == "e" or optkey == "l" then + stderr:write("'" .. arg[erropt] .. "' needs argument\n") + else + stderr:write("unrecognized option '" .. arg[erropt] .. "'\n") + end + stderr:write(tconcat { + ("usage: " .. progname .. "[options] [script [args]]\n"), + ("Available options are:\n"), + (" -e stat execute string 'stat'\n"), + (" -i enter interactive mode after executing 'script'\n"), + (" -l mod require library 'mod' into global 'mod'\n"), + (" -l g=mod require library 'mod' into global 'g'\n"), + (" -v show version information\n"), + (" -W turn warnings on\n"):sub(1, warn and -1 or 0), + (" -- stop handling options\n"), + (" - stop handling options and execute stdin\n"), + }) + stderr:flush() + pcall(exit, false) + exit(1) +end + +-- do we have a script? if so, shift args now +local cratera_first_arg = 1 +local cratera_last_arg = #arg +if script then + local minarg = 0 + while arg[minarg] do + minarg = minarg - 1 + end + minarg = minarg + 1 + local oldarg = minarg + local newarg = minarg - script + while arg[oldarg] do + arg[newarg] = arg[oldarg] + oldarg, newarg = oldarg + 1, newarg + 1 + end + while arg[newarg] do + arg[newarg] = nil + newarg = newarg + 1 + end + -- the goal is to have the script name at arg[0] + -- while the original arg[0] was the cratera repl/interpreter + -- thus, arg[-script] should end up as the cratera repl/interpreter + assert(arg[-script] == progname) + cratera_first_arg = -script + 1 + cratera_last_arg = -1 + script = arg[0] +end + +local function report(status, ...) + if not status then + if progname then + stderr:write(progname, ": ") + end + stderr:write(..., "\n") + stderr:flush() + end + return status, ... +end + +local function do_call(wrapper_func) + return report(xpcall(function() + return wrapper_func() + end, function(err) + if type(err) ~= "string" then + if dgetmetatable then + local meta = dgetmetatable(err) + if type(meta) == "table" and rawget(meta, "__tostring") then + err = rawget(meta, "__tostring")(err) + end + end + if type(err) ~= "string" then + err = "(error object is a " .. type(err) .. " value)" + end + end + return dtraceback(err) + end)) +end + +local function do_chunk(func, err) + if report(func, err) then + return do_call(func) + end + return func, err +end + +local function do_string(chunk, name) + return do_chunk(cratera_load(chunk, name)) +end + +local function do_file(filename) + return do_chunk(cratera_loadfile(filename)) +end + +local function do_init() + if getenv("CRATERA_INIT") then + local init_script = getenv("CRATERA_INIT") + if ssub(init_script, 1, 1) == "@" then + return do_file(ssub(init_script, 2)) + else + return do_string(init_script, "=CRATERA_INIT") + end + else + return true + end +end + +local function do_library(mod) + local globalname = sfind(mod, "=", 1, true) + if globalname then + globalname, mod = ssub(mod, 1, globalname - 1), ssub(mod, globalname + 1) + else + globalname = mod + end + -- N.B. use cratera.require and NOT lua.require. + -- (by default, cratera.require gets proxied to lua.require, but a + -- previous -e/-l may override it) + local require = cratera.require + return do_call(function() + cratera[globalname] = require(mod) + end) +end + +local function do_args() + -- -e/-l may modify global arg + local origarg = arg + local arg = {} + for i=cratera_first_arg,cratera_last_arg do + arg[i] = origarg[i] + end + local i = cratera_first_arg + while i < cratera_last_arg do + assert(ssub(arg[i], 1, 1) == "-") + local key = ssub(arg[i], 2, 2) + if key == "e" or key == "l" then + local extra = ssub(arg[i], 3) + if extra == "" then + i = i + 1 + extra = arg[i] + assert(extra ~= nil) + end + local ok + if key == "e" then + ok = do_string(extra, "=(command line)") + elseif key == "l" then + ok = do_library(extra) + end + if not ok then return false end + elseif key == "W" then + warn("@on") + end + i = i + 1 + end + return true +end + +local function do_version() + stdout:write(cratera_VERSION, "\n") + stdout:flush() +end + +local function do_script() + local func, err = cratera_loadfile(script) + local arg = cratera.arg + if type(arg) ~= "table" then + error("'arg' is not a table") + end + if func then + return do_call(function() + return func(tunpack(arg)) + end) + end + return report(func, err) +end + +local function do_repl() + local function get_prompt(first_line) + local ok, prompt = do_call(function() + local prompt = cratera[first_line and "_PROMPT" or "_PROMPT2"] + if prompt == nil then + return nil + end + return tostring(prompt) + end) + if not ok or prompt == nil then + return first_line and "> " or ">> " + else + return prompt + end + end + local function readline(first_line) + local prompt = get_prompt(first_line) + stdout:write(prompt) + stdout:flush() + local ok, line = stdin:read(0, "*l") + if not ok and not line then + return nil + elseif ok then + return line + else + error("NYI: read error") + end + end + local function incomplete(ok, err) + if not ok then + return ssub(err, -5, -1) == "<eof>" or ssub(err, -7, -1) == "'<eof>'" + end + return false + end + local function loadline() + local line = readline(true) + if line == nil then return nil end + local retline = "return " .. line .. ";" + local ok, err = cratera_load(retline, "=stdin") + if ok then + return ok + end + -- handle multiline, normal chunks, etc + while true do + local ok, err = cratera_load(line, "=stdin") + if not incomplete(ok, err) then + return ok, err + end + local continuation = readline(false) + if not continuation then + return ok, err + end + line = line .. "\n" .. continuation + end + end + + local function l_print(ok, ...) + if not ok then return end + if select("#", ...) > 0 then + local ok, err = pcall(cratera.print, ...) + if not ok then + if type(err) ~= "string" then + err = "(error object is a " .. type(err) .. " value)" + end + stderr:write("error calling 'print' (" .. err .. ")\n") + end + end + end + + local oldprogname = progname + progname = nil + + while true do + local line, err = loadline() + if line == nil and err == nil then + break + end + if line then + l_print(do_call(line)) + else + report(line, err) + end + end + stdout:write('\n') + stdout:flush() +end + +local function do_main() + if version then + do_version() + end + if not do_init() then + return false + end + if not do_args() then + return false + end + if script then + if not do_script() then + return false + end + end + if interactive then + do_repl() + elseif (not script) and not (exec or version) then + -- no pure lua tty detection :/ + do_version() + do_repl() + end + return true +end + +local status, result = report(pcall(do_main)) -error("not implemented") +pcall(exit, status and result) +if not (status and result) then + exit(1) +end |