The situation in NOCMODL is relatively simple. User-defined identifiers start with [a-zA-Z]. Which leaves all variables that start with _[a-z] free (_[A-Z] are taken by the compiler/standard library).
This is then used to implement simple mnemonic schemes, for example LOCAL is f"_l{var_name}", all function arguments are prefixed with _, e.g. _iml, _ml, _nt, etc. (if none of them starts with _l they can't clash with LOCALs).
For NMODL I don't fully understand how it avoids naming collisions systematically. For example: LOCAL variables are just double {var_name} and same for internal variables, e.g. id (for _iml), nt (for _nt), etc.
Therefore,
INITIAL { LOCAL id
x = 42.0
}
results in:
for (int id = 0; id < nodecount; id++) {
// ...
double id;
inst->x[id] = 42.0;
}
Similar collisions can be created for a number of other internal variables, I tested nt, ml and inst.