nurpax / c64jasm

C64 6502 assembler in TypeScript
51 stars 14 forks source link

how to prevent including same file multiple times? #56

Closed neochrome closed 5 years ago

neochrome commented 5 years ago

If I have some file with defined constants or something and I want to use those from within a utility function in another file I can just do !include "constants.asm in that file. But if I also want to use those constants in the main program I get errors like:

Variable 'border_color' already defined

(yeah, I'm lame, I don't remember all VIC registers by heart...yet)

I found that if I put a !filescope before including, it kinda works - because the constants are "redefined" within the scope of the file this time.

In KickAss I think there is some directive #importonce or similar that prevents it from happening, or you can guard your stuff C-style with #ifdef, #define and #endif. Neither of those options are available in c64jasm as far as I can tell?

If I try to use !if (...) {} to guard my stuff, it fails because the variable to test against isn't yet defined...

Is there some other way of architecting ones files to circumvent the problem all together or is this something that might be of consideration to add?

nurpax commented 5 years ago

Yup, this is not supported.

What I’ve done to workaround this is to

!include ”defs.inc”

once in my main.asm before anything else is included.

Not sure what’s the best general solution. Current ”load in your top source file” is simple. But sth like !importonce might be nice but won’t work well with filescope and also what if the included file outputs code or data? That gets emitted only once then?

neochrome commented 5 years ago

Yeah, I've also put my includes once in my main - and that sure works.

Also I don't think !importonce or similar is the way to go - it's a bit too specific and rigid and adds unnecessary complexity to the assembler.

If something is to be done in this direction, I think adding a !ifdef / !ifndef or have the current !if handle undefined variables will be a more flexible solution, where you can guard definitions but still output stuff if needed.

But all of this is just me airing some thoughts, it's perfectly fine to include stuff at the top of the main, I'm still adjusting my brain around having everything at a "global" level :)

nurpax commented 5 years ago

One alternative that I thought about would be to have a built-in defined function. So then you could do:

!if !defined(foo_asm_included) {
!include "foo.asm"
}

in foo.asm:

!let foo_asm_included = 1
...

The implementation of defined(symbol) in c64jasm would just check if symbol is declared and return true if it is, false otherwise.

But this doesn't quite work either.. because the if-body goes into its own anonymous scope. So none of the stuff that was included inside of !if defined(foo_asm_included) will be visible in the outer scope. Kind of defeating the purpose of including the file in the first place, LOL. Unless you could override !if body to go into the parent scope. But then we get into relative scope territory which gets confusing quickly.

Re: KA's import, include, importonce: TBH, when I learned KickAssembler about a year ago, I couldn't quite workout what's the difference between include and import were. Also the namespacing felt really wonky to me, IIRC, stuff like macro names behaved differently from other symbols. I'm hoping to avoid a similar situation with c64jasm if possible, so that's one motivation to keep things simple.

In a similar vein, KA has .label, .const, .var. In contrast, c64jasm has only !let. I think the label/const/var features provide some flexibility that c64jasm's !let might not have (probably related to forward defs), but I still prefer not to have have so many ways to do similar, but not exactly the same things.

neochrome commented 5 years ago

Re: KA's import, include, importonce: TBH, when I learned KickAssembler about a year ago, I couldn't quite workout what's the difference between include and import were. Also the namespacing felt really wonky to me, IIRC, stuff like macro names behaved differently from other symbols. I'm hoping to avoid a similar situation with c64jasm if possible, so that's one motivation to keep things simple.

Yup, KA seems a bit complex - and the simplicity (and speed) of c64jasm is what intrigues me most :) Also how easy it is to write plugins/extensions (I'm mainly a JS dev nowadays).

Also, if the intent of including a file with defs/contants from each utils file that happen to use them, I think putting in a !filescope in said utils file and then !include what's needed is fine. Even if a local "copy" is created in the file's scope - it's only constants and macro defs anyway (at least in my usecase).

neochrome commented 5 years ago

btw, when playing around a bit more with file inclusions and scopes I found that accessing macros seem to behave a little bit different depending whether the macro is defined local to a scope or not. Example:

scope: {

    !macro m1() {
        lda #1
    }

    !macro m2() {
        lda #2
        +scope::m1()
        +m1() ; <--- this fails, m1 not found
    }

}

!macro m3 () {
    lda #3
}

!macro m4 () {
    lda #4
    +m3() ; <--- this works, m3 is found
}

+m4()
+scope::m2()

Is it only me that got it wrong, or shouldn't m1 be accessible from within m2 since they're in the same parent scope?

nurpax commented 5 years ago

Looks like you've stumbled into somewhat uncharted territory. :)

I'm not entirely sure what's the right thing to do here would be. The way macro expansion works, is that once you've resolved a macro name (say m2 above), it will basically just paste its body into the current code emission context. So when you expand scope::m2 at the bottom, it will basically just copy&paste this:

        lda #2
        +scope::m1()
        +m1() ; <--- this fails, m1 not found

into top-level scope (or generally, which ever scope we're in when calling a macro with +macro_name). But since there is no m1 on the top level scope, compilation fails.

If this was just an isolated example, maybe it wouldn't be such a big problem. But I guess you can hit this with just macros defined inside a !filescope calling each other.. and that'd suck for using !filescope with macro libraries.

This is obviously not great. I guess one way to fix this would be to extend the way macros are expanded.. So let's say if you call a macro by +scope::m2, when compiling the m2 body, it'd first search for symbols in scope, and if a symbol is not found from that scope, it'd search in the current expansion scope.

Another, perhaps more conventional approach would be to not let macros see any other scope other than where it was declared. This is pretty much how functions work in say JavaScript. But I'm not entirely sure if that's ideal, as I've found some pretty nice uses for macros being able to look into the current scope where it's expanded. Like this implicit zp parameter I talk about here: https://nurpax.github.io/posts/2019-07-27-c64jasm-object-literals.html

Any thoughts or recommendations?

nurpax commented 5 years ago

@neochrome check out https://github.com/nurpax/c64jasm/commit/c1cd6d52bfa7dbbcb4c196aeccf639feb36b8699, it seems to fix this macro symbol lookup problem.

I'll add some more test cases to see how it behaves but all functional tests (94 tests) and error tests (51) passed with this, so it shouldn't be too bad. Also compiled two of my past C64 demos with the new compiler and they both resulted in bit-exact results compared to the previous c64jasm version.

This has the side-effect that the 'zp' object literal thing I was so excited about doesn't work anymore. But I have some ideas for an alternative way to implement such implicit argument passing. (Some type of default parameter argument syntax, probably.)

nurpax commented 5 years ago

Btw if you want to try the compiler yourself, I've been using this:

yarn 
yarn gen && yarn compile
yarn asm --out x.prg ./src/main.asm  # the usual c64jasm command line args, this just calls CLI from npm
nurpax commented 5 years ago

Yup, KA seems a bit complex - and the simplicity (and speed) of c64jasm is what intrigues me most :) Also how easy it is to write plugins/extensions (I'm mainly a JS dev nowadays).

Thanks, I like to think it's simple and fast too. :) I like the "watch mode" a lot too. Also big thanks for taking the time to issue these bug reports, especially this macro scope discussions has been very useful.

Also, if the intent of including a file with defs/contants from each utils file that happen to use them, I think putting in a !filescope in said utils file and then !include what's needed is fine. Even if a local "copy" is created in the file's scope - it's only constants and macro defs anyway (at least in my usecase).

BTW the "local copy" compilation speed should be OK for including macros and defs. C64jasm internally cache parser results with includes. So if you have a 1000 line macro_defs.asm, and you include it into a 100 source files, macro_defs.inc is in fact parsed only once.

Re: filescopes. So something like this should work: assume you have a macro_defs.asm that you'd like to include in multiple files. Then:

; a.asm
!filescope a
!include "macro_defs.inc"
; b.asm
!filescope b
!include "macro_defs.inc"

~I'll write a test case to see that it actually works~ EDIT: seems to work fine. See referenced tests in https://github.com/nurpax/c64jasm/commit/51b51c095b60cd0b8abacf54904524952a03a846.

neochrome commented 5 years ago

@neochrome check out c1cd6d5, it seems to fix this macro symbol lookup problem.

It seems to work just fine with those changes!

I'll add some more test cases to see how it behaves but all functional tests (94 tests) and error tests (51) passed with this, so it shouldn't be too bad. Also compiled two of my past C64 demos with the new compiler and they both resulted in bit-exact results compared to the previous c64jasm version.

:+1:

This has the side-effect that the 'zp' object literal thing I was so excited about doesn't work anymore. But I have some ideas for an alternative way to implement such implicit argument passing. (Some type of default parameter argument syntax, probably.)

Hmm, that is a bit sad, I can see how it might be useful. At the same time I don't think it's super clear which value is seen prior the change (what you describe in your post). After the change it's as clear (!?) as with scopes/closures in JavaScript... I'll think a bit more about it and give more feedback in #57.

Also big thanks for taking the time to issue these bug reports, especially this macro scope discussions has been very useful.

It's just fun to use what you've built and if my feedback is helpful I'll keep sending it your way :) Hopefully without drowning you with ideas and corner cases... :)

nurpax commented 5 years ago

At the same time I don't think it's super clear which value is seen prior the change (what you describe in your post). After the change it's as clear (!?) as with scopes/closures in JavaScript...

Yup, I think lexical scoping like in JavaScript is the way to go. No need to innovate Pythonesque solutions here. :)

In fact, macros in c64jasm are now in fact really a lot like just normal functions. Maybe at some point I'll add support for returning a value too.

So I'm merging the changes and closing this.

nurpax commented 5 years ago

Will be releasing 0.7 today which contains all the changes discussed in this thread.

neochrome commented 5 years ago

Sweet! I'll take it for a spin and make sure to update my vim syntax plugin as well :)

On Mon, Aug 5, 2019, 18:36 Janne Hellsten notifications@github.com wrote:

Will be releasing 0.7 today which contains all the changes discussed in this thread.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nurpax/c64jasm/issues/56?email_source=notifications&email_token=AACR6UR42TSQPML24EPCECLQDBJIDA5CNFSM4IJBOZVKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3SL2OA#issuecomment-518307128, or mute the thread https://github.com/notifications/unsubscribe-auth/AACR6URRIIPHTWJQYIOXZ63QDBJIDANCNFSM4IJBOZVA .

nurpax commented 5 years ago

@neochrome 0.7.0 is up on npm now :)

is your vim syntax plugin publicly available? I don't use vim but someone else might be interested in it.

neochrome commented 5 years ago

The vim syntax plugin will be made available as open source here at github as soon as I've fixed some minor issues and updated for the latest changes in 0.7.0 :)

neochrome commented 5 years ago

There, the plugin is just released for c64jasm 0.7.0 and may be found at https://github.com/neochrome/vim-c64jasm if you want to put in a link somewhere :)

nurpax commented 5 years ago

Thanks, looks great! I will include it in the docs and tweet a link.

Sorry to take so long to reply. I’ve been busy on this c64 compo: https://gist.github.com/nurpax/96285c08710387e73ea5e039e27980af :)