Page 1 of 1

gcc/make problem with define

Posted: 01 Apr 2017 14:32
by Transportman
Hi all,

For a recode of the 2cc TrainsInNML I'm working on, I want to ease the reuse of some code that is coming back regularly. One of the things I'm working on, are capacity switches for several passenger vehicles. I'm using the following definition:

Code: Select all

#define ENGINECAPACITYSWITCH(VEHICLE_NAME, TYPE) \
switch(FEAT_TRAINS, SELF, switch_VEHICLEIDENTIFIER_capacity_engine, cargo_classes) { \
	bitmask(CC_MAIL): HEAD_CAPACITY/2; \
	bitmask(CC_ARMOURED): HEAD_CAPACITY/4; \
	HEAD_CAPACITY; \
}
The VEHICLE_NAME and TYPE input are not used, I'm aware of that, but that is not the problem. Both VEHICLEIDENTIFIER and HEAD_CAPACITY are defined before this ENGINECAPACITYSWITCH is included for a vehicle, but the problem that I'm having is that VEHICLEIDENTIFIER is not replaced, with the defined value, while HEAD_CAPACITY is.

As I also have other switches for which I want to do something like this, I would really like to have this working.

Re: gcc/make problem with define

Posted: 01 Apr 2017 15:31
by TadeuszD
Transportman wrote:Both VEHICLEIDENTIFIER and HEAD_CAPACITY are defined before this ENGINECAPACITYSWITCH is included for a vehicle, but the problem that I'm having is that VEHICLEIDENTIFIER is not replaced, with the defined value, while HEAD_CAPACITY is.
To concatenate defined strings you should use '##'. For example:

Code: Select all

#define ENGINE_RUNNING_COST_SWITCH(Engine) \
switch (FEAT_TRAINS, PARENT, switch_##Engine##_running_cost, current_speed ) { \
	0: return HALTED_COST_##Engine; \
	return RUNNING_COST_##Engine; \
}
But it works only in the scope of current #define directive. You can not use other definitions. The example below will throw the error, ##ANY_STRING will not be replaced:

Code: Select all

#define ANY_STRING 123
...
#define ENGINE_RUNNING_COST_SWITCH(Engine) \
switch (FEAT_TRAINS, PARENT, switch_##Engine##_running_cost, current_speed ) { \
	0: return HALTED_COST_##Engine; \
	return RUNNING_COST_##ANY_STRING; \
}
You should pass the ANY_STRING value as new argument in your definition:

Code: Select all

#define ANY_STRING 123
...
#define ENGINE_RUNNING_COST_SWITCH(Engine, AnyString) \
switch (FEAT_TRAINS, PARENT, switch_##Engine##_running_cost, current_speed ) { \
	0: return HALTED_COST_##Engine; \
	return RUNNING_COST_##AnyString; \
}
...
ENGINE_RUNNING_COST_SWITCH(MY_ENGINE, ANY_STRING)

Re: gcc/make problem with define

Posted: 01 Apr 2017 18:40
by Transportman
Thanks for the response, but I still cannot get it to work, it replaces the argument with the name of the #define instead of the value of the define. And I can't understand why HEAD_CAPACITY is replaced just fine.

Could I otherwise send you the source so that you can take a look at it? It might be something really small that is making it not working for me, but I just can't see it.

Re: gcc/make problem with define

Posted: 02 Apr 2017 08:56
by Alberth
Transportman wrote:Thanks for the response, but I still cannot get it to work, it replaces the argument with the name of the #define instead of the value of the define. And I can't understand why HEAD_CAPACITY is replaced just fine.
"cpp" is a C pre-processor, and it applies C conventions on names in replacements. In particular, it does not break names to replace parts of it with #define content.

It finds "switch_VEHICLEIDENTIFIER_capacity_engine" as name, and compares that with "VEHICLEIDENTIFIER" #define name, and concludes the names are not the same, and does not do replacement.
If finds "HEAD_CAPACITY" as name, and compares that with "HEAD_CAPACITY" #define name, and concludes it's the same, and replace the HEAD_CAPACITY with the content of the #define.

To get VEHICLEIDENTIFIER replaced, the ## magic comes in ( https://gcc.gnu.org/onlinedocs/cpp/Conc ... catenation ). Basically, it allows parameters from the #define to get replaced before joining them together to a legal C name. The documentation doesn't say anything about replacing definitions first, it doesn't seem designed to do this at all. Your best bet imho is not to try this, and instead specify the value of VEHICLEIDENTIFIER as parameter

Code: Select all

#define HEAD_CAPACITY infinite
#define ENGINECAPACITYSWITCH(VEHICLEIDENTIFIER, VEHICLE_NAME, TYPE) \
switch(FEAT_TRAINS, SELF, switch_ ## VEHICLEIDENTIFIER ## _capacity_engine, cargo_classes) { \
   bitmask(CC_MAIL): HEAD_CAPACITY/2; \
   bitmask(CC_ARMOURED): HEAD_CAPACITY/4; \
   HEAD_CAPACITY; \
}

ENGINECAPACITYSWITCH(vi, abc, nonetype)
results in

Code: Select all

switch(FEAT_TRAINS, SELF, switch_vi_capacity_engine, cargo_classes) { bitmask(CC_MAIL): infinite/2; bitmask(CC_ARMOURED): infinite/4; infinite; }

Far from ideal, I agree. Basically, you need something better than cpp. m4 would work, but it comes with its own quirks. Andythenorth basically dropped cpp, and uses Python with templating instead. Perhaps someone needs to make a better processing language on top of nml (like cpp, but better), or nml could be extended.
The latter doesn't work nicely with the parser though in an experiment I once tried, so it involves much more work than it seems at first sight.

Re: gcc/make problem with define

Posted: 02 Apr 2017 09:46
by andythenorth
For this case (concatenating switch names) you need variadic macros with the C pre-processor.

I found it better to switch to a more appropriate templating language at this point. I use Chameleon because I'm familiar with it, but there are other choices.

The case of templating switch names is so common that ideally nml would provide for this, but it's not trivial to do that.

Re: gcc/make problem with define

Posted: 02 Apr 2017 11:29
by Transportman
Thanks for the help and suggestions.

I found a workaround using CPP, while still passing the #define to it. Instead of directly calling the define that defines the switch, I call a define that calls the define that defines the switch.

Code: Select all

#define ENGINECAPACITYSWITCHNAME(VEHIDCODE) EXPANDEDENGINECAPACITYSWITCHNAME(VEHIDCODE)
#define ENGINECAPACITYSWITCHCALL(VEHIDCODE) EXPANDEDENGINECAPACITYSWITCHCALL(VEHIDCODE)
#define ENGINECAPACITYSWITCH(VEHIDCODE) EXPANDEDENGINECAPACITYSWITCH(VEHIDCODE)


#define EXPANDEDENGINECAPACITYSWITCHNAME(VEHIDCODE) switch_ ## VEHIDCODE ## _capacity_engine
#define EXPANDEDENGINECAPACITYSWITCHCALL(VEHIDCODE) cargo_capacity: switch_ ## VEHIDCODE ## _capacity_engine;

#define EXPANDEDENGINECAPACITYSWITCH(VEHIDCODE) \
switch(FEAT_TRAINS, SELF, switch_ ## VEHIDCODE ## _capacity_engine, cargo_classes) { \
	bitmask(CC_MAIL): HEAD_CAPACITY/2; \
	bitmask(CC_ARMOURED): HEAD_CAPACITY/4; \
	HEAD_CAPACITY; \
}
andythenorth wrote:For this case (concatenating switch names) you need variadic macros with the C pre-processor.

I found it better to switch to a more appropriate templating language at this point. I use Chameleon because I'm familiar with it, but there are other choices.
For me there is not much use to switch to something else, although I do have to admit that it is not the nicest code I now have.
The case of templating switch names is so common that ideally nml would provide for this, but it's not trivial to do that.
Not only that, but a way to pass along values as arguments to the switch would be nice. Then I could just use those arguments in a simple switch that is the same for all vehicles, and be done with it. A bit like templates for graphics, just give in some arguments and the rest is done automatically.