diff --git a/compiler/res/prog8lib/c64utils.p8 b/compiler/res/prog8lib/c64utils.p8 index 9533cca08..84e56ec80 100644 --- a/compiler/res/prog8lib/c64utils.p8 +++ b/compiler/res/prog8lib/c64utils.p8 @@ -916,7 +916,7 @@ asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () { } asmsub input_chars (uword buffer @ AY) -> clobbers(A) -> (ubyte @ Y) { - ; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. + ; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well) ; It assumes the keyboard is selected as I/O channel! %asm {{ diff --git a/compiler/res/prog8lib/prog8lib.asm b/compiler/res/prog8lib/prog8lib.asm index bcd3827ae..0138c041b 100644 --- a/compiler/res/prog8lib/prog8lib.asm +++ b/compiler/res/prog8lib/prog8lib.asm @@ -1112,8 +1112,7 @@ _gtequ dey _result_minw .word 0 .pend - -func_len_str .proc +func_strlen .proc ; -- push length of 0-terminated string on stack jsr peek_address ldy #0 @@ -1126,15 +1125,6 @@ func_len_str .proc rts .pend -func_len_strp .proc - ; -- push length of pascal-string on stack - jsr peek_address - ldy #0 - lda (c64.SCRATCH_ZPWORD1),y ; first byte is length - sta c64.ESTACK_LO+1,x - rts - .pend - func_rnd .proc ; -- put a random ubyte on the estack jsr math.randbyte diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index b56366992..109fc4a91 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -296,7 +296,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, DataType.STR, DataType.STR_S -> { val rawStr = heap.get(v.second.heapId).str!! val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } - out("${v.first}\t; ${v.second.type} \"${escape(rawStr)}\"") + out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "")}\"") for (chunk in bytes.chunked(16)) out(" .byte " + chunk.joinToString()) } diff --git a/compiler/src/prog8/compiler/target/c64/Petscii.kt b/compiler/src/prog8/compiler/target/c64/Petscii.kt index 6ab42ddb6..17fec04ff 100644 --- a/compiler/src/prog8/compiler/target/c64/Petscii.kt +++ b/compiler/src/prog8/compiler/target/c64/Petscii.kt @@ -1055,11 +1055,12 @@ class Petscii { val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase return text.map { val petscii = lookup[it] - if(petscii==null) { - val case = if(lowercase) "lower" else "upper" + petscii?.toShort() ?: if(it=='\u0000') + 0.toShort() + else { + val case = if (lowercase) "lower" else "upper" throw CharConversionException("no ${case}case Petscii character for '$it'") } - petscii.toShort() } } @@ -1072,11 +1073,12 @@ class Petscii { val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase return text.map{ val screencode = lookup[it] - if(screencode==null) { - val case = if(lowercase) "lower" else "upper" + screencode?.toShort() ?: if(it=='\u0000') + 0.toShort() + else { + val case = if (lowercase) "lower" else "upper" throw CharConversionException("no ${case}Screencode character for '$it'") } - screencode.toShort() } } diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index db4af20ed..1c33e7528 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -84,6 +84,7 @@ val BuiltinFunctions = mapOf( BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("numwords", setOf(DataType.UWORD)), BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null), + "strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen), "vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null), @@ -302,6 +303,20 @@ private fun builtinAvg(args: List, position: Position, namespace:IN return numericLiteral(result, args[0].position) } +private fun builtinStrlen(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { + if (args.size != 1) + throw SyntaxError("strlen requires one argument", position) + val argument = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() + if(argument.type !in StringDatatypes) + throw SyntaxError("strlen must have string argument", position) + val string = argument.strvalue(heap) + val zeroIdx = string.indexOf('\u0000') + return if(zeroIdx>=0) + LiteralValue.optimalInteger(zeroIdx, position=position) + else + LiteralValue.optimalInteger(string.length, position=position) +} + private fun builtinLen(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. if(args.size!=1) diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 77d141021..6ad833c33 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -51,6 +51,7 @@ enum class Syscall(val callNr: Short) { FUNC_RNDF(91), // push a random float on the stack (between 0.0 and 1.0) FUNC_LEN_STR(105), FUNC_LEN_STRS(106), + FUNC_STRLEN(107), FUNC_ANY_B(109), FUNC_ANY_W(110), FUNC_ANY_F(111), @@ -1736,7 +1737,11 @@ class StackVm(private var traceOutputFile: String?) { DataType.ARRAY_B -> array.array!![index] = value.integerValue() DataType.STR, DataType.STR_S -> { val chars = array.str!!.toCharArray() - chars[index] = Petscii.decodePetscii(listOf(value.integerValue().toShort()), true)[0] + val ps = Petscii.decodePetscii(listOf(value.integerValue().toShort()), true)[0] + if(ps=='\ufffe') // undefined + chars[index] = '\u0000' + else + chars[index] = ps heap.update(variable.heapId, chars.joinToString("")) } else -> throw VmExecutionException("not a proper array/string var with byte elements") @@ -1994,6 +1999,13 @@ class StackVm(private var traceOutputFile: String?) { val text = heap.get(strPtr).str!! evalstack.push(Value(DataType.UBYTE, text.length)) } + Syscall.FUNC_STRLEN -> { + val strPtr = evalstack.pop().integerValue() + val text = heap.get(strPtr).str!! + val zeroIdx = text.indexOf('\u0000') + val len = if(zeroIdx>=0) zeroIdx else text.length + evalstack.push(Value(DataType.UBYTE, len)) + } Syscall.FUNC_READ_FLAGS -> { val carry = if(P_carry) 1 else 0 val zero = if(P_zero) 2 else 0 diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 011a304fd..a891f9f57 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -639,6 +639,11 @@ len(x) Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual length of the string during execution, the value of len(string) may no longer be correct! + (use strlen function if you want to dynamically determine the length) + +strlen(str) + Number of bytes in the string. This value is determined during runtime and counts upto + the first terminating 0 byte in the string, regardless of the size of the string during compilation time. lsb(x) Get the least significant byte of the word x. Equivalent to the cast "x as ubyte". diff --git a/examples/hello.p8 b/examples/hello.p8 index fe782fa58..136f0fe5a 100644 --- a/examples/hello.p8 +++ b/examples/hello.p8 @@ -32,7 +32,6 @@ float minutes = floor(clock_seconds / 60) clock_seconds = floor(clock_seconds - minutes * 60.0) - ; @todo implement strcpy/strcat/strlen? c64scr.print("system time in ti$ is ") c64flt.print_f(hours) c64.CHROUT(':') diff --git a/examples/test.p8 b/examples/test.p8 index 700c7d027..0159e7524 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -8,6 +8,27 @@ sub start() { - c64.CLEARSCR() ; @todo empty stack exception in vm + str s1 = "hello\u0000abcd12345" + str_s s2 = "hellothere\u0000bcde" + + c64scr.print(s1) + c64.CHROUT('\n') + c64scr.print_ub(len(s1)) + c64.CHROUT('\n') + c64scr.print_ub(strlen(s1)) + c64.CHROUT('\n') + s1[2]=0 + c64scr.print_ub(strlen(s1)) + c64.CHROUT('\n') + c64.CHROUT('\n') + + c64scr.print_ub(len(s2)) + c64.CHROUT('\n') + c64scr.print_ub(strlen(s2)) + c64.CHROUT('\n') + s2[7]=0 + c64scr.print_ub(strlen(s2)) + c64.CHROUT('\n') + } }