still wip modularisation, game somehow boots

This commit is contained in:
minjaesong
2018-06-21 17:33:22 +09:00
parent 6bbfd5d167
commit 8daf0a2c38
266 changed files with 2409 additions and 1122 deletions

View File

@@ -0,0 +1,6 @@
local args = {...}
if (#args ~= 2) then
print([[usage: cp source_file target_file
cp source_file target_directory]])
return end
fs.cp(os.expandPath(args[1]), os.expandPath(args[2]))

View File

@@ -0,0 +1,257 @@
local args = {...}
os.dshenv = {}
--[[
DUMBSHELL: semi sh-compatible language interpreter
SYNOPSIS
dsh [option] [file]
sh [option] [file]
OPTIONS
-c string If the -c option is present, then commands are read from
string. If there are arguments after the string, they are
assigned to the positional parameters, starting with $0.
]]
-- returns full path. if p starts with "/", only the p is returned
local function expandPath(p)
return (p:byte(1) == 47) and p or os.expandPath(p)
end
local function startsFromRoot(p)
return p:byte(1) == 47
end
local function endsWithSlash(p)
return p:byte(#p) == 47
end
--__DSHDEBUG__ = 0x51621D
local function debug(msg)
if __DSHDEBUG__ then print("DEBUG", msg) end
end
local function printErr(msg)
print(DLE..msg)
end
local function shallowCopy(t)
return {table.unpack(t)}
end
-- BUILTINS -------------------------------------------------------------------
local function cd(tArgs)
local dir = tArgs[1]
if (dir == nil or #dir < 1) then return end
local oldWorkingDir = shallowCopy(os.workingDir)
-- parse dir by delimeter '/'
if (dir:byte(1) == 47) then -- if dir begins with '/'
os.setWorkingDir(dir)
else
for word in string.gmatch(dir, "[^/]+") do
machine.println("CD word: "..word)
-- 'execute' directory
-- Rules: '..' pops os.workingDir
-- if dir begins with '/', re-build os.workingDir
-- otherwise, push the 'word' to os.workingDir
if (word == "..") then
os.popWorkingDir()
elseif (word == ".") then
-- pass
else
os.pushWorkingDir(word)
end
end
end
-- check if the directory exists
if not fs.isDir(os.fullWorkPath()) then
os.errorNoSuchFileOrDir("cd: "..dir)
os.workingDir = shallowCopy(oldWorkingDir)
return
end
end
local function exit(tArgs)
exitshell = true
end
local function exec(tArgs)
debug("EXECARGS\t"..table.concat(tArgs, ", "))
if (tArgs[1] == nil or #tArgs[1] < 1) then return end
local filePath = tArgs[1]
local fullFilePath = expandPath(tArgs[1])
local execArgs = {}
for i, v in ipairs(tArgs) do
if (i >= 2) then table.insert(execArgs, v) end
end
local execByPathFileExists = false
local execByPathArg = ""
-- do some sophisticated file-matching
-- step 1: exact file
if fs.isFile(fullFilePath) then
shell.run(fullFilePath, execArgs)
-- step 2: try appending ".lua"
elseif fs.isFile(fullFilePath..".lua") then
shell.run(fullFilePath..".lua", execArgs)
-- step 3: parse os.path (just like $PATH)
-- step 3.1: exact file; step 3.2: append ".lua"
elseif not startsFromRoot(filePath) then
for path in string.gmatch(os.path, "[^;]+") do
-- check if 'path' ends with '/'
if not endsWithSlash(path) then path = path.."/" end
debug(path..filePath)
if fs.isFile(path..filePath) then
execByPathArg = path..filePath
execByPathFileExists = true
break
elseif fs.isFile(path..filePath..".lua") then
execByPathArg = path..filePath..".lua"
execByPathFileExists = true
break
end
end
end
-- step else: file not found
if execByPathFileExists then
shell.run(execByPathArg, execArgs)
return EXIT_SUCCESS
else
if filePath:byte(1) == 46 or filePath:byte(1) == 47 then
os.errorNoSuchFile(filePath)
else
os.errorCmdNotFound(filePath)
end
end
return false
end
-- SYNTAX PARSER --------------------------------------------------------------
-- tables with functions
local builtins = {
cd = cd,
exit = exit,
exec = exec,
clear = term.clear
}
local function runcommand(str, recurse)
if #str < 1 then return end
local cmdFound = false
-- simple cmd parse: WORD ARG1 ARG2 ARG3 ...
local args = {}
local command = ""
for word in string.gmatch(str, "[^ ]+") do
if #command < 1 then command = word -- first word will be a name of command
else table.insert(args, word) end
end
if builtins[command] then -- try for builtins table
builtins[command](args)
cmdFound = true
return true
else
-- FIXME: 'exec programname args' works, but not 'programname args'
-- try for os.dshenv.aliases
if os.dshenv.aliases[command] then
--builtins[os.dshenv.aliases[command]](args)
if not recurse then
cmdFound = runcommand(os.dshenv.aliases[command], true)
end
else
-- try to launch as program
if not recurse then
cmdFound = runcommand("exec "..str, true)
end
end
end
-- command not found (really)
if not cmdFound then
os.errorCmdNotFound(command)
end
end
-- END OF SYNTAX PARSER -------------------------------------------------------
-- INIT SHELL -----------------------------------------------------------------
exitshell = false
-- load up aliases
if fs.isFile("/etc/.dshrc") then
fs.dofile("/etc/.dshrc")
machine.println("[dummix/dsh.lua] Dsh aliases successfully loaded.")
end
-- END OF INIT SHELL ----------------------------------------------------------
-- run interpreter and quit
if (args[1]) then
local f = fs.open(args[1], "r")
local line = ""
local s = ""
-- treat interpreter key (#!) properly
-- I'm assuming I was called because I'm the right one
-- I have a full trust on "shell.run()" that it rightfully redirected to me
--
-- NOTE: shell redirection should only apply in interactive mode AND the input
-- was like "./filename", or else I'm the right one. Period.
-- (and that's how BASH works)
repeat
line = f.readLine()
if line == nil then break end
if line:sub(1,2) ~= "#!" then -- ignore line that contains hashbang
s = s.." "..line
end
until line == nil
f.close()
runcommand(s)
exitshell = true
end
function getPromptText()
--return DC4..os.workingDir[#os.workingDir]..DC3.."# "..DC4 -- we're root! omgwtf
return DC4..os.fullWorkPath()..DC3.."# "..DC4 -- we're root! omgwtf
end
-- interactive mode
local time = os.date()
print(time)
repeat
term.setCursorBlink(true)
io.write(getPromptText())
local s = input.readLine()
runcommand(s)
until exitshell
collectgarbage()
return EXIT_SUCCESS

View File

@@ -0,0 +1,170 @@
--[[
LESS IS MORE
SYNOPSIS:
lessismore [filename]
less [filename]
more [filename]
]]
local args = {...}
displayLineNo = true
local prompt = function()
term.setForeCol(3)
term.emitString(" scroll ", 3, term.height())
term.emitString(" quit", 14, term.height())
term.setForeCol(1)
term.emit(18, 1, term.height())
term.emit(29, 2, term.height())
term.emit(81, 13, term.height())
term.setForeCol(3)
term.setBackCol(0)
end
local function printUsage()
print("More: no file specified.")
print("Usage: more [filename]")
end
if args[1] == nil or #args[1] <= 0 then printUsage() return end
filepath = os.expandPath(args[1])
if not fs.isFile(filepath) then os.errorNoSuchFile(filepath) return end
function log10(n)
if n < 1 then return 0
elseif n < 10 then return 1
elseif n < 100 then return 2
elseif n < 1000 then return 3
elseif n < 10000 then return 4
elseif n < 100000 then return 5
elseif n < 1000000 then return 6
elseif n < 10000000 then return 7
elseif n < 100000000 then return 8
elseif n < 1000000000 then return 9
else return 10
end
end
----------------
-- fetch text --
----------------
lines = {}
displayHeight = term.height() - 1 -- bottom one line for prompt
local file = fs.open(filepath, "r")
local line = ""
repeat
line = file.readLine()
table.insert(lines, line)
until line == nil
lineNoLen = log10(#lines)
-----------
-- input --
-----------
local function scrollDownAction(n)
term.clearLine() -- prevent prompt to be scrolled
curY = curY + n
-- prevent overscroll
if (curY > #lines - displayHeight) then
curY = #lines - displayHeight
end
term.scroll(n)
for i = 0, n - 1 do
drawString(curY + displayHeight - i, displayHeight - i) -- redraw newline
end
end
local function scrollUpAction(n)
curY = curY - n
-- prevent overscroll
if (curY < 1) then
curY = 1
end
term.scroll(-n)
for i = 0, n - 1 do
drawString(curY + i, i + 1) -- redraw prev line
end
term.setCursor(n, term.height())
end
local function processInput()
if input.isKeyDown(keys.q) then quit = true end
if input.isKeyDown(keys.down) and curY < #lines - displayHeight then
scrollDownAction(1)
prompt()
elseif input.isKeyDown(keys.pageDown) and curY < #lines - displayHeight then
scrollDownAction(8)
prompt()
elseif input.isKeyDown(keys.up) and curY > 1 then
scrollUpAction(1)
term.clearLine() -- make space for prompt
prompt()
elseif input.isKeyDown(keys.pageUp) and curY > 1 then
scrollUpAction(8)
term.clearLine() -- make space for prompt
prompt()
end
machine.sleep(50)
end
-------------
-- display --
-------------
displayWidth = term.width() - 1 - (displayLineNo and lineNoLen or 0)
function drawString(lineNo, y)
local string = (lineNo > #lines) and ""
or lines[lineNo]:sub(curX, curX + displayWidth)
if (displayLineNo) then
local lineNoStr = DC3..string.format("%"..lineNoLen.."d", curY + y - 1)..DC4
string = lineNoStr..string
end
local strDrawX = curX
term.emitString(string, strDrawX, y)
end
function redrawText()
for i = curY, #lines do
if (i >= displayHeight + curY) then break end
drawString(i, i - curY + 1)
end
end
curY = 1
curX = 1
quit = false
if term.isTeletype() then
for _, l in ipairs(line) do
term.print(l)
end
quit = true
end
term.clear()
term.setCursorBlink(false)
redrawText()
repeat
prompt()
term.setCursor(1, term.height())
processInput()
until quit
term.clearLine()
return

View File

@@ -0,0 +1,10 @@
local args = {...}
local dir = os.fullWorkPath()--(#args < 1) and os.fullWorkPath() or args[1]
local list = fs.list("/"..dir)
table.sort(list)
for _, v in ipairs(list) do
print(v)
end

View File

@@ -0,0 +1,76 @@
local args = {...}
local _APPVERSION = 0.3
--[[
MOONSHELL: basically just lua.lua
SYNOPSIS
msh [file]
msh: Runs shell in interactive mode
msh [file]: Try to execute file as Lua script
]]
-- run interpreter and quit
if (args[1]) then
local f = fs.open(args[1], "r")
local line = ""
local s = ""
-- treat interpreter key (#!) properly
-- I'm assuming I was called because I'm the right one
-- I have a full trust on "shell.run()" that it rightfully redirected to me
repeat
line = f.readLine()
if line == nil then break end
if line:sub(1,2) ~= "#!" then -- ignore line that contains hashbang
s = s.." "..line
end
until line == nil
f.close()
xpcall(
function() _G.runscript(s, "="..args[1]) end,
function(err) print(DLE..err) end
)
goto terminate
end
-- interactive mode. This is a copy of BOOT.lua
run = shell.run
print("Moonshell "..DC2.._APPVERSION..DC4..", running "..DC2.._VERSION..DC4)
print("Lua is copyrighted (C) 1994-2013 Lua.org, PUC-Rio")
print("Run run(path) to execute program on 'path'.")
print("Run exit() to quit.")
while not machine.isHalted() do
term.setCursorBlink(true)
io.write(DC3.."lua"..computer.prompt)
local s = input.readLine()
if s == "exit()" then break end
xpcall(
function()
if s:byte(1) == 61 then -- print out value
s1 = string.sub(s, 2)
_G.runscript("print(tostring("..s1.."))\n", "=stdin")
else
_G.runscript(s, "=stdin")
end
end,
function(err) print(DLE..err) end -- it catches logical errors
)
end
::terminate::
collectgarbage()
return EXIT_SUCCESS

View File

@@ -0,0 +1,6 @@
local args = {...}
if (#args ~= 2) then
print([[usage: mv source target
mv source ... directory]])
return end
fs.mv(os.expandPath(args[1]), os.expandPath(args[2]))

View File

@@ -0,0 +1 @@
fs.dofile("/etc/_boot.lua")

View File

@@ -0,0 +1,8 @@
-- dsh aliases
os.dshenv.aliases = {
lua = "msh",
sh = "dsh",
shutdown = "exit", -- should be a separate program that actually halts the system
less = "lessismore",
more = "lessismore"
}

View File

@@ -0,0 +1,117 @@
--[[
Bootloader for Operation System
Created by minjaesong on 2016-09-21
]]
-- check directories
dirlist = {
"/boot",
"/bin", -- crucial binaries (e.g. cat, ls, sh(ell), cp, rm, mkdir), it's loosely an UNIX system
"/usr",
"/usr/bin", -- more utilities and binaries (e.g. less/more, nano)
"/home", -- home directory for user
"/home/bin", -- user-installed apps
"/media" -- auto mounts (e.g. "/media/fd1", "/media/hdb", "/media/sda")
}
-- just make them if they don't exist
for _, dir in ipairs(dirlist) do
fs.mkdir(dir)
end
if not _G.os then _G.os = {} end
os.version = "0.0"
os.EXIT_SUCCESS = 0
os.workingDir = {"home"}
os.path = "home/bin/;/usr/bin/;/bin/" -- infamous $path
os.fullWorkPath = function()
local ret = table.concat(os.workingDir, "/") -- there's nothing wrong with this.
if computer.verbose then
machine.println("workingDir size: "..#os.workingDir)
machine.println("fullWorkPath: "..ret)
end
return ret
end
os.setWorkingDir = function(s)
if s:byte(#s) == 47 then
s = string.sub(s, 1, #s - 1)
end
if s:byte(1) == 47 then
s = string.sub(s, 2, #s)
end
if computer.verbose then
machine.println("renew working dir; '"..s.."'")
end
local oldWorkingDir = {table.unpack(os.workingDir)}
-- renew working directory, EVEN IF s STARTS WITH '/'
local t = {}
for word in string.gmatch(s, "[^/]+") do
table.insert(t, word)
end
os.workingDir = t
-- check if the directory exists
if not fs.isDir(s) then
os.errorNoSuchFileOrDir("cd: "..s)
os.workingDir = oldWorkingDir
return
end
end
os.pushWorkingDir = function(s)
if (s == "..") then
error("cannot push '..' to working directory.")
else
table.insert(os.workingDir, s)
if computer.verbose then
machine.println("pushing '"..s.."' to working directory.")
end
end
end
os.popWorkingDir = function()
if (#os.workingDir > 1) then
table.remove(os.workingDir)
end
end
-- @param "path/of/arbitrary"
-- @return /working/dir/path/of/arbitrary
-- input path's trailing '/' is PRESERVED.
os.expandPath = function(p)
-- not applicable if the path starts with /
if p:byte(1) == 47 or p:byte(1) == 92 then
return p
end
return os.fullWorkPath().."/"..p
end
os.defaultshell = "/bin/dsh.lua"
os.clock = function() return machine.milliTime() / 1000 end -- uptime of the computer, in seconds
function os.errorCmdNotFound(cmd)
print(cmd..": command not found")
end
function os.errorNoSuchFile(cmd)
print(cmd..": No such file")
end
function os.errorNoSuchFileOrDir(cmd)
print(cmd..": No such file or directory")
end
function os.errorIsDir(cmd)
print(cmd.." is a directory")
end
-- run default shell
fs.dofile(os.defaultshell)
-- quit properly
shell.status = shell.halt

View File

@@ -0,0 +1,12 @@
NAME
msh - the Moonshell
SYNOPSIS
msh [file]
COPYRIGHT
See copyright information for the game you are actually playing.
DESCRIPTION
Msh is a Lua prompt that reads lua script from the user, or execute
a file user had put as an argument.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
--[[
From https://github.com/prapin/LuaBrainFuck/blob/master/brainfuck.lua
LuaBrainFuck License
--------------------
LuaBrainFuck is placed under the same license as Lua itself,
so licensed under terms of the MIT license reproduced below.
This means that the library is free software and can be used for both academic
and commercial purposes at absolutely no cost.
===============================================================================
Copyright (C) 2012 Patrick Rapin, CH-1543 Grandcour
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.
===============================================================================
(end of COPYRIGHT)
Example usage: require "brainfuck" "+++>>> your BF code here <<<---"
]]
return function(s)
local subst = {["+"]="v=v+1 ", ["-"]="v=v-1 ", [">"]="i=i+1 ", ["<"]="i=i-1 ",
["."] = "w(v)", [","]="v=r()", ["["]="while v~=0 do ", ["]"]="end "}
local env = setmetatable({ i=0, t=setmetatable({},{__index=function() return 0 end}),
r=function() return io.read(1):byte() end, w=function(c) io.write(string.char(c)) end },
{__index=function(t,k) return t.t[t.i] end, __newindex=function(t,k,v) t.t[t.i]=v end })
load(s:gsub("[^%+%-<>%.,%[%]]+",""):gsub(".", subst), "brainfuck", "t", env)()
end

View File

@@ -0,0 +1,159 @@
--[[
-- ComputerCraft API compatibility layer
Usage: require("CCAPI")
Created by minjaesong on 2016-09-16.
--]]
--------------
-- PREAMBLE --
--------------
if term.isTeletype() then error("This is a teletype; cannot use CCAPI layer") end
table.insert(computer.loadedCLayer, "CCAPI")
local function intLog2(i)
if i == 0 then return 0 end
local log = 0
if bit32.band(i, 0xffff0000) ~= 0 then i = bit32.rshift(i, 16) log = 16 end
if i >= 256 then i = bit32.rshift(i, 8) log = log + 8 end
if i >= 16 then i = bit32.rshift(i, 8) log = log + 4 end
if i >= 4 then i = bit32.rshift(i, 8) log = log + 2 end
return log + bit32.rshift(i, 1)
end
-----------------------
-- BIT API Extension --
-----------------------
bit.blshift = bit32.lshift(n, bits)
bit.brshift = bit32.arshift(n, bits)
bit.blogic_rshift = bit32.rshift(n, bits)
----------------
-- COLORS API --
----------------
_G.colors = {}
colors.white = 0x1
colors.orange = 0x2
colors.magenta = 0x4
colors.lightBlue = 0x8
colors.yellow = 0x10
colors.lime = 0x20
colors.pink = 0x40
colors.gray = 0x80
colors.grey = 0x80
colors.lightGray = 0x100
colors.lightGrey = 0x100
colors.cyan = 0x200
colors.purple = 0x400
colors.blue = 0x800
colors.brown = 0x1000
colors.green = 0x2000
colors.red = 0x4000
colors.black = 0x8000
local function normaliseCCcol(cccol)
if cccol >= 0x1 and cccol <= 0xFFFF then
return intLog2(cccol)
else
error("invalid CC Colors: "..cccol)
end
end
_G.colours = _G.colors
--------------
-- TERM API --
--------------
-- paint_index -> Terminal colour index
local ccToGameCol = {--pink
1, 5, 7, 10, 4, 11, 15, 2, 3, 10, 8, 9, 14, 12, 6, 0
}
-- "a" -> 10, "3" -> 3
local function cHexToInt(c)
if type(c) == "number" then -- char
if c >= 48 and c <= 57 then
return c - 48
elseif c >= 65 and c <= 70 then
return c - 65 + 10
elseif c >= 97 and c <= 102 then
return c - 97 + 10
else
return 0
end
elseif type(c) == "string" then -- single-letter string
if c:byte(1) >= 48 and c:byte(1) <= 57 then
return c:byte(1) - 48
elseif c:byte(1) >= 65 and c:byte(1) <= 70 then
return c:byte(1) - 65 + 10
elseif c:byte(1) >= 97 and c:byte(1) <= 102 then
return c:byte(1) - 97 + 10
else
--error("unrepresentable: " .. c)
-- return black, as defined in http://www.computercraft.info/wiki/Term.blit
return 0
end
else
error("bad argument (string or number expected, got "..type(c)..")")
end
end
-- str, str, str
term.blit = function(text, foreCol, backCol)
assert(
type(text) == "string" and type(backCol) == "string" and type(foreCol) == "string",
"bad argument: (string, string, string expected, got "..type(text)..", "..type(foreCol)..", "..type(backCol)..")"
)
if #text ~= #foreCol or #text ~= #backCol or #foreCol ~= #backCol then
error("arguments must be the same length")
end
for i = 1, #text do
term.setForeCol(ccToGameCol[1 + cHexToInt(foreCol:byte(i))])
term.setBackCol(ccToGameCol[1 + cHexToInt(backCol:byte(i))])
term.emit(text:byte(i))
term.moveCursor(term.getX() + 1, term.getY())
end
end
term.getCursorPos = term.getCursor
term.setCursorPos = term.moveCursor
term.setCursorBlink = term.blink
term.isColor = term.isCol
term.getSize = term.size
term.setTextColor = function(cccol) term.setForeCol(ccToGameCol[normaliseCCcol(cccol)]) end
term.getTextColor = term.getForeCol
term.setBackgroundColor = function(cccol) term.setBackCol(ccToGameCol[normaliseCCcol(cccol)]) end
term.getBackgroundColor = term.getBackCol
--------------------
-- FILESYSTEM API --
--------------------
fs.makeDir = fs.mkdir
fs.move = fs.mv
fs.copy = fs.cp
fs.delete = fs.rm
fs.combine = fs.concat
fs.getDir = fs.parent
fs.run = fs.dofile
------------------
-- DOWN AND OUT --
------------------
if computer.verbose then print("ComputerCraft compatibility layer successfully loaded.") end

View File

@@ -0,0 +1,434 @@
--[[
Created by minjaesong on 2016-09-15.
--]]
-------------
-- ALIASES --
-------------
--_G.io = {} -- we make our own sandbox'd system
--[[fs.dofile = function(p, ...)
local f = fs.open(p, "r")
local s = f.readAll()
_G.runscript(s, "="..p, ...)
end]] -- implementation moved to BOOT.lua
_G.loadstring = _G.load
--_G.dofile = function(f) fs.dofile(f) end
fs.fetchText = function(p)
local file = fs.open(p, "r")
local text = file.readAll()
file.close()
return text
end
-----------------------------------------
-- INPUTSTREAM AND SCANNER (java-like) --
-----------------------------------------
--[[
In whatever code that actually runs everything (computer),
there must be:
override fun keyPressed(key: Int, c: Char) {
super.keyPressed(key, c)
vt.keyPressed(key, c)
if (key == Key.RETURN) {
val input = vt.closeInputString()
}
}
...it basically says to close the input if RETURN is hit,
and THIS exact part will close the input for this function.
]]
_G.__scanforline__ = function(echo) -- pass '1' to not echo; pass nothing to echo
machine.closeInputString()
machine.openInput(echo or 0)
_G.__scanMode__ = "line"
local s
repeat -- we can do this ONLY IF lua execution process is SEPARATE THREAD
s = machine.__readFromStdin()
until s
-- input is closed when RETURN is hit. See above comments.
return s
end
-- use Keys API to identify the keycode
--[[_G.__scanforkey__ = function(echo) -- pass '1' to not echo; pass nothing to echo
machine.closeInputString()
machine.openInput(echo or 0)
_G.__scanMode__ = "a_key"
local key
repeat -- we can do this ONLY IF lua execution process is SEPARATE THREAD
key = machine.getLastKeyPress()
until key
-- input is closed when any key is hit. See above comments.
return key
end]] -- DELETED: use _G.input.isKeyDown(keycode)
--- ---
-- IO IMPLEMENTATION --
--- ---
io.__openfile__ = "stdin"
io.stdin = "stdin"
io.stdout = "stdout"
io.stderr = "stderr"
io.open = fs.open
io.input = function(luafile)
io.__openfile__ = luafile
end
io.read = function(option)
if io.__openfile__ == "stdin" then
local input = {}
-- RETURN not hit
while true do
local inkey = machine.__readFromStdin()
if inkey == 13 or inkey == 10 then
break
elseif inkey == 8 or inkey == 127 then
io.write(string.char(inkey))
table.remove(input)
elseif inkey > 0 then
io.write(string.char(inkey))
table.insert(input, string.char(inkey))
end
end
-- RETURN finally hit
io.write("\n")
return table.concat(input)
end
function _readAll()
return io.open(io.__openfile__).readAll()
end
function _readLine()
return io.open(io.__openfile__).readLine()
end
options = {}
options["*n"] = function() error("Read number is not supported, yet!") end--_readNumber
options["*a"] = _readAll
options["*l"] = _readLine
end
-----------------
-- PRINTSTREAM --
-----------------
-- only useful when IO is "opening" stdin
--[[io.write = function(...)
local args = {...}
for _, v in ipairs(args) do
local s
if v == nil then
s = "nil"
else
s = tostring(v)
end
term.write(s)
end
end]]
-- for some reason, inputstream above kills 'print' function.
-- So we rewrite it.
--[[_G.print = function(...) -- dependent on above io.write reimpl
local args = {...}
io.write(args[1])
if (#args > 1) then
for i = 2, #args do
io.write("\t")
io.write(args[i])
end
end
io.write("\n")
end]]
---------------
-- SHELL API --
---------------
_G.shell = {}
shell.status = shell.ok
-- run a script with path (string) and argstable (table)
shell.run = function(path, argstable)
-- check for interpreter key "#!"
local f = fs.open(path, "r")
local s = f.readAll()
f.close()
if s:sub(1,2) == "#!" then
local interpreter = s:sub(3)
if not argstable then
xpcall(function() fs.dofile(interpreter..".lua", path) end, function(err) print(DLE..err) end)
else
xpcall(function() fs.dofile(interpreter..".lua", path, table.unpack(argstable)) end, function(err) print(DLE..err) end)
end
else
if not argstable then
xpcall(function() fs.dofile(path) end, function(err) print(DLE..err) end)
else
xpcall(function() fs.dofile(path, table.unpack(argstable)) end, function(err) print(DLE..err) end)
end
end
end
shell.ok = 0
shell.halt = 127
--------------
-- HEXUTILS --
--------------
_G.hexutils = {}
_G.hexutils.toHexString = function(byteString)
assert(type(byteString) == "string", error("Expected string."))
-- speedup
local function iToHex(i)
if i == 0 then return "0"
elseif i == 1 then return "1"
elseif i == 2 then return "2"
elseif i == 3 then return "3"
elseif i == 4 then return "4"
elseif i == 5 then return "5"
elseif i == 6 then return "6"
elseif i == 7 then return "7"
elseif i == 8 then return "8"
elseif i == 9 then return "9"
elseif i == 10 then return "a"
elseif i == 11 then return "b"
elseif i == 12 then return "c"
elseif i == 13 then return "d"
elseif i == 14 then return "e"
elseif i == 15 then return "f"
else error("unrepresentable: " .. i)
end
end
local ret = ""
for i = 1, #byteString do
local c = byteString:byte(i)
local msb = iToHex(bit32.rshift(c, 4) % 16)
local lsb = iToHex(c % 16)
ret = ret .. (msb .. lsb)
end
return ret
end
--------------
-- KEYS API --
--------------
-- ComputerCraft compliant
local keycodeNumToName = {
["30"] = "a",
["48"] = "b",
["46"] = "c",
["32"] = "d",
["18"] = "e",
["33"] = "f",
["34"] = "g",
["35"] = "h",
["23"] = "i",
["36"] = "j",
["37"] = "k",
["38"] = "l",
["50"] = "m",
["49"] = "n",
["24"] = "o",
["25"] = "p",
["16"] = "q",
["19"] = "r",
["31"] = "s",
["20"] = "t",
["22"] = "u",
["47"] = "v",
["17"] = "w",
["45"] = "x",
["21"] = "y",
["44"] = "z",
["2"] = "one",
["3"] = "two",
["4"] = "three",
["5"] = "four",
["6"] = "five",
["7"] = "six",
["8"] = "seven",
["9"] = "eight",
["10"] = "nine",
["11"] = "zero",
["12"] = "minus",
["13"] = "equals",
["14"] = "backspace",
["15"] = "tab",
["26"] = "leftBracket",
["27"] = "rightBracket",
["28"] = "enter",
["29"] = "leftCtrl",
["39"] = "semiColon",
["40"] = "apostrophe",
["41"] = "grave",
["42"] = "leftShift",
["43"] = "backslash",
["51"] = "comma",
["52"] = "period",
["53"] = "slash",
["54"] = "rightShift",
["55"] = "multiply",
["56"] = "leftAlt",
["57"] = "space",
["58"] = "capsLock",
["59"] = "f1",
["60"] = "f2",
["61"] = "f3",
["62"] = "f4",
["63"] = "f5",
["64"] = "f6",
["65"] = "f7",
["66"] = "f8",
["67"] = "f9",
["68"] = "f10",
["69"] = "numLock",
["70"] = "scollLock",
["87"] = "f11",
["88"] = "f12",
["89"] = "f13",
["90"] = "f14",
["91"] = "f15",
["144"] = "cimcumflex",
["145"] = "at",
["146"] = "colon",
["147"] = "underscore",
["157"] = "rightCtrl",
["184"] = "rightAlt",
["197"] = "pause",
["199"] = "home",
["200"] = "up",
["201"] = "pageUp",
["203"] = "left",
["205"] = "right",
["207"] = "end",
["208"] = "down",
["209"] = "pageDown",
["210"] = "insert",
["211"] = "delete",
["219"] = "leftCommand"
}
_G.keys = {
["a"] = 30,
["b"] = 48,
["c"] = 46,
["d"] = 32,
["e"] = 18,
["f"] = 33,
["g"] = 34,
["h"] = 35,
["i"] = 23,
["j"] = 36,
["k"] = 37,
["l"] = 38,
["m"] = 50,
["n"] = 49,
["o"] = 24,
["p"] = 25,
["q"] = 16,
["r"] = 19,
["s"] = 31,
["t"] = 20,
["u"] = 22,
["v"] = 47,
["w"] = 17,
["x"] = 45,
["y"] = 21,
["z"] = 44,
["one"] = 2,
["two"] = 3,
["three"] = 4,
["four"] = 5,
["five"] = 6,
["six"] = 7,
["seven"] = 8,
["eight"] = 9,
["nine"] = 10,
["zero"] = 11,
["minus"] = 12,
["equals"] = 13,
["backspace"] = 14,
["tab"] = 15,
["leftBracket"] = 26,
["rightBracket"] = 27,
["enter"] = 28,
["leftCtrl"] = 29,
["semiColon"] = 39,
["apostrophe"] = 40,
["grave"] = 41,
["leftShift"] = 42,
["backslash"] = 43,
["comma"] = 51,
["period"] = 52,
["slash"] = 53,
["rightShift"] = 54,
["multiply"] = 55,
["leftAlt"] = 56,
["space"] = 57,
["capsLock"] = 58,
["f1"] = 59,
["f2"] = 60,
["f3"] = 61,
["f4"] = 62,
["f5"] = 63,
["f6"] = 64,
["f7"] = 65,
["f8"] = 66,
["f9"] = 67,
["f10"] = 68,
["numLock"] = 69,
["scollLock"] = 70,
["f11"] = 87,
["f12"] = 88,
["f13"] = 89,
["f14"] = 90,
["f15"] = 91,
["cimcumflex"] = 144,
["at"] = 145,
["colon"] = 146,
["underscore"] = 147,
["rightCtrl"] = 157,
["rightAlt"] = 184,
["pause"] = 197,
["home"] = 199,
["up"] = 200,
["pageUp"] = 201,
["left"] = 203,
["right"] = 205,
["end"] = 207,
["down"] = 208,
["pageDown"] = 209,
["insert"] = 210,
["delete"] = 211,
["leftCommand"] = 219
}
_G.keys.getName = function(code) return keycodeNumToName[tostring(code)] end

View File

@@ -0,0 +1,497 @@
--[[
TBASIC: Simple BASIC language based on the Commodore BASIC Version 2.
(C64 rulz? Nope.)
How to use in your program:
1. load the script by:
if you're using ComputerCraft, use:
os.loadAPI "TBASEXEC.lua"
else, use:
require "TBASEXEC"
2. run:
_TBASIC.EXEC(string of whole command)
]]
if os and os.loadAPI then -- ComputerCraft
os.loadAPI "TBASINCL.lua"
else
require "TBASINCL"
end
table.concat = function(t, delimeter)
if #t == 0 then return "" end
local outstr = t[1]
for i = 2, #t do
outstr = outstr..delimeter..tostring(t[i])
end
return outstr
end
-- Copy from TBASINCL; looks like OpenComputers has a bug...
function string_hash(str)
local hash = 2166136261
for i = 1, #str do
hash = hash * 16777619
hash = bit.bxor(hash, str:byte(i))
end
return hash
end
-- INTERPRETER STATUS ---------------------------------------------------------
local programlist = {}
-- LEXER ----------------------------------------------------------------------
local function appendcommand(lineno, statement)
if lineno > _TBASIC._INTPRTR.MAXLINES then
_TBASIC._ERROR.LINETOOBIG()
elseif lineno < 0 then
_TBASIC._ERROR.NOLINENUM()
else
programlist[lineno] = statement
end
end
do -- Avoid heap allocs for performance
local tokens = {" ", "\t", ",", "(", ")"} -- initial obvious tokens
local longest_token_len = 0
-- build 'tokens' table from list of operators from the language
for _, v in ipairs(_TBASIC._OPERATR) do
if not v:match("[A-Za-z]") then -- we want non-alphabetic operators as a token
table.insert(tokens, v)
-- get longest_token_len, will be used for 'lookahead'
local tokenlen = #v
if longest_token_len < #v then
longest_token_len = #v
end
end
end
-- sort them out using ther hash for binary search
table.sort(tokens, function(a, b) return string_hash(a) < string_hash(b) end)
function parsewords(line)
if line == nil then return end
-----------------------
-- check line sanity --
-----------------------
-- filter for IF statement
if line:sub(1, 2):upper() == "IF" then
-- no matching THEN
if not line:match("[Tt][Hh][Ee][Nn]") then
_TBASIC._ERROR.NOMATCHING("IF", "THEN")
-- assignment on IF clause
elseif line:match("[Ii][Ff][^\n]+[Tt][Hh][Ee][Nn]"):match("[^=+%-*/%%<>!]=[^=<>]") or
line:match("[Ii][Ff][^\n]+[Tt][Hh][Ee][Nn]"):match(":=") then
_TBASIC._ERROR.ASGONIF()
end
end
--------------------------------------------------
-- automatically infer and insert some commands --
--------------------------------------------------
-- (This is starting to get dirty...)
-- unary minus
for matchobj in line:gmatch("%-[0-9]+") do
local newline = line:gsub(matchobj, "MINUS "..matchobj:sub(2, #matchobj))
line = newline
end
-- conditional for IF
-- if IF statement has no appended paren
if line:sub(1, 2):upper() == "IF" and not line:match("[Ii][Ff][ ]*%(") then
local newline = line:gsub("[Ii][Ff]", "IF ( ", 1):gsub("[Tt][Hh][Ee][Nn]", " ) THEN", 1)
line = newline
end
-- special treatment for FOR
if line:sub(1, 3):upper() == "FOR" then
if line:match("[0-9]?%.[0-9]") then -- real number used (e.g. "3.14", ".5")
_TBASIC._ERROR.ILLEGALARG()
else
local varnameintm = line:match(" [^\n]+[ =]")
if varnameintm then
local varname = varnameintm:match("[^= ]+")
if varname then
local newline = line:gsub(" "..varname.."[ =]", " $"..varname.." "..varname.." = ")
line = newline:gsub("= =", "=")
else
_TBASIC._ERROR.SYNTAX()
end
end
-- basically, "FOR x x = 1 TO 10", which converts to "x x 1 10 TO = FOR",
-- which is executed (in RPN) in steps of:
-- "x x 1 10 TO = FOR"
-- "x x (arr) = FOR"
-- "x FOR" -- see this part? we need extra 'x' to feed for the FOR statement to function
end
end
printdbg("parsing line", line)
lextable = {}
isquote = false
quotemode = false
wordbuffer = ""
local function flush()
if (#wordbuffer > 0) then
table.insert(lextable, wordbuffer)
wordbuffer = ""
end
end
local function append(char)
wordbuffer = wordbuffer..char
end
local function append_no_whitespace(char)
if char ~= " " and char ~= "\t" then
wordbuffer = wordbuffer..char
end
end
-- return: lookless_count on success, nil on failure
local function isdelimeter(string)
local cmpval = function(table_elem) return string_hash(table_elem) end
local lookless_count = #string
local ret = nil
repeat
ret = table.binsearch(tokens, string:sub(1, lookless_count), cmpval)
lookless_count = lookless_count - 1
until ret or lookless_count < 1
return ret and lookless_count + 1 or false
end
local i = 1 -- Lua Protip: variable in 'for' is immutable, and is different from general variable table, even if they have same name
while i <= #line do
local c = string.char(line:byte(i))
local lookahead = line:sub(i, i+longest_token_len)
if isquote then
if c == [["]] then
flush()
isquote = false
else
append(c)
end
else
if c == [["]] then
isquote = true
append_no_whitespace("~")
else
local delimsize = isdelimeter(lookahead) -- returns nil if no matching delimeter found
if delimsize then
flush() -- flush buffer
append_no_whitespace(lookahead:sub(1, delimsize))
flush() -- flush this delimeter
i = i + delimsize - 1
else
append_no_whitespace(c)
end
end
end
i = i + 1
end
flush() -- don't forget this!
return lextable
end
end
local function readprogram(program)
for line in program:gmatch("[^\n]+") do
lineno = line:match("[0-9]+ ", 1)
if not lineno then
_TBASIC._ERROR.NOLINENUM()
end
statement = line:sub(#lineno + 1)
appendcommand(tonumber(lineno), statement)
end
end
do -- Avoid heap allocs for performance
local function stackpush(t, v)
t[#t + 1] = v
end
local function stackpop(t)
local v = t[#t]
t[#t] = nil
return v
end
local function stackpeek(t)
local v = t[#t]
return v
end
local function unmark(word)
if type(word) == "table" then return word end
return word:sub(2, #word)
end
local function isoperator(word)
if word == nil then return false end
return word:byte(1) == 35
end
local isvariable = _TBASIC.isvariable
local isnumber = _TBASIC.isnumber
local isstring = _TBASIC.isstring
local function isuserfunc(word)
if type(word) == "table" then return false end
if word == nil then return false end
return word:byte(1) == 64
end
local function isbuiltin(word)
if type(word) == "table" then return false end
if word == nil then return false end
return word:byte(1) == 38
end
local function iskeyword(word)
if word == nil then return false end
return isoperator(word) or isuserfunc(word) or isbuiltin(word)
end
local function isassign(word)
if word == nil then return false end
return word ~= "==" and word ~= ">=" and word ~= "<=" and word:byte(#word) == 61
end
-- returns truthy value "terminate_loop" upon termination of loop; nil otherwise.
local function execword(word, args)
if not _TBASIC.__appexit then
printdbg("--> execword", word)
printdbg("--> execword_args", table.unpack(args))
if word == "IF" then
printdbg("--> branch statement 'IF'")
if not _TBASIC.__readvar(args[1]) then -- if condition 'false'
printdbg("--> if condition 'false'", table.unpack(args))
return "terminate_loop" -- evaluated as 'true' to Lua
else
printdbg("--> if condition 'true'", table.unpack(args))
end
end
printdbg("--> execword_outarg", table.unpack(args))
result = _TBASIC.LUAFN[word][1](table.unpack(args))
printdbg("--> result", result)
stackpush(execstack, result)
end
end
function printdbg(...)
local debug = false
if debug then print("DBG", ...) end
end
function interpretline(line)
if not _TBASIC.__appexit then
--[[
impl
1. (normalise expr using parsewords)
2. use _TBASIC.RPNPARSR to convert to RPN
3. execute RPN op set like FORTH
* "&" - internal functions
* "@" - user-defined functions
* "$" - variables (builtin constants and user-defined) -- familiar, eh?
* "#" - operators
* "~" - strings
* none prepended - data (number or string)
]]
lextable = parsewords(line)
local vararg = -13 -- magic
if lextable and lextable[1] ~= nil then
if lextable[1]:upper() == "REM" then return nil end
printdbg("lextable", table.concat(lextable, "|"))
-- execute expression
exprlist = _TBASIC.TORPN(lextable) -- 2 2 #+ &PRINT for "PRINT 2+2"
printdbg("trying to exec", table.concat(exprlist, " "), "\n--------")
execstack = {}
for _, word in ipairs(exprlist) do
printdbg("stack before", table.concat(execstack, " "))
printdbg("word", word)
if iskeyword(word) then
printdbg("is keyword")
funcname = unmark(word)
args = {}
argsize = _TBASIC._GETARGS(funcname)
printdbg("argsize", argsize)
if not argsize then
_TBASIC._ERROR.DEV_UNIMPL(funcname)
else
if argsize ~= vararg then
-- consume 'argsize' elements from the stack
for argcnt = argsize, 1, -1 do
if #execstack == 0 then
_TBASIC._ERROR.ARGMISSING(funcname)
end
args[argcnt] = stackpop(execstack)
end
else
-- consume entire stack
local reversedargs = {}
while #execstack > 0 and
(isvariable(stackpeek(execstack)) or isnumber(stackpeek(execstack)) or
isstring(stackpeek(execstack)))
do
stackpush(reversedargs, stackpop(execstack))
end
-- reverse 'args'
while #reversedargs > 0 do
stackpush(args, stackpop(reversedargs))
end
end
local terminate_loop = execword(funcname, args)
if terminate_loop then
printdbg("--> termination of loop")
printdbg("--------")
break
end
end
elseif isvariable(word) then
printdbg("is variable")
stackpush(execstack, word) -- push raw variable ($ sign retained)
else
printdbg("is data")
stackpush(execstack, word) -- push number or string
end
printdbg("stack after", table.concat(execstack, " "))
printdbg("--------")
end
-- if execstack is not empty, something is wrong
if #execstack > 0 then
_TBASIC._ERROR.SYNTAX() -- cannot reliably pinpoint which statement has error; use generic error
end
end
end
end
end
local function termination_condition()
return terminated or
_TBASIC.__appexit or
#_TBASIC._INTPRTR.CALLSTCK > _TBASIC._INTPRTR.STACKMAX
end
local function fetchnextcmd()
cmd = nil
repeat
_TBASIC._INTPRTR.PROGCNTR = _TBASIC._INTPRTR.PROGCNTR + 1
cmd = programlist[_TBASIC._INTPRTR.PROGCNTR]
if _TBASIC._INTPRTR.PROGCNTR > _TBASIC._INTPRTR.MAXLINES then
terminated = true
break
end
until cmd ~= nil
if cmd ~= nil then
if _TBASIC._INTPRTR.TRACE then
print("PC", _TBASIC._INTPRTR.PROGCNTR)
end
return cmd
end
end
local function interpretall()
terminated = false
repeat
interpretline(fetchnextcmd())
until termination_condition()
end
-- END OF LEXER ---------------------------------------------------------------
-- _TBASIC.SHOWLUAERROR = false -- commented; let the shell handle it
local testprogram = nil
_G._TBASIC.EXEC = function(cmdstring) -- you can access this interpreter with this global function
_TBASIC._INTPRTR.RESET()
programlist = {} -- wipe out previous commands from interpreter (do not delete)
readprogram(cmdstring)
interpretall()
end
if testprogram then
_TBASIC._INTPRTR.RESET()
programlist = {} -- wipe out previous commands from interpreter (do not delete)
readprogram(testprogram)
interpretall()
end
--[[
Terran BASIC (TBASIC)
Copyright (c) 2016 Torvald (minjaesong) and the contributors.
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.
]]

View File

@@ -0,0 +1,34 @@
-- TBASIC extension
-- these are utilities. Do not touch these lines
local __assert = _TBASIC.__assert
local __assertlhand = _TBASIC.__assertlhand
local __assertrhand = _TBASIC.__assertrhand
local __checknumber = _TBASIC.__checknumber
local __checkstring = _TBASIC.__checkstring
local __readvar = _TBASIC.__readvar
local __resolvevararg = _TBASIC.__resolvevarar
local vararg = -13 -- magic
-- end of utilities
-- these are the sample code for defining your own words
--[[
-- actual function that does the job
local function _fnupgoer(n)
print("Up-goer "..__checknumber(n).." goes up!")
end
-- add the word UPGOER to word list
table.insert(_TBASIC._FNCTION, "UPGOER")
-- add the actual function '_fnupgoer' and its number of arguments (1) to
-- '_TBASIC.LUAFN'. 'UPGOER' part should match with the word you just
-- inserted to _TBASIC._FNCTION.
_TBASIC.LUAFN.UPGOER = {_fnupgoer, 1}
]]
-- little debugger's blessing
local function _fnenableluatrace() _TBASIC.SHOWLUAERROR = true end
table.insert(_TBASIC._FNCTION, "LUATRACEON")
_TBASIC.LUAFN.LUATRACEON = {_fnenableluatrace, 0}

View File

@@ -0,0 +1,292 @@
--[[
TBASIC shell
Synopsis: TBASIC (filename)
If no file is specified, interactive mode will be started
To debug EXEC and/or INCL, there's line ```local debug = false``` on each file; change it to ```true``` manually
and you are all set.
]]
if os and os.loadAPI then -- ComputerCraft
os.loadAPI "TBASINCL.lua"
os.loadAPI "TBASEXEC.lua"
else
require "TBASINCL"
require "TBASEXEC"
end
args = {...}
print(_G._TBASIC._HEADER)
_TBASIC.PROMPT()
_TBASIC.SHOWLUAERROR = false
local function concat_lines(lines, startindex, endindex)
local out = ""
for i = startindex or 1, endindex or _TBASIC._INTPRTR.MAXLINES do
if lines[i] ~= nil then
out = out.."\n"..tostring(i).." "..lines[i]
end
end
return out
end
if args[1] then
local prog = nil
if fs and fs.open then -- ComputerCraft
local inp = assert(fs.open(args[1], "r"))
prog = inp:readAll()
inp:close()
else
local inp = assert(io.open(args[1], "r"))
prog = inp:read("*all")
inp:close()
end
_TBASIC.EXEC(prog)
else
local terminate_app = false
local ptn_nums = "[0-9]+"
local renum_targets = {"GOTO[ ]+"..ptn_nums, "GOSUB[ ]+"..ptn_nums }
local lines = {}
local linenum_match = "[0-9]+ "
local get_linenum = function(line) return line:sub(1,6):match(linenum_match, 1) end -- line:sub(1,6) limits max linumber to be 99999
local split_num_and_statements = function(line)
local linenum = get_linenum(line)
local statements = line:sub(#linenum + 1)
return tonumber(linenum), statements
end
while not terminate_app do
local __read = false
local line = io.read()
-- tokenise line by " "
local args = {} -- shadows system args
for word in line:gmatch("[^ ]+") do
table.insert(args, word:upper())
end
-- TODO more elegant code than IF-ELSEIF-ELSE
-- massive if-else for running command, cos implementing proper command executor is too expensive here
if line:sub(1,6):match(linenum_match) then -- enter new command
local linenum, statements = split_num_and_statements(line)
lines[tonumber(linenum)] = statements
__read = true
elseif args[1] == "NEW" then
lines = {}
elseif args[1] == "RUN" then
_TBASIC.EXEC(concat_lines(lines))
elseif args[1] == "LIST" then -- LIST, LIST 42, LIST 10-80
if not args[2] then
print(concat_lines(lines))
else
if args[2]:match("-") then -- ranged
local range = {}
for n in args[2]:gmatch("[^-]+") do
table.insert(range, n)
end
local rangestart = tonumber(range[1])
local rangeend = tonumber(range[2])
if not rangestart or not rangeend then
_TBASIC._ERROR.ILLEGALARG()
else
print(concat_lines(lines, rangestart, rangeend))
end
else
local linenum = tonumber(args[2])
if not linenum then
_TBASIC._ERROR.ILLEGALARG()
else
print(concat_lines(lines, linenum, linenum))
end
end
end
_TBASIC.PROMPT()
__read = true
elseif args[1] == "DELETE" then -- DELETE 30, DELETE 454-650
if not args[2] then
_TBASIC._ERROR.ILLEGALARG()
else
if args[2]:match("-") then -- ranged
local range = {}
for n in args[2]:gmatch("[^-]+") do
table.insert(range, n)
end
local rangestart = tonumber(range[1])
local rangeend = tonumber(range[2])
if not rangestart or not rangeend then
_TBASIC._ERROR.ILLEGALARG()
else
for i = rangestart, rangeend do
lines[i] = nil
end
end
else
local linenum = tonumber(args[2])
if not linenum then
_TBASIC._ERROR.ILLEGALARG()
else
lines[linenum] = nil
end
end
end
elseif args[1] == "EXIT" then
terminate_app = true
break
elseif args[1] == "SAVE" then
local status, err = pcall(function()
if fs and fs.open then -- computercraft
local file = fs.open(args[2], "w")
file.write(concat_lines(lines))
file.close()
else
local file = assert(io.open(args[2], "w"))
file:write(concat_lines(lines))
file:close()
end
end
)
if err then
if _TBASIC.SHOWLUAERROR then
print(err)
end
_TBASIC._ERROR.IOERR()
else
print("FILE SAVED")
end
elseif args[1] == "LOAD" then
local status, err = pcall(function()
lines = {}
if fs and fs.open then -- computercraft
local file = fs.open(args[2], "r")
local data = file.readAll("*all")
for dataline in data:gmatch("[^\n]+") do
if #dataline > 0 then
local linenum, statements = split_num_and_statements(dataline)
lines[linenum] = statements
end
end
file.close()
else
local file = assert(io.open(args[2], "r"))
local data = file:read("*all")
for dataline in data:gmatch("[^\n]+") do
if #dataline > 0 then
local linenum, statements = split_num_and_statements(dataline)
lines[linenum] = statements
end
end
file:close()
end
end
)
if err then
if _TBASIC.SHOWLUAERROR then
error(err)
end
_TBASIC._ERROR.IOERR()
else
print("FILE LOADED")
end
elseif args[1] == "RENUM" then
local statement_table = {}
local renumbering_table = {}
local new_linenum_counter = 10
-- first, get the list of commands, without line number indexing
for i = 1, _TBASIC._INTPRTR.MAXLINES do
if lines[i] ~= nil then
--table.insert(statement_table, lines[i])
statement_table[new_linenum_counter] = lines[i]
renumbering_table[i] = new_linenum_counter
-- test
--print("old line", i, "new line", new_linenum_counter)
new_linenum_counter = new_linenum_counter + 10
end
end
-- copy statement_table into lines table
lines = statement_table
-- re-number GOTO and GOSUB line numbers
local line_counter = 0 -- loop counter
for line_pc = 0, _TBASIC._INTPRTR.MAXLINES do
local line = lines[line_pc]
if line then
line_counter = line_counter + 1
-- replace
-- extract a <- "GOTO 320"
-- extract n_from from a (320), make n_to from it
-- make new string b <- "GOTO "..n_to
for _, match_string in ipairs(renum_targets) do
local match = line:match(match_string)
if match then
local matching_statement = match:gsub("[ ]+"..ptn_nums, "")
local target_line_old = tonumber(match:match(ptn_nums))
local target_line_new = renumbering_table[target_line_old]
local gsub_from = match
local gsub_to = matching_statement.." "..target_line_new
-- test
--print("matching_statement", matching_statement, "target_line_old", target_line_old, "target_line_new", target_line_new)
--print("gsub_from", gsub_from, "gsub_to", gsub_to)
-- substitute
lines[line_pc] = line:gsub(gsub_from, gsub_to)
end
end
end
end
elseif #line == 0 and line:byte(1) ~= 10 and line:byte(1) ~= 13 then
__read = true
else
_TBASIC.EXEC("1 "..line) -- execute command right away
end
-- reset
if not __read then
_TBASIC.PROMPT()
end
end
end
--[[
Terran BASIC (TBASIC)
Copyright (c) 2016 Torvald (minjaesong) and the contributors.
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.
]]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
// TODO Fill in from work_files/romapidoc/romapidoc.tex