Integer numeric rendering into decimal digits is almost always done in backwards order to a fixed-size temp buffer of size appropriate for the numeric type, then that rendered buffer pointer is sent to the final output. There might be algorithms to do it forward, but I'm not familiar with any of them, and I've dug into a fair number of standard libraries. If you're just doing byte-aligned output in hex, though, that's another story, and is better suited for native 8-bit code than bothering with VM overhead.
Barring the output object dispatch, though, is the rest that indicative of language power? Howabout a malloc next? Linked list manipulations? A 16-bit sort algorithm? Fixed-point math? All of these environments have means to call or inline native routines, so doing what native code already does well doesn't seem like it shows much. However, you should write what I did in your platform for comparison: 16-bit unsigned decimal rendering with no leading zeros (unless the number is zero itself), returning a pointer to the string.
I do have good support for function lookup tables, specifically because I also recognized it as something hard in native code, and takes advantage of lda(zp),y in the implementation instead of relying on regular VM instructions to add indexes slowly:
Code:
ldmi r0, r1, $14 ; r0 = memory(r1 + $14)
callp ; call subroutine at rP
Regarding rendering hex bytes, I've got nybble swap, but my CMP equivalents aren't quite fleshed out yet. A table-based approach is easier for now, but I can jimmy up an add-based version based on just current instructions:
Code:
byte = r0
tmp = r1
copy tmp, byte ; tmp = byte
nswap ; nybble swap, to work with high nybble first
andp #$0f
decloop tmp, 10, :+ ; decrement by 10, branch if it didn't underflow, which would normally continue the loop
subp 'a' - '0' ; the number was less than 10, base it off '0' rather than 'a'
: addp 'a' + 10 ; add back the 10 that decloop took out
...call output...
with byte ; low nybble, same thing
andp #$0f
...
This could be collapsed into a subroutine call per nybble, of course. Or I could hop into native code (or create an instruction) to convert the low byte of a reg into a 2-byte ASCII word filling the reg. Then it'd be appropriate for 16-bit writes from VM code which seems like a much better idea than working with 8-bit value operations at this layer.
I'll work on finishing this, but two things:
- If convertString returns a pointer to the terminating zero, that's not a pointer to the string anymore.
- "Please note: registers 1 and 2 may not be destroyed (except for routine 1)." is kind of funny given the discussion about stack based systems, which always destroy their arguments.
In all the languages' low-level calling semantics that I know, parameter values are generally free to be overwritten unless specifically exempted (if that's even possible), and return values often share the same location as input parameters. It really doesn't matter here as there isn't much register pressure, though.