diff --git a/src/net/torvald/aa/AAFrame.kt b/src/net/torvald/aa/AAFrame.kt index 65e0058ca..30a5610e2 100644 --- a/src/net/torvald/aa/AAFrame.kt +++ b/src/net/torvald/aa/AAFrame.kt @@ -34,7 +34,7 @@ constructor(var width: Int, var height: Int, var terminal: SimpleTextTerminal) { throw ArrayIndexOutOfBoundsException("x: $x, y; $y") frameBuffer[y * width + x] = ((c.toInt().and(0xFF)) + colourKey.shl(8)).toChar() - terminal.redraw() + //terminal.redraw() } fun drawBuffer(x: Int, y: Int, raw: Char): Boolean = @@ -42,7 +42,7 @@ constructor(var width: Int, var height: Int, var terminal: SimpleTextTerminal) { false else { frameBuffer[y * width + x] = raw - terminal.redraw() + //terminal.redraw() true } @@ -51,7 +51,7 @@ constructor(var width: Int, var height: Int, var terminal: SimpleTextTerminal) { val char = (other[i].toUint().shl(8) + other[i + 1].toUint()).toChar() frameBuffer[i.ushr(1)] = char } - terminal.redraw() + //terminal.redraw() } fun getBackgroundColour(x: Int, y: Int): Int { @@ -78,7 +78,7 @@ constructor(var width: Int, var height: Int, var terminal: SimpleTextTerminal) { drawBuffer(x, y, 0.toChar(), background.shl(4)) } } - terminal.redraw() + //terminal.redraw() } fun drawFromOther(other: AAFrame) { @@ -88,7 +88,7 @@ constructor(var width: Int, var height: Int, var terminal: SimpleTextTerminal) { frameBuffer[y * width + x] = other.getRaw(x, y)!! } } - terminal.redraw() + //terminal.redraw() } private fun checkOOB(x: Int, y: Int) = (x < 0 || y < 0 || x >= width || y >= height) diff --git a/src/net/torvald/spriteanimation/SpriteAnimation.kt b/src/net/torvald/spriteanimation/SpriteAnimation.kt index f0561525e..de1464bb9 100644 --- a/src/net/torvald/spriteanimation/SpriteAnimation.kt +++ b/src/net/torvald/spriteanimation/SpriteAnimation.kt @@ -4,8 +4,6 @@ package net.torvald.spriteanimation -import net.torvald.terrarum.StateInGame -import net.torvald.terrarum.Terrarum import com.jme3.math.FastMath import net.torvald.terrarum.gameactors.ActorWithSprite import org.newdawn.slick.Graphics @@ -214,26 +212,6 @@ class SpriteAnimation(val parentActor: ActorWithSprite, val cellWidth: Int, val private fun getScaledSprite(scale: Float): Image { val selectedImage = spriteImage!!.getSprite(currentFrame - 1, currentRow - 1) - //Image selectedImage = sprites[currentRow - 1][currentFrame - 1]; - - // resample - /*float nearestResampleScale = (scale > 1) ? Math.round(scale) : 1; - float linearResampleScale = scale / nearestResampleScale; - - // scale 1.8 -> resample in 2(nearest), then resample in 0.9(linear) - // scale by nearestResampleScale (2, 3, ...) - selectedImage.setFilter(Image.FILTER_NEAREST); - Image selImgNearestScaled = selectedImage.getScaledCopy(nearestResampleScale); - // scale by linearResampleScale (.x) - Image selImgLinearScaled; - if (scale % 1 > 0) { - selImgNearestScaled.setFilter(Image.FILTER_LINEAR); - selImgLinearScaled = selImgNearestScaled.getScaledCopy(linearResampleScale); - return selImgLinearScaled; - } - else { - return selImgNearestScaled; - }*/ selectedImage.filter = Image.FILTER_NEAREST return selectedImage.getScaledCopy(scale) } diff --git a/src/net/torvald/terrarum/StateFontTester.kt b/src/net/torvald/terrarum/StateFontTester.kt index 6814355ec..ed276f299 100644 --- a/src/net/torvald/terrarum/StateFontTester.kt +++ b/src/net/torvald/terrarum/StateFontTester.kt @@ -14,16 +14,23 @@ class StateFontTester : BasicGameState() { lateinit var canvas: Graphics - lateinit var segfont: Font + //lateinit var segfont: Font + + lateinit var mtfont: Font override fun init(gc: GameContainer, game: StateBasedGame) { canvas = Graphics(1024, 1024) Terrarum.gameLocale = "fiFI" - segfont = SpriteSheetFont( + /*segfont = SpriteSheetFont( SpriteSheet("./assets/graphics/fonts/24-seg_red.tga", 22, 31), ' ' + )*/ + + mtfont = SpriteSheetFont( + SpriteSheet("./assets/graphics/fonts/mt-32.tga", 12, 16), + 0.toChar() ) } @@ -50,9 +57,10 @@ class StateFontTester : BasicGameState() { }*/ //g.font = Terrarum.fontSmallNumbers - g.font = segfont + //g.font = segfont + g.font = mtfont - val line = """print("Lua is copyrighted (C) 1994-2013 Lua.org, PUC-Rio")""" + val line = " **** TERRAN BASIC V0.5 **** " g.drawString(line, 10f, 10f) } diff --git a/src/net/torvald/terrarum/StateVTTest.kt b/src/net/torvald/terrarum/StateVTTest.kt index e4f88ebd7..ef36a843d 100644 --- a/src/net/torvald/terrarum/StateVTTest.kt +++ b/src/net/torvald/terrarum/StateVTTest.kt @@ -38,7 +38,7 @@ class StateVTTest : BasicGameState() { override fun update(container: GameContainer, game: StateBasedGame, delta: Int) { Terrarum.appgc.setTitle("VT — F: ${container.fps}" + - " — M: ${Terrarum.memInUse}M / ${Terrarum.totalVMMem}M") + " — M: ${Terrarum.memInUse}M / ${Terrarum.memXmx}M") vt.update(container, delta) computerInside.update(container, delta) } diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index f4e360105..7a290ae64 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -195,8 +195,10 @@ constructor(gamename: String) : StateBasedGame(gamename) { private set val memInUse: Long - get() = ManagementFactory.getMemoryMXBean().heapMemoryUsage.used shr 20 - val totalVMMem: Long + get() = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) shr 20 + val memTotal: Long + get() = Runtime.getRuntime().totalMemory() shr 20 + val memXmx: Long get() = Runtime.getRuntime().maxMemory() shr 20 lateinit var environment: RunningEnvironment diff --git a/src/net/torvald/terrarum/console/AVTracker.kt b/src/net/torvald/terrarum/console/AVTracker.kt index da57ad653..053d29844 100644 --- a/src/net/torvald/terrarum/console/AVTracker.kt +++ b/src/net/torvald/terrarum/console/AVTracker.kt @@ -18,7 +18,7 @@ object AVTracker : ConsoleCommand { try { val actorID = args[1].toInt() - if (Terrarum.ingame.hasActor(actorID)) { + if (Terrarum.ingame.theGameHasActor(actorID)) { jPanelInstances.add(ActorValueTracker(Terrarum.ingame.getActorByID(actorID))) } else { diff --git a/src/net/torvald/terrarum/gameactors/Actor.kt b/src/net/torvald/terrarum/gameactors/Actor.kt index 6f80946a7..af1e58e65 100644 --- a/src/net/torvald/terrarum/gameactors/Actor.kt +++ b/src/net/torvald/terrarum/gameactors/Actor.kt @@ -39,8 +39,8 @@ abstract class Actor(val renderOrder: ActorOrder) : Comparable, Runnable * override var referenceID: Int = generateUniqueReferenceID() */ fun generateUniqueReferenceID(): Int { - fun checkForCollision(value: Int) = - Terrarum.ingame.hasActor(value) || + fun itIsNotValid(value: Int) = + Terrarum.ingame.theGameHasActor(value) || value < ItemCodex.ITEM_COUNT_MAX || value < when (renderOrder) { ActorOrder.BEHIND -> ItemCodex.ITEM_COUNT_MAX @@ -58,7 +58,7 @@ abstract class Actor(val renderOrder: ActorOrder) : Comparable, Runnable var ret: Int do { ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID - } while (checkForCollision(ret)) // check for collision + } while (itIsNotValid(ret)) // check for collision return ret } diff --git a/src/net/torvald/terrarum/gameactors/ParticleBase.kt b/src/net/torvald/terrarum/gameactors/ParticleBase.kt index e27f76960..662426c63 100644 --- a/src/net/torvald/terrarum/gameactors/ParticleBase.kt +++ b/src/net/torvald/terrarum/gameactors/ParticleBase.kt @@ -31,7 +31,7 @@ open class ParticleBase(renderOrder: ActorOrder, maxLifeTime: Int? = null) : Run open val velocity = Vector2(0.0, 0.0) open val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0) - open lateinit var body: Image + open lateinit var body: Image // you might want to use SpriteAnimation open var glow: Image? = null init { diff --git a/src/net/torvald/terrarum/gameworld/WorldTime.kt b/src/net/torvald/terrarum/gameworld/WorldTime.kt index 531c369d0..6c6438bb5 100644 --- a/src/net/torvald/terrarum/gameworld/WorldTime.kt +++ b/src/net/torvald/terrarum/gameworld/WorldTime.kt @@ -66,7 +66,7 @@ class WorldTime { val YEAR_DAYS: Int = 365 fun parseTime(s: String): Int = - if (s.length >= 4) { + if (s.length >= 4 && s.contains('h')) { s.toLowerCase().substringBefore('h').toInt() * WorldTime.HOUR_SEC + s.toLowerCase().substringAfter('h').toInt() * WorldTime.MINUTE_SEC } diff --git a/src/net/torvald/terrarum/mapdrawer/TilesDrawer.kt b/src/net/torvald/terrarum/mapdrawer/TilesDrawer.kt index 74e3ec7b3..3f7534b09 100644 --- a/src/net/torvald/terrarum/mapdrawer/TilesDrawer.kt +++ b/src/net/torvald/terrarum/mapdrawer/TilesDrawer.kt @@ -11,6 +11,7 @@ import net.torvald.terrarum.concurrent.ThreadParallel import net.torvald.terrarum.blendMul import net.torvald.terrarum.blendNormal import net.torvald.terrarum.mapdrawer.FeaturesDrawer.TILE_SIZE +import net.torvald.terrarum.mapdrawer.LightmapRenderer.normaliseToColour import net.torvald.terrarum.mapdrawer.MapCamera.x import net.torvald.terrarum.mapdrawer.MapCamera.y import net.torvald.terrarum.mapdrawer.MapCamera.height diff --git a/src/net/torvald/terrarum/tileproperties/TilePropCSV.kt b/src/net/torvald/terrarum/tileproperties/TilePropCSV.kt index 649e742c2..c54cf5162 100644 --- a/src/net/torvald/terrarum/tileproperties/TilePropCSV.kt +++ b/src/net/torvald/terrarum/tileproperties/TilePropCSV.kt @@ -48,7 +48,7 @@ object TilePropCSV { "8"; "4";"TILE_GEM_DIAMOND" ; "33587232"; "25";"2400";"rock"; "0"; "1"; "0"; "0"; "8"; "4"; "0"; "0"; "N/A"; "0";"16" "8"; "5";"TILE_GEM_AMETHYST" ; "33587232"; "25";"2400";"rock"; "0"; "1"; "0"; "0"; "8"; "5"; "0"; "0"; "N/A"; "0";"16" "9"; "0";"TILE_SNOW" ; "33587232"; "6"; "500";"snow"; "0"; "1"; "1"; "0"; "9"; "0"; "0"; "0"; "N/A"; "0";"16" - "9"; "1";"TILE_ICE_FRAGILE" ; "13644813"; "1"; "930";"icei"; "0"; "1"; "0"; "0"; "9"; "1"; "0"; "0"; "N/A"; "0";"16" + "9"; "1";"TILE_ICE_FRAGILE" ; "13644813"; "1"; "930";"icei"; "0"; "1"; "0"; "0"; "9"; "1"; "0"; "0"; "N/A"; "0"; "4" "9"; "2";"TILE_ICE_NATURAL" ; "27289626"; "25"; "930";"icei"; "0"; "1"; "1"; "0"; "9"; "2"; "0"; "0"; "N/A"; "0"; "4" "9"; "3";"TILE_ICE_CLEAR_MAGICAL" ; "33587232"; "25";"3720";"icex"; "0"; "1"; "1"; "19955770"; "9"; "3"; "0"; "0"; "N/A"; "0"; "4" "9"; "4";"TILE_GLASS_CRUDE" ; "3146755"; "1";"2500";"glas"; "0"; "1"; "1"; "0"; "9"; "4"; "0"; "0"; "N/A"; "0";"16" @@ -135,7 +135,7 @@ object TilePropCSV { "255"; "13";"TILE_WATER" ; "27282445"; "100";"1000";"watr"; "1"; "0"; "0"; "0"; "N/A"; "N/A"; "0"; "0"; "16"; "0";"16" "255"; "14";"TILE_WATER" ; "27282445"; "100";"1000";"watr"; "1"; "0"; "0"; "0"; "N/A"; "N/A"; "0"; "0"; "16"; "0";"16" "255"; "15";"TILE_WATER" ; "27282445"; "100";"1000";"watr"; "1"; "0"; "0"; "0"; "N/A"; "N/A"; "0"; "0"; "16"; "0";"16" -"256"; "0";"TILE_NULL" ; "0"; "-1";"2600";"null"; "0"; "0"; "0"; "0"; "N/A"; "N/A"; "0"; "0"; "N/A"; "0";"16" +"256"; "0";"TILE_NULL" ;"1073741823"; "-1";"2600";"null"; "0"; "0"; "1"; "0"; "N/A"; "N/A"; "0"; "0"; "N/A"; "0";"16" ## Notes ## diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index 19b9e8e41..15d42efb0 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -153,16 +153,19 @@ class BasicDebugInfoWindow : UICanvas { */ g.color = GameFontBase.codeToCol["y"] - g.drawString("${ccY}MEM ", (Terrarum.WIDTH - 15 * 8 - 2).toFloat(), 2f) + g.drawString("${ccY}MEM ", (Terrarum.WIDTH - 21 * 8 - 2).toFloat(), 2f) //g.drawString("${ccY}FPS $ccG${Terrarum.appgc.fps}", (Terrarum.WIDTH - 6 * 8 - 2).toFloat(), 10f) g.drawString("${ccY}CPUs ${if (Terrarum.MULTITHREAD) ccG else ccR}${Terrarum.THREADS}", (Terrarum.WIDTH - 2 - 6*8).toFloat(), 10f) g.color = GameFontBase.codeToCol["g"] g.drawString("${Terrarum.memInUse}M", - (Terrarum.WIDTH - 11 * 8 - 2).toFloat(), 2f) - g.drawString("/${Terrarum.totalVMMem}M", - (Terrarum.WIDTH - 6 * 8 - 2).toFloat(), 2f) + (Terrarum.WIDTH - 17 * 8 - 2).toFloat(), 2f) + g.drawString("/${Terrarum.memTotal}M/", + (Terrarum.WIDTH - 12 * 8 - 2).toFloat(), 2f) + g.color = GameFontBase.codeToCol["m"] + g.drawString("${Terrarum.memXmx}M", + (Terrarum.WIDTH - 5 * 8 - 2).toFloat(), 2f) /** * Bottom left @@ -174,7 +177,7 @@ class BasicDebugInfoWindow : UICanvas { (2 + 17*8).toFloat(), Terrarum.HEIGHT - 10f) g.drawString("${ccY}Dormant $ccG${Terrarum.ingame.actorContainerInactive.size}", (2 + 28*8).toFloat(), Terrarum.HEIGHT - 10f) - g.drawString("${ccM}Particles $ccG${Terrarum.ingame.particlesContainer.elemCount}", + g.drawString("${ccM}Particles $ccG${Terrarum.ingame.particlesActive}", (2 + 41*8).toFloat(), Terrarum.HEIGHT - 10f) } diff --git a/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASEXEC.lua b/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASEXEC.lua index b4e18b7f2..90df86867 100644 --- a/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASEXEC.lua +++ b/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASEXEC.lua @@ -6,30 +6,40 @@ TBASIC: Simple BASIC language based on the Commodore BASIC Version 2. 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" + 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) + 2. run: + _TBASIC.EXEC(string of whole command) ]] if os and os.loadAPI then -- ComputerCraft - os.loadAPI "TBASINCL.lua" + os.loadAPI "TBASINCL.lua" else - require "TBASINCL" + 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 + if #t == 0 then return "" end + local outstr = t[1] + for i = 2, #t do + outstr = outstr..delimeter..tostring(t[i]) + end - return outstr + 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 @@ -43,457 +53,423 @@ 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 + 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) + 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 + function parsewords(line) + if line == nil then return end - ----------------------- - -- check line sanity -- - ----------------------- + ----------------------- + -- check line sanity -- + ----------------------- - -- filter for IF statement - if line:match("[Ii][Ff]") 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 + -- 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...) + -------------------------------------------------- + -- automatically infer and insert some commands -- + -------------------------------------------------- + -- (This is starting to get dirty...) - -- unary minus - local matchobj = line:find("%-[0-9]") - if matchobj then -- in this pattern, it always returns a number - local newline = line:sub(1, matchobj - 1) .. "MINUS " .. line:sub(matchobj + 1, #line) - line = newline - end - -- conditional for IF - -- if IF statement has no appended paren - if not line:match("[Ii][Ff][ ]*%(") then - local newline = line:gsub("[Ii][Ff]", "IF ( "):gsub("[Tt][Hh][Ee][Nn]", " ) THEN") - 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]+[ =]") + -- 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 + 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) + 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 + 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 + -- 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 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) + 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 + 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! + i = i + 1 + end + flush() -- don't forget this! - return lextable - end + 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 + 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) + statement = line:sub(#lineno + 1) - appendcommand(tonumber(lineno), statement) - end + appendcommand(tonumber(lineno), statement) + end end do -- Avoid heap allocs for performance - local function stackpush(t, v) - t[#t + 1] = v - end + 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 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 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 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 function isoperator(word) + if word == nil then return false end + return word:byte(1) == 35 + end - local isvariable = _TBASIC.isvariable + 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 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 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 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 + local function isassign(word) + if word == nil then return false end + return word ~= "==" and word ~= ">=" and word ~= "<=" and word:byte(#word) == 61 + end - local function isnoresolvevar(word) - local novarresolve = {"NEXT"} + -- 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)) - for _, w in ipairs(novarresolve) do -- linear search, because the array is small - if word:upper() == w then - return true - end - end + 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 - return false - end + printdbg("--> execword_outarg", table.unpack(args)) + result = _TBASIC.LUAFN[word][1](table.unpack(args)) - local function execword(word, args) - if not _TBASIC.__appexit then - printdbg("--> execword", word) - printdbg("--> inargs", table.unpack(args)) + printdbg("--> result", result) + stackpush(execstack, result) + end + end - -- selectively resolve variable (if it's assign func, bottommost var -- target of assignation -- will not be resolved) - -- for command "NEXT": DO NOT RESOLVE, pass its name (Call by Name) - if not isnoresolvevar(word) then - for i = isassign(word) and 2 or 1, #args do - arg = args[i] - - printdbg("--> resolvevar arg", arg) - - if isvariable(arg) then - var = unmark(arg) - - if type(var) ~= "table" then - value = _TBASIC._INTPRTR.CNSTANTS[var:upper()] -- try for pre-def - - if value == nil then - value = _TBASIC._INTPRTR.VARTABLE[var:upper()] -- try for user-def - end - - if value == nil then - _TBASIC._ERROR.NULVAR(var) - end - - args[i] = value - end - end - end - end - - if word == "IF" then - printdbg("--> branch statement 'IF'") - if not 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 printdbg(...) + local debug = false + if debug then print("DBG", ...) end + end - function interpretline(line) - if not _TBASIC.__appexit then - --[[ - impl + 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 + 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) - ]] + * "&" - 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 + lextable = parsewords(line) + local vararg = -13 -- magic - if lextable and lextable[1] ~= nil then - if lextable[1]:upper() == "REM" then return nil end + if lextable and lextable[1] ~= nil then + if lextable[1]:upper() == "REM" then return nil end - printdbg("lextable", table.concat(lextable, "|")) + printdbg("lextable", table.concat(lextable, "|")) - -- execute expression - exprlist = _TBASIC.TORPN(lextable) -- 2 2 #+ &PRINT for "PRINT 2+2" + -- execute expression + exprlist = _TBASIC.TORPN(lextable) -- 2 2 #+ &PRINT for "PRINT 2+2" - printdbg("trying to exec", table.concat(exprlist, " "), "\n--------") + printdbg("trying to exec", table.concat(exprlist, " "), "\n--------") - execstack = {} + execstack = {} - for _, word in ipairs(exprlist) do - printdbg("stack before", table.concat(execstack, " ")) - printdbg("word", word) + for _, word in ipairs(exprlist) do + printdbg("stack before", table.concat(execstack, " ")) + printdbg("word", word) - if iskeyword(word) then - printdbg("is keyword") + if iskeyword(word) then + printdbg("is keyword") - funcname = unmark(word) - args = {} - argsize = _TBASIC._GETARGS(funcname) + funcname = unmark(word) + args = {} + argsize = _TBASIC._GETARGS(funcname) - printdbg("argsize", argsize) + 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)) 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 + 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 = {} - printdbg("stack after", table.concat(execstack, " ")) - printdbg("--------") - end + 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 - -- 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 + 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._INTPRTR.GOTOCNTR > _TBASIC._INTPRTR.GOTOLMIT or - _TBASIC.__appexit or - #_TBASIC._INTPRTR.CALLSTCK > _TBASIC._INTPRTR.STACKMAX + 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] + 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 _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 + if cmd ~= nil then + if _TBASIC._INTPRTR.TRACE then + print("PC", _TBASIC._INTPRTR.PROGCNTR) + end - return cmd - end + return cmd + end end local function interpretall() - terminated = false + terminated = false - repeat - interpretline(fetchnextcmd()) - until termination_condition() - - if _TBASIC._INTPRTR.GOTOCNTR > _TBASIC._INTPRTR.GOTOLMIT then - _TBASIC._ERROR.TOOLONGEXEC() - end + 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 = {} - readprogram(cmdstring) - interpretall() + _TBASIC._INTPRTR.RESET() + programlist = {} -- wipe out previous commands from interpreter (do not delete) + readprogram(cmdstring) + interpretall() end if testprogram then - _TBASIC._INTPRTR.RESET() - programlist = {} - readprogram(testprogram) - interpretall() + _TBASIC._INTPRTR.RESET() + programlist = {} -- wipe out previous commands from interpreter (do not delete) + readprogram(testprogram) + interpretall() end diff --git a/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASIC.lua b/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASIC.lua index fbe0a85d6..40e54bb18 100644 --- a/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASIC.lua +++ b/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASIC.lua @@ -8,71 +8,260 @@ If no file is specified, interactive mode will be started if os and os.loadAPI then -- ComputerCraft - os.loadAPI "TBASINCL.lua" - os.loadAPI "TBASEXEC.lua" + os.loadAPI "TBASINCL.lua" + os.loadAPI "TBASEXEC.lua" else - require "TBASINCL" - require "TBASEXEC" + require "TBASINCL" + require "TBASEXEC" end args = {...} -print(_G._TBASIC._VERSION) +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 + 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) + _TBASIC.EXEC(prog) else - local terminate_app = false + 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 + line = io.read() + + -- tokenise line by " " + args = {} + for word in line:gmatch("[^ ]+") do + table.insert(args, word:upper()) + end - local lines = {} - local lineno = 1 - while not terminate_app do - local __read = false - line = io.read() + -- TODO more elegant code than IF-ELSEIF-ELSE - if line:upper() == "NEW" then - lines = {} - lineno = 1 - elseif line:upper() == "RUN" then - _TBASIC.EXEC(table.concat(lines, "\n")) - elseif line:upper() == "LIST" then - print() - print(table.concat(lines, "\n")) - _TBASIC.PROMPT() - __read = true - elseif line:upper() == "EXIT" then - terminate_app = true - break - elseif line:match("[0-9]+ ") then - table.insert(lines, line) - lineno = lineno + 1 - __read = true - elseif #line == 0 and line:byte(1) ~= 10 and line:byte(1) ~= 13 then - __read = true - else - _TBASIC.EXEC("1 "..line) - end + -- 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 + range = {} + for n in args[2]:gmatch("[^-]+") do + table.insert(range, n) + end + local rangestart = tonumber(range[1]) + local rangeend = tonumber(range[2]) - -- reset - if not __read then - _TBASIC.PROMPT() - end - end + 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 + 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 diff --git a/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASINCL.lua b/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASINCL.lua index 4cc900ecc..a7446a07b 100644 --- a/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASINCL.lua +++ b/src/net/torvald/terrarum/virtualcomputer/assets/lua/TBASINCL.lua @@ -1,11 +1,11 @@ -- TBASIC includes if not _G.bit and not _G.bit32 then - error("This lua implementation does not have bit/bit32 library, aborting.") + error("This lua implementation does not have bit/bit32 library, aborting.") end if not _G.unpack and not table.unpack then - error("This lua implementation does not have unpack() function, aborting.") + error("This lua implementation does not have unpack() function, aborting.") end if _G.bit32 then _G.bit = bit32 end -- Lua 5.2 and LuaJIT compatibility (which has 'bit32' but no 'bit') @@ -14,697 +14,1092 @@ if _G.unpack and not _G.table.unpack then _G.table.unpack = unpack end -- LuaJIT -- simple binary search stole and improved from Kotlin Language -- @param cmpval: function that returns numerical value of the value used for searching. --- implementation: function(s) return whateverhashornumber(s) end +-- implementation: function(s) return whateverhashornumber(s) end -- e.g. function(s) return string.hash(s) end -- for string values --- you must implement it by yourself! +-- you must implement it by yourself! do -- Avoid heap allocs for performance - local default_cmp_fn = function(s) return string.hash(tostring(s)) end + local default_cmp_fn = function(s) return string.hash(tostring(s)) end - function table.binsearch(t, value, cmpval) - local low = 1 - local high = #t - local cmp = cmpval or default_cmp_fn + function table.binsearch(t, value, cmpval) + local low = 1 + local high = #t + local cmp = cmpval or default_cmp_fn - local value = cmp(value) + local value = cmp(value) - while low <= high do - local mid = bit.rshift((low + high), 1) - local midVal = t[mid] + while low <= high do + local mid = bit.rshift((low + high), 1) + local midVal = t[mid] - if value > cmp(midVal) then - low = mid + 1 - elseif value < cmp(midVal) then - high = mid - 1 - else - return mid -- key found - end - end - return nil -- key not found - end + if value > cmp(midVal) then + low = mid + 1 + elseif value < cmp(midVal) then + high = mid - 1 + else + return mid -- key found + end + end + return nil -- key not found + end end _G._TBASIC = {} _G._TBASIC._VERNUM = 0x0004 -- 0.4 -_G._TBASIC._VERSION = string.format(" **** TERRAN BASIC V%d.%d **** ", bit.rshift(_TBASIC._VERNUM, 8), bit.band(_TBASIC._VERNUM, 0xFF)) +_G._TBASIC._VERSION = tonumber(string.format("%d.%d", bit.rshift(_TBASIC._VERNUM, 8), bit.band(_TBASIC._VERNUM, 0xFF))) +_G._TBASIC._HEADER = string.format(" **** TERRAN BASIC V%d.%d **** ", bit.rshift(_TBASIC._VERNUM, 8), bit.band(_TBASIC._VERNUM, 0xFF)) _G._TBASIC.PROMPT = function() print("\nREADY.") end _G._TBASIC._INVOKEERR = function(msg, msg1) - if msg1 then - print("?L".._G._TBASIC._INTPRTR.PROGCNTR..": "..msg.." "..msg1) - else - print("?L".._G._TBASIC._INTPRTR.PROGCNTR..": "..msg, "ERROR") - end - if _TBASIC.SHOWLUAERROR then error("Error thrown") end - --os.exit(1) -- terminate - _G._TBASIC.__appexit = true -- duh, computercraft + if msg1 then + print("?L".._G._TBASIC._INTPRTR.PROGCNTR..": "..msg.." "..msg1) + else + print("?L".._G._TBASIC._INTPRTR.PROGCNTR..": "..msg, "ERROR") + end + if _TBASIC.SHOWLUAERROR then error("Error thrown") end + --os.exit(1) -- terminate + _G._TBASIC.__appexit = true -- duh, computercraft end _G._TBASIC._ERROR = { - SYNTAX = function() _TBASIC._INVOKEERR("SYNTAX") end, - SYNTAXAT = function(word) _TBASIC._INVOKEERR("SYNTAX ERROR AT", "'"..word.."'") end, - TYPE = function() _TBASIC._INVOKEERR("TYPE MISMATCH") end, - ILLEGALNAME = function(name, reason) - if reason then - _TBASIC._INVOKEERR("ILLEGAL NAME: ".."'"..name.."'", "REASON:"..reason) - else - _TBASIC._INVOKEERR("ILLEGAL NAME:", "'"..name.."'") - end - end, - ILLEGALARG = function(expected, got) - if (not expected) and (not got) then - _TBASIC._INVOKEERR("ILLEGAL QUANTITY") - elseif not got then - _TBASIC._INVOKEERR(expected:upper().." EXPECTED") - else - _TBASIC._INVOKEERR(expected:upper().." EXPECTED,", "GOT "..got:upper()) - end - end, - NOSUCHLINE = function(line) _TBASIC._INVOKEERR("NO SUCH LINE:", line) end, - NULFN = function(var) _TBASIC._INVOKEERR("UNDEFINED FUNCTION:", "'"..var.."'") end, - NULVAR = function(var) _TBASIC._INVOKEERR("UNDEFINED VARIABLE:", "'"..var.."'") end, - DIV0 = function() _TBASIC._INVOKEERR("DIVISION BY ZERO") end, - NAN = function() _TBASIC._INVOKEERR("NOT A NUMBER") end, - INDETERMINANT = function() _TBASIC._INVOKEERR("INDETERMINANT MATH") end, -- 0^0 is NOT indeterminant, it's 1. This is the language spec. - STACKOVFL = function() _TBASIC._INVOKEERR("TOO MANY RECURSION") end, - LINETOOBIG = function() _TBASIC._INVOKEERR("TOO BIG LINE NUMBER") end, - NOLINENUM = function() _TBASIC._INVOKEERR("NO LINE NUMBER") end, - ABORT = function(reason) - if reason then - _TBASIC._INVOKEERR("PROGRAM", "ABORTED: "..reason) - else - _TBASIC._INVOKEERR("PROGRAM", "ABORTED") - end - end, - ARGMISSING = function(fname, remark) - if remark then - _TBASIC._INVOKEERR("MISSING ARGUMENT(S) FOR", "'"..fname.."' ("..remark..")") - else - _TBASIC._INVOKEERR("MISSING ARGUMENT(S) FOR", "'"..fname.."'") - end - end, - NOMATCHING = function(fname, match) _TBASIC._INVOKEERR("'"..fname.."' HAS NO MACTHING", "'"..match.."'") end, - TOOLONGEXEC = function() _TBASIC._INVOKEERR("TOO LONG WITHOUT YIELDING") end, - RETURNWOSUB = function() _TBASIC._INVOKEERR("RETURN WITHOUT GOSUB") end, - NEXTWOFOR = function() _TBASIC._INVOKEERR("NEXT WITHOUT FOR") end, - ASGONIF = function() _TBASIC._INVOKEERR("ASSIGNMENT ON IF CLAUSE") end, - SHELLCMD = function() _TBASIC._INVOKEERR("THIS IS A SHELL COMMAND") end, + SYNTAX = function() _TBASIC._INVOKEERR("SYNTAX") end, + SYNTAXAT = function(word) _TBASIC._INVOKEERR("SYNTAX ERROR AT", "'"..word.."'") end, + TYPE = function() _TBASIC._INVOKEERR("TYPE MISMATCH") end, + ILLEGALNAME = function(name, reason) + if reason then + _TBASIC._INVOKEERR("ILLEGAL NAME: ".."'"..name.."'", "REASON:"..reason) + else + _TBASIC._INVOKEERR("ILLEGAL NAME:", "'"..name.."'") + end + end, + ILLEGALARG = function(expected, got) + if (not expected) and (not got) then + _TBASIC._INVOKEERR("ILLEGAL QUANTITY") + elseif not got then + _TBASIC._INVOKEERR(expected:upper().." EXPECTED") + else + _TBASIC._INVOKEERR(expected:upper().." EXPECTED,", "GOT "..got:upper()) + end + end, + NOSUCHLINE = function(line) _TBASIC._INVOKEERR("NO SUCH LINE:", line) end, + NULFN = function(var) _TBASIC._INVOKEERR("UNDEFINED FUNCTION:", "'"..var.."'") end, + NULVAR = function(var) _TBASIC._INVOKEERR("UNDEFINED VARIABLE:", "'"..var.."'") end, + DIV0 = function() _TBASIC._INVOKEERR("DIVISION BY ZERO") end, + NAN = function() _TBASIC._INVOKEERR("NOT A NUMBER") end, + INDETERMINANT = function() _TBASIC._INVOKEERR("INDETERMINANT MATH") end, -- 0^0 is NOT indeterminant, it's 1. This is the language spec. + STACKOVFL = function() _TBASIC._INVOKEERR("TOO MANY RECURSION") end, + LINETOOBIG = function() _TBASIC._INVOKEERR("TOO BIG LINE NUMBER") end, + NOLINENUM = function() _TBASIC._INVOKEERR("NO LINE NUMBER") end, + ABORT = function(reason) + if reason then + _TBASIC._INVOKEERR("PROGRAM", "ABORTED: "..reason) + else + _TBASIC._INVOKEERR("PROGRAM", "ABORTED") + end + end, + ARGMISSING = function(fname, remark) + if remark then + _TBASIC._INVOKEERR("MISSING ARGUMENT(S) FOR", "'"..fname.."' ("..remark..")") + else + _TBASIC._INVOKEERR("MISSING ARGUMENT(S) FOR", "'"..fname.."'") + end + end, + NOMATCHING = function(fname, match) _TBASIC._INVOKEERR("'"..fname.."' HAS NO MACTHING", "'"..match.."'") end, + TOOLONGEXEC = function() _TBASIC._INVOKEERR("TOO LONG WITHOUT YIELDING") end, + RETURNWOSUB = function() _TBASIC._INVOKEERR("RETURN WITHOUT GOSUB") end, + NEXTWOFOR = function() _TBASIC._INVOKEERR("NEXT WITHOUT FOR") end, + ASGONIF = function() _TBASIC._INVOKEERR("ASSIGNMENT ON IF CLAUSE") end, + SHELLCMD = function() _TBASIC._INVOKEERR("THIS IS A SHELL COMMAND") end, + IOERR = function() _TBASIC._INVOKEERR("READ/WRITE") end, - DEV_FUCKIT = function() _TBASIC._INVOKEERR("FEELING DIRTY") end, - DEV_UNIMPL = function(fname) _TBASIC._INVOKEERR("UNIMPLEMENTED SYNTAX:", "'"..fname.."'") end + DEV_FUCKIT = function() _TBASIC._INVOKEERR("FEELING DIRTY") end, + DEV_UNIMPL = function(fname) _TBASIC._INVOKEERR("UNIMPLEMENTED SYNTAX:", "'"..fname.."'") end } _G._TBASIC._FNCTION = { - -- variable control - "CLR", -- deletes all user-defined variables and functions - "DIM", -- allocates an array - "DEF", -- defines new function. Synopsis "DEF FN FOOBAR(arg)" - "FN", -- denotes function - -- flow control - "GO", "GOTO", -- considered harmful - "GOSUB", "RETURN", - "FOR", "NEXT", - "DO", -- reserved only - "IF", "THEN", + -- variable control + "CLR", -- deletes all user-defined variables and functions + "DIM", -- allocates an array + "DEF", -- defines new function. Synopsis "DEF FN FOOBAR(arg)" + "FN", -- denotes function + -- flow control + "GO", "GOTO", -- considered harmful + "GOSUB", "RETURN", + "FOR", "NEXT", "IN", + "DO", -- reserved only + "IF", "THEN", + "LABEL", -- line number alias --"ELSE", "ELSEIF", -- reserved only, will not be implemented - "END", -- terminate program cleanly - "ABORT", -- break as if an error occured - "ABORTM", -- ABORT with message - -- stdio - "PRINT", - "INPUT", - "GET", -- read single key - "HTAB", "TAB", -- set cursor's X position - "VTAB", -- set cursor's Y position - "SCROLL", - "CLS", -- clear screen - "TEXTCOL", -- foreground colour - "BACKCOL", -- background colour - -- mathematics - "ABS", "SIN", "COS", "TAN", "FLOOR", "CEIL", "ROUND", "LOG", - "INT", -- integer part of a number (3.78 -> 3, -3.03 -> -3) - "RND", -- random number 0.0 <= x < 1.0 - "SGN", -- sign of a number (-1, 0, 1) - "SQRT", -- square root - "CBRT", -- cubic root - "MAX", "MIN", - "INV", -- returns (1.0 / arg) - -- string functions - "LEN", - "LEFT", -- just like in Excel - "MID", -- -- just like in Excel (substring) - "RIGHT", -- just like in Excel - -- type conversion - "ASC", -- converts a charactor into its code point - "CHR", -- converts an integer into corresponding character - "STR", -- number to string - "VAL", -- string to number - -- misc - "REM", -- mark this line as comment - "NEW", -- clean up any programs on the buffer (this is a Shell function) - -- pc speaker - "BEEP", -- beeps. Synopsis: "BEEP", "BEEP [pattern]" (not for CC) - "TEMIT", -- emits a tone. Synopsis: "TEMIT [frequency] [seconds]" (not for CC) - -- commands - "RUN", -- run a program or a line. Synopsis: "RUN", "RUN [line]" (this is a Shell function) - "LIST", -- list currently entered program. Synopsis: "LIST", "LIST [line]", "LIST [from "-" to]" (this is a Shell function) - -- external IO - "LOAD", -- file load. Synopsis: "LOAD [filename]" - "SAVE", -- file save. Synopsis: "SAVE [filename]" + "END", -- terminate program cleanly + "ABORT", -- break as if an error occured + "ABORTM", -- ABORT with message + -- stdio + "PRINT", + "INPUT", + "GET", -- read single key + "HTAB", "TAB", -- set cursor's X position + "VTAB", -- set cursor's Y position + "SCROLL", + "CLS", -- clear screen + "TEXTCOL", -- foreground colour + "BACKCOL", -- background colour + -- mathematics + "ABS", "SIN", "COS", "TAN", "FLOOR", "CEIL", "ROUND", "LOG", + "INT", -- integer part of a number (3.78 -> 3, -3.03 -> -3) + "RND", -- random number 0.0 <= x < 1.0 + "SGN", -- sign of a number (-1, 0, 1) + "SQRT", -- square root + "CBRT", -- cubic root + "MAX", "MIN", + "INV", -- returns (1.0 / arg) + "RAD", -- converts deg into rad + -- string manipulation + "LEN", + "LEFT", -- just like in Excel + "MID", -- -- just like in Excel (substring) + "RIGHT", -- just like in Excel + -- type conversion + "ASC", -- converts a charactor into its code point + "CHR", -- converts an integer into corresponding character + "STR", -- number to string + "VAL", -- string to number + -- misc + "REM", -- mark this line as comment + "NEW", -- clean up any programs on the buffer (this is a Shell function) + -- pc speaker + "BEEP", -- beeps. Synopsis: "BEEP", "BEEP [pattern]" (not for CC) + "TEMIT", -- emits a tone. Synopsis: "TEMIT [frequency] [seconds]" (not for CC) + -- commands + "RUN", -- run a program or a line. Synopsis: "RUN", "RUN [line]" (this is a Shell function) + "LIST", -- list currently entered program. Synopsis: "LIST", "LIST [line]", "LIST [from "-" to]" (this is a Shell function) + "NEW", -- clear program lines buffer (this is a Shell function) + "RENUM", -- re-number BASIC statements (this is a Shell function) + "DELETE", -- delete line (this is a Shell function) + -- external IO + "LOAD", -- file load. Synopsis: "LOAD [filename]" + "SAVE", -- file save. Synopsis: "SAVE [filename]" } _G._TBASIC._OPERATR = { - -- operators - ">>>", "<<", ">>", "|", "&", "XOR", "!", -- bitwise operations - ";", -- string concatenation - "SIZEOF", -- LENGTH OF string/array. This is not C - "==", ">", "<", "<=", "=<", ">=", "=>", - "!=", "<>", "><", -- not equal - "=", ":=", -- assign - "AND", "OR", "NOT", - "^", -- math.pow, 0^0 should return 1. - "*", "/", "+", "-", -- arithmetic operations - "%", -- math.fmod - "TO", "STEP", -- integer sequence operator - "MINUS", -- unary minus + -- operators + ">>>", "<<", ">>", "|", "&", "XOR", "!", -- bitwise operations + ";", -- string concatenation + "==", ">", "<", "<=", "=<", ">=", "=>", + "!=", "<>", "><", -- not equal + "=", ":=", -- assign + "AND", "OR", "NOT", + "^", -- math.pow, 0^0 should return 1. + "*", "/", "+", "-", -- arithmetic operations + "%", -- math.fmod + "TO", "STEP", -- integer sequence operator + "MINUS", -- unary minus + "+=", "-=", "*=", "/=", "%=" -- C-style assign } _G._TBASIC._INTPRTR = {} _G._TBASIC._INTPRTR.TRACE = false -- print program counter while execution _G._TBASIC.SHOWLUAERROR = true local function stackpush(t, v) - t[#t + 1] = v + t[#t + 1] = v end local function stackpop(t) - local v = t[#t] - t[#t] = nil - return v + local v = t[#t] + t[#t] = nil + return v end local function stackpeek(t) - local v = t[#t] - return v + local v = t[#t] + return v end 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 + local hash = 2166136261 + for i = 1, #str do + hash = hash * 16777619 + hash = bit.bxor(hash, str:byte(i)) + end + return hash end ---sort builtin keywords list -table.sort(_TBASIC._FNCTION, function(a, b) return string.hash(a) < string.hash(b) end) - _G._TBASIC._INTPRTR.RESET = function() - _TBASIC.__appexit = false - _G._TBASIC._INTPRTR.PROGCNTR = 0 - _G._TBASIC._INTPRTR.MAXLINES = 63999 - _G._TBASIC._INTPRTR.VARTABLE = {} -- table of variables. [NAME] = data - _G._TBASIC._INTPRTR.FNCTABLE = {} -- table of functions. [NAME] = array of strings? (TBA) - _G._TBASIC._INTPRTR.CALLSTCK = {} - _G._TBASIC._INTPRTR.STACKMAX = 200 - _G._TBASIC._INTPRTR.CNSTANTS = { - M_PI = 3.14159265359, - M_2PI = 6.28318530718, - M_E = 2.71828182846, - M_ROOT2 = 1.41421356237, - TRUE = true, - FALSE = false, - NIL = nil - } - _G._TBASIC._INTPRTR.GOTOCNTR = 0 - _G._TBASIC._INTPRTR.GOTOLMIT = 16384 + _TBASIC.__appexit = false + _TBASIC._INTPRTR.PROGCNTR = 0 + _TBASIC._INTPRTR.MAXLINES = 63999 + _TBASIC._INTPRTR.VARTABLE = {} -- table of variables. [NAME] = data + _TBASIC._INTPRTR.FNCTABLE = {} -- table of functions. [NAME] = array of strings? (TBA) + _TBASIC._INTPRTR.CALLSTCK = {} -- return points (line number) + _TBASIC._INTPRTR.LINELABL = {} -- LABEL statement table + _TBASIC._INTPRTR.STACKMAX = 2000 + _TBASIC._INTPRTR.CNSTANTS = { + M_PI = 3.141592653589793, -- this is a standard implementation + M_2PI = 6.283185307179586, -- this is a standard implementation + M_E = 2.718281828459045, -- this is a standard implementation + M_ROOT2 = 1.414213562373095, -- this is a standard implementation + TRUE = true, + FALSE = false, + NIL = nil, + _VERSION = _TBASIC._VERSION + } end -- FUNCTION IMPLEMENTS -------------------------------------------------------- -local function __assert(arg, expected) - if type(arg) ~= expected then - _TBASIC._ERROR.ILLEGALARG(expected, type(arg)) - end +local function __readvar(varname) + -- if varname is a string that can be represented as number, returns tonumber(varname) ("4324" -> 4324) + -- if varname is a TBASIC string, return resolved string ("~FOOBAR" -> "FOOBAR") + -- if varname is a TBASIC variable, return resolved variable ("$FOO" -> any value stored in variable 'FOO') + + --print("readvar_varname", varname) + + if type(varname) == "table" or type(varname) == "nil" or type(varname) == "boolean" then + return varname + end + + if tonumber(varname) then + return tonumber(varname) + end + + if varname:byte(1) == 126 then + return varname:sub(2, #varname) + end + + if varname:byte(1) == 36 then + local data = varname:sub(2, #varname) + if tonumber(data) then + return tonumber(data) + else + -- try for constants + local retval = _TBASIC._INTPRTR.CNSTANTS[data:upper()] + if retval ~= nil then return retval + -- try for variable table + else return _TBASIC._INTPRTR.VARTABLE[data:upper()] end + end + else + return varname -- already resolved + end end -local function __assertlhand(lval, expected) - if type(lval) ~= expected then - _TBASIC._ERROR.ILLEGALARG("LHAND: "..expected, type(lval)) - end +local function __assert(aarg, expected) + local arg = __readvar(aarg) + + if type(arg) ~= expected then + _TBASIC._ERROR.ILLEGALARG(expected, type(arg)) + return + end end -local function __assertrhand(rval, expected) - if type(rval) ~= expected then - _TBASIC._ERROR.ILLEGALARG("RHAND: "..expected, type(rval)) - end +local function __assertlhand(llval, expected) + local lval = __readvar(llval) + + if type(lval) ~= expected then + _TBASIC._ERROR.ILLEGALARG("LHAND: "..expected, type(lval)) + return + end end -local function __checknumber(arg) - if arg == nil then - _TBASIC._ERROR.ILLEGALARG("number", type(arg)) - else - if type(arg) == "table" then - repeat - tval = arg[1] - arg = tval - until type(tval) ~= "table" - end +local function __assertrhand(rrval, expected) + local rval = __readvar(rrval) - n = tonumber(arg) - if n == nil then _TBASIC._ERROR.ILLEGALARG("number", type(arg)) - else return n end - end + if type(rval) ~= expected then + _TBASIC._ERROR.ILLEGALARG("RHAND: "..expected, type(rval)) + return + end end -local function __checkstring(arg) - if type(arg) == "function" then - _TBASIC._ERROR.ILLEGALARG("STRING/NUMBER/BOOL", type(arg)) - end +local function __checknumber(aarg) + local arg = __readvar(aarg) - if type(arg) == "table" then - repeat - tval = arg[1] - arg = tval - until type(tval) ~= "table" - end + if arg == nil then + _TBASIC._ERROR.ILLEGALARG("number", type(arg)) + return + else + if type(arg) == "table" then + repeat + tval = arg[1] + arg = tval + until type(tval) ~= "table" + end - local strarg = tostring(arg) - return strarg:byte(1) == 126 and strarg:sub(2, #strarg) or strarg + n = tonumber(arg) + if n == nil then + _TBASIC._ERROR.ILLEGALARG("number", type(arg)) + return + else + return n + end + end end +local function __checkstring(aarg) + local arg = __readvar(aarg) + + if type(arg) == "function" then + _TBASIC._ERROR.ILLEGALARG("STRING/NUMBER/BOOL", type(arg)) + return + end + + if type(arg) == "table" then + repeat + tval = arg[1] + arg = tval + until type(tval) ~= "table" + end + + local strarg = tostring(arg) + return strarg:byte(1) == 126 and strarg:sub(2, #strarg) or strarg +end + +local function __resolvevararg(...) + local ret = {} + for _, varname in ipairs({...}) do + table.insert(ret, __readvar(varname)) + end + return ret +end + +_G._TBASIC.__assert = __assert +_G._TBASIC.__assertlhand = __assertlhand +_G._TBASIC.__assertrhand = __assertrhand +_G._TBASIC.__checknumber = __checknumber +_G._TBASIC.__checkstring = __checkstring +_G._TBASIC.__readvar = __readvar +_G._TBASIC.__resolvevararg = __resolvevararg -local function _fnprint(arg) - if type(arg) == "function" then - _TBASIC._ERROR.SYNTAX() - return - end +--[[ +Function implementations - if type(arg) == "boolean" then - if arg then print(" TRUE") - else print(" FALSE") end - elseif _TBASIC.isstring(arg) then - print(__checkstring(arg)) - elseif _TBASIC.isnumber(arg) then -- if argument can be turned into a number (e.g. 14321, "541") - print(" "..arg) - elseif type(arg) == "table" then - _fnprint(arg[1]) -- recursion - else - print(tostring(arg)) - end + Cautions: +* Every function that returns STRING must prepend "~" + ]] + + +local function _fnprint(...) + function printarg(arg) + if type(arg) == "function" then + _TBASIC._ERROR.SYNTAX() + return + end + + if type(arg) == "boolean" then + if arg then io.write(" TRUE") + else io.write(" FALSE") end + elseif _TBASIC.isstring(arg) then + io.write(__checkstring(arg)) + elseif _TBASIC.isnumber(arg) then -- if argument can be turned into a number (e.g. 14321, "541") + io.write(" "..arg) + elseif type(arg) == "table" then + printarg(arg[1]) -- recursion + else + io.write(tostring(arg)) + end + end + + local args = __resolvevararg(...) + + if #args < 1 then + io.write "" + else + for i, arg in ipairs(args) do + if i > 1 then io.write "\t" end + + printarg(arg) + end + end + + io.write "\n" end local function _fngoto(lnum) - local linenum = __checknumber(lnum) + local linenum = nil + if _TBASIC.isnumber(lnum) then + linenum = __checknumber(lnum) + else + linenum = _TBASIC._INTPRTR.LINELABL[__checkstring(lnum)] + end - if linenum < 1 then - _TBASIC._ERROR.NOSUCHLINE(linenum) - return - end + if linenum == nil or linenum < 1 then + _TBASIC._ERROR.NOSUCHLINE(linenum) + return + end - _TBASIC._INTPRTR.GOTOCNTR = _TBASIC._INTPRTR.GOTOCNTR + 1 - _TBASIC._INTPRTR.PROGCNTR = linenum - 1 + _TBASIC._INTPRTR.PROGCNTR = linenum - 1 end local function _fnnewvar(varname, value) - _TBASIC._INTPRTR.VARTABLE[varname:upper()] = value + _TBASIC._INTPRTR.VARTABLE[varname:upper()] = __readvar(value) end local function _fngosub(lnum) - local linenum = __checknumber(lnum) + local linenum = nil + if _TBASIC.isnumber(lnum) then + linenum = __checknumber(lnum) + else + linenum = _TBASIC._INTPRTR.LINELABL[__checkstring(lnum)] + end - stackpush(_TBASIC._INTPRTR.CALLSTCK, _TBASIC._INTPRTR.PROGCNTR) -- save current line number - _fngoto(linenum) + stackpush(_TBASIC._INTPRTR.CALLSTCK, _TBASIC._INTPRTR.PROGCNTR) -- save current line number + _fngoto(linenum) end local function _fnreturn() - if #_TBASIC._INTPRTR.CALLSTCK == 0 then -- nowhere to return - _TBASIC._ERROR.RETURNWOSUB() - return - end + if #_TBASIC._INTPRTR.CALLSTCK == 0 then -- nowhere to return + _TBASIC._ERROR.RETURNWOSUB() + return + end - local return_line = stackpop(_TBASIC._INTPRTR.CALLSTCK) + 1 -- the line has GOSUB, so advance one - _fngoto(return_line) + local return_line = stackpop(_TBASIC._INTPRTR.CALLSTCK) + 1 -- the line has GOSUB, so advance one + _fngoto(return_line) end local function _fnabort() - _TBASIC._ERROR.ABORT() + _TBASIC._ERROR.ABORT() end local function _fnabortmsg(reason) - _TBASIC._ERROR.ABORT(__checkstring(reason)) + _TBASIC._ERROR.ABORT(__checkstring(__readvar(reason))) end -local function _fnif(bool) - __assert(bool, "boolean") +local function _fnif(bbool) + local bool = __readvar(bbool) - if bool == nil then - _TBASIC._ERROR.ILLEGALARG() - end + __assert(bool, "boolean") - if not bool then - _TBASIC._INTPRTR.PROGCNTR = _TBASIC._INTPRTR.PROGCNTR + 1 - end + if bool == nil then + _TBASIC._ERROR.ILLEGALARG() + return + end + + if not bool then + _TBASIC._INTPRTR.PROGCNTR = _TBASIC._INTPRTR.PROGCNTR + 1 + end end local function _fnnop() - return + return end local function _fnfor(seq) - --print("TEST: INTEGER SEQUENCE") - --print(table.concat(seq, " ")) - - stackpush(_TBASIC._INTPRTR.CALLSTCK, _TBASIC._INTPRTR.PROGCNTR) + stackpush(_TBASIC._INTPRTR.CALLSTCK, _TBASIC._INTPRTR.PROGCNTR) end local function _fnnext(...) - if #_TBASIC._INTPRTR.CALLSTCK == 0 then -- nowhere to return - _TBASIC._ERROR.NEXTWOFOR() - end + if #_TBASIC._INTPRTR.CALLSTCK == 0 then -- nowhere to return + _TBASIC._ERROR.NEXTWOFOR() + return + end - local variables = {...} -- array of strings(varname) e.g. "$X, $Y, $Z" - local branch = false - -- dequeue intsequences - for i, v in ipairs(variables) do - local t = nil - if _TBASIC.isvariable(v) then - t = _TBASIC._INTPRTR.VARTABLE[v:sub(2, #v)] - - if type(t) ~= "table" then - _TBASIC._ERROR.ILLEGALARG("ARRAY", type(t)) - end + local variables = {...} -- array of strings(varname) e.g. "$X, $Y, $Z" + local branch = false + -- dequeue intsequences + for i, v in ipairs(variables) do + local t = nil + if _TBASIC.isvariable(v) then + t = _TBASIC._INTPRTR.VARTABLE[v:sub(2, #v)] + + if type(t) ~= "table" then + _TBASIC._ERROR.ILLEGALARG("ARRAY", type(t)) + return + end - table.remove(t, 1) + table.remove(t, 1) - -- unassign variable - if #t == 0 then - _TBASIC._INTPRTR.VARTABLE[v] = nil - branch = true - end - else - _TBASIC._ERROR.ILLEGALARG("ARRAY", type(t)) - end - end + -- unassign variable + if #t == 0 then + _TBASIC._INTPRTR.VARTABLE[v] = nil + branch = true + end + else + _TBASIC._ERROR.ILLEGALARG("ARRAY", type(t)) + return + end + end - -- branch? or go back? - if not branch then - _fngoto(stackpeek(_TBASIC._INTPRTR.CALLSTCK) + 1) -- the line has FOR statement - else - stackpop(_TBASIC._INTPRTR.CALLSTCK) -- dump the stack - end + -- branch? or go back? + if not branch then + _fngoto(stackpeek(_TBASIC._INTPRTR.CALLSTCK) + 1) -- the line has FOR statement + else + stackpop(_TBASIC._INTPRTR.CALLSTCK) -- dump the stack + end end +local function _fnabs(n) + return math.abs(__checknumber(n)) +end + +local function _fnsin(n) + return math.sin(__checknumber(n)) +end + +local function _fncos(n) + return math.cos(__checknumber(n)) +end + +local function _fntan(n) + return math.tan(__checknumber(n)) +end + +local function _fntorad(n) + return math.rad(__checknumber(n)) +end + +local function _fnascii(char) + return __checkstring(char):byte(1) +end + +local function _fncbrt(n) + return __checknumber(n)^3 +end + +local function _fnceil(n) + return math.ceil(__checknumber(n)) +end + +local function _fnchar(code) + return "~"..string.char(__checknumber(code)) -- about "~".. ? read the cautions above! +end + +local function _fnfloor(n) + return math.floor(__checknumber(n)) +end + +local function _fngetkeycode(...) + -- TODO get a single character from the keyboard and saves the code of the character to the given variable(s) +end + +local function _fnint(n) + num = __checknumber(n) + return num >= 0 and math.floor(n) or math.ceil(n) +end + +local function _fnmultinv(n) -- multiplicative invert + return 1.0 / __checknumber(n) +end + +local function _fnsubstrleft(str, n) + return "~"..__checkstring(str):sub(1, __checknumber(n)) +end + +local function _fnsubstr(str, left, right) + return "~"..__checkstring(str):sub(__checknumber(left), __checknumber(right)) +end + +local function _fnsubstrright(str, n) + return "~"..__checkstring(str):sub(-__checknumber(n)) +end + +local function _fnlen(var) + local value = __readvar(var) + return #value +end + +local function _fnloge(n) + return math.log(__checknumber(n)) +end + +local function _fnmax(...) + local args = __resolvevararg(...) + if #args < 1 then + _TBASIC._ERROR.ARGMISSING("MAX") + return + end + + local max = -math.huge + for _, i in ipairs(args) do + local n = __checknumber(i) + if max < n then max = n end + end + return max +end + +local function _fnmin(...) + local args = __resolvevararg(...) + if #args < 1 then + _TBASIC._ERROR.ARGMISSING("MIN") + return + end + + local min = math.huge + for _, i in ipairs(args) do + local n = __checknumber(i) + if min > n then min = n end + end + return min +end + +local function _fnrand() + return math.random() +end + +local function _fnround(n) + return math.floor(__checknumber(n) + 0.5) +end + +local function _fnsign(n) + local num = __checknumber(n) + return num > 0 and 1.0 or num < 0 and -1.0 or 0.0 +end + +local function _fnsqrt(n) + return __checknumber(n)^(0.5) +end + +local function _fntostring(n) + local ret = tostring(__checknumber(n)) + if not ret then + _TBASIC._ERROR.ILLEGALARG() + return + else + return "~"..ret + end +end + +local function _fntonumber(s) + if tonumber(s) then return s end + return tonumber(__checkstring(s)) +end + +local function _fntan(n) + return math.tan(__checknumber(n)) +end + +local function _fninput(...) -- INPUT(var1, [var2, var3 ...]) + local args = {...} + local prompt = "YOUR INPUT ? " + local prompt_numbered = "YOUR INPUT (%d OF %d) ? " + + function prompt_and_get_input() + -- if there's two or more input, a number will be shown + if #args >= 2 then + io.write(string.format(prompt_numbered, argcount, #args)) + else + io.write(prompt) + end + io.flush() -- print out the line right away + + local value = io.read() + + return value + end + + if #args < 1 then + _TBASIC._ERROR.ARGMISSING("INPUT") + return + else + for argcount, varname in ipairs(args) do + local inputvalue = nil + while inputvalue == nil or inputvalue == "" do + inputvalue = prompt_and_get_input() + _opassign(varname, inputvalue) + end + end + end +end + +local function _fnlabel(lname) + _TBASIC._INTPRTR.LINELABL[__checkstring(lname)] = _TBASIC._INTPRTR.PROGCNTR +end -- OPERATOR IMPLEMENTS -------------------------------------------------------- local function booleanise(bool) - return bool and "$TRUE" or "$FALSE" + return bool and "$TRUE" or "$FALSE" end -local function _opconcat(lval, rval) - if type(lval) == "function" then _TBASIC._ERROR.ILLEGALARG("VALUE", "FUNCTION") end - if type(rval) == "function" then _TBASIC._ERROR.ILLEGALARG("VALUE", "FUNCTION") end +function _opconcat(llval, rrval) + local lval = __readvar(llval) + local rval = __readvar(rrval) - local l = (type(lval) == "string" and lval:byte(1)) == 126 and lval:sub(2, #lval) or __checkstring(lval) - local r = (type(rval) == "string" and rval:byte(1)) == 126 and rval:sub(2, #rval) or __checkstring(rval) + if type(lval) == "function" then _TBASIC._ERROR.ILLEGALARG("VALUE", "FUNCTION") return end + if type(rval) == "function" then _TBASIC._ERROR.ILLEGALARG("VALUE", "FUNCTION") return end - ret = l..r - return ret:byte(1) == 126 and "~"..ret or ret -- re-append missing "~" if applicable + local l = (type(lval) == "string" and lval:byte(1)) == 126 and lval:sub(2, #lval) or __checkstring(lval) + local r = (type(rval) == "string" and rval:byte(1)) == 126 and rval:sub(2, #rval) or __checkstring(rval) + + return "~"..l..r end -local function _opplus(lval, rval) - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opplus(lval, rval) + local l = __checknumber(lval) + local r = __checknumber(rval) - return l + r + return l + r end -local function _optimes(lval, rval) - local l = __checknumber(lval) - local r = __checknumber(rval) +function _optimes(lval, rval) + local l = __checknumber(lval) + local r = __checknumber(rval) - return l * r + return l * r end -local function _opminus(lval, rval) return _opplus(lval, -rval) end +function _opminus(lval, rval) + local l = __checknumber(lval) + local r = __checknumber(rval) -local function _opdiv(lval, rval) - local l = __checknumber(lval) - local r = __checknumber(rval) - - if l == 0 and r == 0 then - _TBASIC._ERROR.INDETERMINANT() - elseif r == 0 then - _TBASIC._ERROR.DIV0() - else - return _optimes(l, 1.0 / r) - end + return l - r end -local function _opmodulo(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opdiv(lval, rval) + local l = __checknumber(lval) + local r = __checknumber(rval) - return math.fmod(l, r) + if l == 0 and r == 0 then + _TBASIC._ERROR.INDETERMINANT() + return + elseif r == 0 then + _TBASIC._ERROR.DIV0() + return + else + return _optimes(l, 1.0 / r) + end end -local function _oppower(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opmodulo(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return math.pow(l, r) -- 0^0 is 1 according to the spec, and so is the Lua's. + return math.fmod(l, r) end -local function _opassign(var, value) - if _TBASIC.isnumber(var) or _TBASIC.isfunction(var) or _TBASIC.isoperator(var) or _TBASIC.isargsep(var) then - _TBASIC._ERROR.ILLEGALNAME(var) - end +function _oppower(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - -- remove missed "$" - local varname = var:byte(1) == 36 and var:sub(2, #var) or var - - -- if it still has "$", the programmer just broke the law - if varname:byte(1) == 36 then - _TBASIC._ERROR.ILLEGALNAME(varname, "HAS ILLEGAL CHARACTER '$'") - end - - _TBASIC._INTPRTR.VARTABLE[varname:upper()] = value + return math.pow(l, r) -- 0^0 is 1 according to the spec, and so is the Lua's. end -local function _opeq(lval, rval) return booleanise(__checkstring(lval) == __checkstring(rval)) end -local function _opne(lval, rval) return booleanise(__checkstring(lval) ~= __checkstring(rval)) end -local function _opgt(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opassign(var, value) + if _TBASIC.isnumber(var) or _TBASIC.isfunction(var) or _TBASIC.isoperator(var) or _TBASIC.isargsep(var) then + _TBASIC._ERROR.ILLEGALNAME(var) + return + end - return booleanise(l > r) + -- remove missed "$" + local varname = var:byte(1) == 36 and var:sub(2, #var) or var + + -- if it still has "$", the programmer just broke the law + if varname:byte(1) == 36 then + _TBASIC._ERROR.ILLEGALNAME(varname, "HAS ILLEGAL CHARACTER '$'") + return + end + + _TBASIC._INTPRTR.VARTABLE[varname:upper()] = __readvar(value) end -local function _oplt(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opeq(llval, rrval) + local lval = __readvar(llval) + local rval = __readvar(rrval) - return booleanise(l < r) + if tonumber(lval) and tonumber(rval) then + return booleanise(tonumber(lval) == tonumber(rval)) + else + return booleanise(__checkstring(lval) == __checkstring(rval)) + end end -local function _opge(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opne(llval, rrval) + local lval = __readvar(llval) + local rval = __readvar(rrval) - return booleanise(l >= r) + if tonumber(lval) and tonumber(rval) then + return booleanise(tonumber(lval) ~= tonumber(rval)) + else + return booleanise(__checkstring(lval) ~= __checkstring(rval)) + end end -local function _ople(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opgt(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return booleanise(l <= r) + return booleanise(l > r) end -local function _opband(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _oplt(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return bit.band(l, r) + return booleanise(l < r) end -local function _opbor(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opge(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return bit.bor(l, r) + return booleanise(l >= r) end -local function _opbxor(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _ople(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return bit.bxor(l, r) + return booleanise(l <= r) end -local function _opbnot(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opband(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return bit.bnot(l, r) + return bit.band(l, r) end -local function _oplshift(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opbor(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return bit.lshift(l, r) + return bit.bor(l, r) end -local function _oprshift(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opbxor(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) - return bit.arshift(l, r) + return bit.bxor(l, r) end -local function _opurshift(lval, rval) - local expected = "number" - local l = __checknumber(lval) - local r = __checknumber(rval) +function _opbnot(val) + local expected = "number" + local v = __checknumber(val) - return bit.rshift(l, r) + return bit.bnot(v) end -local function _opsizeof(target) - if type(target) == "table" then - -- TODO return dimensional size - return #target - else - _TBASIC._ERROR.ILLEGALARG("string or array", type(lval)) - end +function _oplshift(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) + + return bit.lshift(l, r) end -local function _opland(lhand, rhand) - return booleanise(lhand and rhand) +function _oprshift(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) + + return bit.arshift(l, r) end -local function _oplor(lhand, rhand) - return booleanise(lhand or rhand) +function _opurshift(lval, rval) + local expected = "number" + local l = __checknumber(lval) + local r = __checknumber(rval) + + return bit.rshift(l, r) end -local function _oplnot(rhand) - return booleanise(not rhand) +function _opland(lhand, rhand) + return booleanise(__readvar(lhand) and __readvar(rhand)) end -local function _opintrange(x, y) -- x TO y -> {x..y} - local from = __checknumber(x) - local to = __checknumber(y) - - local seq = {} - if from < to then - for i = from, to do - table.insert(seq, i) - end - else - for i = from, to, -1 do - table.insert(seq, i) - end - end - - return seq +function _oplor(lhand, rhand) + return booleanise(__readvar(lhand) or __readvar(rhand)) end -local function _opintrangestep(seq, stp) -- i know you can just use "for i = from, to, step" - local step = __checknumber(stp) -- but that's just how not this stack machine works... - __assert(seq, "table") - - if step == 1 then return seq end - if step < 1 then _TBASIC._ERROR.ILLEGALARG() end - - local newseq = {} - for i, v in ipairs(seq) do - if i % step == 1 then - table.insert(newseq, v) - end - end - - return newseq +function _oplnot(rhand) + return booleanise(not __readvar(rhand)) end -local function _opunaryminus(n) - local num = __checknumber(n) - return -num +function _opintrange(x, y) -- x TO y -> {x..y} + local from = __checknumber(x) + local to = __checknumber(y) + + local seq = {} + if from < to then + for i = from, to do + table.insert(seq, i) + end + else + for i = from, to, -1 do + table.insert(seq, i) + end + end + + return seq end +function _opintrangestep(sseq, sstp) -- i know you can just use "for i = from, to, step" + local seq = __readvar(sseq) + local stp = __readvar(sstp) + local step = __checknumber(stp) -- but that's just how not this stack machine works... + __assert(seq, "table") + + if step == 1 then return seq end + if step < 1 then _TBASIC._ERROR.ILLEGALARG() return end + + local newseq = {} + for i, v in ipairs(seq) do + if i % step == 1 then + table.insert(newseq, v) + end + end + + return newseq +end + +function _opunaryminus(n) + local num = __checknumber(n) + return -num +end + +function _opplusassign(var, value) + if type(__readvar(var)) == "number" then + _opassign(var, __readvar(var) + __checknumber(value)) + else + _TBASIC._ERROR.ILLEGALARG() + return + end +end + +function _opminusassign(var, value) + if type(__readvar(var)) == "number" then + _opassign(var, __readvar(var) - __checknumber(value)) + else + _TBASIC._ERROR.ILLEGALARG() + return + end +end + +function _optimesassign(var, value) + if type(__readvar(var)) == "number" then + _opassign(var, __readvar(var) * __checknumber(value)) + else + _TBASIC._ERROR.ILLEGALARG() + return + end +end + +function _opdivassign(var, value) + if type(__readvar(var)) == "number" then + if __checknumber(value) == 0 then + _TBASIC._ERROR.DIV0() + return + else + _opassign(var, __readvar(var) / __checknumber(value)) + end + else + _TBASIC._ERROR.ILLEGALARG() + return + end +end + +function _opmodassign(var, value) + if type(__readvar(var)) == "number" then + if __checknumber(value) == 0 then + _TBASIC._ERROR.DIV0() + return + else + _opassign(var, math.fmod(__readvar(var), __checknumber(value))) + end + else + _TBASIC._ERROR.ILLEGALARG() + return + end +end + + local vararg = -13 -- magic -_G._TBASIC.LUAFN = { - -- variable control - CLR = {function() _TBASIC._INTPRTR.VARTABLE = {} end, 0}, - -- flow control - IF = {_fnif, 1}, - THEN = {_fnnop, 0}, - GOTO = {_fngoto, 1}, - GOSUB = {_fngosub, 1}, - RETURN = {_fnreturn, 0}, - END = {function() _G._TBASIC.__appexit = true end, 0}, - ABORT = {_fnabort, 0}, - ABORTM = {_fnabortmsg, 1}, - FOR = {_fnfor, 1}, - NEXT = {_fnnext, vararg}, - -- stdio - PRINT = {_fnprint, 1}, - --------------- - -- operators -- - --------------- - [";"] = {_opconcat, 2}, - ["+"] = {_opplus, 2}, - ["*"] = {_optimes, 2}, - ["-"] = {_opminus, 2}, - ["/"] = {_opdiv, 2}, - ["%"] = {_opmodulo, 2}, - ["^"] = {_oppower, 2}, - ["=="] = {_opeq, 2}, - ["!="] = {_opne, 2}, {["<>"] = _opne, 2}, {["><"] = _opne, 2}, - [">="] = {_opge, 2}, {["=>"] = _opge, 2}, - ["<="] = {_ople, 2}, {["=<"] = _ople, 2}, - [">"] = {_opgt, 2}, - ["<"] = {_oplt, 2}, - ["="] = {_opassign, 2}, {[":="] = _opassign, 2}, - SIZEOF = {_opsizeof, 1}, - MINUS = {_opunaryminus, 1}, - -- logical operators - AND = {_opland, 2}, - OR = {_oplor, 2}, - NOT = {_oplnot, 1}, - -- bit operators - ["<<"] = {_oplshift, 2}, - [">>"] = {_oprshift, 2}, -- bit.arshift - [">>>"] = {_opurshift, 2}, -- bit.rshift - ["|"] = {_opbor, 2}, - ["&"] = {_opband, 2}, - ["!"] = {_opbnot, 2}, - XOR = {_opbxor, 2}, - -- int sequence - TO = {_opintrange, 2}, - STEP = {_opintrangestep, 2}, - -- misc - REM = {function() end, 0} +_G._TBASIC.LUAFN = { + -- variable control + CLR = {function() _TBASIC._INTPRTR.VARTABLE = {} end, 0}, + -- flow control + IF = {_fnif, 1}, + THEN = {_fnnop, 0}, + GOTO = {_fngoto, 1}, + GOSUB = {_fngosub, 1}, + RETURN = {_fnreturn, 0}, + END = {function() _G._TBASIC.__appexit = true end, 0}, + ABORT = {_fnabort, 0}, + ABORTM = {_fnabortmsg, 1}, + FOR = {_fnfor, 1}, + NEXT = {_fnnext, vararg}, + LABEL = {_fnlabel, 1}, + -- stdio + PRINT = {_fnprint, vararg}, + INPUT = {_fninput, vararg}, + -- mathematics + ABS = {_fnabs, 1}, + CBRT = {_fncbrt, 1}, + CEIL = {_fnceil, 1}, + COS = {_fncos, 1}, + FLOOR = {_fnfloor, 1}, + INT = {_fnint, 1}, + INV = {_fnmultinv, 1}, + LOG = {_fnloge, 1}, + MAX = {_fnmax, vararg}, + MIN = {_fnmin, vararg}, + RAD = {_fntorad, 1}, + RND = {_fnrand, 0}, + ROUND = {_fnround, 1}, + SGN = {_fnsign, 1}, + SIN = {_fnsin, 1}, + SQRT = {_fnsqrt, 1}, + TAN = {_fntan, 1}, + -- string manipulation + LEFT = {_fnsubstrleft, 2}, + LEN = {_fnlen, 1}, + MID = {_fnsubstr, 3}, + RIGHT = {_fnsubstrright, 2}, + -- type conversion + ASC = {_fnascii, 1}, + CHR = {_fnchar, 1}, + STR = {_fntostring, 1}, + VAL = {_fntonumber, 1}, + --------------- + -- operators -- + --------------- + [";"] = {_opconcat, 2}, + ["+"] = {_opplus, 2}, + ["*"] = {_optimes, 2}, + ["-"] = {_opminus, 2}, + ["/"] = {_opdiv, 2}, + ["%"] = {_opmodulo, 2}, + ["^"] = {_oppower, 2}, + ["=="] = {_opeq, 2}, + ["!="] = {_opne, 2}, ["<>"] = {_opne, 2}, ["><"] = {_opne, 2}, + [">="] = {_opge, 2}, ["=>"] = {_opge, 2}, + ["<="] = {_ople, 2}, ["=<"] = {_ople, 2}, + [">"] = {_opgt, 2}, + ["<"] = {_oplt, 2}, + ["="] = {_opassign, 2}, [":="] = {_opassign, 2}, + ["+="] = {_opplusassign, 2}, ["-="] = {_opminusassign, 2}, + ["*="] = {_optimesassign, 2}, ["/="] = {_opdivassign, 2}, ["%="] = {_opmodassign, 2}, + MINUS = {_opunaryminus, 1}, + -- logical operators + AND = {_opland, 2}, + OR = {_oplor, 2}, + NOT = {_oplnot, 1}, + -- bit operators + ["<<"] = {_oplshift, 2}, + [">>"] = {_oprshift, 2}, -- bit.arshift + [">>>"] = {_opurshift, 2}, -- bit.rshift + ["|"] = {_opbor, 2}, + ["&"] = {_opband, 2}, + ["!"] = {_opbnot, 1}, + XOR = {_opbxor, 2}, + -- int sequence + TO = {_opintrange, 2}, + STEP = {_opintrangestep, 2}, + -- misc + REM = {_fnnop, 0} } _G._TBASIC._GETARGS = function(func) - local f = _TBASIC.LUAFN[func] - if f == nil then return nil end - return f[2] + local f = _TBASIC.LUAFN[func] + if f == nil then return nil end + return f[2] end @@ -712,266 +1107,284 @@ end -- PARSER IMPL ---------------------------------------------------------------- local opprecedence = { - {":=", "="}, -- least important - {"OR"}, - {"AND"}, - {"|"}, - {"XOR"}, - {"&"}, - {"==", "!=", "<>", "><"}, - {"<=", ">=", "=<", "=>", "<", ">"}, - {"TO", "STEP"}, - {">>>", "<<", ">>"}, - {";"}, - {"+", "-"}, - {"*", "/", "%"}, - {"NOT", "!"}, - {"^", "SIZEOF"}, -- most important - {"MINUS"} + {":=", "=", "+=", "-=", "*=", "/=", "%="}, -- least important + {"OR"}, + {"AND"}, + {"|"}, + {"XOR"}, + {"&"}, + {"==", "!=", "<>", "><"}, + {"<=", ">=", "=<", "=>", "<", ">"}, + {"TO", "STEP"}, + {">>>", "<<", ">>"}, + {";"}, + {"+", "-"}, + {"*", "/", "%"}, + {"NOT", "!"}, + {"^"}, -- most important + {"MINUS"} } local opassoc = { - rtl = {";", "^", "NOT", "!", "SIZEOF"} + rtl = {";", "^", "NOT", "!"} } local function exprerr(token) - _TBASIC._ERROR.SYNTAXAT(token) + _TBASIC._ERROR.SYNTAXAT(token) end -local function _op_precd(op) - -- take care of prematurely prepended '#' - local t1 = op:byte(1) == 35 and op:sub(2, #op) or op - op = t1:upper() +function _op_precd(op) + -- take care of prematurely prepended '#' + local t1 = op:byte(1) == 35 and op:sub(2, #op) or op + op = t1:upper() - for i = 1, #opprecedence do - for _, op_in_quo in ipairs(opprecedence[i]) do - if op == op_in_quo then - return i - end - end - end - exprerr("precedence of "..op) + for i = 1, #opprecedence do + for _, op_in_quo in ipairs(opprecedence[i]) do + if op == op_in_quo then + return i + end + end + end + exprerr("precedence of "..op) end -local function _op_isrtl(op) - for _, v in ipairs(opassoc.rtl) do - if op == v then return true end - end - return false +function _op_isrtl(op) + for _, v in ipairs(opassoc.rtl) do + if op == v then return true end + end + return false end -local function _op_isltr(op) - return not _op_isrtl(op) +function _op_isltr(op) + return not _op_isrtl(op) end function _G._TBASIC.isnumber(token) - return tonumber(token) and true or false + return tonumber(token) and true or false end function _G._TBASIC.isoperator(token) - if token == nil then return false end + if token == nil then return false end - -- take care of prematurely prepended '#' - local t1 = token:byte(1) == 35 and token:sub(2, #token) or token - token = t1 + -- take care of prematurely prepended '#' + local t1 = token:byte(1) == 35 and token:sub(2, #token) or token + token = t1 - for _, tocheck in ipairs(_TBASIC._OPERATR) do - if tocheck == token:upper() then return true end - end - return false + for _, tocheck in ipairs(_TBASIC._OPERATR) do + if tocheck == token:upper() then return true end + end + return false end function _G._TBASIC.isvariable(word) - if type(word) == "number" then return false end - if type(word) == "boolean" then return true end - if type(word) == "table" then return true end - if word == nil then return false end - return word:byte(1) == 36 + if type(word) == "number" then return false end + if type(word) == "boolean" then return true end + if type(word) == "table" then return true end + if word == nil then return false end + return word:byte(1) == 36 end function _G._TBASIC.isargsep(token) - return token == "," + return token == "," end function _G._TBASIC.isfunction(token) - if token == nil then return false end + if token == nil then return false end - -- take care of prematurely prepended '&' - local t1 = token:byte(1) == 38 and token:sub(2, #token) or token - token = t1 + -- take care of prematurely prepended '&' + local t1 = token:byte(1) == 38 and token:sub(2, #token) or token + token = t1 - -- try for builtin - local cmpval = function(table_elem) return string.hash(table_elem) end - - local found = table.binsearch(_TBASIC._FNCTION, token, cmpval) + -- try for builtin + local cmpval = function(table_elem) return string.hash(table_elem) end + + local found = table.binsearch(_TBASIC._FNCTION, token, cmpval) - if found then - return true - end + if found then + return true + end - -- try for user-defined functions - found = table.binsearch(_TBASIC._INTPRTR.FNCTABLE, token, cmpval) - if found then -- found is either Table or Nil. We want boolean value. - return true - else - return false - end + -- try for user-defined functions + found = table.binsearch(_TBASIC._INTPRTR.FNCTABLE, token, cmpval) + if found then -- found is either Table or Nil. We want boolean value. + return true + else + return false + end end function _G._TBASIC.isstring(token) - if type(token) ~= "string" then return false end - return token:byte(1) == 126 + if type(token) ~= "string" then return false end + return token:byte(1) == 126 end local function printdbg(...) - local debug = false - if debug then print("TBASINCL", ...) end + local debug = false + if debug then print("TBASINCL", ...) end end -- implementation of the Shunting Yard algo _G._TBASIC.TORPN = function(exprarray) - local stack = {} - local outqueue = {} + local stack = {} + local outqueue = {} - local loophookkeylist = {} - local function infloophook(key) - if not _G[key] then - _G[key] = 0 - table.insert(loophookkeylist, key) - end - _G[key] = _G[key] + 1 + local loophookkeylist = {} + local function infloophook(key) + if not _G[key] then + _G[key] = 0 + table.insert(loophookkeylist, key) + end + _G[key] = _G[key] + 1 - if _G[key] > 50000 then - error(key..": too long without yielding") - end - end + if _G[key] > 50000 then + error(key..": too long without yielding") + end + end - local isfunction = _TBASIC.isfunction - local isoperator = _TBASIC.isoperator - local isargsep = _TBASIC.isargsep - local isnumber = _TBASIC.isnumber + local isfunction = _TBASIC.isfunction + local isoperator = _TBASIC.isoperator + local isargsep = _TBASIC.isargsep + local isnumber = _TBASIC.isnumber + local isstring = _TBASIC.isstring - for _, token in ipairs(exprarray) do--expr:gmatch("[^ ]+") do - if token == nil then error("Token is nil!") end + for _, token in ipairs(exprarray) do--expr:gmatch("[^ ]+") do + if token == nil then error("Token is nil!") end - -- hack: remove single prepended whitespace - t1 = token:byte(1) == 32 and token:sub(2, #token) or token - token = t1 + -- hack: remove single prepended whitespace + t1 = token:byte(1) == 32 and token:sub(2, #token) or token + token = t1 - printdbg("TOKEN", "'"..token.."'") - if isfunction(token:upper()) then - printdbg("is function") + printdbg("TOKEN", "'"..token.."'") + if isfunction(token:upper()) then + printdbg("is function") - stackpush(stack, "&"..token:upper()) - elseif isargsep(token) then - printdbg("is argument separator") + stackpush(stack, "&"..token:upper()) + elseif isargsep(token) then + printdbg("is argument separator") - if not (stackpeek(stack) == "(" or #stack == 0) then - repeat - stackpush(outqueue, stackpop(stack)) + if not (stackpeek(stack) == "(" or #stack == 0) then + repeat + stackpush(outqueue, stackpop(stack)) - infloophook("repeat1") - until stackpeek(stack) == "(" or #stack == 0 - end - -- no left paren encountered, ERROR! - if #stack == 0 then exprerr(token) end -- misplaces sep or mismatched parens - elseif isoperator(token) then - printdbg("is operator") + infloophook("repeat1") + until stackpeek(stack) == "(" or #stack == 0 + end + -- no left paren encountered, ERROR! + if #stack == 0 then exprerr(token) end -- misplaces sep or mismatched parens + elseif isoperator(token) then + printdbg("is operator") - local o1 = token + local o1 = token - while isoperator(stackpeek(stack)) and ( - (_op_isltr(o1) and _op_precd(o1) <= _op_precd(stackpeek(stack))) or - (_op_isrtl(o1) and _op_precd(o1) < _op_precd(stackpeek(stack))) - ) do - local o2 = stackpeek(stack) - - printdbg("--> push o2 to stack, o2:", o2) + while isoperator(stackpeek(stack)) and ( + (_op_isltr(o1) and _op_precd(o1) <= _op_precd(stackpeek(stack))) or + (_op_isrtl(o1) and _op_precd(o1) < _op_precd(stackpeek(stack))) + ) do + local o2 = stackpeek(stack) + + printdbg("--> push o2 to stack, o2:", o2) - stackpop(stack) -- drop - stackpush(outqueue, (o2:byte(1) == 35) and o2 or "#"..o2:upper()) -- try to rm excess '#' + stackpop(stack) -- drop + stackpush(outqueue, (o2:byte(1) == 35) and o2 or "#"..o2:upper()) -- try to rm excess '#' - infloophook("while") - end + infloophook("while") + end - stackpush(stack, "#"..o1:upper()) - elseif token == "(" then - stackpush(stack, token) - elseif token == ")" then - while stackpeek(stack) ~= "(" do - if #stack == 0 then - exprerr(token) - end + stackpush(stack, "#"..o1:upper()) + elseif token == "(" then + stackpush(stack, token) + elseif token == ")" then + while stackpeek(stack) ~= "(" do + if #stack == 0 then + exprerr(token) + end - printdbg("--> stack will pop", stackpeek(stack)) - - stackpush(outqueue, stackpop(stack)) - - infloophook("") - end + printdbg("--> stack will pop", stackpeek(stack)) + + stackpush(outqueue, stackpop(stack)) + + infloophook("") + end - printdbg("--> will drop", stackpeek(stack), "(should be left paren!)") - - --[[found_left_paren = false - if stackpeek(stack) ~= "(" then - exprerr(token) - else - found_left_paren = true - end]] - stackpop(stack) -- drop + printdbg("--> will drop", stackpeek(stack), "(should be left paren!)") + + --[[found_left_paren = false + if stackpeek(stack) ~= "(" then + exprerr(token) + else + found_left_paren = true + end]] + stackpop(stack) -- drop - printdbg("--> stack peek after drop", stackpeek(stack)) + printdbg("--> stack peek after drop", stackpeek(stack)) - if isfunction(stackpeek(stack)) then - printdbg("--> will enq fn", stackpeek(stack)) - stackpush(outqueue, stackpop(stack)) - end - printdbg("--> STACKTRACE_ITMD", table.concat(stack, " ")) - printdbg("--> OUTPUT_ITMD", table.concat(outqueue, " ")) + if isfunction(stackpeek(stack)) then + printdbg("--> will enq fn", stackpeek(stack)) + stackpush(outqueue, stackpop(stack)) + end + printdbg("--> STACKTRACE_ITMD", table.concat(stack, " ")) + printdbg("--> OUTPUT_ITMD", table.concat(outqueue, " ")) - -- stack empty without finding left paren, ERROR! - --if not found_left_paren and #stack == 0 then exprerr(token) end -- mismatched parens - elseif _TBASIC._INTPRTR.VARTABLE[token:upper()] ~= nil or - _TBASIC._INTPRTR.CNSTANTS[token:upper()] ~= nil then -- if the token is variable - printdbg("is variable") + -- stack empty without finding left paren, ERROR! + --if not found_left_paren and #stack == 0 then exprerr(token) end -- mismatched parens + elseif isstring(token) then + printdbg("is data") + stackpush(outqueue, token) -- arbitrary data + else -- a word without '~' or anything; assume it's a variable name + printdbg("is variable") + stackpush(outqueue, "$"..token:upper()) + end + printdbg("STACKTRACE", table.concat(stack, " ")) + printdbg("OUTPUT", table.concat(outqueue, " ")) + printdbg() + end - stackpush(outqueue, "$"..token:upper()) - else - printdbg("is data") + while #stack > 0 do + if stackpeek(stack) == "(" or stackpeek(stack) == ")" then + exprerr("(paren)") -- mismatched parens + end + stackpush(outqueue, stackpop(stack)) - stackpush(outqueue, token) -- arbitrary data - end - printdbg("STACKTRACE", table.concat(stack, " ")) - printdbg("OUTPUT", table.concat(outqueue, " ")) - printdbg() - end + infloophook("while3") + end - while #stack > 0 do - if stackpeek(stack) == "(" or stackpeek(stack) == ")" then - exprerr("(paren)") -- mismatched parens - end - stackpush(outqueue, stackpop(stack)) + printdbg("FINAL RESULT: "..table.concat(outqueue, " ")) - infloophook("while3") - end + for _, key in ipairs(loophookkeylist) do + _G[key] = nil + end - printdbg("FINAL RESULT: "..table.concat(outqueue, " ")) - - for _, key in ipairs(loophookkeylist) do - _G[key] = nil - end - - return outqueue + return outqueue end -- INIT ----------------------------------------------------------------------- +-- load extensions +local status, err = pcall( + function() + if os and os.loadAPI then -- ComputerCraft + os.loadAPI "TBASEXTN.lua" + else + require "TBASEXTN" + end + end +) +if err then + error(err) +end + + +--sort builtin keywords list +table.sort(_TBASIC._FNCTION, function(a, b) return string.hash(a) < string.hash(b) end) + + _G._TBASIC._INTPRTR.RESET() + --[[ Terran BASIC (TBASIC) Copyright (c) 2016 Torvald (minjaesong) and the contributors. diff --git a/src/net/torvald/terrarum/virtualcomputer/terminal/SimpleTextTerminal.kt b/src/net/torvald/terrarum/virtualcomputer/terminal/SimpleTextTerminal.kt index 1190708af..75e1767ad 100644 --- a/src/net/torvald/terrarum/virtualcomputer/terminal/SimpleTextTerminal.kt +++ b/src/net/torvald/terrarum/virtualcomputer/terminal/SimpleTextTerminal.kt @@ -4,6 +4,7 @@ import net.torvald.aa.AAFrame import net.torvald.aa.ColouredFastFont import net.torvald.terrarum.* import net.torvald.terrarum.gameactors.abs +import net.torvald.terrarum.gamecontroller.Key import net.torvald.terrarum.virtualcomputer.computer.BaseTerrarumComputer import org.lwjgl.BufferUtils import org.lwjgl.openal.AL @@ -106,8 +107,6 @@ open class SimpleTextTerminal( private set - private var redrawSemaphore = false - override fun getColor(index: Int): Color = colours[index] @@ -142,45 +141,40 @@ open class SimpleTextTerminal( * pass UIcanvas to the parameter "g" */ override fun render(gc: GameContainer, g: Graphics) { - // FIXME don't redraw every time it's slow g.font = font - // don't redraw() every fucking time, you're wasting your precious process cycle - if (redrawSemaphore) { + blendNormal() - blendNormal() - - // black background (this is mandatory) - g.color = Color.black - g.fillRect(0f, 0f, displayW.toFloat(), displayH.toFloat()) + // black background (this is mandatory) + g.color = Color.black + g.fillRect(0f, 0f, displayW.toFloat(), displayH.toFloat()) - // screen buffer - for (y in 0..height - 1) { - for (x in 0..width - 1) { - val ch = screenBuffer.getChar(x, y) + // screen buffer + for (y in 0..height - 1) { + for (x in 0..width - 1) { + val ch = screenBuffer.getChar(x, y) - // background - g.color = getColor(screenBuffer.getBackgroundColour(x, y)) - g.fillRect(fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize, - fontW.toFloat(), fontH.toFloat()) + // background + g.color = getColor(screenBuffer.getBackgroundColour(x, y)) + g.fillRect(fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize, + fontW.toFloat(), fontH.toFloat()) - // foreground - if (ch.toInt() != 0 && ch.toInt() != 32) { - g.color = getColor(screenBuffer.getForegroundColour(x, y)) - g.drawString( - Character.toString(ch), - fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize) - } + // foreground + if (ch.toInt() != 0 && ch.toInt() != 32) { + g.color = getColor(screenBuffer.getForegroundColour(x, y)) + g.drawString( + Character.toString(ch), + fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize) } } - } + // cursor if (cursorBlinkOn) { - g.color = getColor(if (cursorBlink) foreDefault else backDefault) screen colourScreen mul phosphor + g.color = getColor(if (cursorBlink) foreDefault else backDefault) g.fillRect( fontW * cursorX.toFloat() + borderSize, @@ -189,48 +183,23 @@ open class SimpleTextTerminal( fontH.toFloat() ) } - else { - val x = cursorX - val y = cursorY - val ch = screenBuffer.getChar(x, y) - // background - g.color = getColor(screenBuffer.getBackgroundColour(x, y)) screen colourScreen mul phosphor - g.fillRect(fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize, - fontW.toFloat(), fontH.toFloat()) + // colour base + g.color = colourScreen + blendScreen() + g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize) - // foreground - if (ch.toInt() != 0 && ch.toInt() != 32) { - g.color = getColor(screenBuffer.getForegroundColour(x, y)) screen colourScreen mul phosphor - g.drawString( - Character.toString(ch), - fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize) - } - } - - if (redrawSemaphore) { - // colour base - g.color = colourScreen - blendScreen() - g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize) - - // colour overlay - g.color = phosphor - blendMul() - g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize) + // colour overlay + g.color = phosphor + blendMul() + g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize) - redrawSemaphore = false - } blendNormal() } - fun redraw() { - redrawSemaphore = true - } - /** Unlike lua function, this one in Zero-based. */ override fun setCursor(x: Int, y: Int) { cursorX = x @@ -444,7 +413,7 @@ open class SimpleTextTerminal( else if (keyPressVisible) printChar(c) if (!asciiControlInUse.contains(c)) sb.append(c) - else if (c == ASCII_DEL && sb.length > 0) sb.deleteCharAt(sb.length - 1) + else if (key == Key.BACKSPACE && sb.isNotEmpty()) sb.deleteCharAt(sb.length - 1) } }