ca65 scopes and macros

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

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: Select all

.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.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Post by GradualGames »

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: Select all

.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: Select all

.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: Select all

.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.
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

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: Select all

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: Select all

    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.
User avatar
Movax12
Posts: 541
Joined: Sun Jan 02, 2011 11:50 am

Re: ca65 scopes and macros

Post by Movax12 »

miau wrote:using the macros like this:

Code: Select all

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: Select all

.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.
Post Reply