just updating things so that I can have a backup point...

Former-commit-id: 0a5a6d7f68ee1a96562532572c8d45fe102d3c25
Former-commit-id: a1a78f61f2fe2a8707e47633caa6cd67a829b35e
This commit is contained in:
Song Minjae
2017-01-26 16:58:06 +09:00
parent 06296983b5
commit 2203f74429
16 changed files with 1765 additions and 1226 deletions

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -39,8 +39,8 @@ abstract class Actor(val renderOrder: ActorOrder) : Comparable<Actor>, 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<Actor>, 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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 ##

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}