@@ -3280,6 +3280,87 @@ static int str_eq_ci(const char *a, const char *b)
32803280 return (*a == *b);
32813281}
32823282
3283+ /* Active build identity for compile-time #IF blocks during load.
3284+ * The interpreter is a modern/native host, so it keeps MODERN + PORTABLE
3285+ * (tier) blocks by default and only keeps a `#IF TARGET <id>` block when
3286+ * --target names that id. This mirrors the transpiler's per-target strip,
3287+ * so a .bas with per-platform branches runs here the way it would compile
3288+ * for the chosen machine. Kept in sync with tools/rgc_lint/directives.py. */
3289+ static char active_target[32] = "native";
3290+ static char active_tier[16] = "modern";
3291+
3292+ /* Compare one comma-separated id list entry (case-insensitive). Returns 1
3293+ * if `active_target` equals a token in [ids .. end-of-string). */
3294+ static int target_in_list(const char *ids)
3295+ {
3296+ size_t tlen = strlen(active_target);
3297+ while (*ids) {
3298+ const char *start;
3299+ size_t n;
3300+ while (*ids == ' ' || *ids == '\t' || *ids == ',') ids++;
3301+ start = ids;
3302+ while (*ids && *ids != ',' && *ids != ' ' && *ids != '\t') ids++;
3303+ n = (size_t)(ids - start);
3304+ if (n == tlen) {
3305+ size_t k;
3306+ for (k = 0; k < n; k++) {
3307+ if (toupper((unsigned char)start[k]) !=
3308+ toupper((unsigned char)active_target[k])) break;
3309+ }
3310+ if (k == n) return 1;
3311+ }
3312+ }
3313+ return 0;
3314+ }
3315+
3316+ /* If `s` starts with keyword `kw` (case-insensitive) followed by end-of-
3317+ * string or whitespace, return a pointer to the first non-space char after
3318+ * `kw`; otherwise NULL. Used to parse #IF/#ELSEIF/#ELSE/#END IF. */
3319+ static const char *dir_after_kw(const char *s, const char *kw)
3320+ {
3321+ size_t i = 0;
3322+ while (kw[i]) {
3323+ if (toupper((unsigned char)s[i]) != toupper((unsigned char)kw[i])) return NULL;
3324+ i++;
3325+ }
3326+ if (s[i] != '\0' && s[i] != ' ' && s[i] != '\t') return NULL;
3327+ while (s[i] == ' ' || s[i] == '\t') i++;
3328+ return s + i;
3329+ }
3330+
3331+ /* Evaluate an #IF / #ELSEIF condition. `cond` points at the condition text
3332+ * (leading whitespace already skipped, no trailing keyword). */
3333+ static int directive_cond_matches(const char *cond)
3334+ {
3335+ while (*cond == ' ' || *cond == '\t') cond++;
3336+ if (toupper((unsigned char)cond[0]) == 'T' &&
3337+ toupper((unsigned char)cond[1]) == 'A' &&
3338+ toupper((unsigned char)cond[2]) == 'R' &&
3339+ toupper((unsigned char)cond[3]) == 'G' &&
3340+ toupper((unsigned char)cond[4]) == 'E' &&
3341+ toupper((unsigned char)cond[5]) == 'T' &&
3342+ (cond[6] == ' ' || cond[6] == '\t')) {
3343+ return target_in_list(cond + 6);
3344+ }
3345+ if (str_eq_ci(cond, "PORTABLE")) return 1;
3346+ if (str_eq_ci(cond, "MODERN")) return str_eq_ci(active_tier, "modern");
3347+ if (str_eq_ci(cond, "RETRO")) return str_eq_ci(active_tier, "portable");
3348+ /* Unknown condition: keep (matches the Python preprocessor default). */
3349+ return 1;
3350+ }
3351+
3352+ static void set_active_target(const char *v)
3353+ {
3354+ strncpy(active_target, v, sizeof(active_target) - 1);
3355+ active_target[sizeof(active_target) - 1] = '\0';
3356+ }
3357+
3358+ static void set_active_tier(const char *v)
3359+ {
3360+ strncpy(active_tier, v, sizeof(active_tier) - 1);
3361+ active_tier[sizeof(active_tier) - 1] = '\0';
3362+ }
3363+
32833364/* Apply option from #OPTION directive (file overrides CLI). Returns 0 on success, -1 on error. */
32843365static int apply_option_directive(const char *name, const char *value)
32853366{
@@ -20630,6 +20711,12 @@ static void load_file_into_program(const char *path, const char *base_dir, int i
2063020711 int number;
2063120712 const char *resolved;
2063220713 int i;
20714+ /* Compile-time #IF/#ELSEIF/#ELSE/#END IF state for THIS file. Fresh per
20715+ * invocation, so an #INCLUDEd file gets its own chain (same as the Python
20716+ * preprocessor, which recurses per file). */
20717+ int if_in_block = 0; /* inside an #IF ... #END IF chain */
20718+ int if_branch_taken = 0; /* some branch in this chain already matched */
20719+ int if_keep = 1; /* keep lines in the current branch */
2063320720 /* Snapshot base_dir into a stack-local buffer. The caller's `base_dir`
2063420721 * may alias get_base_dir's static buffer, which our recursive
2063520722 * #INCLUDE call would overwrite — corrupting the parent's view of
@@ -20744,6 +20831,78 @@ static void load_file_into_program(const char *path, const char *base_dir, int i
2074420831 continue;
2074520832 }
2074620833
20834+ /* Compile-time conditionals: #IF / #ELSEIF / #ELSE / #END IF.
20835+ * Only the '#'-prefixed spelling is a directive; a bare IF/ELSE is a
20836+ * BASIC statement. Chain-control lines are processed even inside a
20837+ * dropped branch so the state machine stays correct; the interpreter
20838+ * keeps the matching branch and drops the rest (see
20839+ * directive_cond_matches / active_target). Mirrors the Python
20840+ * preprocessor in tools/rgc_lint/directives.py. */
20841+ if (p[0] == '#') {
20842+ const char *d = p + 1;
20843+ const char *rest;
20844+ while (*d == ' ' || *d == '\t') d++;
20845+ /* #ELSEIF / #ELSE IF <cond> — before #ELSE and #IF. */
20846+ if ((rest = dir_after_kw(d, "ELSEIF")) != NULL ||
20847+ ((rest = dir_after_kw(d, "ELSE")) != NULL &&
20848+ (rest = dir_after_kw(rest, "IF")) != NULL)) {
20849+ *first_line_seen = 1;
20850+ if (!if_in_block) {
20851+ fprintf(stderr, "#ELSEIF without #IF\n");
20852+ } else if (if_branch_taken) {
20853+ if_keep = 0;
20854+ } else {
20855+ if_keep = directive_cond_matches(rest);
20856+ if (if_keep) if_branch_taken = 1;
20857+ }
20858+ free(linebuf);
20859+ continue;
20860+ }
20861+ /* #ELSE (exact). */
20862+ if ((rest = dir_after_kw(d, "ELSE")) != NULL && *rest == '\0') {
20863+ *first_line_seen = 1;
20864+ if (!if_in_block) {
20865+ fprintf(stderr, "#ELSE without #IF\n");
20866+ } else {
20867+ if_keep = !if_branch_taken;
20868+ if_branch_taken = 1;
20869+ }
20870+ free(linebuf);
20871+ continue;
20872+ }
20873+ /* #END IF / #ENDIF. */
20874+ if (((rest = dir_after_kw(d, "END")) != NULL &&
20875+ (rest = dir_after_kw(rest, "IF")) != NULL) ||
20876+ ((rest = dir_after_kw(d, "ENDIF")) != NULL)) {
20877+ *first_line_seen = 1;
20878+ if_in_block = 0;
20879+ if_branch_taken = 0;
20880+ if_keep = 1;
20881+ free(linebuf);
20882+ continue;
20883+ }
20884+ /* #IF <cond>. */
20885+ if ((rest = dir_after_kw(d, "IF")) != NULL) {
20886+ *first_line_seen = 1;
20887+ if (if_in_block) {
20888+ fprintf(stderr, "nested #IF not supported\n");
20889+ } else {
20890+ if_in_block = 1;
20891+ if_keep = directive_cond_matches(rest);
20892+ if_branch_taken = if_keep;
20893+ }
20894+ free(linebuf);
20895+ continue;
20896+ }
20897+ }
20898+
20899+ /* Dropped branch: suppress every other line (blank, code, #OPTION,
20900+ * #INCLUDE) until the next chain-control directive. */
20901+ if (if_in_block && !if_keep) {
20902+ free(linebuf);
20903+ continue;
20904+ }
20905+
2074720906 /* Meta directives: #OPTION / #INCLUDE, or OPTION / INCLUDE (IDE / 8bitworkshop style, no #). */
2074820907 {
2074920908 int meta = 0;
@@ -21342,6 +21501,18 @@ int basic_parse_arg_flags(int argc, char **argv, int start, int expect_program_p
2134221501 return -1;
2134321502 }
2134421503#endif
21504+ } else if (strcmp(argv[i], "-target") == 0 || strcmp(argv[i], "--target") == 0) {
21505+ if (i + 1 >= argc) {
21506+ fprintf(stderr, "Missing value for --target\n");
21507+ return -1;
21508+ }
21509+ set_active_target(argv[++i]);
21510+ } else if (strcmp(argv[i], "-tier") == 0 || strcmp(argv[i], "--tier") == 0) {
21511+ if (i + 1 >= argc) {
21512+ fprintf(stderr, "Missing value for --tier\n");
21513+ return -1;
21514+ }
21515+ set_active_tier(argv[++i]);
2134521516 } else if (argv[i][0] == '-') {
2134621517 fprintf(stderr, "Unknown option: %s\n", argv[i]);
2134721518 return -1;
@@ -21815,7 +21986,7 @@ int main(int argc, char **argv)
2181521986 }
2181621987
2181721988 if (argc < 2) {
21818- fprintf(stderr, "Usage: %s [-v|--version] [-petscii] [-petscii-plain] [-charset upper|lower|c64-*|pet-*] [-palette ansi|c64] [-maxstr N] [-columns N] [-nowrap] [-wrap] [-diagnostics] [-json-status] <program.bas>\n", argv[0]);
21989+ fprintf(stderr, "Usage: %s [-v|--version] [-petscii] [-petscii-plain] [-charset upper|lower|c64-*|pet-*] [-palette ansi|c64] [-maxstr N] [-columns N] [-target ID] [-tier modern|portable] [- nowrap] [-wrap] [-diagnostics] [-json-status] <program.bas>\n", argv[0]);
2181921990 return 1;
2182021991 }
2182121992
@@ -21924,9 +22095,21 @@ int main(int argc, char **argv)
2192422095 json_status_mode = 1;
2192522096 } else if (strcmp(argv[i], "-trace") == 0 || strcmp(argv[i], "--trace") == 0) {
2192622097 trace_enabled = 1;
22098+ } else if (strcmp(argv[i], "-target") == 0 || strcmp(argv[i], "--target") == 0) {
22099+ if (i + 1 >= argc) {
22100+ fprintf(stderr, "Missing value for --target\n");
22101+ return 1;
22102+ }
22103+ set_active_target(argv[++i]);
22104+ } else if (strcmp(argv[i], "-tier") == 0 || strcmp(argv[i], "--tier") == 0) {
22105+ if (i + 1 >= argc) {
22106+ fprintf(stderr, "Missing value for --tier\n");
22107+ return 1;
22108+ }
22109+ set_active_tier(argv[++i]);
2192722110 } else if (argv[i][0] == '-') {
2192822111 fprintf(stderr, "Unknown option: %s\n", argv[i]);
21929- fprintf(stderr, "Usage: %s [-petscii] [-petscii-plain] [-charset upper|lower|c64-*|pet-*] [-palette ansi|c64] [-maxstr N] [-columns N] [-nowrap] [-wrap] [-trace] [-diagnostics] [-json-status] <program.bas>\n", argv[0]);
22112+ fprintf(stderr, "Usage: %s [-petscii] [-petscii-plain] [-charset upper|lower|c64-*|pet-*] [-palette ansi|c64] [-maxstr N] [-columns N] [-target ID] [-tier modern|portable] [- nowrap] [-wrap] [-trace] [-diagnostics] [-json-status] <program.bas>\n", argv[0]);
2193022113 return 1;
2193122114 } else {
2193222115 prog_path = argv[i];
0 commit comments