mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-16 16:46:07 +09:00
still wip modularisation, game somehow boots
This commit is contained in:
@@ -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]))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]))
|
||||
@@ -0,0 +1 @@
|
||||
fs.dofile("/etc/_boot.lua")
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
]]
|
||||
@@ -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}
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
// TODO Fill in from work_files/romapidoc/romapidoc.tex
|
||||
Reference in New Issue
Block a user