Lua Interpreter In Lua


Here's is a Lua 5.1 interpreter (lua.c) reimplemented in Lua. The primary goal is to most faithfully reimplement the standard Lua 5.1 interpreter in pure Lua. The goal is a bit different from InteractiveLua, which also implements a Lua interpreter but has differences and extensions. The task here is also partly analogous to LuaCompilerInLua (the Lua compiler front-end, luac.c, partially reimplemented in Lua).

Possible applications

Here's some things this might be used for:

Status

WARNING: This is not completed but was quickly done just an experiment. Fix omissions/bugs and test if you want to use this in production. Particularly pay attention to error handling.

Source

-- lua.lua - Lua 5.1 interpreter (lua.c) reimplemented in Lua.
--
-- WARNING: This is not completed but was quickly done just an experiment.
-- Fix omissions/bugs and test if you want to use this in production.
-- Particularly pay attention to error handling.
--
-- (c) David Manura, 2008-08
-- Licensed under the same terms as Lua itself.
-- Based on lua.c from Lua 5.1.3.
-- Improvements by Shmuel Zeigerman.
-- Variables analogous to those in luaconf.h
local LUA_INIT = "LUA_INIT"
local LUA_PROGNAME = "lua"
local LUA_PROMPT = "> "
local LUA_PROMPT2 = ">> "
local function LUA_QL(x) return "'" .. x .. "'" end
-- Variables analogous to those in lua.h
local LUA_RELEASE = "Lua 5.1.3"
local LUA_COPYRIGHT = "Copyright (C) 1994-2008 Lua.org, PUC-Rio"
-- Note: don't allow user scripts to change implementation.
-- Check for globals with "cat lua.lua | luac -p -l - | grep ETGLOBAL"
local _G = _G
local assert = assert
local collectgarbage = collectgarbage
local loadfile = loadfile
local loadstring = loadstring
local pcall = pcall
local rawget = rawget
local select = select
local tostring = tostring
local type = type
local unpack = unpack
local xpcall = xpcall
local io_stderr = io.stderr
local io_stdout = io.stdout
local io_stdin = io.stdin
local string_format = string.format
local string_sub = string.sub
local os_getenv = os.getenv
local os_exit = os.exit
local progname = LUA_PROGNAME
-- Use external functions, if available
local lua_stdin_is_tty = function() return true end
local setsignal = function() end
local function print_usage()
 io_stderr:write(string_format(
 "usage: %s [options] [script [args]].\n" ..
 "Available options are:\n" ..
 " -e stat execute string " .. LUA_QL("stat") .. "\n" ..
 " -l name require library " .. LUA_QL("name") .. "\n" ..
 " -i enter interactive mode after executing " ..
 LUA_QL("script") .. "\n" ..
 " -v show version information\n" ..
 " -- stop handling options\n" ..
 " - execute stdin and stop handling options\n"
 ,
 progname))
 io_stderr:flush()
end
local function l_message (pname, msg)
 if pname then io_stderr:write(string_format("%s: ", pname)) end
 io_stderr:write(string_format("%s\n", msg))
 io_stderr:flush()
end
local function report(status, msg)
 if not status and msg ~= nil then
 msg = (type(msg) == 'string' or type(msg) == 'number') and tostring(msg)
 or "(error object is not a string)"
 l_message(progname, msg);
 end
 return status
end
local function tuple(...)
 return {n=select('#', ...), ...}
end
local function traceback (message)
 local tp = type(message)
 if tp ~= "string" and tp ~= "number" then return message end
 local debug = _G.debug
 if type(debug) ~= "table" then return message end
 local tb = debug.traceback
 if type(tb) ~= "function" then return message end
 return tb(message, 2)
end
local function docall(f, ...)
 local tp = {...} -- no need in tuple (string arguments only)
 local F = function() return f(unpack(tp)) end
 setsignal(true)
 local result = tuple(xpcall(F, traceback))
 setsignal(false)
 -- force a complete garbage collection in case of errors
 if not result[1] then collectgarbage("collect") end
 return unpack(result, 1, result.n)
end
local function dofile(name)
 local f, msg = loadfile(name)
 if f then f, msg = docall(f) end
 return report(f, msg)
end
local function dostring(s, name)
 local f, msg = loadstring(s, name)
 if f then f, msg = docall(f) end
 return report(f, msg)
end
local function dolibrary (name)
 return report(docall(_G.require, name))
end
local function print_version()
 l_message(nil, LUA_RELEASE .. " " .. LUA_COPYRIGHT)
end
local function getargs (argv, n)
 local arg = {}
 for i=1,#argv do arg[i - n] = argv[i] end
 if _G.arg then
 local i = 0
 while _G.arg[i] do
 arg[i - n] = _G.arg[i]
 i = i - 1
 end
 end
 return arg
end
--FIX? readline support
local history = {}
local function saveline(s)
-- if #s > 0 then
-- history[#history+1] = s
-- end
end
local function get_prompt (firstline)
 -- use rawget to play fine with require 'strict'
 local pmt = rawget(_G, firstline and "_PROMPT" or "_PROMPT2")
 local tp = type(pmt)
 if tp == "string" or tp == "number" then
 return tostring(pmt)
 end
 return firstline and LUA_PROMPT or LUA_PROMPT2
end
local function incomplete (msg)
 if msg then
 local ender = LUA_QL("<eof>")
 if string_sub(msg, -#ender) == ender then
 return true
 end
 end
 return false
end
local function pushline (firstline)
 local prmt = get_prompt(firstline)
 io_stdout:write(prmt)
 io_stdout:flush()
 local b = io_stdin:read'*l'
 if not b then return end -- no input
 if firstline and string_sub(b, 1, 1) == '=' then
 return "return " .. string_sub(b, 2) -- change '=' to `return'
 else
 return b
 end
end
local function loadline ()
 local b = pushline(true)
 if not b then return -1 end -- no input
 local f, msg
 while true do -- repeat until gets a complete line
 f, msg = loadstring(b, "=stdin")
 if not incomplete(msg) then break end -- cannot try to add lines?
 local b2 = pushline(false)
 if not b2 then -- no more input?
 return -1
 end
 b = b .. "\n" .. b2 -- join them
 end
 saveline(b)
 return f, msg
end
local function dotty ()
 local oldprogname = progname
 progname = nil
 while true do
 local result
 local status, msg = loadline()
 if status == -1 then break end
 if status then
 result = tuple(docall(status))
 status, msg = result[1], result[2]
 end
 report(status, msg)
 if status and result.n > 1 then -- any result to print?
 status, msg = pcall(_G.print, unpack(result, 2, result.n))
 if not status then
 l_message(progname, string_format(
 "error calling %s (%s)",
 LUA_QL("print"), msg))
 end
 end
 end
 io_stdout:write"\n"
 io_stdout:flush()
 progname = oldprogname
end
local function handle_script(argv, n)
 _G.arg = getargs(argv, n) -- collect arguments
 local fname = argv[n]
 if fname == "-" and argv[n-1] ~= "--" then
 fname = nil -- stdin
 end
 local status, msg = loadfile(fname)
 if status then
 status, msg = docall(status, unpack(_G.arg))
 end
 return report(status, msg)
end
local function collectargs (argv, p)
 local i = 1
 while i <= #argv do
 if string_sub(argv[i], 1, 1) ~= '-' then -- not an option?
 return i
 end
 local prefix = string_sub(argv[i], 1, 2)
 if prefix == '--' then
 if #argv[i] > 2 then return -1 end
 return argv[i+1] and i+1 or 0
 elseif prefix == '-' then
 return i
 elseif prefix == '-i' then
 if #argv[i] > 2 then return -1 end
 p.i = true
 p.v = true
 elseif prefix == '-v' then
 if #argv[i] > 2 then return -1 end
 p.v = true
 elseif prefix == '-e' then
 p.e = true
 if #argv[i] == 2 then
 i = i + 1
 if argv[i] == nil then return -1 end
 end
 elseif prefix == '-l' then
 if #argv[i] == 2 then
 i = i + 1
 if argv[i] == nil then return -1 end
 end
 else
 return -1 -- invalid option
 end
 i = i + 1
 end
 return 0
end
local function runargs(argv, n)
 local i = 1
 while i <= n do if argv[i] then
 assert(string_sub(argv[i], 1, 1) == '-')
 local c = string_sub(argv[i], 2, 2) -- option
 if c == 'e' then
 local chunk = string_sub(argv[i], 3)
 if chunk == '' then i = i + 1; chunk = argv[i] end
 assert(chunk)
 if not dostring(chunk, "=(command line)") then return false end
 elseif c == 'l' then
 local filename = string_sub(argv[i], 3)
 if filename == '' then i = i + 1; filename = argv[i] end
 assert(filename)
 if not dolibrary(filename) then return false end
 end
 i = i + 1
 end end
 return true
end
local function handle_luainit()
 local init = os_getenv(LUA_INIT)
 if init == nil then
 return -- status OK
 elseif string_sub(init, 1, 1) == '@' then
 dofile(string_sub(init, 2))
 else
 dostring(init, "=" .. LUA_INIT)
 end
end
local import = _G.import
if import then
 lua_stdin_is_tty = import.lua_stdin_is_tty or lua_stdin_is_tty
 setsignal = import.setsignal or setsignal
 LUA_RELEASE = import.LUA_RELEASE or LUA_RELEASE
 LUA_COPYRIGHT = import.LUA_COPYRIGHT or LUA_COPYRIGHT
 _G.import = nil
end
if _G.arg and _G.arg[0] and #_G.arg[0] > 0 then progname = _G.arg[0] end
local argv = {...}
handle_luainit()
local has = {i=false, v=false, e=false}
local script = collectargs(argv, has)
if script < 0 then -- invalid args?
 print_usage()
 os_exit(1)
end
if has.v then print_version() end
local status = runargs(argv, (script > 0) and script-1 or #argv)
if not status then os_exit(1) end
if script ~= 0 then
 status = handle_script(argv, script)
 if not status then os_exit(1) end
else
 _G.arg = nil
end
if has.i then
 dotty()
elseif script == 0 and not has.e and not has.v then
 if lua_stdin_is_tty() then
 print_version()
 dotty()
 else dofile(nil) -- executes stdin as a file
 end
end

See Also


RecentChanges · preferences
edit · history
Last edited August 14, 2023 5:19 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /