3

I have spent hours trying to figure out how to get conditional include paths functional in CA65 (or really, any conditional string values at all). Is it even possible?

What I want is to be able to define a constant string (which has to be a .DEFINE due to limitations in CA65) that is the path exclusively for configuration includes for a reusable library. I cannot find a way to define these values via the command line (the -D VALUE=123 format does not work for strings since it uses the standard VALUE = 123 format), nor have I found a way to detect if a .DEFINE has already been defined in the actual .inc/.asm/.s files via .DEFINED or .DEFINEDMACRO or any other means to use a default config directory or error if it wasn't specified.

For example, assume my structure is:

MyFile.asm
Config/String.inc
Library/String.asm
Library/Config/String.inc

In this example, Config/String.inc is my custom configuration file that I want Library/String.asm to use. However, I want to be able to add code to Library/String.asm to set the configuration directory to Config/ OR output a fatal error and immediately exit if a configuration directory is not defined. Since I have not figured out a way to pass the directory and also detect it, I can only currently just blindly use the configuration directory constant/macro (which, if not used, outputs a crap-ton of errors due to the missing include) or hard-code it to use a specific directory, which is definitely not ideal.

How do I set it up so my files can include library files and override the path for them (or have a fatal error) for including config files?

If I use:

.DEFINE CONFIGPATH "../Config/"
.IFNDEF CONFIGPATH
  .FATAL "This always happens"
.ENDIF

This causes:

2: Error: Identifier Expected
1: Note: Macro was defined here
2: Error: Unexpected trailing garbage characters
1: Note: Macro was defined here
Fatal error: User error: This always happens

If I use:

.DEFINE CONFIGPATH "../Config/"
.IF .NOT .DEFINEDMACRO(CONFIGPATH)
  .FATAL "This always happens"
.ENDIF

This causes:

2: Error: Identifier Expected
1: Note: Macro was defined here
Fatal error: User error: This always happens

If I try to use a macro:

.MACRO INCLUDECONFIGFILE includefile
  .INCLUDE .CONCAT("../Config/", .STRING(includefile))
.ENDMACRO

CA65 just immediately runs the .INCLUDE, even if I never use the macro, and complains about an invalid string parameter since includefile doesn't actually exist at that point. I can actually detect this macro with .DEFINEDMACRO, but that does me no good since it doesn't work.

Trying to use a standard macro to just output a string also doesn't work.

Am I missing something?

0

1 Answer 1

3

Based on my reading of the ca65 manual, .define creates definitions that are expanded before literally any other processing takes place, with the only exception being .undefine:

A separate command for .DEFINE style macros was necessary, because the name of such a macro is replaced by its replacement list on a very low level. To get the actual name, macro replacement has to be switched off when reading the argument to .UNDEFINE.

Based on that wording, I would not be surprised if define-style macros were expanded immediately at the lexing stage. This passage also implies there is no directive that checks for the existence of define-style macros: .defined checks for the existence of symbols, while .definedmacro checks for the existence of instruction-like ‘classic’ macros (those defined with .macro).

However, while the problem cannot be solved in full generality, it’s not all hopeless. Since you know you intend for the macro to expand to a string, you can work around this design limitation by employing the .match pseudo-function:

.if .not .match(CONFIGPATH, "")
.fatal "CONFIGPATH not defined correctly!"
.endif

The above checks whether CONFIGPATH is a string token. This can only happen if the CONFIGPATH token has already been expanded into its replacement token list before being received as an argument by .match. If the CONFIGPATH is defined to expand to something else than a string, the above will behave the same as if the macro was not defined.

4
  • 1
    This actually works! Boy that is confusing. It seems that by using .IF .MATCH(CONFIGPATH, "") is the equivalent of a non-existent .IF .ISSTRING(CONFIGPATH) operation. Dec 13, 2022 at 20:19
  • Since .isstring is non-existent, you can imagine it having any semantics whatsoever, including having it launch NetHack, in which case it would not be equivalent… but whatever helps you. Dec 14, 2022 at 11:12
  • I don't see how it is illogical to expect an .ISSTRING to exist in an assembler that has a clear distinction between a string and a non-string. If you don't believe me, try using .CONCAT without converting values via .STRING or .SPRINTF or the equivalent. It's even in the same format as other commands, such as .ISMNEMONIC. Dec 14, 2022 at 22:18
  • Sure, but I still find it funny to explain the behaviour of a directive that exists in terms of another directive that doesn’t exist and hasn’t been defined by anyone. For completeness, one would have to also describe how that non-existent directive would behave were it to exist (language is arbitrary after all, you can’t presume you understand something just by knowing its name), in which case, why not cut out the middleman? Dec 15, 2022 at 0:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .