diff --git a/compiler/examples/stackvmtest.txt b/compiler/examples/stackvmtest.txt index 1746ef5d0..4a63961c8 100644 --- a/compiler/examples/stackvmtest.txt +++ b/compiler/examples/stackvmtest.txt @@ -26,7 +26,9 @@ loop: push_var main.textcolor syscall GFX_PIXEL ; syscall WRITE_VAR "input.prompt" -; syscall INPUT_VAR "input.result" +; push b:10 +; syscall INPUT_STR +; pop_var input.result ; syscall WRITE_VAR "input.result" ; push b:8d ; syscall WRITE_CHAR diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 18f17fe1a..9476c5b3e 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -1043,7 +1043,8 @@ class FunctionCall(override var target: IdentifierReference, "asin" -> builtinAsin(arglist, position, namespace) "tan" -> builtinTan(arglist, position, namespace) "atan" -> builtinAtan(arglist, position, namespace) - "log" -> builtinLog(arglist, position, namespace) + "ln" -> builtinLn(arglist, position, namespace) + "log2" -> builtinLog2(arglist, position, namespace) "log10" -> builtinLog10(arglist, position, namespace) "sqrt" -> builtinSqrt(arglist, position, namespace) "max" -> builtinMax(arglist, position, namespace) diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 57d454b1e..0694f0031 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -497,8 +497,8 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: return false } val rangeSize=range.size() - if(rangeSize!=null && (rangeSize<1 || rangeSize>255)) { - checkResult.add(ExpressionError("size of range for string must be 1..255, instead of $rangeSize", range.position)) + if(rangeSize!=null && (rangeSize<0 || rangeSize>255)) { + checkResult.add(ExpressionError("size of range for string must be 0..255, instead of $rangeSize", range.position)) return false } return true diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 932c90483..f5d059e66 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -183,11 +183,15 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva private fun translate(stmt: Continue) { stackvmProg.line(stmt.position) TODO("translate CONTINUE") + // * ..continue statement: goto continue + // we somehow have to know what the correct 'continue' label is } private fun translate(stmt: Break) { stackvmProg.line(stmt.position) TODO("translate BREAK") + // * ..break statement: goto break + // we somehow have to know what the correct 'break' label is } private fun translate(branch: BranchStatement) { diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index 032004648..a2c475cd6 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -1,17 +1,16 @@ package prog8.functions import prog8.ast.* -import kotlin.math.abs -import kotlin.math.floor +import kotlin.math.log2 val BuiltinFunctionNames = setOf( "P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr", "sin", "cos", "abs", "acos", "asin", "tan", "atan", "rnd", "rndw", "rndf", - "log", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil", + "ln", "log2", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil", "max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb", "_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char", - "_vm_write_str", "_vm_input_var", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text" + "_vm_write_str", "_vm_input_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text" ) @@ -46,15 +45,25 @@ fun builtinFunctionReturnType(function: String, args: List, namespa } return when (function) { - "sin", "cos", "tan", "asin", "acos", "atan", "log", "log10", "sqrt", "rad", "deg", "avg", "rndf" -> DataType.FLOAT + "sin", "cos", "tan", "asin", "acos", "atan", "ln", "log2", "log10", "sqrt", "rad", "deg", "avg", "rndf" -> DataType.FLOAT "lsb", "msb", "any", "all", "rnd" -> DataType.BYTE "rndw" -> DataType.WORD "rol", "rol2", "ror", "ror2", "P_carry", "P_irqd" -> null // no return value so no datatype "abs" -> args.single().resultingDatatype(namespace) - "max", "min", "sum" -> datatypeFromListArg(args.single()) + "max", "min" -> datatypeFromListArg(args.single()) "round", "floor", "ceil", "lsl", "lsr" -> integerDatatypeFromArg(args.single()) + "sum" -> { + val dt=datatypeFromListArg(args.single()) + when(dt) { + DataType.BYTE, DataType.WORD -> DataType.WORD + DataType.FLOAT -> DataType.FLOAT + DataType.ARRAY, DataType.ARRAY_W -> DataType.WORD + DataType.MATRIX -> DataType.BYTE + else -> throw FatalAstException("cannot sum over type $dt") + } + } "len" -> { - // len of a str is always 1..255 so always a byte, + // len of a str is always 0..255 so always a byte, // len of other things is assumed to need a word (even though the actual length could be less than 256) val arg = args.single() when(arg) { @@ -64,7 +73,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa is VarDecl -> { val value = stmt.value if(value is LiteralValue) { - if(value.isString) return DataType.BYTE // strings are 1..255 + if(value.isString) return DataType.BYTE // strings are 0..255 } } } @@ -76,7 +85,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa } "_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char", "_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text" -> null // no return value for these - "_vm_input_var" -> DataType.STR + "_vm_input_str" -> DataType.STR else -> throw FatalAstException("invalid builtin function $function") } } @@ -173,9 +182,12 @@ fun builtinTan(args: List, position: Position, namespace:INameScope fun builtinAtan(args: List, position: Position, namespace:INameScope): LiteralValue = oneDoubleArg(args, position, namespace, Math::atan) -fun builtinLog(args: List, position: Position, namespace:INameScope): LiteralValue +fun builtinLn(args: List, position: Position, namespace:INameScope): LiteralValue = oneDoubleArg(args, position, namespace, Math::log) +fun builtinLog2(args: List, position: Position, namespace:INameScope): LiteralValue + = oneDoubleArg(args, position, namespace, ::log2) + fun builtinLog10(args: List, position: Position, namespace:INameScope): LiteralValue = oneDoubleArg(args, position, namespace, Math::log10) @@ -196,8 +208,8 @@ fun builtinAbs(args: List, position: Position, namespace:INameScope val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() val number = constval.asNumericValue return when (number) { - is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position) - is Double -> numericLiteral(abs(number.toDouble()), args[0].position) + is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position) + is Double -> numericLiteral(Math.abs(number.toDouble()), args[0].position) else -> throw SyntaxError("abs requires one numeric argument", position) } } @@ -258,7 +270,7 @@ fun builtinAll(args: List, position: Position, namespace:INameScope private fun numericLiteral(value: Number, position: Position): LiteralValue { val floatNum=value.toDouble() val tweakedValue: Number = - if(floatNum==floor(floatNum) && floatNum in -32768..65535) + if(floatNum==Math.floor(floatNum) && floatNum in -32768..65535) floatNum.toInt() // we have an integer disguised as a float. else floatNum diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 3fb60bb3f..1e35d12c0 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -128,46 +128,42 @@ enum class Syscall(val callNr: Short) { WRITE_NUM(12), // pop from the evaluation stack and print it as a number WRITE_CHAR(13), // pop from the evaluation stack and print it as a single petscii character WRITE_STR(14), // pop from the evaluation stack and print it as a string - INPUT_VAR(15), // user input a string into a variable + INPUT_STR(15), // user input a string onto the stack, with max length (truncated) given by value on stack GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order GFX_CLEARSCR(17), // clear the screen with color pushed on stack GFX_TEXT(18), // write text on screen at (x,y,color,text) pushed on stack in that order FUNC_P_CARRY(64), FUNC_P_IRQD(65), - FUNC_ROL(66), - FUNC_ROR(67), - FUNC_ROL2(68), - FUNC_ROR2(69), - FUNC_LSL(70), - FUNC_LSR(71), - FUNC_SIN(72), - FUNC_COS(73), - FUNC_ABS(74), - FUNC_ACOS(75), - FUNC_ASIN(76), - FUNC_TAN(77), - FUNC_ATAN(78), - FUNC_LOG(79), - FUNC_LOG10(80), - FUNC_SQRT(81), - FUNC_RAD(82), - FUNC_DEG(83), - FUNC_ROUND(84), - FUNC_FLOOR(85), - FUNC_CEIL(86), - FUNC_MAX(87), - FUNC_MIN(88), - FUNC_AVG(89), - FUNC_SUM(90), - FUNC_LEN(91), - FUNC_ANY(92), - FUNC_ALL(93), - FUNC_LSB(94), - FUNC_MSB(95), - FUNC_RND(96), // push a random byte on the stack - FUNC_RNDW(97), // push a random word on the stack - FUNC_RNDF(98) // push a random float on the stack (between 0.0 and 1.0) + FUNC_SIN(66), + FUNC_COS(67), + FUNC_ABS(68), + FUNC_ACOS(69), + FUNC_ASIN(70), + FUNC_TAN(71), + FUNC_ATAN(72), + FUNC_LN(73), + FUNC_LOG2(74), + FUNC_LOG10(75), + FUNC_SQRT(76), + FUNC_RAD(77), + FUNC_DEG(78), + FUNC_ROUND(79), + FUNC_FLOOR(80), + FUNC_CEIL(81), + FUNC_MAX(82), + FUNC_MIN(83), + FUNC_AVG(84), + FUNC_SUM(85), + FUNC_LEN(86), + FUNC_ANY(87), + FUNC_ALL(88), + FUNC_RND(89), // push a random byte on the stack + FUNC_RNDW(90), // push a random word on the stack + FUNC_RNDF(91) // push a random float on the stack (between 0.0 and 1.0) + + // note: not all builtin functions of the Prog8 language are present as functions: + // some of them are already opcodes (such as MSB and ROL)! } class Memory { @@ -814,7 +810,8 @@ class StackVm(val traceOutputFile: String?) { private val evalstack = MyStack() // evaluation stack private val callstack = MyStack() // subroutine call stack private var variables = mutableMapOf() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name - private var carry: Boolean = false + private var P_carry: Boolean = false + private var P_irqd: Boolean = false private var program = listOf() private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null private lateinit var currentIns: Instruction @@ -924,7 +921,7 @@ class StackVm(val traceOutputFile: String?) { Opcode.ARRAY -> { val amount = ins.arg!!.integerValue() val array = mutableListOf() - for (i in 0..amount) { + for (i in 1..amount) { val value = evalstack.pop() if(value.type!=DataType.BYTE && value.type!=DataType.WORD) throw VmExecutionException("array requires values to be all byte/word") @@ -986,8 +983,8 @@ class StackVm(val traceOutputFile: String?) { } Opcode.ROL -> { val v = evalstack.pop() - val (result, newCarry) = v.rol(carry) - this.carry = newCarry + val (result, newCarry) = v.rol(P_carry) + this.P_carry = newCarry evalstack.push(result) } Opcode.ROL2 -> { @@ -996,8 +993,8 @@ class StackVm(val traceOutputFile: String?) { } Opcode.ROR -> { val v = evalstack.pop() - val (result, newCarry) = v.ror(carry) - this.carry = newCarry + val (result, newCarry) = v.ror(P_carry) + this.P_carry = newCarry evalstack.push(result) } Opcode.ROR2 -> { @@ -1054,19 +1051,10 @@ class StackVm(val traceOutputFile: String?) { DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> print(value.arrayvalue) } } - Syscall.INPUT_VAR -> { - // TODO: replace with regular INPUT_STR that simply puts a str on the stack (1 argument: the max. length of the input) - val varname = ins.callLabel ?: throw VmExecutionException("$syscall expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - val input = readLine() ?: throw VmExecutionException("expected user input") - val value = when(variable.type) { - DataType.BYTE -> Value(DataType.BYTE, input.toShort()) - DataType.WORD -> Value(DataType.WORD, input.toInt()) - DataType.FLOAT -> Value(DataType.FLOAT, input.toDouble()) - DataType.STR -> Value(DataType.STR, null, input) - else -> throw VmExecutionException("invalid datatype") - } - variables[varname] = value + Syscall.INPUT_STR -> { + val maxlen = evalstack.pop().integerValue() + val input = readLine()?.substring(0, maxlen) ?: "" + evalstack.push(Value(DataType.STR, null, input)) } Syscall.GFX_PIXEL -> { // plot pixel at (x, y, color) from stack @@ -1100,13 +1088,93 @@ class StackVm(val traceOutputFile: String?) { Syscall.FUNC_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_ROUND -> evalstack.push(Value(DataType.WORD, evalstack.pop().numericValue().toDouble().roundToInt())) - // todo: implement remaining functions + Syscall.FUNC_P_CARRY -> P_carry = evalstack.pop().asBooleanValue + Syscall.FUNC_P_IRQD -> P_irqd = evalstack.pop().asBooleanValue + Syscall.FUNC_ABS -> { + val value = evalstack.pop() + val absValue= + when(value.type) { + DataType.BYTE -> Value(DataType.BYTE, value.numericValue()) + DataType.WORD -> Value(DataType.WORD, value.numericValue()) + DataType.FLOAT -> Value(DataType.FLOAT, value.numericValue()) + else -> throw VmExecutionException("cannot get abs of $value") + } + evalstack.push(absValue) + } + Syscall.FUNC_ACOS -> evalstack.push(Value(DataType.FLOAT, acos(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_ASIN -> evalstack.push(Value(DataType.FLOAT, asin(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_TAN -> evalstack.push(Value(DataType.FLOAT, tan(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_ATAN -> evalstack.push(Value(DataType.FLOAT, atan(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_LN -> evalstack.push(Value(DataType.FLOAT, ln(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_LOG2 -> evalstack.push(Value(DataType.FLOAT, log2(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_LOG10 -> evalstack.push(Value(DataType.FLOAT, log10(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_SQRT -> evalstack.push(Value(DataType.FLOAT, sqrt(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_RAD -> evalstack.push(Value(DataType.FLOAT, Math.toRadians(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_DEG -> evalstack.push(Value(DataType.FLOAT, Math.toDegrees(evalstack.pop().numericValue().toDouble()))) + Syscall.FUNC_FLOOR -> { + val value = evalstack.pop() + val result = + when(value.type) { + DataType.BYTE -> Value(DataType.BYTE, value.numericValue()) + DataType.WORD -> Value(DataType.WORD, value.numericValue()) + DataType.FLOAT -> Value(DataType.WORD, floor(value.numericValue().toDouble())) + else -> throw VmExecutionException("cannot get floor of $value") + } + evalstack.push(result) + } + Syscall.FUNC_CEIL -> { + val value = evalstack.pop() + val result = + when(value.type) { + DataType.BYTE -> Value(DataType.BYTE, value.numericValue()) + DataType.WORD -> Value(DataType.WORD, value.numericValue()) + DataType.FLOAT -> Value(DataType.WORD, ceil(value.numericValue().toDouble())) + else -> throw VmExecutionException("cannot get ceil of $value") + } + evalstack.push(result) + } + Syscall.FUNC_MAX -> { + val array = evalstack.pop() + val dt = + when { + array.type==DataType.ARRAY -> DataType.BYTE + array.type==DataType.ARRAY_W -> DataType.WORD + else -> throw VmExecutionException("invalid array datatype $array") + } + evalstack.push(Value(dt, array.arrayvalue!!.max())) + } + Syscall.FUNC_MIN -> { + val array = evalstack.pop() + val dt = + when { + array.type==DataType.ARRAY -> DataType.BYTE + array.type==DataType.ARRAY_W -> DataType.WORD + else -> throw VmExecutionException("invalid array datatype $array") + } + evalstack.push(Value(dt, array.arrayvalue!!.min())) + } + Syscall.FUNC_AVG -> { + val array = evalstack.pop() + evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average())) + } + Syscall.FUNC_SUM -> { + val array = evalstack.pop() + evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum())) + } + Syscall.FUNC_ANY -> { + val array = evalstack.pop() + evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0)) + } + Syscall.FUNC_ALL -> { + val array = evalstack.pop() + evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0)) + } else -> throw VmExecutionException("unimplemented syscall $syscall") } } - Opcode.SEC -> carry = true - Opcode.CLC -> carry = false + Opcode.SEC -> P_carry = true + Opcode.CLC -> P_carry = false Opcode.TERMINATE -> throw VmTerminationException("terminate instruction") Opcode.BREAKPOINT -> throw VmBreakpointException() @@ -1157,30 +1225,30 @@ class StackVm(val traceOutputFile: String?) { Opcode.ROL_MEM -> { val addr = ins.arg!!.integerValue() val value = Value(DataType.BYTE, mem.getByte(addr)) - val (newValue, newCarry) = value.rol(carry) + val (newValue, newCarry) = value.rol(P_carry) mem.setByte(addr, newValue.integerValue().toShort()) - carry = newCarry + P_carry = newCarry } Opcode.ROL_MEM_W -> { val addr = ins.arg!!.integerValue() val value = Value(DataType.WORD, mem.getWord(addr)) - val (newValue, newCarry) = value.rol(carry) + val (newValue, newCarry) = value.rol(P_carry) mem.setWord(addr, newValue.integerValue()) - carry = newCarry + P_carry = newCarry } Opcode.ROR_MEM -> { val addr = ins.arg!!.integerValue() val value = Value(DataType.BYTE, mem.getByte(addr)) - val (newValue, newCarry) = value.ror(carry) + val (newValue, newCarry) = value.ror(P_carry) mem.setByte(addr, newValue.integerValue().toShort()) - carry = newCarry + P_carry = newCarry } Opcode.ROR_MEM_W -> { val addr = ins.arg!!.integerValue() val value = Value(DataType.WORD, mem.getWord(addr)) - val (newValue, newCarry) = value.ror(carry) + val (newValue, newCarry) = value.ror(P_carry) mem.setWord(addr, newValue.integerValue()) - carry = newCarry + P_carry = newCarry } Opcode.ROL2_MEM -> { val addr = ins.arg!!.integerValue() @@ -1209,9 +1277,9 @@ class StackVm(val traceOutputFile: String?) { Opcode.JUMP -> {} // do nothing; the next instruction is wired up already to the jump target Opcode.BCS -> - return if(carry) ins.next else ins.nextAlt!! + return if(P_carry) ins.next else ins.nextAlt!! Opcode.BCC -> - return if(carry) ins.nextAlt!! else ins.next + return if(P_carry) ins.nextAlt!! else ins.next Opcode.BEQ -> return if(evalstack.pop().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!! Opcode.BNE -> @@ -1253,16 +1321,16 @@ class StackVm(val traceOutputFile: String?) { Opcode.ROL_VAR -> { val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - val (newValue, newCarry) = variable.rol(carry) + val (newValue, newCarry) = variable.rol(P_carry) variables[varname] = newValue - carry = newCarry + P_carry = newCarry } Opcode.ROR_VAR -> { val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - val (newValue, newCarry) = variable.ror(carry) + val (newValue, newCarry) = variable.ror(P_carry) variables[varname] = newValue - carry = newCarry + P_carry = newCarry } Opcode.ROL2_VAR -> { val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 00566f0cf..4e2c06f78 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -443,11 +443,14 @@ tan(x) atan(x) Arctangent. -log(x) - Natural logarithm. +ln(x) + Natural logarithm (base E). + +log2(x) + Base 2 logarithm. log10(x) - Base-10 logarithm. + Base 10 logarithm. sqrt(x) Square root.