This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

ca65 goodies

ca65 goodies
by on (#127082)
Well, this is a bit random, but I came up with this ca65 macro for converting a nibble (number $0..$F) to its corresponding ASCII character code, when said nibble isn't a compile time constant, and thought I would share it. The main use case would be some sort of debugging. One could, for example, generate a textual log file similar to using .out, but without the limitations of .out.
Code:
.macro nibbleToHexAscii nibble
    .local x0, x1, x2, x3
    .local y0, y1, y2, y3, y4
   
    x3 = ( nibble >> 3 ) & 1
    x2 = ( nibble >> 2 ) & 1
    x1 = ( nibble >> 1 ) & 1
    x0 = ( nibble >> 0 ) & 1
   
    y4 = ( x3 & x1 ) | ( x3 & x2 )
    y3 = ( x3 & ~x2 & ~x1 )
    y2 = ( ~x3 & x2 ) | ( x2 & x0 ) | ( x2 & x1 )
    y1 = ( ~x3 & x1 ) | ( x1 & x0 ) | ( x3 & x2 & ~x1 & ~x0 )
    y0 = ( ~x3 & x0 ) | ( ~x2 & ~x1 & x0 ) | ( x3 & x1 & ~x0 ) | ( x3 & x2 & ~x0 )

    .byte <( ( y4 << 4 | y3 << 3 | y2 << 2 | y1 << 1 | y0 << 0 ) + '0' )
.endmacro

This macro can then be used to output a 16-bit number as a hex ASCII string:
Code:
.macro wordToHexAscii addr
    .repeat 4, i
        nibbleToHexAscii { (addr >> ( 4 * ( 3 - i ) ) ) & $F }
    .endrepeat
.endmacro

Usage example:
Code:
.byte "Current address is: "
wordToHexAscii *

I'd love to hear about other ca65 tips, tricks and workarounds like this.
Re: ca65 goodies
by on (#127084)
Here's the number of days since a project started, and the percentage full:
Code:
;these variables are filled by the linker
.import __RODATA_LOAD__, __RODATA_SIZE__, __CODE_LOAD__, __CODE_SIZE__

ROMSIZE = __CODE_SIZE__ + __RODATA_SIZE__ + 6
ROMPCT = (1000 * ROMSIZE + 16384) / 32768
; started this project on Sun 2013-08-11
BUILDDAY = (.TIME / 86400) - 15928

title_txt:
  .byt .sprintf("Day %d: ROM ", BUILDDAY)
  .byt '0'|<(ROMPCT / 100 .MOD 10)
  .byt '0'|<(ROMPCT / 10 .MOD 10)
  .byt '.','0'|<(ROMPCT .MOD 10),"% full",LF,LF


Are you sure this still doesn't work?
Code:
.macro nibbleToHexAscii nibble
  .byte '0' + ('A'-'9'-1)*(nibble > 9) + nibble
.endmacro
Re: ca65 goodies
by on (#127086)
tepples wrote:
Are you sure this doesn't work?
Code:
.macro nibbleToHexAscii nibble
  .byte '0' + ('A'-'9'-1)*(nibble > 9)
.endmacro

Already I'm glad I started this thread. :)

The thing you pasted doesn't work (didn't check why), but this one should. Same concept anyways, didn't occur to me that the boolean expressions evaluate to integers.
Code:
.macro nibbleToHexAscii nibble
  .byte (nibble + '0')*(nibble < 10) + (nibble - 10 + 'A')*(nibble >= 10)
.endmacro
Re: ca65 goodies
by on (#127087)
tepples wrote:
Are you sure this doesn't work?
Code:
.macro nibbleToHexAscii nibble
  .byte '0' + ('A'-'9'-1)*(nibble > 9)
.endmacro
I wouldn't expect that to work; I think you would need also + nibble isn't it?
Re: ca65 goodies
by on (#127089)
Oops, my bad. It's hard to test certain things without creating a new project.
Re: ca65 goodies
by on (#127094)
thefox wrote:
didn't occur to me that the boolean expressions evaluate to integers.


Yes. The assembler evaluates true to 1 and false to 0. Non zero values are true.

Code:
 flag .set (.xmatch(.mid(0,1, {express}) , C)) * 1 + (.xmatch(.mid(0,1, {express}) , Z)) * 2 + (.xmatch(.mid(0,1,{express}) , N)) * 3 + (.xmatch(.mid(0,1,{express}) , V)) * 4 + (.xmatch(.mid(0,1,{express}) , G)) * 5


Without the context it might not be clear, but some members are familiar with my high-level macro code - that's a line from the flag evaluation: I abuse the boolean values to return a value from 1 to 5. A value of zero is an error.

Edit:Now you got me thinking though.. I might be able to simulate an array with .ident() and .sprintf(), but too tired at the moment.
Re: ca65 goodies
by on (#127096)
tepples wrote:
Oops, my bad. It's hard to test certain things without creating a new project.

I usually test small things like this by making a new file with whatever I want to test...
Code:
.byte "test"

...and then: cl65 -t none foo.s

The default configuration (not sure which one it uses, actually) tends to be fine for this type of platform-independent tests.
Re: ca65 goodies
by on (#127097)
Then I must have an installation problem:
Code:
$ cl65 -t none n2ha.s
ld65: Error: Cannot find config file `none.cfg'
Re: ca65 goodies
by on (#127115)
tepples wrote:
Oops, my bad. It's hard to test certain things without creating a new project.
That is one reason why some people don't use ca65. (I don't use ca65 either, but I can understand some of things you write in here, some of which might help people using any system, I suppose.)

Movax12 wrote:
Without the context it might not be clear, but some members are familiar with my high-level macro code - that's a line from the flag evaluation: I abuse the boolean values to return a value from 1 to 5. A value of zero is an error.
O, I "abuse" boolean values like that a lot...
Re: ca65 goodies
by on (#127116)
tepples wrote:
Then I must have an installation problem:
Code:
$ cl65 -t none n2ha.s
ld65: Error: Cannot find config file `none.cfg'


Guessing a bit, but if you specify a -t none you should also have a -C config.cfg. If cl65 is going to be assembling, it doesn't matter, but if it is going to be linking, you have to use -C
Re: ca65 goodies
by on (#127119)
Movax12 wrote:
Guessing a bit, but if you specify a -t none you should also have a -C config.cfg. If cl65 is going to be assembling, it doesn't matter, but if it is going to be linking, you have to use -C

Not sure why it works here without -C, then. I can't easily check what configuration it defaults to, either, but it doesn't seem to be the C64 config (which is otherwise the default).
Re: ca65 goodies
by on (#127126)
zzo38 wrote:
tepples wrote:
Oops, my bad. It's hard to test certain things without creating a new project.
That is one reason why some people don't use ca65. (I don't use ca65 either, but I can understand some of things you write in here, some of which might help people using any system, I suppose.)

I don't know why tepples talks about creating a project: there's no notion of "project" with cl65, and if he's talking about NESICIDE projects, it's unnecessary to use it to test those macros, as the shell is sufficient and the simplest way to test simple things like the macros of this thread.

Besides, an important information for troubleshooting is lacking: version information of cl65.

Here's my full test session:
Code:
$ cl65 --version
cl65 V2.13.9 - (C) Copyright 1998-2011 Ullrich von Bassewitz
cl65: No input files
$ cat foo.s
cat: foo.s: No such file or directory
$ touch foo.s
$ cl65 -t none foo.s
$ xxd foo
$ echo '.byte $42' > foo.s
$ cl65 -t none foo.s
$ xxd foo
0000000: 42                                       B
$ cat thefox.s
.macro nibbleToHexAscii nibble
    .local x0, x1, x2, x3
    .local y0, y1, y2, y3, y4

    x3 = ( nibble >> 3 ) & 1
    x2 = ( nibble >> 2 ) & 1
    x1 = ( nibble >> 1 ) & 1
    x0 = ( nibble >> 0 ) & 1

    y4 = ( x3 & x1 ) | ( x3 & x2 )
    y3 = ( x3 & ~x2 & ~x1 )
    y2 = ( ~x3 & x2 ) | ( x2 & x0 ) | ( x2 & x1 )
    y1 = ( ~x3 & x1 ) | ( x1 & x0 ) | ( x3 & x2 & ~x1 & ~x0 )
    y0 = ( ~x3 & x0 ) | ( ~x2 & ~x1 & x0 ) | ( x3 & x1 & ~x0 ) | ( x3 & x2 & ~x0 )

    .byte <( ( y4 << 4 | y3 << 3 | y2 << 2 | y1 << 1 | y0 << 0 ) + '0' )
.endmacro

.macro wordToHexAscii addr
    .repeat 4, i
        nibbleToHexAscii { (addr >> ( 4 * ( 3 - i ) ) ) & $F }
    .endrepeat
.endmacro

.byte "Current address is: "
wordToHexAscii *
$ cl65 -t none thefox.s
$ xxd thefox
0000000: 4375 7272 656e 7420 6164 6472 6573 7320  Current address
0000010: 6973 3a20 3032 3137                      is: 0217
$ cl65 -t none --start-addr 0x8000 thefox.s
$ xxd thefox
0000000: 4375 7272 656e 7420 6164 6472 6573 7320  Current address
0000010: 6973 3a20 3830 3137                      is: 8017

tl;dr -t none works for me too without giving a config file. Mind that the default start address is $200, that's why the first example gives 0217.

I use the latest available version of the debian repo available here. Note that the latest version available in the github repo is ~2.14 at the time of writing.

Nifty macro btw.
Re: ca65 goodies
by on (#127128)
Jarhmander wrote:
Besides, an important information for troubleshooting is lacking: version information of cl65.

Unfortunately there are a lot of "snapshot builds" of cc65 out in the wild with the same version numbers and nothing to distinguish each other. It looks like the recent snapshot versions do include the first 7 characters of the Git revision hash, though, which is good.

It also looks like some older versions included the SVN revision as well, but only for the C compiler (cc65):
Code:
C:\Users\f>cc65 --version
cc65 V2.13.9
SVN version: 5963

C:\Users\f>ld65 --version
ld65 V2.13.9 - (C) Copyright 1998-2009, Ullrich von Bassewitz
ld65.exe: Error: No object files to link

C:\Users\f>ca65 --version
ca65 V2.13.9 - (C) Copyright 1998-2011 Ullrich von Bassewitz
ca65.exe: No input files
Re: ca65 goodies
by on (#127129)
Jarhmander wrote:
there's no notion of "project" with cl65

I used "project" to refer to a folder containing at least one source code file and at least one linker configuration file. My copy of cl65 2.14.0 cannot link with -t none.

thefox wrote:
Unfortunately there are a lot of "snapshot builds" of cc65 out in the wild with the same version numbers and nothing to distinguish each other.

That's why I made the macro: so I could make an automatic tool to distinguish builds of my own projects by their build date.
Re: ca65 goodies
by on (#127212)
Here's a little something.

I made some macros to make parameter passing just a little bit easier to manage. I call it xparam. I used this quite a lot in STREEMERZ.

Here's how you declare a function (in a header file):
Code:
; Function declaration.
xdecl testFunc
    ; Function parameters.
    foo     .byte
    bar     .word
    ; Parameters/locals can be structs too (testStruct is declared elsewhere)
    xxx     .tag testStruct
   
    ; Local variables of the function.
    xlocals
        xyzzy   .dword
        dog     .byte
    endxlocals
endxdecl

Function parameters and local variables can be accessed in the function:
Code:
xproc testFunc
    lda param foo
    sta local dog   
    rts
endxproc

Here's how you would call an xparam function:
Code:
ldx #111
xinvoke testFunc, foo: x, bar: #$5566, xxx: structInMemory

Inputs can be registers, immediate values or memory addresses. Everything is stored on the zero page. See README.txt in ZIP for caveats/more info. :)

Also note that build.cmd in the ZIP file requires that the ZEROPAGE segment is at least 32 bytes big (it uses the "none" configuration, which by default for some strange reason only has $1F bytes of zero page). To fix it, modify none.cfg in your ca65 installation directory.

Here's a commented disassembly of the binary produced by the files in the ZIP:
Code:
        .setcpu "6502"

L0226           := $0226
L024E           := $024E
        ldx     #$6F
       
        ; testFunc invocation (in test.s)
        ; x is directly stored at $00 (foo)
        stx     $00
        ; $5566 is loaded at $01 (bar)
        lda     #$66
        sta     $01
        lda     #$55
        sta     $02
        ; The struct (3 bytes) is loaded from memory
        lda     $0223
        sta     $03
        lda     $0224
        sta     $04
        lda     $0225
        sta     $05
        ; The actual function call to testFunc.
        jsr     L0226
       
        ; "xparam" macro demonstrations (to access parameter "bar").
        lda     $01
        lda     $02
        .byte   111 ; byteInMemory
        .byte   123 ; structInMemory
        .word   $DEA
       
        ; testFunc (test-module.s):
        lda     $00
        lda     $01
        lda     $02
        lda     $03
        lda     $04
        lda     $05
        ; testFunc local variable accesses:
        lda     #$7B
        sta     $10
        sta     $11
        sta     $12
        sta     $13
        sta     $14
        ; testFunc invokes nestedFunc:
        lda     #$7B
        sta     $06
        lda     #$67
        sta     $07
        lda     #$45
        sta     $08
        jsr     L024E
        rts

        ; nestedFunc (nested-module.s):
        lda     $06
        lda     $07
        rts
Re: ca65 goodies
by on (#127422)
In the spirit of the last post, here's some "goodies" of mine. They are pseudo-ops that just reduce the number of lines of code, however one should always be cautious because they can possibly emit alot of instructions, so they can create a false impression of program compactness. Also, one may call it heresy that those pseudo-ops are more than 3 characters, and that it may look like another cpu instruction set: no, my goal isn't to implement another processor in a 6502, it is just to save a bit of typing while having some fun with ca65's macro system.

push/pop:
Code:
.macro push a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15
    .ifnblank a0
        .if .xmatch({a0},p) .or .xmatch({a0},P)
            php
        .else
            .if .match({a0},x)
                txa
            .elseif .match({a0},y)
                tya
            .elseif .match(.left(1,{a0}),=)
                lda #>(.right(.tcount({a0})-1,{a0}))
                pha
                lda #<(.right(.tcount({a0})-1,{a0}))
            .elseif !(.match({a0},a))
                lda a0
            .endif
            pha
        .endif
        push a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15
    .endif
.endmacro

.macro pop a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15
    .ifnblank a0
        pop a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15
        .if .xmatch({a0},p) .or .xmatch({a0},P)
            plp
        .else
            pla
            .if .match({a0},x)
                tax
            .elseif .match({a0},y)
                tay
            .elseif !(.match({a0},a))
                sta a0
            .endif
        .endif
    .endif
.endmacro

The idea here is that you specify what you want to push/pull to/from the stack; A, X, Y and P can be specified along with memory locations. push pushes all the args in the order specified and pop pops them in reverse order, so the following:
Code:
    push A,X,Y,somevar
    pop  A,X,Y,somevar

...will save and restore correctly the specified registers and the memory location. Of course, A should be first (or second, if P is first) in the list if one want to save A, or else A won't be correctly saved/restored without a warning.

Also, push accepts regular immediate (8-bit) arguments but also 'absolute' arguments with =: one can push absolute addresses, and those addresses are pushed in the same order that jsr and brk does. So the following is a slow and convoluted way to jump at somelabel:
Code:
    ; using rts
    push =somelabel-1
    rts

    ; using rti
    push =somelabel, P
    rti


mov/movw:
Code:
.macro mov dest, src
    lda src
    sta dest
.endmacro

.macro movw dest, src
    .local sepd, seps
    sepd .set 0
    seps .set 0

    .if .match({.right(2,{dest})},{,x}) .or .match({.right(2,{dest})},{,y})
        sepd .set 2
    .endif
    .if .match({.right(2,{src})},{,x}) .or .match({.right(2,{src})},{,y})
        seps .set 2
    .endif
   
    .if .match(.left(1, {src}),#)
        mov {.left(.tcount({dest})-sepd,dest)+0 .right(sepd,dest)}, #<(.right(.tcount({src})-1,{src}))
        mov {.left(.tcount({dest})-sepd,dest)+1 .right(sepd,dest)}, #>(.right(.tcount({src})-1,{src}))
    .else
        mov {.left(.tcount({dest})-sepd,dest)+0 .right(sepd,dest)}, {.left(.tcount({src})-seps,src)+0 .right(seps,src)}
        mov {.left(.tcount({dest})-sepd,dest)+1 .right(sepd,dest)}, {.left(.tcount({src})-seps,src)+1 .right(seps,src)}
    .endif
.endmacro

mov is trivial so won't explain, but is included here nevertheless because movw uses it. The latter moves a word (which can be immediate) into some address; it assumes words are stored into consecutive byte addresses. dest and src can even use absolute or zeropage indexed addressing mode if you use curly braces:
Code:
    movw {$4000,X}, {APU_virtualregs,X}

Limitations: it can only use A for transfers, it does not fit well with the use of the struct-of-array "idiom", and if you try to use indexed indirect addressing, it will silently break because it will be converted into absolute indexed (oh nevermind, it gives errors fortunately, but one should be cautious when using parens, it should not begin with an open parens, that how ca65 tells if it's absolute or indirect addressing).
Re: ca65 goodies
by on (#127674)
Was working on the aforementioned stuff and came up with an idea to help with .define style macros.
These can be very helpful in some cases: they can be used anywhere, as compared to regular macros which are required to be at the beginning of a line. As well, they are the only "variable" in ca65 that can be assigned a string. They are constants, but they can be undefined and redefined, so they can be treated as variables. The problem is that they can get really tricky, for example:

Code:
.define FOO BAR

.ifdef FOO
    .out "YES"
.else
    .out "NO"
.endif


This will output NO, since the .define macros always resolve to their value, which is BAR, and BAR is not defined. They only way they don't resolve to their value is when using .undefine

To make things simpler I use this:
Code:
.define inlineIsDefined(i)  ( .defined(::.ident(.sprintf("_ISDEFINED_%s", i) )) .and ::.ident(.sprintf("_ISDEFINED_%s", i) ) )

.macro define i, v
    ; if defined and if defined as 1
    .if inlineIsDefined i
        .undefine .ident(i)
    .endif
   
    .define .ident(i) v
    ::.ident(.sprintf("_ISDEFINED_%s", i)) .set 1
   
.endmacro

.macro undefine i
    .if inlineIsDefined i
        .undefine .ident(i)
    .endif
   
    ::.ident(.sprintf("_ISDEFINED_%s", i)) .set 0
.endmacro


With these macros you can:
Code:
define "FOO", BAR

; output is YES
.if inlineIsDefined "FOO"
    .out "YES"
.else
    .out "NO"
.endif

; redefine:
define "FOO", "This is a string"


This also has the advantage that you can use define or undefine at anytime, whereaas with the original .define and .undefine, ca65 would output an error if the identifier was not defined/undefined properly first.
Re: ca65 goodies
by on (#127755)
How to evaluate unknown amount of parameters:

You could probably do this with just a repeat as well, but I find recursion the easiest way. The simplest way is to just list a bunch of parameters and hope you have enough and recurse on them, but more flexible is something like:

Code:
.macro myMacro param

    ; quickly find next comma:
    .local COMMA_POSITION
    COMMA_POSITION .set 0
   
    .repeat .tcount({param}), I
        .if .not COMMA_POSITION
            .if .xmatch( {.mid(I, 1, {param}) } ,  {,}   )
                COMMA_POSITION .set I
            .endif
        .endif
    .endrepeat

    .if COMMA_POSITION
        .define PARAM  .mid(0, COMMA_POSITION, {param})
    .else
        .define PARAM param
    .endif
   
    ; -----   
    ; Do some stuff with PARAM
    ; -----
   
    .undefine PARAM
    ; do again with next param if there was a comma:
    .if COMMA_POSITION
        myMacro { .mid ( COMMA_POSITION + 1 , .tcount({param}) - COMMA_POSITION - 1, {param} ) }
    .endif

.endmacro


Then one would call the macro with as many parameters as they want, but the parameters must be in in curly braces "{}".
This isn't a big deal but it allows for some flexibility where you may want other parameters passed that are not a part of the variable amount of parameters:
Code:
call myFunctionName, {x: #34, y: foo, a: bar, p1: baz}, banked
Re: ca65 goodies
by on (#129836)
Here's a fairly convenient way to create custom character mappings based on a tileset. "#" is used to notate unused characters.

EDIT: Note that line continuation has to be enabled with ".linecont +" for this to work as is.

Code:
.define CHARSET .concat( "################", \
                         "################", \
                         " ###############", \
                         "################", \
                         "#ABCDEFGHIJKLMNO", \
                         "PQRSTUVWXYZ#####", \
                         "0123456789######", \
                         "#()###########-=", \
                         "#abcdefghijklmno", \
                         "pqrstuvwxyz#####", \
                         "################", \
                         "##,.##`'##,.:;!?" )

.repeat .strlen( CHARSET ), i
    .if .strat( CHARSET, i ) <> '#'
        .charmap .strat( CHARSET, i ), i
    .endif
.endrepeat

.undefine CHARSET
Re: ca65 goodies
by on (#129839)
That's pretty nice. Just note to be careful that redefining the charset doesn't cause things like "NES" in the iNES header to be changed.
Re: ca65 goodies
by on (#129841)
Yep, that was the first pitfall I encountered when using the cc65 suite. cl65 uses the c64 charmap by default (because the default target is c64), and that "corrupted" the iNES header.
Re: ca65 goodies
by on (#129846)
On the same note, here are a couple of macros I've used to save/restore the charmap before modifying it. They save the original character map, and then set a "direct" mapping 1..255 => 1..255. Note that in this implementation __saveCharMap opens a new scope, so it may not be usable under all circumstances (but was fine for what I was doing).

Code:
.macro __saveCharMap
    ; In scope so that CHARMAP_xxxs are local.
    .scope
        ; Find out character mappings.
        .repeat 255, i
            ; Change mapping only if it's not already direct to avoid flooding
            ; the debug information with unnecessary symbols.
            .if i + 1 <> .strat( .sprintf( "%c", i + 1 ), 0 )
                ; Save the current mapping.
                .ident( .sprintf ("__CHARMAP_%d", i + 1 ) ) .set .strat( .sprintf( "%c", i + 1 ), 0 )
                ; Set mapping to direct (1..255 -> 1..255).
                .charmap i + 1, i + 1
            .endif
        .endrepeat
.endmacro

.macro __restoreCharMap
        ; Now restore the original mapping.
        .repeat 255, i
           ; Restore only those char mappings which were modified.
            .ifdef .ident( .sprintf ("__CHARMAP_%d", i + 1 ) )
                .charmap i + 1, .ident( .sprintf ("__CHARMAP_%d", i + 1 ) )
            .endif
        .endrepeat
    .endscope
.endmacro
Re: ca65 goodies
by on (#129848)
Is it possible in ca65 to tell some string literals to use ASCII and some to use custom character sets? My own assembler has this feature, and if you need it in ca65 too then perhaps you should add such a thing.
Re: ca65 goodies
by on (#129850)
ca65 can change the charmap "dynamically", so yes it is possible (or thefox' last macro wouldn't work/would have no practical use). It would even be possible to do it less verbosely using macros—which would look much like the last macro.
Re: ca65 goodies
by on (#168534)
Wrote a little macro to parse fixed point numbers (to be used as constants in code) from strings. It has some limitations, main one being that only a limited number of digits can be used (otherwise overflows in the 32-bit arithmetic). Improvements are welcome.

Example of usage:
Code:
parseFixedPoint myNumber1, "-5.4321", 8
; myNumber1 = -1391 (-1391/256 = -5.43359375)

parseFixedPoint myNumber2, "18.95", 8
; myNumber2 = 4851 (4851/256 = 18.94921875)


Code:
.macro parseFixedPoint targetSymbol, number, fractBits
    .local numberInt
    numberInt .set 0
    .local foundDecimalPoint
    foundDecimalPoint .set 0
    .local fractMultiplier
    fractMultiplier .set 1
    .local sign
    sign .set 1
    .repeat .strlen( number ), i
        .if .strat( number, i ) = '.'
            .if foundDecimalPoint
                .error "multiple decimal points found"
            .endif
            foundDecimalPoint .set 1
        .elseif .strat( number, i ) = '-' .or .strat( number, i ) = '+'
            .if i <> 0
                .error "sign can only appear in the beginning"
            .endif
            sign .set -1*( .strat( number, i ) = '-' )
        .else
            ; Check for overflow in case of too many digits.
            .if numberInt >= ( ( 1 << 31 ) - 1 ) / 10
                .error "can't fit digits in an integer, use less digits"
            .endif
            numberInt .set 10*numberInt + .strat( number, i ) - '0'
            .if foundDecimalPoint
                fractMultiplier .set 10*fractMultiplier
            .endif
        .endif
    .endrepeat
    ; \todo Warn if result not exact?
    ; \todo Other rounding options?
    ; \todo Is there a better way to check for overflow?
    .if numberInt * ( 1 << fractBits ) / ( 1 << fractBits ) <> numberInt
        .error "overflow in multiplication, use less digits"
    .endif
    ; \todo Break down the calculations so that more digits can be handled
    ;       without overflow.
    targetSymbol = ( numberInt * ( 1 << fractBits ) + fractMultiplier/2 ) / fractMultiplier * sign
.endmacro
Re: ca65 goodies
by on (#186088)
Here's a simple one I came up with a while ago to deal with the problem of referencing structures of arrays generated by external tools:
Code:
.macro StartStructureOfArrays _Label0, _Label1, _Label2, _Label3, _Label4, _Label5, _Label6, _Label7, _Label8, _Label9, _Label10, _Label11, _Label12, _Label13, _Label14, _Label15
   CreateLabelsForArraysInStructure *, (:+ - *) / .paramcount, _Label0, _Label1, _Label2, _Label3, _Label4, _Label5, _Label6, _Label7, _Label8, _Label9, _Label10, _Label11, _Label12, _Label13, _Label14, _Label15
.endmacro

.macro CreateLabelsForArraysInStructure _Address, _ArrayLength, _Label0, _Label1, _Label2, _Label3, _Label4, _Label5, _Label6, _Label7, _Label8, _Label9, _Label10, _Label11, _Label12, _Label13, _Label14, _Label15
   .ifnblank _Label0
      _Label0 := _Address
      CreateLabelsForArraysInStructure _Address + _ArrayLength, _ArrayLength, _Label1, _Label2, _Label3, _Label4, _Label5, _Label6, _Label7, _Label8, _Label9, _Label10, _Label11, _Label12, _Label13, _Label14, _Label15
   .endif
.endmacro

.macro EndStructureOfArrays
   :
.endmacro

Example:
Code:
StartStructureOfArrays Tile0, Tile1, Tile2, Tile3, Palette, Type
   .incbin "metatiles.bin"
EndStructureOfArrays

It just measures the size of the included file and divides it by the number of labels (which is the number of arrays) to find the length of the arrays and distribute the labels accordingly.
Re: ca65 goodies
by on (#206185)
Today I found myself needing to trim strings from the left or from the right, so I came up with these:

Code:
.macro ExtractLeft _Source, _Target, _Length
   .define _Target .sprintf(.sprintf("%%.%ds", _Length * (_Length > 0)), _Source)
.endmacro

.macro ExtractRight _Source, _Target, _Length, _String, _Position
   .ifblank _String _Position
      ExtractRight _Source, _Target, _Length, "", .strlen(_Source) - 1
   .else
      .if .strlen(_String) < _Length .and _Position > -1
         ExtractRight _Source, _Target, _Length, {.sprintf("%c%s", .strat(_Source, _Position), _String)}, _Position - 1
      .else
         .define _Target _String
      .endif
   .endif
.endmacro

Usage:

Code:
ExtractLeft "SomeString", Output, 4
.out Output
.undefine Output

ExtractRight "SomeString", Output, 6
.out Output
.undefine Output

Getting characters from the left is easy, .sprintf alone can do it, but getting them from the right was much trickier. The function calls itself recursively to build the final string character by character. Does anyone know of a better way to do this?
Re: ca65 goodies
by on (#206197)
Not sure if it'd work in ca65, but in C sprintf you'd do
Code:
sprintf(foo, "%.6s", "SomeString" + 4);


Dynamic length, etc left as an exercise to the viewer.
Re: ca65 goodies
by on (#206204)
calima wrote:
Not sure if it'd work in ca65, but in C sprintf you'd do
Code:
sprintf(foo, "%.6s", "SomeString" + 4);

Doesn't work in ca65, strings are not pointers there.
Re: ca65 goodies
by on (#206212)
Strings are bit of second class citizens in ca65, so it's not uncommon to end up with hacks like that. Maybe new builtin functions (e.g., .strleft, .strright and .strmid) should be added to complement .left, .right and .mid. The macro you gave might expand to quite inefficient code (not exactly sure when ca65 evaluates certain parts of it).
Re: ca65 goodies
by on (#206214)
I don't have a solution for the string problem (TBH if it was something I needed I'd probably just write a substring directive into ca65 and submit a pull request to make it a standard feature), but here's a ca65 goodie I find useful for when I'm trying to write timed branches:

Code:
.macro assert_branch_page label_
   .assert >(label_) = >*, error, "Page crossing detected!"
.endmacro

test:
   beq :+
   assert_branch_page :+ ; .assert will happen if this would cross a page
      nop
   :
   rts
Re: ca65 goodies
by on (#206218)
thefox wrote:
Maybe new builtin functions (e.g., .strleft, .strright and .strmid) should be added to complement .left, .right and .mid.

That sure would be nice.

Quote:
The macro you gave might expand to quite inefficient code (not exactly sure when ca65 evaluates certain parts of it).

Yeah, that's why I was wondering if anyone had thought of a better way to do this.