mirror of
https://github.com/irmen/prog8.git
synced 2025-11-03 04:17:16 +00:00
cleanup
This commit is contained in:
@@ -99,7 +99,7 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
|
|||||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
||||||
companion object {
|
companion object {
|
||||||
// all automatically generated labels everywhere need to have the same label name prefix:
|
// all automatically generated labels everywhere need to have the same label name prefix:
|
||||||
const val GeneratedLabelPrefix = "p8_label_gen_"
|
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
|||||||
when(node) {
|
when(node) {
|
||||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||||
is PtBlock -> node.name = "p8b_${node.name}"
|
is PtBlock -> node.name = "p8b_${node.name}"
|
||||||
is PtLabel -> if(!node.name.startsWith(PtLabel.GeneratedLabelPrefix)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
|
is PtLabel -> if(!node.name.startsWith(PtLabel.GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
|
||||||
is PtConstant -> node.name = "p8c_${node.name}"
|
is PtConstant -> node.name = "p8c_${node.name}"
|
||||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||||
}
|
}
|
||||||
@@ -118,14 +118,14 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
|||||||
|
|
||||||
private fun prefixScopedName(name: String, type: Char): String {
|
private fun prefixScopedName(name: String, type: Char): String {
|
||||||
if('.' !in name) {
|
if('.' !in name) {
|
||||||
if(name.startsWith(PtLabel.GeneratedLabelPrefix))
|
if(name.startsWith(PtLabel.GENERATED_LABEL_PREFIX))
|
||||||
return name
|
return name
|
||||||
return "p8${type}_$name"
|
return "p8${type}_$name"
|
||||||
}
|
}
|
||||||
val parts = name.split('.')
|
val parts = name.split('.')
|
||||||
val firstPrefixed = "p8b_${parts[0]}"
|
val firstPrefixed = "p8b_${parts[0]}"
|
||||||
val lastPart = parts.last()
|
val lastPart = parts.last()
|
||||||
val lastPrefixed = if(lastPart.startsWith(PtLabel.GeneratedLabelPrefix)) lastPart else "p8${type}_$lastPart"
|
val lastPrefixed = if(lastPart.startsWith(PtLabel.GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||||
// the parts in between are assumed to be subroutine scopes.
|
// the parts in between are assumed to be subroutine scopes.
|
||||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||||
@@ -1369,7 +1369,7 @@ $repeatLabel""")
|
|||||||
|
|
||||||
internal fun makeLabel(postfix: String): String {
|
internal fun makeLabel(postfix: String): String {
|
||||||
generatedLabelSequenceNumber++
|
generatedLabelSequenceNumber++
|
||||||
return "${PtLabel.GeneratedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
return "${PtLabel.GENERATED_LABEL_PREFIX}${generatedLabelSequenceNumber}_$postfix"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignConstFloatToPointerAY(number: PtNumber) {
|
internal fun assignConstFloatToPointerAY(number: PtNumber) {
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ or *_afterif labels.
|
|||||||
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
|
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val autoLabelPrefix = PtLabel.GeneratedLabelPrefix
|
val autoLabelPrefix = PtLabel.GENERATED_LABEL_PREFIX
|
||||||
if(first=="beq +" && second=="lda #1" && third=="+") {
|
if(first=="beq +" && second=="lda #1" && third=="+") {
|
||||||
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
|
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
|
||||||
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
|
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ internal class AssemblyProgram(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeGeneratedLabelsFromMonlist() {
|
private fun removeGeneratedLabelsFromMonlist() {
|
||||||
val pattern = Regex("""al (\w+) \S+${PtLabel.GeneratedLabelPrefix}.+?""")
|
val pattern = Regex("""al (\w+) \S+${PtLabel.GENERATED_LABEL_PREFIX}.+?""")
|
||||||
val lines = viceMonListFile.toFile().readLines()
|
val lines = viceMonListFile.toFile().readLines()
|
||||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
|
|||||||
@@ -1799,7 +1799,7 @@ class IRCodeGen(
|
|||||||
private var labelSequenceNumber = 0
|
private var labelSequenceNumber = 0
|
||||||
internal fun createLabelName(): String {
|
internal fun createLabelName(): String {
|
||||||
labelSequenceNumber++
|
labelSequenceNumber++
|
||||||
return "${PtLabel.GeneratedLabelPrefix}$labelSequenceNumber"
|
return "${PtLabel.GENERATED_LABEL_PREFIX}$labelSequenceNumber"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
|
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
|
||||||
|
|||||||
@@ -317,23 +317,6 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkArray(variable: VarDecl): Iterable<IAstModification> {
|
|
||||||
return when (variable.value) {
|
|
||||||
null -> {
|
|
||||||
val arraySpec = variable.arraysize!!
|
|
||||||
val size = arraySpec.indexExpr.constValue(program)?.number?.toInt() ?: throw FatalAstException("no array size")
|
|
||||||
return if(size==0)
|
|
||||||
replaceWithFalse()
|
|
||||||
else
|
|
||||||
noModifications
|
|
||||||
}
|
|
||||||
is ArrayLiteral -> {
|
|
||||||
checkArray((variable.value as ArrayLiteral).value)
|
|
||||||
}
|
|
||||||
else -> noModifications
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
|
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
|
||||||
if(stringVal.value.isEmpty())
|
if(stringVal.value.isEmpty())
|
||||||
return replaceWithFalse()
|
return replaceWithFalse()
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class Program(val name: String,
|
|||||||
|
|
||||||
fun makeLabel(postfix: String): String {
|
fun makeLabel(postfix: String): String {
|
||||||
generatedLabelSequenceNumber++
|
generatedLabelSequenceNumber++
|
||||||
return "${PtLabel.GeneratedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
return "${PtLabel.GENERATED_LABEL_PREFIX}${generatedLabelSequenceNumber}_$postfix"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun makeLabel(postfix: String, position: Position): Label {
|
fun makeLabel(postfix: String, position: Position): Label {
|
||||||
|
|||||||
@@ -594,13 +594,13 @@ private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expressi
|
|||||||
val identifier = addressOf.scoped_identifier()
|
val identifier = addressOf.scoped_identifier()
|
||||||
if(addressOf.ADDRESS_OF_LSB()!=null && identifier==null)
|
if(addressOf.ADDRESS_OF_LSB()!=null && identifier==null)
|
||||||
throw SyntaxError("&< is only valid on array variables", toPosition())
|
throw SyntaxError("&< is only valid on array variables", toPosition())
|
||||||
if(addressOf.ADDRESS_OF_MSB()!=null) {
|
return if(addressOf.ADDRESS_OF_MSB()!=null) {
|
||||||
return if (identifier != null)
|
if (identifier != null)
|
||||||
AddressOfMsb(addressof().scoped_identifier().toAst(), toPosition())
|
AddressOfMsb(addressof().scoped_identifier().toAst(), toPosition())
|
||||||
else
|
else
|
||||||
throw SyntaxError("&> is only valid on array variables", toPosition())
|
throw SyntaxError("&> is only valid on array variables", toPosition())
|
||||||
} else {
|
} else {
|
||||||
return if (identifier != null)
|
if (identifier != null)
|
||||||
AddressOf(addressof().scoped_identifier().toAst(), null, toPosition())
|
AddressOf(addressof().scoped_identifier().toAst(), null, toPosition())
|
||||||
else {
|
else {
|
||||||
val array = addressOf.arrayindexed()
|
val array = addressOf.arrayindexed()
|
||||||
|
|||||||
@@ -106,11 +106,11 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
|||||||
constval.type == BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, -constval.number, constval.position)
|
constval.type == BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, -constval.number, constval.position)
|
||||||
else -> throw ExpressionError("can only take negative of int or float", constval.position)
|
else -> throw ExpressionError("can only take negative of int or float", constval.position)
|
||||||
}
|
}
|
||||||
"~" -> when {
|
"~" -> when (constval.type) {
|
||||||
constval.type == BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, constval.number.toInt().inv().toDouble(), constval.position)
|
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, constval.number.toInt().inv().toDouble(), constval.position)
|
||||||
constval.type == BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
|
BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
|
||||||
constval.type == BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
|
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
|
||||||
constval.type == BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
|
BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
|
||||||
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
|
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
|
||||||
}
|
}
|
||||||
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
|
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ Future Things and Ideas
|
|||||||
Need to add some way to generate a stable jump table at a given address.
|
Need to add some way to generate a stable jump table at a given address.
|
||||||
Need library to not call init_system AND init_system_phase2 not either.
|
Need library to not call init_system AND init_system_phase2 not either.
|
||||||
Library must not include prog8_program_start stuff either.
|
Library must not include prog8_program_start stuff either.
|
||||||
- Add a LZSA decompressor to the compression library to be able to decompress lzsa when you don't have it in ROM or when the ROM is banked out or unavailable
|
|
||||||
Problem is: on the X16, it should replicate the Kernal's behavior with decompressing to Vram / not incrementing the output address
|
|
||||||
- Add TSCrunch or ZX0 decruncher to compression lib. Same requirement on X16 again to be able to decompress into vram.
|
|
||||||
- Fix missing cases where regular & has to return the start of the split array in memory whatever byte comes first. Search TODO("address of split word array")
|
- Fix missing cases where regular & has to return the start of the split array in memory whatever byte comes first. Search TODO("address of split word array")
|
||||||
- Add a syntax to access specific bits in a variable, to avoid manually shifts&ands, something like variable[4:8] ? (or something else this may be too similar to regular array indexing)
|
- Add a syntax to access specific bits in a variable, to avoid manually shifts&ands, something like variable[4:8] ? (or something else this may be too similar to regular array indexing)
|
||||||
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
|
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
|
||||||
@@ -75,6 +72,9 @@ IR/VM
|
|||||||
Libraries
|
Libraries
|
||||||
---------
|
---------
|
||||||
- monogfx: flood fill should be able to fill stippled
|
- monogfx: flood fill should be able to fill stippled
|
||||||
|
- Add a LZSA decompressor to the compression library to be able to decompress lzsa when you don't have it in ROM or when the ROM is banked out or unavailable
|
||||||
|
Problem is: on the X16, it should replicate the Kernal's behavior with decompressing to Vram / not incrementing the output address
|
||||||
|
- Add TSCrunch or ZX0 decruncher to compression lib. Same requirement on X16 again to be able to decompress into vram.
|
||||||
- pet32 target: make syslib more complete (missing kernal routines)?
|
- pet32 target: make syslib more complete (missing kernal routines)?
|
||||||
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
|
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
|
||||||
- fix the problems in atari target, and flesh out its libraries.
|
- fix the problems in atari target, and flesh out its libraries.
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ object SysCalls {
|
|||||||
returnValue(callspec.returns.single(), result, vm)
|
returnValue(callspec.returns.single(), result, vm)
|
||||||
}
|
}
|
||||||
Syscall.MUL16_LAST_UPPER -> {
|
Syscall.MUL16_LAST_UPPER -> {
|
||||||
returnValue(callspec.returns.single(), vm.mul16_last_upper, vm)
|
returnValue(callspec.returns.single(), vm.mul16LastUpper, vm)
|
||||||
}
|
}
|
||||||
Syscall.FLOAT_TO_STR -> {
|
Syscall.FLOAT_TO_STR -> {
|
||||||
val (buffer, number) = getArgValues(callspec.arguments, vm)
|
val (buffer, number) = getArgValues(callspec.arguments, vm)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
|||||||
var statusNegative = false
|
var statusNegative = false
|
||||||
internal var randomGenerator = Random(0xa55a7653)
|
internal var randomGenerator = Random(0xa55a7653)
|
||||||
internal var randomGeneratorFloats = Random(0xc0d3dbad)
|
internal var randomGeneratorFloats = Random(0xc0d3dbad)
|
||||||
internal var mul16_last_upper = 0u
|
internal var mul16LastUpper = 0u
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val (prg, labelAddr) = VmProgramLoader().load(irProgram, memory)
|
val (prg, labelAddr) = VmProgramLoader().load(irProgram, memory)
|
||||||
@@ -1593,7 +1593,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
|||||||
"-" -> result = left - right
|
"-" -> result = left - right
|
||||||
"*" -> {
|
"*" -> {
|
||||||
result = left.toUInt() * right
|
result = left.toUInt() * right
|
||||||
mul16_last_upper = result shr 16
|
mul16LastUpper = result shr 16
|
||||||
}
|
}
|
||||||
else -> throw IllegalArgumentException("operator word $operator")
|
else -> throw IllegalArgumentException("operator word $operator")
|
||||||
}
|
}
|
||||||
@@ -1608,7 +1608,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
|||||||
"-" -> result = left - value
|
"-" -> result = left - value
|
||||||
"*" -> {
|
"*" -> {
|
||||||
result = left.toUInt() * value
|
result = left.toUInt() * value
|
||||||
mul16_last_upper = result shr 16
|
mul16LastUpper = result shr 16
|
||||||
}
|
}
|
||||||
else -> throw IllegalArgumentException("operator word $operator")
|
else -> throw IllegalArgumentException("operator word $operator")
|
||||||
}
|
}
|
||||||
@@ -1624,7 +1624,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
|||||||
"-" -> result = memvalue - operand
|
"-" -> result = memvalue - operand
|
||||||
"*" -> {
|
"*" -> {
|
||||||
result = memvalue.toUInt() * operand
|
result = memvalue.toUInt() * operand
|
||||||
mul16_last_upper = result shr 16
|
mul16LastUpper = result shr 16
|
||||||
}
|
}
|
||||||
else -> throw IllegalArgumentException("operator word $operator")
|
else -> throw IllegalArgumentException("operator word $operator")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,106 +338,6 @@ class VmProgramLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeWithOneValue(
|
|
||||||
variable: IRStStaticVariable,
|
|
||||||
iElt: IRStArrayElement,
|
|
||||||
startAddress: Int,
|
|
||||||
symbolAddresses: MutableMap<String, Int>,
|
|
||||||
memory: Memory,
|
|
||||||
program: IRProgram
|
|
||||||
) {
|
|
||||||
var address = startAddress
|
|
||||||
when {
|
|
||||||
variable.dt.isString || variable.dt.isUnsignedByteArray -> {
|
|
||||||
val value = getInitializerValue(variable.dt, iElt, symbolAddresses)
|
|
||||||
value.fold(
|
|
||||||
{
|
|
||||||
val integer = it.toInt().toUByte()
|
|
||||||
repeat(variable.length!!) {
|
|
||||||
memory.setUB(address, integer)
|
|
||||||
address++
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throw IRParseException("didn't expect bool") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable.dt.isSignedByteArray -> {
|
|
||||||
val value = getInitializerValue(variable.dt, iElt, symbolAddresses)
|
|
||||||
value.fold(
|
|
||||||
{
|
|
||||||
val integer = it.toInt().toByte()
|
|
||||||
repeat(variable.length!!) {
|
|
||||||
memory.setSB(address, integer)
|
|
||||||
address++
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throw IRParseException("didn't expect bool") },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable.dt.isSplitWordArray -> {
|
|
||||||
val value = getInitializerValue(variable.dt, iElt, symbolAddresses)
|
|
||||||
value.fold(
|
|
||||||
{
|
|
||||||
val integer = it.toUInt()
|
|
||||||
val lsb = (integer and 255u).toUByte()
|
|
||||||
val msb = (integer shr 8).toUByte()
|
|
||||||
repeat(variable.length!!) {
|
|
||||||
memory.setUB(address, lsb)
|
|
||||||
memory.setUB(address + variable.length!!, msb)
|
|
||||||
address++
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throw IRParseException("didn't expect bool") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable.dt.isUnsignedWordArray -> {
|
|
||||||
val value = getInitializerValue(variable.dt, iElt, symbolAddresses)
|
|
||||||
value.fold(
|
|
||||||
{
|
|
||||||
val integer = it.toInt().toUShort()
|
|
||||||
repeat(variable.length!!) {
|
|
||||||
memory.setUW(address, integer)
|
|
||||||
address += 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throw IRParseException("didn't expect bool") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable.dt.isSignedWordArray -> {
|
|
||||||
val value = getInitializerValue(variable.dt, iElt, symbolAddresses)
|
|
||||||
value.fold(
|
|
||||||
{
|
|
||||||
val integer = it.toInt().toShort()
|
|
||||||
repeat(variable.length!!) {
|
|
||||||
memory.setSW(address, integer)
|
|
||||||
address += 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throw IRParseException("didn't expect bool") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable.dt.isFloatArray -> {
|
|
||||||
val value = getInitializerValue(variable.dt, iElt, symbolAddresses)
|
|
||||||
value.fold(
|
|
||||||
{ d ->
|
|
||||||
repeat(variable.length!!) {
|
|
||||||
memory.setFloat(address, d)
|
|
||||||
address += program.options.compTarget.machine.FLOAT_MEM_SIZE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throw IRParseException("didn't expect bool") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw IRParseException("invalid dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInitializerValue(arrayDt: DataType, elt: IRStArrayElement, symbolAddresses: MutableMap<String, Int>): Either<Double, Boolean> {
|
private fun getInitializerValue(arrayDt: DataType, elt: IRStArrayElement, symbolAddresses: MutableMap<String, Int>): Either<Double, Boolean> {
|
||||||
if(elt.addressOfSymbol!=null) {
|
if(elt.addressOfSymbol!=null) {
|
||||||
when {
|
when {
|
||||||
|
|||||||
Reference in New Issue
Block a user