@@ -79,6 +79,11 @@ static int is_ident_char(int c)
7979 return isalpha ((unsigned char )c ) || isdigit ((unsigned char )c ) || c == '$' ;
8080}
8181
82+ static int is_name_char (int c )
83+ {
84+ return isalpha ((unsigned char )c ) || c == '$' ;
85+ }
86+
8287static const TokenMap token_map [] = {
8388 {"WHITE" , 5 },
8489 {"RED" , 28 },
@@ -385,6 +390,40 @@ static char *normalize_keywords_line(const char *input)
385390 }
386391 }
387392
393+ /* FOR followed immediately by identifier/digit: FORI=1TO9 -> FOR I=1TO9 */
394+ if (c1 == 'F' && c2 == 'O' && c3 == 'R' ) {
395+ char next = input [i + 3 ];
396+ if (next != '\0' && !isspace ((unsigned char )next ) && next != ':' ) {
397+ if (out .len > 0 ) {
398+ char prev = out .buf [out .len - 1 ];
399+ if (!isspace ((unsigned char )prev ) && prev != ':' && prev != '(' ) {
400+ sb_append_char (& out , ' ' );
401+ }
402+ }
403+ sb_append_str (& out , "FOR" );
404+ i += 3 ;
405+ sb_append_char (& out , ' ' );
406+ continue ;
407+ }
408+ }
409+
410+ /* NEXT followed immediately by identifier: NEXTI -> NEXT I */
411+ if (c1 == 'N' && c2 == 'E' && c3 == 'X' && c4 == 'T' ) {
412+ char next = input [i + 4 ];
413+ if (out .len > 0 ) {
414+ char prev = out .buf [out .len - 1 ];
415+ if (!isspace ((unsigned char )prev ) && prev != ':' && prev != '(' ) {
416+ sb_append_char (& out , ' ' );
417+ }
418+ }
419+ sb_append_str (& out , "NEXT" );
420+ i += 4 ;
421+ if (next != '\0' && !isspace ((unsigned char )next ) && next != ':' ) {
422+ sb_append_char (& out , ' ' );
423+ }
424+ continue ;
425+ }
426+
388427 /* THEN */
389428 if (c1 == 'T' && c2 == 'H' && c3 == 'E' && c4 == 'N' ) {
390429 /* Insert space before THEN if needed */
@@ -407,6 +446,56 @@ static char *normalize_keywords_line(const char *input)
407446 continue ;
408447 }
409448
449+ /* TO inside numeric ranges: 1TO9 -> 1 TO 9, but never split GOTO. */
450+ if (c1 == 'T' && c2 == 'O' ) {
451+ size_t j ;
452+ char prev_ns = ' ' ;
453+ char next_ns = '\0' ;
454+
455+ /* Skip if this is the TO in GOTO (e.g. ...GOTO 100). */
456+ if (i >= 2 ) {
457+ char g = (char )toupper ((unsigned char )input [i - 2 ]);
458+ char o = (char )toupper ((unsigned char )input [i - 1 ]);
459+ if (g == 'G' && o == 'O' ) {
460+ /* fall through to normal character handling */
461+ } else {
462+ /* Find previous non-space character. */
463+ j = i ;
464+ while (j > 0 ) {
465+ j -- ;
466+ if (!isspace ((unsigned char )input [j ])) {
467+ prev_ns = input [j ];
468+ break ;
469+ }
470+ }
471+ /* Find next non-space character after TO. */
472+ j = i + 2 ;
473+ while (input [j ] != '\0' && isspace ((unsigned char )input [j ])) {
474+ j ++ ;
475+ }
476+ next_ns = input [j ];
477+
478+ /* Treat as TO only when between numeric-ish tokens, like 1TO9. */
479+ if ((isdigit ((unsigned char )prev_ns ) || prev_ns == ')' ) &&
480+ (isdigit ((unsigned char )next_ns ) || next_ns == '+' || next_ns == '-' )) {
481+ if (out .len > 0 ) {
482+ char prev = out .buf [out .len - 1 ];
483+ if (!isspace ((unsigned char )prev ) && prev != '(' ) {
484+ sb_append_char (& out , ' ' );
485+ }
486+ }
487+ sb_append_str (& out , "TO" );
488+ i += 2 ;
489+ if (next_ns != '\0' && !isspace ((unsigned char )next_ns ) &&
490+ next_ns != ':' && next_ns != ')' ) {
491+ sb_append_char (& out , ' ' );
492+ }
493+ continue ;
494+ }
495+ }
496+ }
497+ }
498+
410499 /* AND / OR infix operators without spaces.
411500 * Only treat as operators when they are not embedded in identifiers
412501 * (e.g., avoid splitting FOR into F OR, or ORD into OR D).
0 commit comments