Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
function traceback()
local errors = { }
local ok, err, name
local i = 2
while true do
ok, err = pcall(_G.error, '', i)
name = err:match("^[^:]+")
if name ~= "bios" and name ~= "see" and name ~= "xpcall" and name ~= "pcall" then break end
i = i + 1
end
while true do
ok, err = pcall(_G.error, '', i)
local name = err:match("^[^:]+")
if (name == "bios" or name == "see" or name == "xpcall" or name == "pcall" or name == "shell") or not err then break end
table.insert(errors, err)
i = i + 1
end
return errors
end
function formatTraceback(err, errors, level)
level = level < #errors and levels or #errors
if not err then
err = ""
else
--[[local i = err:find(":")
if i then
local j = err:find(":", i + 1)
if j then
err = err:sub(j + 2, -1)
end
end]]
end
local f = err
for i = 1, level do
err = errors[i]
local ls = err:find(":")
if ls then ls = ls + 1 end
local le = err:find(":", ls)
if le then le = le - 1 end
local line = ls and le and err:sub(ls, le)
local location = ls and err:sub(1, ls - 2) or err
f = f .. "\n@ line " .. (le and line or "?") .. " in " .. location
end
return f .. (#errors - level > 0 and "\n... and " .. (#errors - level) .. " more." or "")
end
local function error(err, level, b)
--print(err)
--while true do end
--[[if term.isColor() then
term.setTextColor(colors.red)
end
print(formatTraceback(err, traceback(), 10))
if term.isColor() then
term.setTextColor(colors.white)
end
_G.error()]]
end
function errorHandler(err)
--print(err)
if term.isColor() then
term.setTextColor(colors.red)
end
print(formatTraceback(err, traceback(), 10))
if term.isColor() then
term.setTextColor(colors.white)
end
end
StandardGlobals = { }
--[[
Creates a new anonymous class. This class' global table will attempt to index from the parent environment.
@param SeeRT:rt The VM to run this function for.
@param string:name The name to use inside the definition.
@param table:annotations The table of annotations to be executed for the class.
@param function:def The function used to define this class.
@return Class The newly created anonymous class.
]]
function StandardGlobals.class(rt, name, annotations, def)
local g = getfenv(def)
local useG = setmetatable({ }, { __index = g })
local anonName = "__anon[" .. tostring(rt.nAnonClasses) .. "]"
rt.nAnonClasses = rt.nAnonClasses + 1
return rt:loadClass(def, annotations, anonName, name, useG)
end
--[[
Gets the type of a given value.
@param SeeRT:rt The VM to run this function for.
@param value:any The value to get the type of.
@return string|table The type identifier if value if primitive, or the class of value if value is an object.
]]
function StandardGlobals.typeof(rt, value)
--print(type(value))
if type(value) == "table" then
if getmetatable(value) == rt.classMT then
return rawget(value, "__class")
end
return "table"
end
return type(value)
end
--[[
Checks if an object is a primitive lua object or a SEE object
@param SeeRT the SEE rt
@param any the object to check
]]
function StandardGlobals.isprimitive(rt, value)
if type(value) == "table" then
return getmetatable(value) ~= rt.classMT
end
return true
end
--[[
Casts a value to a particular class.
@param SeeRT:rt The VM to run this function for.
@param any:value The value to cast.
@param string|table:castType The type to cast to.
]]
function StandardGlobals.cast(rt, value, castType)
if StandardGlobals.typeof(rt, value) == castType then
return value
end
if type(castType) == "string" then
-- Casting to primitive.
if castType == "string" then
if StandardGlobals.isprimitive(rt, value) then
return tostring(value)
else
return value:toString():lstr()
end
elseif castType == "number" then
if StandardGlobals.isprimitive(rt, value) then
return tonumber(value)
else
return value:toString():toNumber()
end
else
-- TODO
end
elseif type(castType) == "table" then
if castType.__cast then
return castType.__cast(value)
else
StandardGlobals.throw(rt, rt:loadClassFromAny("see.rt.CastException"):new(StandardGlobals.typeof(rt, value), castType))
end
-- Casting to Object.
-- TODO
else
--StandardGlobals.throw(rt, rt:loadClassFromAny("see.rt.CastException"):new())
end
end
--[[
Casts an object to a different class by setting its metatable.
@param SeeRT:rt The VM to run this function for.
@param see.base.Object value
]]
function StandardGlobals.reinterpret_cast(rt, value, castType)
if type(value) ~= "table" or not value.__type then
local RuntimeException = self:loadClassFromAny("see.rt.RuntimeException")
self:throw(RuntimeException:new("Cannot use reinterpret_cast to cast a primitive type."))
--error("Cannot use reinterpret_cast to cast a primitive type.")
end
setmetatable(value, castType.__meta)
end
--[[
Throws an exception.
@param SeeRT:rt The VM to run this function for.
@param see.base.Exception The exception to throw.
]]
function StandardGlobals.throw(rt, exception)
if exception then
rt:throw(exception)
else
rt:throw(rt:loadClassFromAny("see.rt.RuntimeException"):new("Invalid argument to throw."))
end
end
--[[
Tries to execute code until an error occurs. Calls catch code if an error occurs.
@param SeeRT:rt The VM to run this function for.
@param function:tryFunc Function to run and catch errors from.
@param function:catchFunc(see.base.Exception) Function to run on error.
]]
function StandardGlobals.try(rt, tryFunc, catchFunc)
local suc, err = pcall(tryFunc)
if suc then return end
if err then rt.lastException = rt:loadClassFromAny("see.rt.RuntimeException"):new(err) end
catchFunc(rt.lastException)
end
--[[
Converts a Lua string to a see.base.String.
@param see.base.String... Strings to concatenate.
]]
function StandardGlobals.STR(rt, ...)
return rt.base.String:new(...)
end
--[[
Create a copy of a table
]]
local function copy(t)
local r = { }
for k, v in pairs(t) do
r[k] = v
end
return r
end
SeeRT = { }
SeeRT.__index = SeeRT
--[[
Creates a new SeeRT.
]]
function SeeRT.new(natives, seePath)
local self = { }
setmetatable(self, SeeRT)
self.seePath = seePath
self.maxThreadID = 0
self.threads = { }
self.objThreads = { }
self.threadFilters = { }
self.threadsToIDs = { }
self.idsToThreads = { }
self.nAnonClasses = 0
self.standardGlobals = { }
for k, v in pairs(StandardGlobals) do
self.standardGlobals[k] = function(...) return StandardGlobals[k](self, ...) end
end
self.base = { }
self.rt = { }
self.event = { }
self.classes = { }
self.classLookup = { }
self.classTables = { }
self.natives = natives
self.natives.error = error
self.archives = { }
self.classPaths = { }
local rt = self
function self.index(t, k)
-- Class access.
if rt.classLookup[t] then
--print(rt.classTables[t].name .. "[" .. k .. "]")
local v = rt.classTables[rt.rt.Class][k]
if v then
return v
end
v = rt.classTables[rt.base.Object][k]
if v then
return v
end
return rt.classTables[t][k]
end
-- Instance access.
if k == "__class" then
return nil
end
local oi
local class = rawget(t, "__class")
--print("[" .. k .. "]")
repeat
--print(rt.classTables[class].__name .. ", super: " .. (self.classTables[class].__super and self.classTables[self.classTables[class].__super].__name or ""))
local v = rt.classTables[class][k]
if v then
return v
end
if not oi then
oi = rt.classTables[class].opIndex
end
class = self.classTables[class].__super
until not class
if oi then
return oi(t, k)
end
end
function self.newindex(t, k, v)
if rt.classTables[t] then
rt.classTables[t][k] = v
else
local oni = rawget(t, "opNewIndex")
if not oni or not oni(t, k, v) then
rawset(t, k, v)
end
end
end
--metamethods mapped to easier method names
self.metaMethodMap = {
opAdd = "__add",
opSub = "__sub",
opMul = "__mul",
opDiv = "__div",
opMod = "__mod",
opPow = "__pow",
opNeg = "__unm",
opConcat = "__concat",
opEq = "__eq",
opLT = "__lt",
opLE = "__le",
opCall = "__call"
}
self.opMap = { }
self.classMT = { __index = self.index, __newindex = self.newindex }
for k, v in pairs(self.metaMethodMap) do
self.opMap[v] = k
self.classMT[v] = function(t, ...)
return t[k](t, ...)
end
end
--temporary loadClass function to load see.base.Object, see.rt.Class and a DefaultClassLoader
self.loadClass = function(def, annotations, name, refName, env)
local className = getPackageName(name)
if self.classes[name] then
return self.classes[name]
end
if not refName then
env = copy(self.standardGlobals)
end
local anon = refName ~= nil
refName = refName or className
local class = { }
local fdtnl = false
if name == "see.base.Object" then
self.base.Object = class
fdtnl = true
end
if name == "see.rt.Class" then
self.rt.Class = class
fdtnl = true
end
self.classes[name] = class
self.classLookup[class] = true
self.classTables[class] = { __name = name }
setmetatable(class, self.classMT)
if not fdtnl then
-- Import base classes.
for k, class in pairs(self.base) do
env[k] = class
end
-- Import Events class for convenience.
env["Events"] = self.event.Events
end
-- Setup class environment.
env[refName] = class
setfenv(def, env)
xpcall(def, errorHandler)
local abool = true
for k, v in pairs(annotations) do
local ret
if anon then
ret = self:executeTableStyleAnnotation(env, class, k, v)
else
ret = self:executeAnnotation(env, class, v)
end
if ret then
abool = false
end
end
-- Extend Object by default.
if abool and self.base.Object and name ~= "see.base.Object" then
--rawset(class, "super", self.base.Object)
self.classTables[class].__super = self.base.Object
end
if not rawget(self.classTables[class], "init") then
rawset(self.classTables[class], "init", function() end)
end
local static = rawget(self.classTables[class], "__static")
if static then
static()
end
return class
end
--use loadClass to load see.base.Object and see.rt.Class
--then set up a real ClassLoader
self:loadClassFromAny("see.base.Object")
self:loadClassFromAny("see.rt.Class")
--start with a class loader to load other classes and class loaders (File and Archive)
local DefaultClassLoader = self:loadClassFromAny("see.rt.DefaultClassLoader")
self.classLoader = DefaultClassLoader:new()
--doesnt technically matter.. but it may confuse a user to call getClassLoader on a class and get nil.. so prevent it
for className, class in pairs(self.classes) do
if not self.classTables[class].__classLoader then
self.classTables[class].__classLoader = self.classLoader
end
end
--start using a real ClassLoader ASAP
--again, not technically needed, but loadClass was intended to be a temporary thing ONLY for setting up a ClassLoader
self.loadClass = nil
--start using a FileClassLoader and an ArchiveClassLoader
local FileClassLoader = self:loadClassFromAny("see.rt.FileClassLoader")
local ArchiveClassLoader = self:loadClassFromAny("see.rt.ArchiveClassLoader")
self.fileClassLoader = FileClassLoader:new()
self.archiveClassLoader = ArchiveClassLoader:new()
--load some base classes
self.base.Exception = self:loadClassFromAny("see.base.Exception")
self.base.System = self:loadClassFromAny("see.base.System")
self.base.Iterators = self:loadClassFromAny("see.base.Iterators")
self.base.Array = self:loadClassFromAny("see.base.Array")
self.base.String = self:loadClassFromAny("see.base.String")
self.event.Events = self:loadClassFromAny("see.event.Events")
self.event.UnknownEvent = self:loadClassFromAny("see.event.impl.UnknownEvent")
self.rt.RuntimeException = self:loadClassFromAny("see.rt.RuntimeException")
local sys = self.base.System
sys.setProperty("see.home", self.seePath)
sys.setProperty("see.version", "420") --todo versions
sys.setProperty("file.separator", "/")
sys.setProperty("line.separator", "\n")
sys.setProperty("path.separator", ":")
sys.setProperty("os.arch", "cc64")
sys.setProperty("os.name", "CraftOS") --todo automate
sys.setProperty("os.version", "1.6") --todo automate
return self
end
function SeeRT:throw(exception, level)
self.lastException = exception
_G.error("Uncaught exception " .. exception:getClass():getName():lstr() .. ': "' .. exception.message:lstr() .. '"', level)
end
--[[
Gets a value out of a table using a valid package string.
@param table:t The table to search.
@param string:package The package to look for.
@param string:del The delimiter to use for the package. default='.'
@return any The value found.
]]
function getByPackage(t, package, del)
if not del then del = "%." end
local f = package:find(del)
if f then
return getByPackage(t[package:sub(1, f - 1)], package:sub(f + 1), del)
else
return t[package]
end
end
--[[
Get package end.
@param string:package The package to compute the name for.
@return string:name Name of the package, or nil if invalid or empty package.
]]
function getPackageName(package, del)
if not del then del = "." end
for i = #package, 0, -1 do
if i == 0 or package:sub(i, i) == del then
return package:sub(i + 1)
end
end
end
--[[
Get the components of a package.
@param string:package The package to extract components from.
@return table[string] The components of the given package.
]]
function getPackageComponents(package)
local components = { }
for component in package:gmatch("%w[%w%d]*") do
table.insert(components, component)
end
return components
end
--[[
Finds all annotations in the given code string.
@param string:code The code to search for annotations in.
@return table List of annotation strings.
]]
function getAnnotations(code)
local annotations = { }
for annotation in code:gmatch("%-%-@[^\n]*") do
table.insert(annotations, annotation:sub(4))
end
return annotations
end
--[[
Spawn a thread
]]
function SeeRT:spawnThread(func, obj)
local thread = coroutine.create(function() xpcall(func, errorHandler) end)
self.objThreads[thread] = obj
table.insert(self.threads, thread)
self.maxThreadID = self.maxThreadID + 1
obj.id = self.maxThreadID
self.threadsToIDs[thread] = self.maxThreadID
self.idsToThreads[self.maxThreadID] = thread
self.threadFilters[#self.threads] = -1
return thread
end
--[[
Interrupt a thread
]]
function SeeRT:interruptThread(thread)
os.queueEvent("__thread_interrupt", thread.id)
end
--[[
Start the SEE runtime
]]
function SeeRT:start(mainClass, ...)
local args = { ... }
xpcall(function() (function(mainClass, ...)
local Events = self.event.Events
local UnknownEvent = self.event.UnknownEvent
local args = self.base.Array:new(...)
local threads = self.threads
local f = function() mainClass.main(args) end
threads[1] = coroutine.create(function() xpcall(f, errorHandler) end)
local t = self:loadClassFromAny("see.concurrent.Thread"):new(f)
t.co = threads[1]
t.id = 1
self.objThreads[threads[1]] = t
self.maxThreadID = 1
self.threadsToIDs[threads[1]] = 1
self.idsToThreads[1] = threads[1]
local threadFilters = self.threadFilters
local event
local isNative
threadFilters[1] = { coroutine.resume(threads[1], args) }
while true do
local threadDeaths = { }
for i = 1, #threads do
if threadFilters[i] == -1 then
threadFilters[i] = { coroutine.resume(threads[i], event) }
if coroutine.status(threads[i]) == "dead" then
table.insert(threadDeaths, i)
self.objThreads[threads[i]] = nil
end
elseif coroutine.status(threads[i]) == "dead" then
table.insert(threadDeaths, i)
self.objThreads[threads[i]] = nil
elseif event then
local passEvent = false
if event[1] ~= "__thread_interrupt" then
if event.ident == "terminate" then
self:throw(self.rt.RuntimeException:new("terminated"))
end
if #threadFilters[i] == 1 then
passEvent = true
else
for j = 2, #threadFilters[i] do
if isNative then
if threadFilters[i][j] == event[1] then
passEvent = true
break
end
elseif threadFilters[i][j] == event.ident then
passEvent = true
break
end
end
end
else
if self.idsToThreads[event[2]] == threads[i] then
passEvent = true
event = event[1]
end
end
if passEvent then
local threadData
if isNative then
threadData = { coroutine.resume(threads[i], unpack(event)) }
else
threadData = { coroutine.resume(threads[i], event) }
end
if not threadData[1] then
error(threadData[2], 1)
table.insert(threadDeaths, i)
self.objThreads[threads[i]] = nil
end
threadFilters[i] = threadData
end
if coroutine.status(threads[i]) == "dead" then
table.insert(threadDeaths, i)
self.objThreads[threads[i]] = nil
end
end
end
local death = false
for i = #threadDeaths, 1, -1 do
table.remove(threads, threadDeaths[i])
table.remove(threadFilters, threadDeaths[i])
death = true
end
if death then
local foundNonDaemon = false
for i = 1, #self.objThreads do
if not self.objThreads[i]:isDaemon() then
foundNonDaemon = true
end
end
if not foundNonDaemon then
return
end
end
if #threads == 0 then break end
local eventData = { coroutine.yield() }
local eventParams = { }
for i = 2, #eventData do
eventParams[i - 1] = eventData[i]
end
if Events.isNativeEvent(eventData[1]) then
isNative = true
event = eventData
else
isNative = false
if type(eventData[2]) == "table" and eventData[2].__type then
event = eventData[2]
else
if eventData[1] == "__thread_interrupt" then
event = { "__thread_interrupt", eventData[2] }
else
local EventClass = Events.getEventClass(eventData[1])
if EventClass == UnknownEvent then
xpcall(function() event = EventClass:new(unpack(eventData)) end, errorHandler)
else
xpcall(function() event = EventClass:new(unpack(eventParams)) end, errorHandler)
end
end
end
end
end
end)(mainClass, unpack(args)) end, errorHandler)
end
--[[
Loads a class by trying the standard library first, then from class paths.
@param string:name
@return table The class loaded.
]]
function SeeRT:loadClassFromAny(name)
local class = self.classes[name]
if class then return class end
-- Load from the standard library.
local err
local p = name:gsub("%.", "/")
class, err = self:loadClassFromFile(fs.combine(fs.combine(self.seePath, "/lib/"), p) .. ".lua", name)
local lerr = err
if class then return class end
-- Load from custom class paths.
for _, classPath in pairs(self.classPaths) do
if fs.isDir(classPath) then
class, err = self:loadClassFromFile(fs.combine(classPath, name:gsub("%.", "/") .. ".lua"), name)
else
class, err = self:loadClassFromArchive(self.archives[classPath], name:gsub("%.", "/") .. ".lua", name)
end
if class then return class end
end
if not lerr:find("Could not read file.") then
err = lerr
end
if not class then
local RuntimeException = self:loadClassFromAny("see.rt.RuntimeException")
self:throw(RuntimeException:new("Could not load class " .. name .. ". " .. err))
--error("Could not load class " .. name .. ". " .. err)
end
end
--[[
Executes annotations onto an environment.
@param table:env The function environment.
@param table:annotation Annotation to execute.
]]
function SeeRT:executeAnnotation(env, class, annotation)
local pindex = annotation:find("%s")
if not pindex then
local RuntimeException = self:loadClassFromAny("see.rt.RuntimeException")
self:throw(RuntimeException:new("Failed to execute annotation for class " .. class.name .. "."))
--error("Failed to execute annotation for class " .. class.name .. ".")
end
local aname = annotation:sub(1, pindex - 1)
if aname == "import" then
local name = annotation:sub(pindex + 1):gsub("%s", "")
local class = self:loadClassFromAny(name)
if not class then
local RuntimeException = self:loadClassFromAny("see.rt.RuntimeException")
self:throw(RuntimeException:new("Failed to import class."))
--error("Failed to import class.")
end
env[getPackageName(name)] = class
elseif aname == "native" then
local name = annotation:sub(pindex + 1):gsub("%s", "")
if name == "__rt" then
env.__rt = self
return
end
local keys = getPackageComponents(name)
local lk
local v = self.natives
local w = env
local nativeValue = ""
for _, key in pairs(keys) do
nativeValue = nativeValue .. key .. "."
if not v[key] then
local RuntimeException = self:loadClassFromAny("see.rt.RuntimeException")
self:throw(RuntimeException:new("Could not find native value " .. nativeValue))
--error("Could not find native value " .. nativeValue)
end
v = v[key]
if not w[key] or type(w) ~= "table" then
w[key] = { }
end
lw = w
w = w[key]
lk = key
end
lw[lk] = v
elseif aname == "abstract" then
local name = annotation:sub(pindex + 1):gsub("%s", "")
self.classTables[class][name] = function()
self:throw(self:loadClassFromAny("see.rt.UnimplementedMethodException"):new("Method \"" .. name .. "\" is not implemented!"))
end
elseif aname == "extends" then
local name = annotation:sub(pindex + 1):gsub("%s", "")
local super = env[getPackageName(name)]
self.classTables[class].__super = super
return true
end
return false
end
--[[
Executes table-style annotations for anonymous classes.
@param table:env The function environment.
@param table:annotation Annotation to execute.
@param string:key The annotation type.
@param any:value The value of the annotation.
]]
function SeeRT:executeTableStyleAnnotation(env, class, key, value)
if key == "import" or key == "native" or key == "abstract" then
for i = 1, #value do
self:executeAnnotation(env, class, key .. " " .. value[i])
end
elseif key == "extends" then
if type(value) == "string" then
self:executeAnnotation(env, class, "extends " .. value)
elseif type(value) == "table" then
--rawset(self.classTables[class], "__super", value)
self.classTables[class].__super = value
return true
else
-- TODO: throw error
end
end
return false
end
--[[
Load a class from a file.
@param string:path The path to the class to load.
@param string:name The full name of the class to load.
@return table The loaded class.
@return string Error message, or nil if successful.
]]
function SeeRT:loadClassFromFile(path, name)
if self.fileClassLoader then
return self.fileClassLoader:loadClass(path, name)
else
local fileHandle = fs.open(path, "r")
if not fileHandle then
return nil, "Could not read file."
end
local code = fileHandle.readAll()
fileHandle.close()
-- Setup class execution environment
local def, err = loadstring(code, name)
if not def then
return nil, err
end
if self.classLoader then
return self.classLoader:loadClass(def, getAnnotations(code), name)
else
return self.loadClass(def, getAnnotations(code), name)
end
end
end
-- TODO: Make file offset table. Currently, it takes O(N) time to find each file where N is its position in the file.
--[[
Load a class from a SEE archive.
@param string:archiveBytes The loaded archive.
@param string:path The path to the class to load.
@param string:name The full name of the class to load.
@return table The loaded class.
@return string Error message, or nil if successful.
]]
function SeeRT:loadClassFromArchive(archiveBytes, path, name)
if self.archiveClassLoader then
self.archiveClassLoader:loadClass(archiveBytes, path, name)
elseif self.classLoader then
local loc = archiveBytes:find("F" .. path .. "\0", 1, true)
if not loc then
return nil, "Could not find class " .. name .. " in archive path " .. path .. "."
end
loc = loc + #path + 2
local fin = archiveBytes:find("\0", loc) - 1
local code = archiveBytes:sub(loc, fin)
-- Setup class execution environment
local def, err = loadstring(code, name)
if not def then
return nil, err
end
if self.classLoader then
return self.classLoader:loadClass(def, getAnnotations(code), name)
else
return self.loadClass(def, getAnnotations(code), name)
end
end
end