From 986ba16d74dcc3c44d3290626d70ac31bb829136 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:30:54 +0000 Subject: [PATCH 1/8] Fixes EOL inside strings in the minimized listing. On platforms where char is signed (x86, x86-64, Windows), the test for $9B never matched, so '-ls' wrote a raw ATASCII EOL inside the string. The cross compiler reads it back, but the native compiler and the IDE split the line there. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/compiler/parser.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.h b/src/compiler/parser.h index d99fcac..ae66604 100644 --- a/src/compiler/parser.h +++ b/src/compiler/parser.h @@ -478,7 +478,7 @@ class parse { if(c == '"') expand.stext += "\"\""; - else if((unsigned) c == 155) + else if((unsigned char)c == 155) { in = false; expand.stext += std::string("\"$") + hexd(c >> 4) + hexd(c); @@ -488,7 +488,7 @@ class parse } else { - if((unsigned) c == 155 || c == '"') + if((unsigned char)c == 155 || c == '"') expand.stext += std::string("$") + hexd(c >> 4) + hexd(c); else { From 4584961a3cd38ad376115cda5e31fee5562080da Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:31:20 +0000 Subject: [PATCH 2/8] Allows lines of exactly N characters in '-ls:N' listings. The documentation says each line is of N characters or less, but lines were only joined up to N-1 characters. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/compiler/compile.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/compile.cc b/src/compiler/compile.cc index 2719ba2..ded58d0 100644 --- a/src/compiler/compile.cc +++ b/src/compiler/compile.cc @@ -125,7 +125,7 @@ static bool parse_line(std::string line, int ln, parse &s, bool show_text, { if(!short_line.size()) short_line = txt; - else if(short_line.size() + 1 + txt.size() < short_text) + else if(short_line.size() + 1 + txt.size() <= short_text) { short_line += ':'; short_line += txt; From 91da76c7a396b4c3aabc7ba8ab13a2b8b8a9c583 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:32:04 +0000 Subject: [PATCH 3/8] Fixes INPUT into byte and floating point DATA arrays. INPUT_VAR tried VAR_WORD_LVALUE_SADDR first, which creates a variable with the name of the DATA array and then accepts it as a word variable, so "INPUT D(1)" gave a parse error at the parenthesis. Try the byte array first, like GET does, and add the floating point DATA alternative at the start of the table. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/syntax/basic.syn | 2 +- src/syntax/float.syn | 6 ++++++ testsuite/tests/fp-input-data.bas | 5 +++++ testsuite/tests/fp-input-data.chk | 8 ++++++++ testsuite/tests/input-data.bas | 11 +++++++++++ testsuite/tests/input-data.chk | 12 ++++++++++++ 6 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 testsuite/tests/fp-input-data.bas create mode 100644 testsuite/tests/fp-input-data.chk create mode 100644 testsuite/tests/input-data.bas create mode 100644 testsuite/tests/input-data.chk diff --git a/src/syntax/basic.syn b/src/syntax/basic.syn index 2790658..b2a1ee1 100644 --- a/src/syntax/basic.syn +++ b/src/syntax/basic.syn @@ -388,8 +388,8 @@ INPUT_VAR_MORE: pass INPUT_VAR: - VAR_WORD_LVALUE_SADDR emit { TOK_INPUT_STR, TOK_VAL, TOK_DPOKE } ARRAY_BYTE_ADDR emit { TOK_SADDR, TOK_INPUT_STR, TOK_VAL, TOK_POKE } + VAR_WORD_LVALUE_SADDR emit { TOK_INPUT_STR, TOK_VAL, TOK_DPOKE } VAR_STR_LVALUE_SADDR emit { TOK_INPUT_STR, TOK_COPY_STR } # Optional WORD array type diff --git a/src/syntax/float.syn b/src/syntax/float.syn index 5395d32..b99bc98 100644 --- a/src/syntax/float.syn +++ b/src/syntax/float.syn @@ -123,6 +123,12 @@ PRINT_ONE: INPUT_VAR: VAR_FP_LVALUE_SADDR emit { TOK_INPUT_STR, TOK_FP_VAL, TOK_FP_STORE } +# Floating point DATA, added at start of the table (<): VAR_WORD_LVALUE_SADDR +# would create a variable with the same name, and accept it +INPUT_VAR:< + emit { TOK_NUM, VT_ARRAY_FLOAT } E_LABEL "%" emit TOK_PUSH PAR_EXPR \ + emit { TOK_MUL6, TOK_ADD, TOK_SADDR, TOK_INPUT_STR, TOK_FP_VAL, TOK_FP_STORE } + # Arrays ARRAY_FLOAT_ADDR: emit { TOK_VAR_ADDR, VT_FLOAT } E_VAR_SEARCH "%" diff --git a/testsuite/tests/fp-input-data.bas b/testsuite/tests/fp-input-data.bas new file mode 100644 index 0000000..8179ac2 --- /dev/null +++ b/testsuite/tests/fp-input-data.bas @@ -0,0 +1,5 @@ +' Test for statement "INPUT" into floating point DATA arrays +DATA F%() = 1.5, 2.5 +? "Start" +input F%(1) +? err(), F%(0), F%(1) diff --git a/testsuite/tests/fp-input-data.chk b/testsuite/tests/fp-input-data.chk new file mode 100644 index 0000000..e1b3767 --- /dev/null +++ b/testsuite/tests/fp-input-data.chk @@ -0,0 +1,8 @@ +Name: Test statement "INPUT" into floating point DATA arrays +Test: run-fp +Input: +0.25 +. +Output: +Start +?1 1.5 0.25 diff --git a/testsuite/tests/input-data.bas b/testsuite/tests/input-data.bas new file mode 100644 index 0000000..ceccf8e --- /dev/null +++ b/testsuite/tests/input-data.bas @@ -0,0 +1,11 @@ +' Test for statement "INPUT" into DATA arrays +DATA B() BYTE = 1, 2, 3 +DATA W() = 1000, 2000 +DIM D(2) BYTE +? "Start" +input B(1) +? err(), B(0); B(1); B(2) +input "Word:", W(1) +? err(), W(0), W(1) +input D(1) +? err(), D(1) diff --git a/testsuite/tests/input-data.chk b/testsuite/tests/input-data.chk new file mode 100644 index 0000000..0956935 --- /dev/null +++ b/testsuite/tests/input-data.chk @@ -0,0 +1,12 @@ +Name: Test statement "INPUT" into DATA arrays +Test: run +Input: +7 +2345 +9 +. +Output: +Start +?1 173 +Word: 1 1000 2345 +?1 9 From a270b3a0168f459d00619d5d7d67e0b711ec462c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:32:26 +0000 Subject: [PATCH 4/8] Native parser: gives an error on lines longer than 255 characters. The loop that copies the line to the buffer indexes with Y until it finds the EOL, so with 256 or more characters it wrapped around and never ended. Costs 4 bytes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/parse.asm | 2 ++ testsuite/tests/err-line-long.bas | 3 +++ testsuite/tests/err-line-long.chk | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 testsuite/tests/err-line-long.bas create mode 100644 testsuite/tests/err-line-long.chk diff --git a/src/parse.asm b/src/parse.asm index 3ea14c8..1ba6748 100644 --- a/src/parse.asm +++ b/src/parse.asm @@ -230,6 +230,8 @@ loop_redo: sta (bptr), y cmp #$9B beq ucase_end ; End upper-casing, C=1 + cpy #$FF + beq too_long ; Line longer than 255 characters cmp #'"' bne skip_str ; Skip string constants diff --git a/testsuite/tests/err-line-long.bas b/testsuite/tests/err-line-long.bas new file mode 100644 index 0000000..1d92161 --- /dev/null +++ b/testsuite/tests/err-line-long.bas @@ -0,0 +1,3 @@ +' Lines longer than 255 characters are an error in the native compiler +? 1 +A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1:A=1 :?A diff --git a/testsuite/tests/err-line-long.chk b/testsuite/tests/err-line-long.chk new file mode 100644 index 0000000..87726ff --- /dev/null +++ b/testsuite/tests/err-line-long.chk @@ -0,0 +1,3 @@ +Name: Check detection of lines too long for the native compiler +Test: compile-error-native +Error: too long at line 3 column 0 From 37b0b70f530d36cdcc3cb0d7d4092b2fb8272457 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:33:02 +0000 Subject: [PATCH 5/8] Cross compiler: keeps DATA and PROC blocks of one line in source order. Blocks were collected from a map ordered by label name, so "jump_lbl_10" came before "jump_lbl_9", and sorted with std::sort, which is not stable. Several DATA arrays in one line could be placed in any order, unlike in the native compiler. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/compiler/parser.h | 13 ++++++++----- testsuite/tests/data-order.bas | 10 ++++++++++ testsuite/tests/data-order.chk | 6 ++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 testsuite/tests/data-order.bas create mode 100644 testsuite/tests/data-order.chk diff --git a/src/compiler/parser.h b/src/compiler/parser.h index ae66604..d3fe1f9 100644 --- a/src/compiler/parser.h +++ b/src/compiler/parser.h @@ -155,6 +155,7 @@ class parse std::set saved_errors; int linenum; std::map> procs; + std::vector proc_order; // procs in order of creation std::vector proc_stack; std::map vars; std::map labels; @@ -584,6 +585,8 @@ class parse void push_proc(std::string l) { proc_stack.push_back(l); + if(!procs.count(l)) + proc_order.push_back(l); code = &procs[l]; } void pop_proc(std::string l) @@ -609,11 +612,11 @@ class parse p.push_back(codew::ctok("TOK_END", 0)); // To emit procs sorted by line number, copy to a vector std::vector *> sprocs; - for(auto &c : procs) - if(!c.first.empty() && c.second.size()) - sprocs.push_back(&c.second); - // Sort by line number - std::sort(std::begin(sprocs), std::end(sprocs), + for(auto &l : proc_order) + if(procs[l].size()) + sprocs.push_back(&procs[l]); + // Sort by line number, keeping the source order of blocks in one line + std::stable_sort(std::begin(sprocs), std::end(sprocs), [](const std::vector *a, const std::vector *b) { return (*a)[0].linenum() < (*b)[0].linenum(); }); // Emit into code diff --git a/testsuite/tests/data-order.bas b/testsuite/tests/data-order.bas new file mode 100644 index 0000000..15d3891 --- /dev/null +++ b/testsuite/tests/data-order.bas @@ -0,0 +1,10 @@ +' Test that DATA arrays in one line are in source order in memory +FOR I=1 TO 2:NEXT I +FOR I=1 TO 2:NEXT I +FOR I=1 TO 2:NEXT I +FOR I=1 TO 2:NEXT I +DATA A() BYTE = 1 : DATA B() BYTE = 2 +? ADR(B) > ADR(A) +DA.C()B.=0:DA.D()B.=1:DA.E()B.=2:DA.F()B.=3:DA.G()B.=4:DA.H()B.=5:DA.I()B.=6:DA.J()B.=7:DA.K()B.=8:DA.L()B.=9:DA.M()B.=10:DA.N()B.=11:DA.O()B.=12:DA.P()B.=13:DA.Q()B.=14:DA.R()B.=15:DA.S()B.=16 +? (ADR(D)>ADR(C))+(ADR(E)>ADR(D))+(ADR(F)>ADR(E))+(ADR(G)>ADR(F))+(ADR(H)>ADR(G))+(ADR(I)>ADR(H))+(ADR(J)>ADR(I))+(ADR(K)>ADR(J)) +? (ADR(L)>ADR(K))+(ADR(M)>ADR(L))+(ADR(N)>ADR(M))+(ADR(O)>ADR(N))+(ADR(P)>ADR(O))+(ADR(Q)>ADR(P))+(ADR(R)>ADR(Q))+(ADR(S)>ADR(R)) diff --git a/testsuite/tests/data-order.chk b/testsuite/tests/data-order.chk new file mode 100644 index 0000000..38be201 --- /dev/null +++ b/testsuite/tests/data-order.chk @@ -0,0 +1,6 @@ +Name: Test order of DATA arrays in one line +Test: run +Output: +1 +8 +8 From c8ded2b879d80d253f83e2f9b64597c7c17bff8a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:33:02 +0000 Subject: [PATCH 6/8] Optimizes "VAR = VAR + 1" to INC VAR. The rule "TOK_VAR / x / TOK_PUSH / TOK_NUM / y / TOK_ADD" matched first, so the existing INC rule never did and "X=X+1" used 5 bytes instead of 2 ("X=X-1" already gave DEC). Saves 3 bytes in fedora.bas and 18 in testloop.bas. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/compiler/peephole.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/compiler/peephole.cc b/src/compiler/peephole.cc index d31794a..b0c12a8 100644 --- a/src/compiler/peephole.cc +++ b/src/compiler/peephole.cc @@ -1113,6 +1113,19 @@ class peephole i--; continue; } + // VAR = VAR + 1 (or 1 + VAR), after the rules above ==> INC VAR + // TOK_NUM / 1 / TOK_ADD_VAR / x / TOK_VAR_STORE / x -> TOK_INCVAR / x + if(mtok(0, "TOK_NUM") && mword(1) && val(1) == 1 && + mtok(2, "TOK_ADD_VAR") && mtok(4, "TOK_VAR_STORE") && varn(3) == varn(5)) + { + set_tok(2, "TOK_INCVAR"); + del(5); + del(4); + del(1); + del(0); + i--; + continue; + } // VAR = VAR + 1 ==> INC VAR // TOK_VAR / x / TOK_PUSH / TOK_NUM / 1 / TOK_ADD / TOK_VAR_STORE / x // -> TOK_INCVAR / x From fa23ab90287dc128d6bb440612bb139454e9c2af Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 20:33:02 +0000 Subject: [PATCH 7/8] Fixes lowercase test in parse::peek(). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- src/compiler/parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/parser.h b/src/compiler/parser.h index d3fe1f9..6117b89 100644 --- a/src/compiler/parser.h +++ b/src/compiler/parser.h @@ -375,7 +375,7 @@ class parse if(pos < str.length()) { char p = str[pos]; - if(p >= 'a' && p >= 'z') + if(p >= 'a' && p <= 'z') p = p - ('a' - 'A'); if(c >= 'a' && c <= 'z') c = c - ('a' - 'A'); From 82aee8dfc46196de0a5e952600c2bfaba92b587a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 05:01:58 +0000 Subject: [PATCH 8/8] Fixes test runner build with newer MinGW, which declares strndup. The Windows fallback was a static "strndup", which conflicts with the declaration in newer MinGW , failing the Windows CI. Use another name for it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NASRhH2Xg6HmeBUZTSPhwM --- testsuite/src/fbtest.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/testsuite/src/fbtest.c b/testsuite/src/fbtest.c index 2e49031..2522b94 100644 --- a/testsuite/src/fbtest.c +++ b/testsuite/src/fbtest.c @@ -29,14 +29,16 @@ #ifdef _WIN32 # define PATH_SEP "\\" -// Implement missing "strndup" -static char *strndup(const char *s, size_t n) +// Implement "strndup", missing in older MinGW versions. Use another name, as +// newer MinGW versions declare it in . +static char *fbtest_strndup(const char *s, size_t n) { char *ret = malloc(n + 1); strncpy(ret, s, n); ret[n] = 0; return ret; -}; +} +# define strndup fbtest_strndup #else # define PATH_SEP "/" #endif