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

ca65 scopes and macros

ca65 scopes and macros
by on (#49613)
Dear experienced ca65 users,
I've run into a problem and hope you can help me.

particle.s:
Code:
.scope peng ;"P"article "ENG"ine

;init - initializes particle engine
init:
   ...
   rts

;run - called once per frame, loops through all particles and updates their position etc.
run:
   ...
   rts


;particle_create - create a particle and return its index in the X register
.macro particle_create
...
.endmacro

.macro particle_setpos px,py
...
.endmacro

;updates the particle specified in the X register
particle_run:
   rts


.endscope


Now, what I can do outside of the scope is:
Code:
jsr peng::init
jsr peng::run
jsr peng::particle_func


However, using the macros like this:
Code:
peng::particle_create #01
peng::particle_setpos #40,#120

doesn't work.
Macros seem to be directly accessible from all scopes(?), so just calling them without the "peng::" part works. That defies the purpose of scopes, though.

My questions: Is there any way around this? Is it even good practice to write code like this or is there a better, recommended way?

It's the first time I've been using scopes in this way, but I've got older similar code that I want to convert to be more readable and organized.

by on (#49616)
This is what I found from the documentation:

------------------------------------------
Note: Macro names are always in the global level and in a separate name space. There is no special reason for this, it's just that I've never had any need for local macro definitions.
------------------------------------------

edit:

Found here, when they talk about scope.

by on (#49619)
Ah, thanks. I've been browsing the documentation for a solution, but totally missed that bit about macros being global.

That sucks, looks like it makes no sense using scopes to wrap my modules then.

by on (#49620)
miau wrote:
That sucks, looks like it makes no sense using scopes to wrap my modules then.


It depends what you want to achieve. I will try to answer from my little experience with ca65. You want to "wrap modules". What does it exactly means? All your code is in one file and want to create some scope to avoid some name clash? I could try to figure out what you're looking for. That will give me more experience with ca65 at the same time.

The way I handle modules in ca65 is this way. You create many files for the modules you want (view module, music module etc). By separating by files you already create some scope, which is the file scope. Then, if you want other modules to see the view module init procedure, you export the init method only. You could always call the method viewInit (subViewInit, anyway that seems clear) to avoid any name clash. The same thing can be done for variables by defining them in that specific file.

For now this is the way I'm doing it. All methods/variables not exported will not be seen by my external modules. The linker will be in charge to export/import the right symbols and glue everything together. Defining scope with the scope symbol seems only useful if you want to create a local scope but still. hmm.. You could always create a scope inside your module, maybe export it and it will give it a C++ like look but I don't know how useful it would be. Already just using .proc/.endproc create a scope for the labels.

Just give me a little bit more information of want you want to do and I will see if I can find anything.

by on (#49624)
By export and import do you mean having a c-like separation of source and header files?
Currently I've just got lots of separate source files for the different tasks that are included from the main source file. It works for small projects, I guess, but you can get dependency problems when one module tries to jump to code from another module and vice versa.
Importing only the functions I need would help. I take it you're refering to the .export and .import commands, if so: it should work with macros, too, right?


Still, I would have liked to use scopes to wrap the different modules.
Here's just a somewhat extreme example if I would be very pedantic with my naming scheme. If not using scopes the macro in my first post would actually have to look like this:
Code:
peng_particle_set_position

peng is a module, particle is a sub-module, but "set" isn't. This is messy.
The scope operator helps make things clear:
Code:
peng::particle::set_position

peng is a module, particle is a sub-module, set_position is the function.

I like keeping my function names as short and expressive as possible, but that doesn't always work.
Maybe changing my naming scheme might help. I.e. from underscore-separated identifiers to justleavingspacesoutentirely or CamelCase:
Code:
peng_particle_SetPosition



What kind of naming scheme do you guys use? Are you strictly following it at all times?
This is something I've been contemplating over for a long time. I don't want to spend most of my time writing variable and function names while coding, plus, it sometimes takes me longer to grasp the essence of a piece of code if identifiers are too long (too much information, same goes for too heavy commenting which people new to programming tend to do). On the other hand, the functions, variables and code structure should be self-explanatory to the outsider.

I can't seem to find a balance between the two. Looks like you have to trade one thing off for the other.

by on (#49627)
miau wrote:
By export and import do you mean having a c-like separation of source and header files?


In a way, yes.

miau wrote:
Currently I've just got lots of separate source files for the different tasks that are included from the main source file. It works for small projects, I guess, but you can get dependency problems when one module tries to jump to code from another module and vice versa.


If you code that way it will work but I think you will miss one of the advantage the cc65 linker give you.

What the import/export do is mostly define scope for your function/variables. It won't import code or anything, it will just tell that X symbol defined in a module is available at N address.

I will try to show an example of scope with import/export. Let say we have 3 modules: main, view an sound. Main will want to init the view and sound handler. Then in the nmi, it will want to play the sound. Here's how it could look like (warning, wall of code sample):

main.asm
Code:
.segment "ZEROPAGE"
zpGameState:    .res 1            ; Only main can access this variable

; I'm importing a variable defined by the sound module, the music handler
.zpimport zpCurrentSong

.segment "CODE"

; I want to use these symbols from other modules
; (usually I will define a H file with the same name
;  as the module that export the symbol)
.import subInitView
.import subInitSoundDriver
.import subPlayNextMusicFrame

reset:
   ; I decide to init the view with the symbol I imported
   jsr subInitView

  ; Let's init the song with the variable I imported
   lda #$03
   sta zpGameState

  ; then init the song
  jsr subInitSoundDriver


; do nothing for now
mainLoop:

     jmp mainLoop

nmi:
  ; call the next frame on sound driver
  jsr  subPlayNextMusicFrame


Now let's check the content of the view manager view.asm
Code:
.segment "ZEROPAGE"
zpMyLocalVariable1:  .res 1     ; Only the view can access this variable
zpMyLocalVariable2:  .res 1     ; Only the view can access this variable

.segment "CODE"

; First export the symbols I want to be public
.export subInitView  ; Only this symbol can be seen by outside module

; this method can be called by anybody who imports it
.proc subInitView
   ; do some stuff here

   ; call private method
   jsr subViewInternalMethod
   rts
.endproc

; Only view module can access this method, making it private
.proc subViewInternalMethod
    rts
.endproc


Finally, the sound handler module:

music.asm
Code:
.segment "ZEROPAGE"
zpMyLocalVariable1:  .res 1     ; Only music module can access this variable
zpMyLocalVariable2:  .res 1     ; same thing here
zpCurrentSong: .res 1

.zpexport zpCurrentSong          ; This variable is now viewable by anybody who decide to import it

.segment "CODE"

; First export the symbols
.export subInitSoundDriver            ; this symbol can be seen by outside module
.export subPlayNextMusicFrame  ; this symbol can be seen by outside module

; this method can be called by anybody who imports it
.proc subInitSoundDriver
   ; do some stuff here

   ; call private method
   jsr subDriverInternalMethod
   rts
.endproc

; this method can be called by anybody who imports it
.proc subPlayNextMusicFrame
  jsr subMusicReadStuff
 rts
.endproc

; Only music module can access this method, making it private
.proc subDriverInternalMethod
    rts
.endproc

; Only music module can access this method, making it private
.proc subMusicReadStuff
   ; do things here
   rts
.endproc


So now you may start to see a pattern. Every module reside in their own file. Each module can define their own private variable/function and if they need to give access to a variable/function, they will export it. If another module requires to use it, they will import it. This way, you reduce the chance of the main/view calling internal functions of the music engine and so it the opposite true. By defining your variable in the module, only that module can access that variable and will help reducing coding errors.

Each module could reside in different segments. Each segments could be in different banks. The segments will be set by the configuration file. Now by exporting symbols, you don't have to worry where is X variable/function etc: the linker will take care of it. Of course, if the segment is in a bank that is not available while calling it (you forgot to switch bank), it will fail but that's nothing the linker can do. That part is still your responsibility.

As for macro, I would be careful. Macro are not functions: they are just a way to avoid rewriting repetitive code. This mean they don't have scope. It's like a define in C. Every time you call it, that code will be included again, and again and again. With modules, the function will be called instead, which is better. Maybe a way to recognize a macro coulb be to use hungarian notation and put a prefix in front of it. I already do this for function (sub) and zero page variables (zp).

I can explain more in details if you want, now it's just that it's 1h in the morning, getting sleepy and I should be in bed since I have to work tomorrow, sorry ^^;;;

by on (#49631)
Thanks, this method is much better than what I've been doing until now. I should have been using it from the start, really not much different from the way you do it in C, except for explicitly having to state .imports and .exports. :)


I like macros for one reason - parameters.

In my opinion the way you usually call functions can get confusing, especially if there are many parameters. you have to know the exact registers and memory locations they have to be stored in and it bloats the code visually.
Example with just a single parameter to keep it simple:
Code:
lda #1
jsr particle_create

vs.
Code:
particle_create #1


I like creating macros to wrap functions that require parameters. So yeah, they usually don't contain the actual code, should have mentioned it. :)
Code:
.macro particle_create id
   .if .match({id}, a)
      ;a as parameter, do nothing
   .else
      ;memory location or constant, just lda it
      lda id
   .endif
   jsr particle_create_f
.endmacro


This way they are flexible and the code you have to change if the memory location or register for a parameter changes is not scattered around the files, but in one central place. I can call them in different ways, so I don't have to waste space or cycles. The above macros handles two cases:
Code:
particle_create $10

lda $10
beq @skip
   particle_create a
@skip:


There might be cases where different parameters aren't easy to handle, that's when I just go for readability over cycle- or space-saving - at least until I hit the optimization stage in my project.


Banshaku wrote:
Maybe a way to recognize a macro coulb be to use hungarian notation and put a prefix in front of it. I already do this for function (sub) and zero page variables (zp).

Yeah, I see the point. I just prefer hacky code with underscores in identifiers, because I'm used to it, I guess (hello unix gurus!). Let's see how other people are doing it.

by on (#49645)
For the macro, as long as you don't put too much logic in it that should fine I guess. I'm not using much macro yet that way, so maybe I should try. The more logic in it, the more change of missing an error thought. For public methods, I would define the macro in the header file.

miau wrote:
Yeah, I see the point. I just prefer hacky code with underscores in identifiers, because I'm used to it, I guess (hello unix gurus!). Let's see how other people are doing it.


There is no best way. As long as you define rules, follow and understand them and feel comfortable with it then everything is fine. There will be always one group that prefer certain rules over others because they're used to it. Don't fall in those religion like wars about syntax ;)

edit:

For the import/export thing, if you find it annoying to always import in every module you can do this with the assembler:

------------------------------
-U Mark unresolved symbols as import
------------------------------

This mean if a symbol is not found in the module, it will try to check automatically if there is an export somewhere. This is one way of doing it but hmm.. for now I prefer to explicitly import it, habit I guess.

edit2:

I found something interesting in my lunch time that may help you with the way you wanted to write code when not using macro thought. For example, let's take back the view module. Let's say the view module will export the init method and , I don't now, processFrame method.

Since init could be re-used elsewhere, you could have a name clash. What I didn't like is that I have to prefix every method with the module it's related. Now I found one solution. Instead of prefixing your method with view, do this in your h files:

Code:
.scope view
   .import init
   .import processFrame
.endscope


So in every module you have imported your h file, if you want to access the view public methods, you have to write:

Code:
jsr view::init
jsr view::processFrame


Still, I know, it doesn't process your parameters but I found that way of scoping interesting.

And I found that in macro you can check the count of parameter and make the compiler fail if it's not the right count. This part seems interesting.

by on (#49659)
Banshaku wrote:
For the import/export thing, if you find it annoying to always import in every module you can do this with the assembler:
------------------------------
-U Mark unresolved symbols as import
------------------------------

This is quite useful if you need to include the header file into the module it belongs to as well (if you've got macros or constants you need to share). Else, .export and .import clash and you get errors like "Symbol `xyz' is already an import".



Unfortunately, problems persist with the source/header file method.

I had hoped to get a bit more object-oriented-ness into my project with it, i.e. having private variables that only functions of the class have access to. So to change a variable you call a function. That seems to be common practice in OOP.

However, it means I can't use some macros as function replacements.
E.g. the following should be a perfectly valid task for a macro:
Code:
.macro peng_particle_setpos px,py
   .ifnblank px
      lda px
      sta p_x,x
   .endif
   .ifnblank py
      lda py
      sta p_y,x
   .endif
.endmacro


This would mean, though, that I would have to export the variables p_x and p_y as well... defies the concept of public/private members. Plus, to avoid future name clash, I would have to assign other, longer names to these variables - which is what I wanted to avoid in the first place.

It would be much easier if macros could be scoped. I understand I might be the only one who would like to use them in such a way.
The problem is never inside the computer, but sitting in front of it, as they say. :)

by on (#49660)
I guess for the includes and how you uses them is a matter of taste so I cannot say much about this.

As for macro, to my knowledge, the #define in C/C++ doesn't have any scope. I'm not aware of scope for macro in assembler (never did enough to confirm). Is it something that you want to reproduce that you where doing in another assembler?

To me, a macro is a pre-processor that will be included inside a file before it will be parsed. Seen that way, it make sense that the macro doesn't have scope since scope will be inspected during the parsing/assembling phase.

edit:

The only thing I could see to avoid the loading the parameters before every method is to compromise this way. For example, you make sure that all functions can only receive parameters with registers (when possible) and always in the same order. You define a macro that receive as a parameter:

- the name of the function
- Parameters 1 to 3

You have to define in every header file which parameters you can receive this way. When there is an exception, you have to document it. So your macro lpr (load ParameteR) could receive this:

lpr view::Init #$01, #$FF

So instead of calling "jrs", you call the lpr (Load ParameteR) macro to do the job. A function is just an address so it should not be an issue. What you have to make sure is the order of parameter is always the same.

An example. hmm.. I never write macro so I will write more like pseudocode:

Code:
; Order is anything you define in your rules
; Note: More validation could be done here too
.macro lpr target, param1, param2, param3
   .ifnblank param1
      lda param1
   .endif
   .ifnblank param2
      ldx param2
   .endif
   .ifnblank param3
      ldy param3
   .endif

    ; finally, load the function
    jsr target
.endmacro


That's one possiblity. Try to make it generic and follow some rules. Then, if you need some exception, you can create Load Parameter Variable (lpv) which receive the name of a variable with a value, 1 to N.

ex: lpv view::Init myVarName, $00, mayVarName2, $35

etc.

If you cannot do it one way because of the limitation, find a way around the limitation. That's what I would do. I don't know if what I said make sense but I will surely try it and see how it goes.

by on (#49661)
Banshaku wrote:
To me, a macro is a pre-processor that will be included inside a file before it will be parsed. Seen that way, it make sense that the macro doesn't have scope since scope will be inspected during the parsing/assembling phase.

Then what would be the direct counterpart to an inline function from C or C++? At this point, I'm pretty sure that's what miau was aiming for: something that's inline like a macro but scoped like a proc.

by on (#49663)
You're right, being just a preprocessor command it makes perfect sense.

Banshaku wrote:
As for macro, to my knowledge, the #define in C/C++ doesn't have any scope. I'm not aware of scope for macro in assembler (never did enough to confirm). Is it something that you want to reproduce that you where doing in another assembler?

No, basically, I want to find a way to reduce the confusion when working with parameters.
Also, in C/C++ you can declare functions as "inline" so instead of a function call the whole code is inserted into the program code whenever you call it. Gives you the speed of macros, but can still be inside a scope. Although I must admit that the concepts of macros and inline functions are completely different.

So yeah, what I'm REALLY trying to do with macros is emulate some of the comforts that higher-level languages offer to make code more readable and flexible in case of changes. I realize that might be a bit overzealous.

I'll just stick to my old method for now and move over to a scope-based system once I find a satisfying solution.

EDIT: tepples was right and faster than me :)

by on (#49664)
I read the complete ca65 doc today (always find new things on the next read when you have more experience) and don't remember anything that would be similar to inline function in C/C++.

I guess you will have to find a workaround for it.

by on (#49666)
miau wrote:
So yeah, what I'm REALLY trying to do with macros is emulate some of the comforts that higher-level languages offer to make code more readable and flexible in case of changes. I realize that might be a bit overzealous.

You could always write your own extended assembly language that compiles to a .s file for ca65. That's arguably what Dennis Ritchie did in 1972 when he invented C.

by on (#49694)
Regarding the comment of making scope to avoid name clash: doesn't work. You need to import first and if you have 2 init method, it will not know which one to put in that scope you want to create.

My mistake. You can rename an export symbol but you cannot do it for import. If it was possible, my idea would have worked.

Oh well. I will forget this concept.

Edit:

I found a work around. Let say I have my Sound driver called SoundManager. In the export, I could do this:

Code:
.export __SoundManagerInit          := subInitSoundDriver
.export __SoundManagerPlayNextFrame := subPlayNextSoundFrame


I export the routine in a way that usually I would never uses. If I would see some code using __Method, I know someone didn't use it properly, kind of. You can call it if you want and it will work. It just that it will not conform to the rules you defined to use the module.

Then, in the header file, I would do this:

Code:
;
; Symbols that can be imported from the music manager
;

.import __SoundManagerInit              ; X: current song
.import __SoundManagerPlayNextFrame

.scope SoundManager
   Init          = __SoundManagerInit
   PlayNextFrame = __SoundManagerPlayNextFrame
.endscope


As you can see, I defined some symbols in that scope with the value of the import. Basically I'm just saying that in the scope SoundManager, the symbol Init is actually the address of __SoundManagerInit.

So after making it work, I tried to do the same thing with a macros. No luck. this mean macros have no symbols/address and are really parsed before the compiler do the job.

If I find a trick to simulate inline methods, I will let you know. What I shown may seem extreme for some people but I'm starting to like it. It just a way of writing code after all.

by on (#49706)
Banshaku: If you're explicitly importing and exporting symbols anyway it doesn't sound like much more work. I'd consider using it myself if I could settle the macro problem which I probably won't be able to.

Which leads me to believe that...
tepples wrote:
You could always write your own extended assembly language that compiles to a .s file for ca65. That's arguably what Dennis Ritchie did in 1972 when he invented C.

might be a good idea. However, I'm not sure I want to add that program to all my sources or want to force people to download or even compile it themselves.

So I'd rather write my own assembler from scratch OR a true cc65 replacement.
Maybe I should first try if cc65 really is as bad as everyone says or if it's actually pretty usable if you take advantage of the things described above (parameters, code structure, etc.), but still write most of the code in ASM.

On that note... with all the dead projects you usually see I thought, for some reason, that cc65 wasn't being updated anymore either. The last snapshot is from a few days ago, though. I better try contacting the author about my ca65 problems.

by on (#49710)
miau wrote:
tepples wrote:
You could always write your own extended assembly language

might be a good idea. However, I'm not sure I want to add that program to all my sources or want to force people to download or even compile it themselves.

I distribute plenty of tools with my own sources. Look at LJ65: the source code distribution includes some audio conversion and nametable compression code, as both source code and Windows binary.

by on (#49713)
I've been using CA65 a lot recently also (due to banshaku's encouragement, but also my own judgement) and I'm coming to like it very much.

Miau: About parameter passing. What would be confusing about doing something like this (this is my current habit):

Code:
;b0 is the X coordinate
;b1 is the Y coordinate
.proc procThatDoesStuff:
  lda b0
  ;do stuff
  lda b1
  ;do stuff
  rts
.endproc


Where b0 and b1 are zp variables. I have several b's and several w's that I use as temp params.

I wonder if the following would work to help make parameters more readable:

Code:
.scope procThatDoesStuffParams
   XCoord = b0
   YCoord = b1
.endscope

.proc procThatDoesStuff:
  lda procThatDoesStuffParams::XCoord
  ;do stuff
  lda procThatDoesStuffParams::YCoord
  ;do stuff
  rts
.endproc


As for avoiding name clashes using Ca65's scope: Under what circumstances do you think this would occur? For my current project, I don't see this as a possibility. Especially since I am the only person working on the programming side of things.

My favorite advantages gained from CA65 are the .import and .export ability, so I can break up my source into smaller chunks. It was such a pain to navigate hundreds of lines of code in one source file before. I also like being able to use local symbols within procedures, no more anonymous labels.

My source had been in ASM6 until recently. I tried to break up my code and .include the other files into the main file, but then I got some dependency issues and I had to know the proper order to include the other files in. CA65 eliminated this problem.

by on (#49755)
tepples wrote:
miau wrote:
tepples wrote:
You could always write your own extended assembly language

might be a good idea. However, I'm not sure I want to add that program to all my sources or want to force people to download or even compile it themselves.

I distribute plenty of tools with my own sources. Look at LJ65: the source code distribution includes some audio conversion and nametable compression code, as both source code and Windows binary.

Yeah, separate utilities for audio conversion or nametable compression make perfect sense to me.
What I really meant (but didn't clearly state, sorry) is that I don't want to add a program for this particular, very simple task. I wouldn't consider it to be useful enough unless it has enough features to be interesting and stand on its own. Plus, people are used to ca65 as it is, what if they wanted to use parts of my code in their own projects?


ZomCoder wrote:
Miau: About parameter passing. What would be confusing about doing something like this (this is my current habit):

Code:
;b0 is the X coordinate
;b1 is the Y coordinate
.proc procThatDoesStuff:
  lda b0
  ;do stuff
  lda b1
  ;do stuff
  rts
.endproc


Where b0 and b1 are zp variables. I have several b's and several w's that I use as temp params.


Yeah, that's what I do - commenting what all temp vars and regs are used for.
Still, the calling code can get quite messy in my opinion and I think Bregalad would agree. I remember him posting about the same problem some time ago.


ZomCoder wrote:
I wonder if the following would work to help make parameters more readable:

Code:
.scope procThatDoesStuffParams
   XCoord = b0
   YCoord = b1
.endscope

.proc procThatDoesStuff:
  lda procThatDoesStuffParams::XCoord
  ;do stuff
  lda procThatDoesStuffParams::YCoord
  ;do stuff
  rts
.endproc

Sounds interesting. If I recall correctly that's one of the solutions Bregalad came up with as well.

ZomCoder wrote:
As for avoiding name clashes using Ca65's scope: Under what circumstances do you think this would occur? For my current project, I don't see this as a possibility. Especially since I am the only person working on the programming side of things.

Yeah, the probability is zero when I'm the only person. But this time I'm planning to release the source to the public, you never know in what context other people will be using it.

ZomCoder wrote:
My favorite advantages gained from CA65 are the .import and .export ability, so I can break up my source into smaller chunks. It was such a pain to navigate hundreds of lines of code in one source file before. I also like being able to use local symbols within procedures, no more anonymous labels.

I've been pondering a bit more after your comments. I'm definitely going to switch to the file-scope, source/header, .export/.import way as there aren't any arguments against it except... laziness.

I can still use macros the way I want, just need to have more elaborate variable names and .export them. My previous solution was definitely worse. So, yep, I'd consider my problems as solved for now. Thanks for everyone's help and suggestions. :)

As for local symbols in ca65: I agree, they make code so much nicer to look at.

by on (#49758)
miau wrote:
ZomCoder wrote:
I wonder if the following would work to help make parameters more readable:

Code:
.scope procThatDoesStuffParams
   XCoord = b0
   YCoord = b1
.endscope

.proc procThatDoesStuff:
  lda procThatDoesStuffParams::XCoord
  ;do stuff
  lda procThatDoesStuffParams::YCoord
  ;do stuff
  rts
.endproc

Sounds interesting. If I recall correctly that's one of the solutions Bregalad came up with as well.


Hmmm... For some reason, there is big warning sign that comes to my mind when I see this. I will elaborate.

Defining an extra symbol for your function is fine but doing it for temp variable. hmm.. I feel debugging nightmare will ensue. There is good chances that you will have only a few of those temp variables that you will use often so this is where it could get messy.

Let say I have 2 temp variable and let's call temp Temp1 and Temp2 to make it simpler.

For example, I decide for function A to rename Temp1 as a::param1 and Temps2 as a::param2. For function B I decide to do the same and call Temp1 b::specialParam.

Many weeks of coding later, for some reason I decide that from method A, I have to call method B. But... I forgot that a::param1 and b::specialParam are actually the same temp variable.

I finish the code but now for some reason, function A doesn't work properly anymore and I cannot understand why. There is some good chances that my scoping will be defined in the header files so I will never see while scanning the file that b::specialParam is now overwriting the content of a::param1 because when I read the code: I see 2 different variables. I will have no clue that they're using the same address, so is the next person that will read your code that doesn't know your code base.

You could always argue that "I know my code! I will remember that fact after 2 months", maybe. But what about the other guy that may use your code and doesn't not yet about that? What if I have 15 temp variables that I shared under different names the same way? I don't think that you will remember all the possible combination of those scoped parameters for the 15 temp variable used N time in your program.

For now I feel it's not a good idea to share temp variables that way unless someone can prove me otherwise. It may have seems a good idea at first but after analyzing it, I can only see pain during the debugging session :)

by on (#49761)
You've got a point there.
But in many cases you can't entirely avoid this problem. Imagine functions that work with temp vars internally, not as parameters.
I wish the stack was more useful. :(

Anyway, it's usually the first thing I check for when encountering an error that, at first sight, makes no sense to me.
Just be sure to comment your parameter usage well and use different temp vars in NMI and you'll most likely get away without problems.

It might be a good idea to write a utility that checks for such errors. Jump tables will still be a problem, though.

by on (#49765)
miau wrote:
I wish the stack was more useful. :(


I may have found a way to make the stack more useful. I made a new thread here. The idea came out from all the conversation we had in this thread about macro, function param and local variable.

I don't know if what I wrote make sense but if it does, it could be interesting ;)

by on (#49767)
miau wrote:
What I really meant (but didn't clearly state, sorry) is that I don't want to add a program for this particular, very simple task. I wouldn't consider it to be useful enough unless it has enough features to be interesting and stand on its own. Plus, people are used to ca65 as it is, what if they wanted to use parts of my code in their own projects?

As long as your preprocessor spits out assembly code suitable for ca65 or object files suitable for ld65, it will still be possible to combine modules in your language with modules designed for straight ca65.

Banshaku wrote:
What if I have 15 temp variables that I shared under different names the same way?

I typically reserve the first 16 bytes of zero page for local variables. At the top of each .proc, I add comments explaining how I use each.

by on (#49799)
tepples wrote:
miau wrote:
What I really meant (but didn't clearly state, sorry) is that I don't want to add a program for this particular, very simple task. I wouldn't consider it to be useful enough unless it has enough features to be interesting and stand on its own. Plus, people are used to ca65 as it is, what if they wanted to use parts of my code in their own projects?

As long as your preprocessor spits out assembly code suitable for ca65 or object files suitable for ld65, it will still be possible to combine modules in your language with modules designed for straight ca65.

Banshaku wrote:
What if I have 15 temp variables that I shared under different names the same way?

I typically reserve the first 16 bytes of zero page for local variables. At the top of each .proc, I add comments explaining how I use each.


It seems to me this is more than acceptable for an NES game. I can't imagine many situations where calls would be nested more than 2, or 3 times. It seems as though it should be very easy in most cases to correct mistakes where routines are trying to use the same temp vars. And of course if you "run out" of temp vars, you can hard code using the stack to save/restore these values. I'm trying not to worry too much about emulating higher level concepts while working on my project, except that which is practical. To me, practical means:

1) easily navigable source code. Not one monolithic file
2) concepts are seperated clearly
3) you have a very good debugger like FCEUXDSP so at any point you can trace exactly what your code is doing at any point

beyond that, we might as well code in C.

by on (#49807)
My point was that if you can find a way to reduce the risk of using at the same time the same temp variable, more power to you. Already just defining local temp variables (file scope) will reduce the amount of code you will need to check if something goes wrong.

If you have 15+ global temp variables that anybody can use at any time, the risk of having a bug that will be hard to figure out increase. And if you use different names for the same memory location for temp variable, even more difficult to figure out.

by on (#56313)
Resurrecting this thread because I have a question.

I'm going through all my code trying to scope everything and I found this thread, which is great btw. I'm not familiar with using .h files. I was just wondering what you guys stick inside them.

Right now my .h files consist of only .import and .export commands. For example, my sound_engine.h file looks exactly like this:

Code:
.export sound_init
.export sound_play_frame
.export sound_load

.importzp temp_ptr1


and my sound_engine.asm has a line at the top:

Code:
.include "sound_engine.h"


Is this all I should include in my .h files? Or do you stick things like constants/defines in there too? I just want to know what is common practice.

by on (#56319)
With my approach, a file named example.h file might look like this:

Code:
.ifndef EXAMPLE_H
EXAMPLE_H = 1

.global some_function
.globalzp some_zp_var


SOME_CONSTANT = 123

.macro some_macro
    ...
.endmacro

.endif


Then, example.s would look like this:
Code:
.include "example.h"

.segment "ZEROPAGE"
some_zp_var: .res 1

.segment "CODE"
.proc some_function
    ...
    rts
.endproc


That's similar to how you'd do it in C. The .global statements work like forward declarations in C, which is quite convenient, they're equivalent to .export when included from example.s (where the actual function or variable is defined) and equivalent to .import when included from anywhere else.

No idea if that's the best way, maybe it could be done more elegantly in ca65... I'm not 100% familiar with all of ca65's preprocessor commands.

by on (#56325)
Interesting this came up as I recently cleaned up my code base. My current approach is that for every procedure or variable label I have something like this:

in .asm file:

Code:
.export decodeMap
.proc decodeMap
  ;code
.endproc

.export testMapCollision
.proc testMapCollision
  ;code

.endproc


in .h file:
Code:
.ifndef MAP_INC
MAP_INC = 1

.import decodeMap
.import testMapCollision

.endif


I like doing it this way because it gives me max control over what gets exported and imported. Plus, having the .export right above the definition of a symbol makes it easier to maintain, and then the inc file is just a long list of imports.

I have some header files that only contain commonly used structs, constants, macros, etc. These I have in a seperate directory from the sort of header file described above.

*edit* now that I think about it, it may be a good idea to move structs/macros/constants that are primarily associated with a particular module into that module's header file.

by on (#56622)
I changed my stance recently on renaming temp variables. If done properly, it can make the code more readable.

One way I do is like this:

Code:
.proc subGetMetatileAttribute
;---------------- Parameters definitions ----------------
.scope Param
   posX      = zpParam1
   posY      = zpParam2
   direction   = zpParam3
.endscope
.scope Local
   metatileRow   = zpTemp16Bit
.endscope
;---------------------------------------------------------
...
.endproc


At first I was finding it messy but actually there is a good point of doing this. When you want to find if a temp variable is used, you don't have to scan the complete code but only the top of the function definition. This makes it easier to find where they are used.

So in the function, when you want to use a parameter you do:

Code:
   lda Param::posX
   ...
   stx Local::metatileRow


when you call the method and want to set a parameter, you do

Code:
   lda someValue
   sta subGetMetatileAttribute::Param::posX


Yes, I know it makes the statement bigger but it's now quite obvious by reading the code what you're trying to do. This is one way that I use scopes.

by on (#56640)
you gave me an idea:
How about, in zp, instead of individually defining a bunch of temp vars, just reserve space for "locals:"

Code:
;in zp
locals: .res 32

;and then for a proc:

.proc MyProcedure
.struct Params
   x .byte
   y .byte
.endstruct

  lda locals+Params::x
  lda locals+Params::y
  rts

.endproc


I guess the problem with that is how can you export (or, not really export but define for where you are using the procedure) the params struct for your procedure? You'd probably have to do something like .struct MyProcedure_Params in the header file for the module. Maybe that wouldn't be so bad. Next to every .import, include a struct for the params of the module. I think I might try this or your method. Thanks for the idea!

by on (#56653)
There is many way to skin a cat ;) As for this idea, hmm... What I don't like is there is no scope to it at all and you must use the local name + struct to access it.

In my example, you cannot mix the scope. If you use the variable outside the function scope, you have to specify the function name. In your example, the param struct is defined at module scope or higher and all code can see it. In my example, the scope is only defined at the function level and the same name can be re-used in all function, which is quite useful and keep your code organized the same way so you know what to expect when using a function.

edit:

I didn't realize that the struct was defined at function level. I was not aware you could do this. But still, the second issue is still there, it makes the variable even longer. Since the scope or struct code is almost the same, you should go with a scope. You can always do:

Code:
.scope Param
   posX      = locals + 1
   posY      = locals + 2
   direction   = locals + 3
.endscope


and now you saved the extra local when accessing it. Another issue is what happen if a function inside a function uses the param? How do you define in the strut that you must use the values in locals after the one that was used inside the first function? I'm not sure how you could do that with strut.

by on (#56672)
Dangit, I guess I can't define a struct inside a procedure. I just tried it. It looks like I'd have to use some kind of naming convention for myself such as, inside my header file I might have:

Code:
.import testMapCollision
.struct testMapCollision_Params
   x .byte
   y .byte
.endstruct


Only, I'm not sure how I could include the header file into the module itself. Maybe I'd have to use the .global directive that miau mentioned earlier, so that when the label is defined it is exported, and when used, imported. That way I could put:

Code:
.global testMapCollision
.struct testMapCollision_Params
   x .byte
   y .byte
.endstruct

and include the header both in the module itself and in any other modules that use it. That would be somewhat similar to C/C++ function prototypes.

I think when all is said and done though I like your approach better. With explicitly named zp variables being used, it'd be easier in the debugger to track bugs than having one flat array of bytes that can be used in any way by procedures' param structs.

Question: Say you are calling a procedure that resides in a seperate module. Are you still able to get at those scopes defined inside the procedure?

*edit* I think I may try a hybrid of the two approaches. In my headers, I think I will do:

Code:
.global MyProcedure
.scope MyProcedure_Params
  x = zpByte0
  y = zpByte1
.endscope
.scope MyProcedure_Locals
  counter = zpByte2
.endscope


And then in the source file, you just include this header and now you can use the procedure and its parameter/local scopes both inside the module and anywhere you use the module.

by on (#56700)
Gradualore wrote:
Question: Say you are calling a procedure that resides in a seperate module. Are you still able to get at those scopes defined inside the procedure?


To my knowledge, the answer would be no. Scope are file scope and cannot be exported. This mean that you have to redefine them in your h file. In my case this is not an issue since the name of the function inside the module and the way to access it outside is completely different.

Code:
Inside asm file

.proc subMyFunction
.scope Param
   posX = zpParam1
.enscope
...
.endproc

-------
Inside H file

.scope MyModule
     myFunction = __subMyModuleMyFunction

    .scope myFunction
        .scope Param
             posX = __myModuleFunctionParam1
        .endscope
    .endscope
.endscope



With those scope, you can do:

Code:
    lda someValue
    sta MyModule::myFunction::Param::posX


Since we call it from outside the module, maybe the Param scope is redundant and should be removed. I'm still testing which approach is better.
Re: ca65 scopes and macros
by on (#112437)
miau wrote:
using the macros like this:
Code:
peng::particle_create #01
peng::particle_setpos #40,#120

doesn't work.
Macros seem to be directly accessible from all scopes(?), so just calling them without the "peng::" part works. That defies the purpose of scopes, though.

My questions: Is there any way around this? Is it even good practice to write code like this or is there a better, recommended way?

It's the first time I've been using scopes in this way, but I've got older similar code that I want to convert to be more readable and organized.


<Insert apology about necro-bump here>

I also wanted to keep things more readable and organized, I figured you can force yourself to use a scope-like syntax without any obvious (so far) downside. One can create a macro with the same name as the scope that acts as a fake scope/namespace and then calls the macro that was intended. Example code:

Code:
.macro SCOPE_NAME name_param1, param2, param3, param4, param5, param6, param7, param8, param9, param10

    aPrivateScope::interfaceValid  .set 1
   
    .if .not .xmatch(.mid(0,1,{name_param1}),::)
        .error "Scope operator expected."
        .exitmacro
    .else
       
        .ifnblank param10
            .define __PARAMS__ , {param2}, {param3}, {param4}, {param5}, {param6}, {param7}, {param8}, {param9}, {param10}
        .elseif .not .blank( param9 )
            .define __PARAMS__ , {param2}, {param3}, {param4}, {param5}, {param6}, {param7}, {param8}, {param9}
        .elseif .not .blank( param8 )
            .define __PARAMS__ , {param2}, {param3}, {param4}, {param5}, {param6}, {param7}, {param8}
        .elseif .not .blank( param7 )
            .define __PARAMS__ , {param2}, {param3}, {param4}, {param5}, {param6}, {param7}
        .elseif .not .blank( param6 )
            .define __PARAMS__ , {param2}, {param3}, {param4}, {param5}, {param6}
        .elseif .not .blank( param5 )
            .define __PARAMS__ , {param2}, {param3}, {param4}, {param5}
        .elseif .not .blank( param4 )
            .define __PARAMS__ , {param2}, {param3}, {param4}
        .elseif .not .blank( param3 )
            .define __PARAMS__ , {param2}, {param3}
        .elseif .not .blank( param2 )
            .define __PARAMS__ , {param2}
        .else
            .define __PARAMS__
        .endif
       
        .if .tcount({name_param1}) = 2 ; scope operator and name, no first param, maybe other params
   
            .mid(1,1,{name_param1}) __PARAMS__
       
        .elseif .tcount({name_param1}) >= 3 ; scope operator, name and param1, maybe other params
   
            .mid(1,1,{name_param1}) .mid(2,255,{name_param1}) __PARAMS__
           
        .else
            .error "Syntax Error."
        .endif
   
        .undefine __PARAMS__
           
    .endif

       
    .endif
   
     aPrivateScope::interfaceValid  .set 0

.endmacro

.macro checkInterface
    .if .not aPrivateScope::interfaceValid
        .error "Please use the proper interface syntax."
    .endif
.endmacro



For each macro that is a part of the scope the first line should be 'checkInterface', to make sure you used the namespace:: syntax. There might be a sneaker way to deal with multiple paramaters. Edit: add curly brackets. Edit: more robust code.