Skip to content

Commit 26004d4

Browse files
committed
thoughts on xc style functions and subs using existing functionality
1 parent 44fbeb4 commit 26004d4

3 files changed

Lines changed: 321 additions & 1 deletion

File tree

basic.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2709,6 +2709,15 @@ static void load_program(const char *path)
27092709
}
27102710
add_or_replace_line(number, p);
27112711
} else {
2712+
/* Numberless mode: reject later lines that suddenly introduce
2713+
* explicit line numbers. Mixed numbered/numberless programs are
2714+
* currently not supported because they make control flow and
2715+
* editing semantics ambiguous.
2716+
*/
2717+
if (isdigit((unsigned char)*p)) {
2718+
fprintf(stderr, "Mixed numbered and numberless lines are not supported: %s\n", linebuf);
2719+
exit(1);
2720+
}
27122721
/* Numberless mode: keep the whole trimmed line as text and
27132722
* assign synthetic line numbers that preserve order. */
27142723
number = auto_line_no;

docs/overview.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,4 +332,5 @@ if (c == 'T' && starts_with_kw(*p, "TEXTAT")) {
332332
- **Match visible behavior, not just theory**:
333333
- For PETSCII/ANSI and `PRINT` semantics, compare against an actual C64 emulator or the original `chr.bas`/`adventure.bas` programs whenever in doubt.
334334

335-
This overview should give you enough context to confidently navigate and extend `basic.c`. When in doubt, follow the patterns used by existing statements like `PRINT`, `INPUT`, `SLEEP`, and `GET`—they are intentionally kept as small, readable reference implementations.*** End Patch`}}} -->
335+
This overview should give you enough context to confidently navigate and extend `basic.c`. When in doubt, follow the patterns used by existing statements like `PRINT`, `INPUT`, `SLEEP`, and `GET`—they are intentionally kept as small, readable reference implementations.
336+

docs/xc-basic-compat-notes.md

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
## XC=BASIC‑style functions & subroutines – design notes
2+
3+
This document captures design ideas for adding XC=BASIC 3–style `FUNCTION` and `SUB` syntax on top of the existing CBM‑style line interpreter, using **syntactic sugar** and incremental changes rather than a full compiler rewrite.
4+
5+
This is a **future‑work** sketch, not an implemented feature.
6+
7+
---
8+
9+
### 1. Goals and non‑goals
10+
11+
**Goals:**
12+
13+
- Allow users to write code that *resembles* XC=BASIC 3:
14+
- `FUNCTION name(...) ... END FUNCTION`
15+
- `SUB name(...) ... END SUB`
16+
- `CALL name(args...)`
17+
- Preserve the existing interpreter architecture:
18+
- Line‑by‑line execution.
19+
- `GOSUB`/`RETURN` with a simple stack.
20+
- Global variables by default.
21+
- Make the first step **pure syntactic sugar**:
22+
- No new type system.
23+
- No genuine local variables yet.
24+
- No overloading, visibility (`PRIVATE`/`SHARED`), or modules.
25+
26+
**Non‑goals (for the first iterations):**
27+
28+
- Implementing the full XC=BASIC function model (typed arguments, static vs dynamic frames, recursion analysis, etc.).
29+
- Changing the core execution model to a block‑structured compiler with separate code generation.
30+
31+
---
32+
33+
### 2. Current building blocks we can reuse
34+
35+
We already have:
36+
37+
- `GOSUB` / `RETURN` with a fixed‑depth stack (`gosub_stack`).
38+
- `DEF FN` user functions (`DEF FNY(X)=...`) stored in `user_funcs` and invoked from expressions.
39+
- Labels (`LABEL:` at start of line) resolved at load time by `build_label_table()`.
40+
- A robust line loader + execution loop.
41+
42+
These are enough to **lower** higher‑level constructs into the existing primitives.
43+
44+
---
45+
46+
### 3. Phase 1: SUB / CALL as sugar for GOSUB
47+
48+
#### 3.1 Syntax subset
49+
50+
Support a minimal subset:
51+
52+
```basic
53+
SUB DrawStatus()
54+
PRINT "STATUS LINE"
55+
END SUB
56+
57+
CALL DrawStatus()
58+
```
59+
60+
Constraints for phase 1:
61+
62+
- No arguments (or arguments only as sugar that writes globals).
63+
- No local variables; everything is global, as in traditional CBM BASIC.
64+
- No overloading, visibility, or forward declarations.
65+
66+
#### 3.2 Lowering strategy
67+
68+
At load time, treat `SUB`/`END SUB` as **labels** plus some book‑keeping.
69+
70+
- Detect `SUB name` lines during loading:
71+
- Register a label called `SUB:name` pointing to the line *after* the `SUB` header.
72+
- Optionally record metadata about the block (`sub_table`) for debugging.
73+
- Strip the `SUB` header text and replace it with a comment/empty line so it does not execute.
74+
75+
- Detect `END SUB` lines:
76+
- Replace with a simple `RETURN` statement so that the existing `gosub_stack` unwinds correctly.
77+
78+
- Detect `CALL name` statements in `execute_statement`:
79+
80+
```c
81+
if (c == 'C' && starts_with_kw(*p, "CALL")) {
82+
*p += 4;
83+
statement_call(p);
84+
return;
85+
}
86+
```
87+
88+
And in `statement_call`:
89+
90+
```c
91+
static void statement_call(char **p)
92+
{
93+
char namebuf[32];
94+
int len = 0;
95+
int target_index;
96+
97+
skip_spaces(p);
98+
/* Read identifier up to '(' or whitespace */
99+
while ((isalpha((unsigned char)**p) || isdigit((unsigned char)**p) || **p == '$') &&
100+
len < (int)sizeof(namebuf) - 1) {
101+
namebuf[len++] = **p;
102+
(*p)++;
103+
}
104+
namebuf[len] = '\0';
105+
106+
/* For now, ignore argument list if present: CALL Name() */
107+
skip_spaces(p);
108+
if (**p == '(') {
109+
/* Skip balanced parentheses; arguments handled in later phases */
110+
int depth = 1;
111+
(*p)++;
112+
while (**p && depth > 0) {
113+
if (**p == '(') depth++;
114+
else if (**p == ')') depth--;
115+
(*p)++;
116+
}
117+
}
118+
119+
/* Resolve to a label like "SUB:Name" to avoid conflicts */
120+
target_index = find_label_line(namebuf /* or "SUB:Name" */);
121+
if (target_index < 0) {
122+
runtime_error("Unknown subroutine in CALL");
123+
return;
124+
}
125+
126+
/* Defer to existing GOSUB machinery */
127+
/* Equivalent to: GOSUB label */
128+
if (gosub_top >= MAX_GOSUB) { runtime_error("GOSUB stack overflow"); return; }
129+
gosub_stack[gosub_top].line_index = current_line;
130+
gosub_stack[gosub_top].position = *p;
131+
gosub_top++;
132+
133+
current_line = target_index;
134+
statement_pos = NULL;
135+
}
136+
```
137+
138+
Effectively, `CALL name()` becomes a type‑checked, label‑based `GOSUB` using syntax that looks like XC=BASIC’s `CALL`.
139+
140+
#### 3.3 Testing strategy
141+
142+
Create small tests under `tests/`:
143+
144+
```basic
145+
REM tests/sub_call_smoke.bas
146+
SUB HELLO()
147+
PRINT "HELLO";
148+
END SUB
149+
150+
PRINT "A";
151+
CALL HELLO()
152+
PRINT "B"
153+
END
154+
```
155+
156+
Expect output: `AHELLOB` (plus final newline).
157+
Add variants:
158+
159+
- Nested CALLs.
160+
- CALLs from inside `FOR`/`IF`.
161+
162+
Use plain runs and `hexdump -C` to verify no extra newlines or stack mis‑handling.
163+
164+
---
165+
166+
### 4. Phase 1.5: FUNCTION / END FUNCTION as sugar for DEF FN
167+
168+
Target syntax (restricted):
169+
170+
```basic
171+
FUNCTION DoubleX(X)
172+
RETURN X*2
173+
END FUNCTION
174+
175+
PRINT DoubleX(5)
176+
END
177+
```
178+
179+
Constraints for the first cut:
180+
181+
- Single expression‑return only (one `RETURN expr` as the last statement).
182+
- No local variables beyond the parameter.
183+
- No early `EXIT FUNCTION` yet.
184+
185+
#### 4.1 Lowering strategy
186+
187+
At load time, transform `FUNCTION` blocks into `DEF FN` definitions:
188+
189+
- Detect:
190+
191+
```basic
192+
FUNCTION Name(arg1, arg2, ...)
193+
RETURN <expr>
194+
END FUNCTION
195+
```
196+
197+
- Concatenate the `RETURN` expression into a single line:
198+
199+
```basic
200+
DEF FNName(arg1, arg2, ...) = <expr>
201+
```
202+
203+
- Feed this synthetic line into the existing `statement_def` logic:
204+
- This populates `user_funcs` with `Name` as a DEF FN‑style function.
205+
206+
This lets existing `eval_factor` / `user_func_lookup` machinery handle calls like `Name(5)`.
207+
208+
#### 4.2 Parser changes
209+
210+
`execute_statement` would gain a branch:
211+
212+
```c
213+
if (c == 'F' && starts_with_kw(*p, "FUNCTION")) {
214+
*p += 8;
215+
statement_function(p);
216+
return;
217+
}
218+
```
219+
220+
`statement_function` would:
221+
222+
- Parse the function header: name + parameter list.
223+
- Collect lines until `END FUNCTION`, buffering the body.
224+
- For the initial subset:
225+
- Require that the body reduces to a simple `RETURN <expr>`.
226+
- Synthesize a `DEF FN` line and call `statement_def` on that synthetic text.
227+
228+
Later phases could extend this to allow multiple statements, early `RETURN`s, and local variables by interpreting the block directly rather than lowering to `DEF FN`.
229+
230+
#### 4.3 Testing strategy
231+
232+
Add tests such as:
233+
234+
```basic
235+
REM tests/function_sugar.bas
236+
FUNCTION DOUBLE(X)
237+
RETURN X*2
238+
END FUNCTION
239+
240+
PRINT DOUBLE(3)
241+
END
242+
```
243+
244+
Expected output: `6` (with newline).
245+
Also test:
246+
247+
- Multiple functions in one file.
248+
- Mixed `DEF FN` and `FUNCTION` definitions.
249+
250+
---
251+
252+
### 5. Phase 2: true locals and arguments (optional)
253+
254+
Once the sugar paths are stable and well‑tested, we can incrementally move toward **real** XC=BASIC semantics:
255+
256+
- Introduce a per‑call frame structure holding:
257+
- A pointer to the previous frame.
258+
- Argument values.
259+
- Local variables.
260+
- When entering a `SUB`/`FUNCTION`:
261+
- Allocate a frame, bind arguments, redirect variable lookup to prefer locals.
262+
- When exiting:
263+
- Pop the frame and restore the previous context.
264+
265+
This would enable:
266+
267+
- Proper local variables.
268+
- Recursive SUBs/FUNCTIONs (if we allow it).
269+
- Closer parity with XC=BASIC’s `SUB`/`FUNCTION` semantics without abandoning the interpreter approach.
270+
271+
This is deliberately sketched only at a high level; we should not attempt it until Phase 1 is battle‑tested.
272+
273+
---
274+
275+
### 6. Regression testing strategy
276+
277+
To keep these features correct over time:
278+
279+
1. **Unit‑style BASIC programs under `tests/`**
280+
- `tests/sub_call_smoke.bas` – simple SUB/CALL behavior.
281+
- `tests/function_sugar.bas` – FUNCTION / RETURN sugar over DEF FN.
282+
- `tests/sub_nesting.bas` – nested CALLs and CALL within loops.
283+
- `tests/function_errors.bas` – malformed FUNCTION blocks that should produce clear errors.
284+
285+
2. **Hex‑level verification when control codes are involved**
286+
- For features touching cursor movement or colors, pipe through `hexdump -C`:
287+
288+
```bash
289+
./basic -petscii -palette c64 tests/sub_call_smoke.bas | hexdump -C
290+
```
291+
292+
3. **CI integration**
293+
- Extend the GitHub Actions workflow to run:
294+
295+
```bash
296+
./basic tests/def_fn.bas
297+
./basic tests/sub_call_smoke.bas
298+
./basic tests/function_sugar.bas
299+
```
300+
301+
- Optionally compare output to golden files using a small shell or Python harness.
302+
303+
4. **Real‑world compatibility tests**
304+
- Port a small XC=BASIC sample that uses only the supported subset:
305+
- A few `SUB`s called with `CALL`.
306+
- A `FUNCTION` or two that can be expressed as a single expression.
307+
- Keep it under `examples/xc-basic-demo.bas` and run it in CI as an integration test.
308+
309+
With this staged, sugar‑based approach and a solid test harness, we can move toward XC=BASIC‑style syntax confidently, without destabilizing the existing CBM BASIC interpreter.
310+

0 commit comments

Comments
 (0)