Phase 0: Referenzdokumente, Fehlerkatalog, Testkorpus, Ratatui-Spike
Entscheidungen festgehalten: TBVM bestaetigt und eingebettet in die Executables; durchgaengig UTF-8 statt CP437 (dokumentierte Abweichung). - docs/: Sprachreferenz, Forms-Referenz, Dateiformate, TBVM-Design - tb-runtime::errors: klassischer Laufzeitfehler-Katalog (implementiert) - tb-ui::screen: 80x25-Unicode-Zellenpuffer mit 16-Farben-Abbildung, Scrollbereich (VIEW PRINT), Letterboxing; Ratatui-Widget + Tests - Spike: cargo run -p tb-ui --example spike (Farben, Unicode, Tasten, Maus) - tests/compat/: erste Referenzprogramme mit byte-genauer Sollausgabe Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Erwartete Ausgaben sind byte-genau (LF, signifikante Leerzeichen am Zeilenende)
|
||||
tests/compat/*.out -text
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -495,6 +495,7 @@ dependencies = [
|
||||
name = "tb-ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossterm",
|
||||
"log",
|
||||
"ratatui",
|
||||
|
||||
59
PLAN.md
59
PLAN.md
@@ -23,17 +23,23 @@ Konvention: `[ ]` offen · `[x]` erledigt · `[~]` in Arbeit
|
||||
## Phase 0 — Exploration und Grundsatzentscheidungen
|
||||
|
||||
### 0.1 Sprach- und Bibliotheksreferenz rekonstruieren
|
||||
- [ ] Sprachreferenz zusammentragen: Anweisungen, Funktionen, Operatoren,
|
||||
- [~] Sprachreferenz zusammentragen: Anweisungen, Funktionen, Operatoren,
|
||||
Typsystem (INTEGER, LONG, SINGLE, DOUBLE, STRING, CURRENCY,
|
||||
feste Strings, `TYPE…END TYPE`), Typ-Suffixe (`% & ! # $ @`),
|
||||
`DEFINT`-Familie, Sichtbarkeitsregeln (`SHARED`, `COMMON`, `STATIC`)
|
||||
- [ ] Forms-/Steuerelemente-Referenz: Steuerelementtypen, Eigenschaften,
|
||||
→ Grundgerüst in [docs/sprachreferenz.md](docs/sprachreferenz.md);
|
||||
Detailfragen (exaktes Verhalten) werden dort als TODO markiert
|
||||
- [~] Forms-/Steuerelemente-Referenz: Steuerelementtypen, Eigenschaften,
|
||||
Methoden, Ereignisse, Standardwerte, Fokus- und Z-Reihenfolge
|
||||
- [ ] Dateiformate dokumentieren: Quelltext (`.BAS`), Formulare (`.FRM`,
|
||||
→ Grundgerüst in [docs/forms-referenz.md](docs/forms-referenz.md)
|
||||
- [~] Dateiformate dokumentieren: Quelltext (`.BAS`), Formulare (`.FRM`,
|
||||
Textformat mit `VERSION`/`Begin…End`-Blöcken), Projekt (`.MAK`)
|
||||
- [ ] Laufzeitfehler-Katalog (Codes + Meldungstexte) erfassen
|
||||
- [ ] Testkorpus aufbauen: kleine Referenzprogramme, deren erwartete Ausgabe
|
||||
dokumentiert ist (Grundlage der Kompatibilitäts-Testsuite)
|
||||
→ [docs/dateiformate.md](docs/dateiformate.md)
|
||||
- [x] Laufzeitfehler-Katalog (Codes + Meldungstexte) erfasst und als
|
||||
`tb_runtime::errors` implementiert (inkl. Tests)
|
||||
- [~] Testkorpus aufbauen: kleine Referenzprogramme, deren erwartete Ausgabe
|
||||
dokumentiert ist → erste Programme in `tests/compat/`, Harness folgt
|
||||
mit Phase 2 (`tbdosc run` + Ausgabevergleich)
|
||||
|
||||
### 0.2 VM-/Runtime-Entscheidung (Aufstellung der Optionen)
|
||||
|
||||
@@ -53,7 +59,10 @@ BASIC-Semantik, serialisierbares Kompilat, gute Fehlerortung (Zeile/Spalte).
|
||||
| **6. Transpilation auf fremde Skript-VM (Lua via mlua/piccolo)** | Ausgereifte VM mit GC geschenkt | Semantik-Mismatch (Zahlentypen, Fehler-/Eventmodell, 1-basierte vs. BASIC-Arrays mit `OPTION BASE`); Fehlerortung und Debugger bilden schlecht ab | Ungeeignet |
|
||||
| **7. Transpilation nach Rust (AOT)** | Native Binaries, keine VM | Kein Interpretermodus → IDE-Kernfeatures (Direktfenster, Start ohne Build-Wartezeit, Debugger) praktisch unmöglich; Rust-Toolchain als Laufzeitvoraussetzung | Ungeeignet als Primärziel |
|
||||
|
||||
**Entscheidung (2026-09-01):** Eigene Stack-basierte Bytecode-VM (**Option 1, TBVM**).
|
||||
**Entscheidung (2026-09-01, bestätigt):** Eigene Stack-basierte Bytecode-VM
|
||||
(**Option 1, TBVM**), sowohl in der Entwicklungsphase als auch **eingebettet in
|
||||
die Executables** (`tbdos`-IDE und von `tbdosc` erzeugte Programme), damit der
|
||||
schnelle Edit-Run-Turnaround überall identisch ist.
|
||||
Die IR/Bytecode-Schicht wird sauber vom Interpreter getrennt, sodass später ein
|
||||
zweites Backend (WASM via Cranelift, Option 3) ergänzt werden kann, ohne das
|
||||
Frontend anzufassen. Für die Ausführungsgeschwindigkeit typischer
|
||||
@@ -61,19 +70,28 @@ Terminal-Programme ist ein Interpreter mehr als ausreichend; entscheidend sind
|
||||
Debugger-Fähigkeit, exakte Fehlersemantik und der schnelle Edit-Run-Zyklus der IDE.
|
||||
|
||||
- [x] Optionen aufstellen und bewerten (siehe Tabelle)
|
||||
- [ ] Bytecode-Grobdesign: Stack-Maschine, Konstantenpool, Zeilentabelle
|
||||
- [~] Bytecode-Grobdesign: Stack-Maschine, Konstantenpool, Zeilentabelle
|
||||
für Diagnostik/Debugger, Aufrufkonventionen (SUB/FUNCTION vs. GOSUB)
|
||||
- [ ] Speichermodell für Werte: Tagged Enum vs. NaN-Boxing; Refcounting
|
||||
→ Entwurf in [docs/tbvm-design.md](docs/tbvm-design.md)
|
||||
- [~] Speichermodell für Werte: Tagged Enum (kein NaN-Boxing); Refcounting
|
||||
für Strings/Arrays (kein Zyklenrisiko im Dialekt → Rc genügt)
|
||||
→ Entwurf in [docs/tbvm-design.md](docs/tbvm-design.md)
|
||||
|
||||
### 0.3 Ratatui-/Terminal-Spike
|
||||
- [ ] 80×25-Zellenpuffer (Zeichen + Farbattribut) als eigenes Widget rendern;
|
||||
Verhalten bei kleineren/größeren Terminals festlegen (Letterboxing?)
|
||||
- [ ] Codepage-437-Abbildung auf Unicode (Rahmenzeichen, Symbole) prüfen —
|
||||
Darstellungstest unter Windows Terminal, gnome-terminal, iTerm2
|
||||
- [ ] 16-Farben-Palette und Blink/Intensity-Attribut abbilden
|
||||
- [ ] Maus-Ereignisse (crossterm) und Sondertasten (F1–F12, Alt-Kombis) testen;
|
||||
bekannte Terminal-Einschränkungen dokumentieren
|
||||
|
||||
**Entscheidung (2026-09-01):** Keine CP437-Emulation — der Textbildschirm ist
|
||||
durchgängig **UTF-8/Unicode**. Strings sind Unicode-Text, `CHR$`/`ASC`
|
||||
arbeiten auf Codepoints. Das ist eine bewusste Abweichung vom Vorbild und wird
|
||||
in der Sprachreferenz unter „Abweichungen" dokumentiert.
|
||||
|
||||
- [x] 80×25-Zellenpuffer (Zeichen + Farbattribut) als eigenes Widget rendern;
|
||||
kleinere Terminals: Hinweis „Terminal zu klein", größere: zentriert
|
||||
(Letterboxing) → `tb-ui::screen`, Demo: `cargo run -p tb-ui --example spike`
|
||||
- [x] 16-Farben-Palette (Vordergrund 0–15, Hintergrund 0–7) auf
|
||||
ANSI-Indexfarben abgebildet; Blink-Attribut vorerst ignoriert (offen)
|
||||
- [~] Maus-Ereignisse (crossterm) und Sondertasten (F1–F12, Alt-Kombis) im
|
||||
Spike sichtbar gemacht; systematischer Test unter Windows Terminal,
|
||||
gnome-terminal, iTerm2 steht aus
|
||||
- [ ] Ereignisschleifen-Architektur: Terminal-Events → Event-Queue → VM-Ticks
|
||||
|
||||
## Phase 1 — Sprach-Frontend (`tb-frontend`)
|
||||
@@ -155,5 +173,12 @@ Debugger-Fähigkeit, exakte Fehlersemantik und der schnelle Edit-Run-Zyklus der
|
||||
- ISAM-Dateiunterstützung des Vorbilds: nachbilden oder als Nicht-Ziel erklären?
|
||||
- `PEEK`/`POKE`/`CALL INTERRUPT` u. ä. Hardware-Nähe: sinnvolle Teilmenge
|
||||
emulieren (z. B. Bildschirmspeicher B800) oder Laufzeitfehler?
|
||||
- Verhalten bei Terminals < 80×25: Mindestgröße erzwingen oder scrollen?
|
||||
- Unicode-Zeichen mit Darstellungsbreite ≠ 1 (Emoji, CJK): das Zellenmodell
|
||||
ist strikt 1 Zeichen = 1 Zelle. Breite Zeichen ablehnen (Fehler 5),
|
||||
ersetzen oder 2 Zellen belegen?
|
||||
- `GET`/`PUT` mit Record-Typen unter UTF-8: feste Strings (`STRING * n`)
|
||||
waren im Vorbild n Bytes — bei uns n Zeichen. Binärlayout der Records
|
||||
festlegen (Vorschlag: UTF-8 mit Padding auf feste Bytelänge? Oder UTF-32?)
|
||||
- Blink-Attribut (`COLOR` mit Vordergrund 16–31): ignorieren, als „hell"
|
||||
darstellen oder echtes Terminal-Blink nutzen (nicht überall unterstützt)?
|
||||
- Lizenzwahl (MIT im Rahmen angenommen — bestätigen)
|
||||
|
||||
@@ -16,7 +16,8 @@ mit [Ratatui](https://ratatui.rs) als Terminal-UI-Schicht.
|
||||
- **Volle Library-Kompatibilität für die DOS-Oberflächenprogrammierung**:
|
||||
die ereignisgesteuerte Forms-Engine mit allen Steuerelementen, Menüs,
|
||||
Maus- und Tastaturereignissen, nachgebildet auf einem emulierten
|
||||
80×25-Textbildschirm (Codepage 437, 16 Farben) über Ratatui.
|
||||
80×25-Textbildschirm (16 Farben) über Ratatui. Bewusste Modernisierung:
|
||||
durchgängig UTF-8/Unicode statt Codepage 437.
|
||||
- **Plattformübergreifend**: läuft in Terminals unter Linux, macOS und Windows.
|
||||
- **Compiler + VM**: der Compiler erzeugt Bytecode für eine eigene virtuelle
|
||||
Maschine (TBVM); die Abwägung der Runtime-Optionen ist in
|
||||
|
||||
@@ -1,4 +1,161 @@
|
||||
//! Laufzeitfehler: Codes und Meldungstexte kompatibel zum Vorbild
|
||||
//! (z. B. 6 = Overflow, 9 = Subscript out of range, 53 = File not found).
|
||||
//! Laufzeitfehler: Codes und Meldungstexte kompatibel zum Vorbild.
|
||||
//!
|
||||
//! Die Codes und (englischen) Meldungstexte entsprechen dem klassischen
|
||||
//! DOS-BASIC-Katalog; Programme prüfen `ERR` gegen diese Nummern, daher
|
||||
//! sind sie Teil des Kompatibilitätsvertrags. Nicht belegte Nummern
|
||||
//! liefern "Unprintable error" (wie im Vorbild bei `ERROR n`).
|
||||
|
||||
// Platzhalter — wird in Phase 3 ausgearbeitet (siehe PLAN.md)
|
||||
/// Ein Laufzeitfehler des Dialekts, identifiziert durch seinen klassischen Code.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RuntimeError(pub u8);
|
||||
|
||||
impl RuntimeError {
|
||||
pub const NEXT_WITHOUT_FOR: Self = Self(1);
|
||||
pub const SYNTAX_ERROR: Self = Self(2);
|
||||
pub const RETURN_WITHOUT_GOSUB: Self = Self(3);
|
||||
pub const OUT_OF_DATA: Self = Self(4);
|
||||
pub const ILLEGAL_FUNCTION_CALL: Self = Self(5);
|
||||
pub const OVERFLOW: Self = Self(6);
|
||||
pub const OUT_OF_MEMORY: Self = Self(7);
|
||||
pub const LABEL_NOT_DEFINED: Self = Self(8);
|
||||
pub const SUBSCRIPT_OUT_OF_RANGE: Self = Self(9);
|
||||
pub const DUPLICATE_DEFINITION: Self = Self(10);
|
||||
pub const DIVISION_BY_ZERO: Self = Self(11);
|
||||
pub const ILLEGAL_IN_DIRECT_MODE: Self = Self(12);
|
||||
pub const TYPE_MISMATCH: Self = Self(13);
|
||||
pub const OUT_OF_STRING_SPACE: Self = Self(14);
|
||||
pub const STRING_TOO_COMPLEX: Self = Self(16);
|
||||
pub const CANNOT_CONTINUE: Self = Self(17);
|
||||
pub const FUNCTION_NOT_DEFINED: Self = Self(18);
|
||||
pub const NO_RESUME: Self = Self(19);
|
||||
pub const RESUME_WITHOUT_ERROR: Self = Self(20);
|
||||
pub const DEVICE_TIMEOUT: Self = Self(24);
|
||||
pub const DEVICE_FAULT: Self = Self(25);
|
||||
pub const FOR_WITHOUT_NEXT: Self = Self(26);
|
||||
pub const OUT_OF_PAPER: Self = Self(27);
|
||||
pub const WHILE_WITHOUT_WEND: Self = Self(29);
|
||||
pub const WEND_WITHOUT_WHILE: Self = Self(30);
|
||||
pub const DUPLICATE_LABEL: Self = Self(33);
|
||||
pub const SUBPROGRAM_NOT_DEFINED: Self = Self(35);
|
||||
pub const ARGUMENT_COUNT_MISMATCH: Self = Self(37);
|
||||
pub const ARRAY_NOT_DEFINED: Self = Self(38);
|
||||
pub const VARIABLE_REQUIRED: Self = Self(40);
|
||||
pub const FIELD_OVERFLOW: Self = Self(50);
|
||||
pub const INTERNAL_ERROR: Self = Self(51);
|
||||
pub const BAD_FILE_NAME_OR_NUMBER: Self = Self(52);
|
||||
pub const FILE_NOT_FOUND: Self = Self(53);
|
||||
pub const BAD_FILE_MODE: Self = Self(54);
|
||||
pub const FILE_ALREADY_OPEN: Self = Self(55);
|
||||
pub const FIELD_STATEMENT_ACTIVE: Self = Self(56);
|
||||
pub const DEVICE_IO_ERROR: Self = Self(57);
|
||||
pub const FILE_ALREADY_EXISTS: Self = Self(58);
|
||||
pub const BAD_RECORD_LENGTH: Self = Self(59);
|
||||
pub const DISK_FULL: Self = Self(61);
|
||||
pub const INPUT_PAST_END_OF_FILE: Self = Self(62);
|
||||
pub const BAD_RECORD_NUMBER: Self = Self(63);
|
||||
pub const BAD_FILE_NAME: Self = Self(64);
|
||||
pub const TOO_MANY_FILES: Self = Self(67);
|
||||
pub const DEVICE_UNAVAILABLE: Self = Self(68);
|
||||
pub const COMM_BUFFER_OVERFLOW: Self = Self(69);
|
||||
pub const PERMISSION_DENIED: Self = Self(70);
|
||||
pub const DISK_NOT_READY: Self = Self(71);
|
||||
pub const DISK_MEDIA_ERROR: Self = Self(72);
|
||||
pub const FEATURE_UNAVAILABLE: Self = Self(73);
|
||||
pub const RENAME_ACROSS_DISKS: Self = Self(74);
|
||||
pub const PATH_FILE_ACCESS_ERROR: Self = Self(75);
|
||||
pub const PATH_NOT_FOUND: Self = Self(76);
|
||||
|
||||
/// Klassischer Fehlercode für `ERR`.
|
||||
pub fn code(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Meldungstext des Vorbilds; unbelegte Codes: "Unprintable error".
|
||||
pub fn message(self) -> &'static str {
|
||||
match self.0 {
|
||||
1 => "NEXT without FOR",
|
||||
2 => "Syntax error",
|
||||
3 => "RETURN without GOSUB",
|
||||
4 => "Out of DATA",
|
||||
5 => "Illegal function call",
|
||||
6 => "Overflow",
|
||||
7 => "Out of memory",
|
||||
8 => "Label not defined",
|
||||
9 => "Subscript out of range",
|
||||
10 => "Duplicate definition",
|
||||
11 => "Division by zero",
|
||||
12 => "Illegal in direct mode",
|
||||
13 => "Type mismatch",
|
||||
14 => "Out of string space",
|
||||
16 => "String formula too complex",
|
||||
17 => "Cannot continue",
|
||||
18 => "Function not defined",
|
||||
19 => "No RESUME",
|
||||
20 => "RESUME without error",
|
||||
24 => "Device timeout",
|
||||
25 => "Device fault",
|
||||
26 => "FOR without NEXT",
|
||||
27 => "Out of paper",
|
||||
29 => "WHILE without WEND",
|
||||
30 => "WEND without WHILE",
|
||||
33 => "Duplicate label",
|
||||
35 => "Subprogram not defined",
|
||||
37 => "Argument-count mismatch",
|
||||
38 => "Array not defined",
|
||||
40 => "Variable required",
|
||||
50 => "FIELD overflow",
|
||||
51 => "Internal error",
|
||||
52 => "Bad file name or number",
|
||||
53 => "File not found",
|
||||
54 => "Bad file mode",
|
||||
55 => "File already open",
|
||||
56 => "FIELD statement active",
|
||||
57 => "Device I/O error",
|
||||
58 => "File already exists",
|
||||
59 => "Bad record length",
|
||||
61 => "Disk full",
|
||||
62 => "Input past end of file",
|
||||
63 => "Bad record number",
|
||||
64 => "Bad file name",
|
||||
67 => "Too many files",
|
||||
68 => "Device unavailable",
|
||||
69 => "Communication-buffer overflow",
|
||||
70 => "Permission denied",
|
||||
71 => "Disk not ready",
|
||||
72 => "Disk-media error",
|
||||
73 => "Feature unavailable",
|
||||
74 => "Rename across disks",
|
||||
75 => "Path/File access error",
|
||||
76 => "Path not found",
|
||||
_ => "Unprintable error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RuntimeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RuntimeError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bekannte_codes() {
|
||||
assert_eq!(RuntimeError::OVERFLOW.code(), 6);
|
||||
assert_eq!(RuntimeError::OVERFLOW.message(), "Overflow");
|
||||
assert_eq!(RuntimeError::SUBSCRIPT_OUT_OF_RANGE.code(), 9);
|
||||
assert_eq!(RuntimeError::FILE_NOT_FOUND.message(), "File not found");
|
||||
assert_eq!(RuntimeError::FEATURE_UNAVAILABLE.code(), 73);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbelegte_codes_sind_unprintable() {
|
||||
assert_eq!(RuntimeError(15).message(), "Unprintable error");
|
||||
assert_eq!(RuntimeError(200).message(), "Unprintable error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,6 @@ ratatui.workspace = true
|
||||
crossterm.workspace = true
|
||||
thiserror.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow.workspace = true
|
||||
|
||||
90
crates/tb-ui/examples/spike.rs
Normal file
90
crates/tb-ui/examples/spike.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! Phase-0-Spike: 80×25-Bildschirmpuffer, 16-Farben-Palette, Unicode,
|
||||
//! Tastatur- und Mausereignisse.
|
||||
//!
|
||||
//! Start: `cargo run -p tb-ui --example spike` — Beenden mit Esc.
|
||||
//! Getippte Zeichen erscheinen im Eingabebereich; Sondertasten und
|
||||
//! Mausereignisse werden in der Statuszeile angezeigt.
|
||||
|
||||
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind};
|
||||
use crossterm::execute;
|
||||
use std::io::stdout;
|
||||
use std::time::Duration;
|
||||
use tb_ui::screen::TextScreen;
|
||||
|
||||
fn testbild(s: &mut TextScreen) {
|
||||
s.set_color(15, 1);
|
||||
s.print(&format!("{:^80}", "Terminal Basic — Phase-0-Spike (Esc beendet)"));
|
||||
|
||||
// Farbraster: alle Vordergrundfarben auf allen Hintergründen
|
||||
for bg in 0u8..8 {
|
||||
s.set_color(15, 0);
|
||||
s.locate(3 + bg as usize, 3).unwrap();
|
||||
s.print(&format!("HG {bg} "));
|
||||
for fg in 0u8..16 {
|
||||
s.set_color(fg, bg);
|
||||
s.print(&format!(" {fg:2}"));
|
||||
}
|
||||
}
|
||||
|
||||
// Unicode statt CP437: Rahmen, Umlaute, Symbole
|
||||
s.set_color(14, 0);
|
||||
s.locate(12, 3).unwrap();
|
||||
s.print("┌─ Unicode ────────────────────────────┐");
|
||||
s.locate(13, 3).unwrap();
|
||||
s.print("│ Äpfel, Öl, Übermaß, ß — ☃ ♥ ♦ ♣ ♠ π λ │");
|
||||
s.locate(14, 3).unwrap();
|
||||
s.print("└──────────────────────────────────────┘");
|
||||
|
||||
s.set_color(7, 0);
|
||||
s.locate(16, 3).unwrap();
|
||||
s.print("Eingabe (Zeichen erscheinen hier):");
|
||||
s.locate(17, 3).unwrap();
|
||||
}
|
||||
|
||||
fn status(s: &mut TextScreen, text: &str) {
|
||||
let (r, c) = (s.csrlin(), s.pos());
|
||||
s.set_color(0, 7);
|
||||
s.locate(25, 1).unwrap();
|
||||
s.print(&format!("{text:<80.80}"));
|
||||
s.set_color(7, 0);
|
||||
s.locate(r, c).unwrap();
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let mut terminal = ratatui::init();
|
||||
execute!(stdout(), EnableMouseCapture)?;
|
||||
|
||||
let mut screen = TextScreen::new();
|
||||
testbild(&mut screen);
|
||||
status(&mut screen, "Bereit. Tasten/Maus testen, Esc beendet.");
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| f.render_widget(&screen, f.area()))?;
|
||||
if !event::poll(Duration::from_millis(100))? {
|
||||
continue;
|
||||
}
|
||||
match event::read()? {
|
||||
Event::Key(k) if k.kind != KeyEventKind::Release => match k.code {
|
||||
KeyCode::Esc => break,
|
||||
KeyCode::Char(c) => {
|
||||
screen.print(&c.to_string());
|
||||
status(&mut screen, &format!("Taste: {:?} Modifier: {:?}", k.code, k.modifiers));
|
||||
}
|
||||
KeyCode::Enter => screen.print("\n"),
|
||||
other => status(&mut screen, &format!("Sondertaste: {other:?} Modifier: {:?}", k.modifiers)),
|
||||
},
|
||||
Event::Mouse(m) => {
|
||||
status(
|
||||
&mut screen,
|
||||
&format!("Maus: {:?} bei Spalte {}, Zeile {}", m.kind, m.column + 1, m.row + 1),
|
||||
);
|
||||
}
|
||||
Event::Resize(w, h) => status(&mut screen, &format!("Resize: {w}x{h}")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
execute!(stdout(), DisableMouseCapture)?;
|
||||
ratatui::restore();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Bildschirm-Runtime und Forms-Engine von Terminal Basic, aufbauend auf Ratatui.
|
||||
//!
|
||||
//! Zwei Schichten:
|
||||
//! 1. `screen`: Emulation des 80×25-Textbildschirms (Codepage 437, 16 Farben,
|
||||
//! 1. `screen`: Emulation des 80×25-Textbildschirms (Unicode/UTF-8, 16 Farben,
|
||||
//! Cursor) als Zeichenpuffer, gerendert über Ratatui. Darauf setzen die
|
||||
//! klassischen Anweisungen `PRINT`, `LOCATE`, `COLOR`, `CLS`, `INPUT` auf.
|
||||
//! 2. `forms`: ereignisgesteuerte Forms-Engine — Fenster, Steuerelemente
|
||||
|
||||
@@ -1,4 +1,284 @@
|
||||
//! Textbildschirm-Emulation: Zellenpuffer (Zeichen + Attribut), Cursor,
|
||||
//! Codepage-437-Abbildung auf Unicode, Rendering über Ratatui.
|
||||
//! Textbildschirm-Emulation: 80×25-Zellenpuffer (Unicode-Zeichen + Farb-
|
||||
//! attribut), Cursor, Scrollen — gerendert als Ratatui-Widget.
|
||||
//!
|
||||
//! Der Puffer ist die Grundlage für `PRINT`, `LOCATE`, `COLOR`, `CLS` usw.
|
||||
//! Entscheidung (siehe PLAN.md): durchgängig Unicode, keine CP437-Emulation.
|
||||
//! Das Zellenmodell ist strikt 1 Zeichen = 1 Zelle; Zeichen mit
|
||||
//! Darstellungsbreite ≠ 1 sind eine offene Frage (PLAN.md).
|
||||
|
||||
// Platzhalter — wird in Phase 0/3 ausgearbeitet (siehe PLAN.md)
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
pub const COLS: usize = 80;
|
||||
pub const ROWS: usize = 25;
|
||||
|
||||
/// Eine Bildschirmzelle: Zeichen plus klassisches Farbattribut.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Cell {
|
||||
pub ch: char,
|
||||
/// Vordergrund 0–15 (klassische Palette).
|
||||
pub fg: u8,
|
||||
/// Hintergrund 0–7.
|
||||
pub bg: u8,
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self {
|
||||
Cell { ch: ' ', fg: 7, bg: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Abbildung der klassischen 16-Farben-Palette auf ANSI-Indexfarben.
|
||||
/// (Klassisch: 1 = Blau, 4 = Rot — ANSI: 1 = Rot, 4 = Blau usw.)
|
||||
pub fn basic_color(n: u8) -> Color {
|
||||
const MAP: [u8; 16] = [0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15];
|
||||
Color::Indexed(MAP[(n & 0x0F) as usize])
|
||||
}
|
||||
|
||||
/// Der emulierte 80×25-Textbildschirm.
|
||||
///
|
||||
/// Koordinaten in der öffentlichen API sind 1-basiert (Zeile 1–25,
|
||||
/// Spalte 1–80), wie bei `LOCATE`/`CSRLIN`/`POS` des Dialekts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextScreen {
|
||||
cells: Vec<Cell>,
|
||||
/// Cursorposition, 0-basiert intern.
|
||||
cur_row: usize,
|
||||
cur_col: usize,
|
||||
pub cursor_visible: bool,
|
||||
/// Aktuelle Ausgabefarben (`COLOR`).
|
||||
pub fg: u8,
|
||||
pub bg: u8,
|
||||
/// Scrollbereich (`VIEW PRINT`), 0-basiert inklusiv.
|
||||
view_top: usize,
|
||||
view_bottom: usize,
|
||||
}
|
||||
|
||||
impl Default for TextScreen {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn new() -> Self {
|
||||
TextScreen {
|
||||
cells: vec![Cell::default(); COLS * ROWS],
|
||||
cur_row: 0,
|
||||
cur_col: 0,
|
||||
cursor_visible: true,
|
||||
fg: 7,
|
||||
bg: 0,
|
||||
view_top: 0,
|
||||
view_bottom: ROWS - 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// `CLS`: Scrollbereich mit aktueller Hintergrundfarbe löschen,
|
||||
/// Cursor an den Anfang des Bereichs.
|
||||
pub fn cls(&mut self) {
|
||||
let blank = Cell { ch: ' ', fg: self.fg, bg: self.bg };
|
||||
for row in self.view_top..=self.view_bottom {
|
||||
self.cells[row * COLS..(row + 1) * COLS].fill(blank);
|
||||
}
|
||||
self.cur_row = self.view_top;
|
||||
self.cur_col = 0;
|
||||
}
|
||||
|
||||
/// `COLOR vg, hg` — Werte werden wie im Vorbild maskiert
|
||||
/// (vg 0–31, wir ignorieren Blink → 0–15; hg 0–7).
|
||||
pub fn set_color(&mut self, fg: u8, bg: u8) {
|
||||
self.fg = fg & 0x0F;
|
||||
self.bg = bg & 0x07;
|
||||
}
|
||||
|
||||
/// `LOCATE zeile, spalte` (1-basiert). Außerhalb des Bildschirms:
|
||||
/// Fehler 5 beim Aufrufer — hier wird geklemmt geprüft.
|
||||
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ()> {
|
||||
if row < 1 || row > ROWS || col < 1 || col > COLS {
|
||||
return Err(());
|
||||
}
|
||||
self.cur_row = row - 1;
|
||||
self.cur_col = col - 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `CSRLIN` (1-basiert).
|
||||
pub fn csrlin(&self) -> usize {
|
||||
self.cur_row + 1
|
||||
}
|
||||
|
||||
/// `POS(0)` (1-basiert).
|
||||
pub fn pos(&self) -> usize {
|
||||
self.cur_col + 1
|
||||
}
|
||||
|
||||
/// `VIEW PRINT oben TO unten` (1-basiert).
|
||||
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ()> {
|
||||
if top < 1 || bottom > ROWS || top > bottom {
|
||||
return Err(());
|
||||
}
|
||||
self.view_top = top - 1;
|
||||
self.view_bottom = bottom - 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cell(&self, row: usize, col: usize) -> Cell {
|
||||
self.cells[(row - 1) * COLS + (col - 1)]
|
||||
}
|
||||
|
||||
/// Text an der Cursorposition ausgeben: Umbruch am rechten Rand,
|
||||
/// Scrollen am unteren Rand des Scrollbereichs. `\n` bricht um,
|
||||
/// `\r` setzt an den Zeilenanfang; andere Steuerzeichen werden
|
||||
/// (noch) als normale Zeichen behandelt.
|
||||
pub fn print(&mut self, text: &str) {
|
||||
for ch in text.chars() {
|
||||
match ch {
|
||||
'\n' => self.newline(),
|
||||
'\r' => self.cur_col = 0,
|
||||
_ => {
|
||||
self.cells[self.cur_row * COLS + self.cur_col] =
|
||||
Cell { ch, fg: self.fg, bg: self.bg };
|
||||
self.cur_col += 1;
|
||||
if self.cur_col >= COLS {
|
||||
self.newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zeilenumbruch inkl. Scrollen im Scrollbereich.
|
||||
fn newline(&mut self) {
|
||||
self.cur_col = 0;
|
||||
if self.cur_row >= self.view_bottom {
|
||||
self.scroll_up();
|
||||
self.cur_row = self.view_bottom;
|
||||
} else {
|
||||
self.cur_row += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Scrollbereich um eine Zeile nach oben schieben; unterste Zeile leeren.
|
||||
pub fn scroll_up(&mut self) {
|
||||
let blank = Cell { ch: ' ', fg: self.fg, bg: self.bg };
|
||||
for row in self.view_top..self.view_bottom {
|
||||
let (a, b) = self.cells.split_at_mut((row + 1) * COLS);
|
||||
a[row * COLS..].copy_from_slice(&b[..COLS]);
|
||||
}
|
||||
self.cells[self.view_bottom * COLS..(self.view_bottom + 1) * COLS].fill(blank);
|
||||
}
|
||||
}
|
||||
|
||||
/// Rendert den Bildschirm zentriert (Letterboxing) in die verfügbare Fläche.
|
||||
/// Ist das Terminal kleiner als 80×25, wird ein Hinweis angezeigt.
|
||||
impl Widget for &TextScreen {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if (area.width as usize) < COLS || (area.height as usize) < ROWS {
|
||||
let msg = format!(
|
||||
"Terminal zu klein: {}x{} — benötigt {}x{}",
|
||||
area.width, area.height, COLS, ROWS
|
||||
);
|
||||
if let Some(cell) = buf.cell_mut((area.x, area.y)) {
|
||||
cell.set_symbol(" ");
|
||||
}
|
||||
buf.set_string(area.x, area.y, msg, Style::default().fg(Color::Red));
|
||||
return;
|
||||
}
|
||||
let x0 = area.x + (area.width - COLS as u16) / 2;
|
||||
let y0 = area.y + (area.height - ROWS as u16) / 2;
|
||||
for row in 0..ROWS {
|
||||
for col in 0..COLS {
|
||||
let c = self.cells[row * COLS + col];
|
||||
if let Some(cell) = buf.cell_mut((x0 + col as u16, y0 + row as u16)) {
|
||||
let mut s = String::new();
|
||||
s.push(c.ch);
|
||||
cell.set_symbol(&s);
|
||||
cell.set_style(
|
||||
Style::default().fg(basic_color(c.fg)).bg(basic_color(c.bg)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cursor als invertierte Zelle darstellen (Terminal-Cursor wird in
|
||||
// der Forms-/Runtime-Schicht später gezielt gesteuert).
|
||||
if self.cursor_visible {
|
||||
let (cx, cy) = (x0 + self.cur_col as u16, y0 + self.cur_row as u16);
|
||||
if let Some(cell) = buf.cell_mut((cx, cy)) {
|
||||
cell.set_style(cell.style().add_modifier(ratatui::style::Modifier::REVERSED));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn print_schreibt_und_bewegt_cursor() {
|
||||
let mut s = TextScreen::new();
|
||||
s.print("AB");
|
||||
assert_eq!(s.cell(1, 1).ch, 'A');
|
||||
assert_eq!(s.cell(1, 2).ch, 'B');
|
||||
assert_eq!((s.csrlin(), s.pos()), (1, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_zeichen_belegen_eine_zelle() {
|
||||
let mut s = TextScreen::new();
|
||||
s.print("Ä☃");
|
||||
assert_eq!(s.cell(1, 1).ch, 'Ä');
|
||||
assert_eq!(s.cell(1, 2).ch, '☃');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn umbruch_am_rechten_rand() {
|
||||
let mut s = TextScreen::new();
|
||||
s.print(&"x".repeat(81));
|
||||
assert_eq!(s.cell(1, 80).ch, 'x');
|
||||
assert_eq!(s.cell(2, 1).ch, 'x');
|
||||
assert_eq!((s.csrlin(), s.pos()), (2, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrollen_am_unteren_rand() {
|
||||
let mut s = TextScreen::new();
|
||||
s.locate(25, 1).unwrap();
|
||||
s.print("unten\n"); // erzwingt Scroll
|
||||
assert_eq!(s.cell(24, 1).ch, 'u'); // Zeile 25 ist nach 24 gerutscht
|
||||
assert_eq!(s.cell(25, 1).ch, ' ');
|
||||
assert_eq!(s.csrlin(), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_print_begrenzt_scrollen() {
|
||||
let mut s = TextScreen::new();
|
||||
s.locate(1, 1).unwrap();
|
||||
s.print("kopf");
|
||||
s.view_print(3, 5).unwrap();
|
||||
s.locate(5, 1).unwrap();
|
||||
s.print("a\nb"); // scrollt nur Zeilen 3–5
|
||||
assert_eq!(s.cell(1, 1).ch, 'k'); // Kopfzeile unberührt
|
||||
assert_eq!(s.cell(4, 1).ch, 'a');
|
||||
assert_eq!(s.cell(5, 1).ch, 'b');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locate_prueft_grenzen() {
|
||||
let mut s = TextScreen::new();
|
||||
assert!(s.locate(0, 1).is_err());
|
||||
assert!(s.locate(26, 1).is_err());
|
||||
assert!(s.locate(25, 80).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn farbabbildung() {
|
||||
assert_eq!(basic_color(1), Color::Indexed(4)); // klassisch Blau
|
||||
assert_eq!(basic_color(4), Color::Indexed(1)); // klassisch Rot
|
||||
assert_eq!(basic_color(14), Color::Indexed(11)); // Gelb
|
||||
assert_eq!(basic_color(15), Color::Indexed(15)); // Weiß
|
||||
}
|
||||
}
|
||||
|
||||
55
docs/dateiformate.md
Normal file
55
docs/dateiformate.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Dateiformate
|
||||
|
||||
Terminal Basic liest und schreibt die Textformate des Vorbilds, durchgängig
|
||||
in UTF-8 (Abweichung: das Vorbild nutzte die DOS-Codepage). Binäre
|
||||
„Fast-Load"-Varianten des Vorbilds sind Nicht-Ziel — nur Textformate.
|
||||
|
||||
## Quelltext: `.BAS`
|
||||
|
||||
Reiner Text, eine Anweisung(sfolge) pro Zeile. Optionale Kopfzeilen der IDE
|
||||
(`DECLARE`-Prototypen) werden beim Speichern erzeugt/aktualisiert.
|
||||
Metabefehle in Kommentaren: `'$INCLUDE: 'datei.bi'`, `'$STATIC`, `'$DYNAMIC`.
|
||||
|
||||
## Formular: `.FRM`
|
||||
|
||||
Textformat, zwei Abschnitte: Formular-Beschreibung, dann Code.
|
||||
|
||||
```
|
||||
VERSION 1.00
|
||||
Begin Form Form1
|
||||
Caption = "Beispiel"
|
||||
Height = 15
|
||||
Left = 10
|
||||
Top = 4
|
||||
Width = 50
|
||||
Begin CommandButton cmdOK
|
||||
Caption = "&OK"
|
||||
Height = 1
|
||||
Left = 18
|
||||
Top = 11
|
||||
Width = 10
|
||||
End
|
||||
End
|
||||
|
||||
SUB cmdOK_Click ()
|
||||
UNLOAD Form1
|
||||
END SUB
|
||||
```
|
||||
|
||||
- `VERSION`-Zeile, dann verschachtelte `Begin <Typ> <Name> … End`-Blöcke
|
||||
mit `Eigenschaft = Wert`-Zeilen (Strings in `"…"`).
|
||||
- Danach normaler BASIC-Code des Formular-Moduls.
|
||||
- TODO: exakte Eigenschaftsnamen/-reihenfolge und Einrückung des Vorbilds
|
||||
an Originaldateien verifizieren (Ziel: Roundtrip-Kompatibilität).
|
||||
|
||||
## Projekt: `.MAK`
|
||||
|
||||
Zeilenweise Liste der Projektdateien (`.BAS`, `.FRM`, `.BI`), TODO:
|
||||
Optionszeilen des Vorbilds prüfen. Terminal Basic akzeptiert zusätzlich
|
||||
Kommentarzeilen mit `'`.
|
||||
|
||||
## Kompilat: `.tbc` (neu, eigenes Format)
|
||||
|
||||
Container für TBVM-Bytecode, Entwurf in [tbvm-design.md](tbvm-design.md).
|
||||
`tbdosc build` erzeugt wahlweise `.tbc` oder ein eigenständiges Executable
|
||||
(Runner + eingebettetes `.tbc`).
|
||||
85
docs/forms-referenz.md
Normal file
85
docs/forms-referenz.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Forms-Referenz Terminal Basic
|
||||
|
||||
Rekonstruierte Referenz der Forms-Engine des Vorbilds (textbasierte,
|
||||
ereignisgesteuerte Oberflächen). Grundlage für `tb-ui::forms` und den
|
||||
Formular-Designer der IDE. Unklare Punkte sind mit `TODO` markiert.
|
||||
|
||||
## Koordinatenmodell
|
||||
|
||||
Alle Maße in **Textzellen** (Zeilen/Spalten), Ursprung links oben.
|
||||
Formulare liegen auf dem 80×25-Bildschirm; Steuerelemente relativ zum
|
||||
Formular-Innenbereich. Eigenschaften `Top`, `Left`, `Height`, `Width`.
|
||||
|
||||
## Steuerelemente (Übersicht)
|
||||
|
||||
| Steuerelement | Zweck | Schlüssel-Ereignisse |
|
||||
|---|---|---|
|
||||
| Form | Fenster (mit/ohne Rahmen, Titel, verschiebbar) | Load, Unload, Activate, Deactivate, Resize, Paint, Click, KeyPress |
|
||||
| Label | statischer Text | Click |
|
||||
| TextBox | ein-/mehrzeilige Texteingabe | Change, KeyPress, GotFocus, LostFocus |
|
||||
| CommandButton | Schaltfläche | Click |
|
||||
| CheckBox | Mehrfachauswahl (Value 0/1/2) | Click |
|
||||
| OptionButton | Einfachauswahl in Gruppe (Value TRUE/FALSE) | Click |
|
||||
| Frame | Gruppierungsrahmen (Container) | — |
|
||||
| ListBox | Liste (List, ListCount, ListIndex, AddItem/RemoveItem) | Click, DblClick |
|
||||
| ComboBox | Eingabe + Liste (Style 0/1/2) | Change, Click, DblClick |
|
||||
| HScrollBar / VScrollBar | Bildlauf (Min, Max, Value, SmallChange, LargeChange) | Change, Scroll |
|
||||
| PictureBox | Text-Zeichenfläche (PRINT/CLS auf Steuerelement) | Click, Paint |
|
||||
| Timer | unsichtbar; Interval in ms (Vorbild: 55-ms-Ticks) | Timer |
|
||||
| Menu | Menüleiste/-einträge (Designer-definiert) | Click |
|
||||
|
||||
## Gemeinsame Eigenschaften
|
||||
|
||||
`Name` (Entwurfszeit), `Caption`/`Text`, `Top`/`Left`/`Height`/`Width`,
|
||||
`Visible`, `Enabled`, `TabIndex`, `TabStop`, `Tag`, `Index` (Steuerelement-
|
||||
Arrays), `ForeColor`/`BackColor` (0–15 bzw. 0–7), `MousePointer` (TODO).
|
||||
Zugriff zur Laufzeit: `form.eigenschaft`, `form!element.eigenschaft`;
|
||||
Standard-Eigenschaft (z. B. `Text` bei TextBox, `Caption` bei Label) bei
|
||||
Zuweisung ohne Eigenschaftsname. TODO: exakte Standard-Eigenschaften prüfen.
|
||||
|
||||
## Gemeinsame Methoden
|
||||
|
||||
`Move top, left [, height, width]` · `SetFocus` · `Refresh` · `Drag` (Nicht-
|
||||
Ziel?) · Form zusätzlich: `Show [modal]`, `Hide`, `Cls`, `Print`, `Line`
|
||||
(Textmodus-Variante? TODO). ListBox/ComboBox: `AddItem text$ [, index]`,
|
||||
`RemoveItem index`, `Clear` (TODO: hieß es `Clear`?).
|
||||
|
||||
## Ereignismodell
|
||||
|
||||
- Ereignisprozeduren heißen `SUB elementname_Ereignis (argumente)` und liegen
|
||||
im Formular-Modul.
|
||||
- Tastatur: `KeyDown(KeyCode%, Shift%)`, `KeyPress(KeyAscii%)`, `KeyUp` —
|
||||
KeyAscii ist bei uns ein Unicode-Codepoint (Abweichung UTF-8).
|
||||
- Maus: `MouseDown/MouseMove/MouseUp(Button%, Shift%, X!, Y!)` in Zellen.
|
||||
- Fokusreihenfolge über `TabIndex`; Tab/Shift+Tab wechseln, Access-Keys per
|
||||
`&` im Caption (z. B. `"&OK"` → Alt+O).
|
||||
- Zentrale Schleife: Ereignisse werden nur bei `DOEVENTS`, `SLEEP`, `INPUT`-
|
||||
artigen Wartezuständen oder nach Ende einer Ereignisprozedur zugestellt
|
||||
(kooperativ, wie im Vorbild — keine Präemption).
|
||||
|
||||
## Globale Objekte und Funktionen
|
||||
|
||||
- `SCREEN`-Objekt: `SCREEN.ActiveForm`, `SCREEN.ActiveControl`,
|
||||
`SCREEN.Height`/`Width` (25/80), `SCREEN.MousePointer` (TODO: Umfang).
|
||||
- `MSGBOX(text$ [, typ% [, titel$]])` als Anweisung und Funktion
|
||||
(Rückgabe: gedrückte Schaltfläche), `INPUTBOX$(prompt$ [, titel$
|
||||
[, standard$ [, spalte%, zeile%]]])`.
|
||||
- `LOAD form` (Load-Ereignis, unsichtbar), `UNLOAD form` (Unload-Ereignis),
|
||||
Formular-Referenzen sind statisch (keine Instanziierung wie in späteren
|
||||
Nachfolgern).
|
||||
|
||||
## Farben und Zeichensatz
|
||||
|
||||
Rahmen, Schatten und Bedienelemente werden mit Unicode-Box-Drawing gezeichnet
|
||||
(Abweichung: UTF-8 statt CP437, identische Optik). Standard-Farbschema des
|
||||
Vorbilds (grauer Dialog, schwarze Schrift, weiße Akzente) wird als Default
|
||||
nachgebildet. TODO: exakte Attributtabelle je Steuerelement-Zustand
|
||||
(normal/fokussiert/deaktiviert) aus Screenshots/Emulator ableiten.
|
||||
|
||||
## Offene Detailfragen (per Emulator-Session zu klären)
|
||||
|
||||
- Exakte Standardwerte aller Eigenschaften je Steuerelement
|
||||
- Z-Reihenfolge/Überlappung von Steuerelementen und Formularen
|
||||
- Verhalten `Show 1` (modal): welche Ereignisse laufen weiter?
|
||||
- Timer-Auflösung und -Reihenfolge bei mehreren Timern
|
||||
- Genauer Umfang von PictureBox im Textmodus
|
||||
240
docs/sprachreferenz.md
Normal file
240
docs/sprachreferenz.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Sprachreferenz Terminal Basic
|
||||
|
||||
Rekonstruierte Referenz des Dialekts (DOS-BASIC, Stand 1992: prozedurale
|
||||
BASIC-Familie mit Forms-Erweiterung). Dieses Dokument ist die verbindliche
|
||||
Grundlage für Frontend und Runtime. Unklare Detailfragen sind mit `TODO`
|
||||
markiert und werden per Testkorpus geklärt bzw. entschieden.
|
||||
|
||||
**Bewusste Abweichungen vom Vorbild** stehen am Ende des Dokuments.
|
||||
|
||||
---
|
||||
|
||||
## 1. Lexik
|
||||
|
||||
- **Zeilenorientiert.** Eine logische Zeile enthält eine oder mehrere
|
||||
Anweisungen, getrennt durch `:`. Keine Zeilenfortsetzung im Vorbild
|
||||
(`_` ist eine spätere Erfindung) — TODO: als Erweiterung erlauben?
|
||||
- **Zeilennummern** sind optional und wirken als Labels. Alphanumerische
|
||||
**Labels** enden mit `:` am Zeilenanfang (`Fehler:`).
|
||||
- **Kommentare:** `REM` (ganze Anweisung) und `'` (bis Zeilenende).
|
||||
`REM`/`'` am Zeilenanfang mit `$STATIC`/`$DYNAMIC`/`$INCLUDE: 'datei'`
|
||||
sind **Metabefehle**.
|
||||
- **Bezeichner:** Buchstabe, dann Buchstaben/Ziffern/`.`, max. 40 Zeichen,
|
||||
case-insensitiv. Optionales Typ-Suffix als letztes Zeichen.
|
||||
- **Typ-Suffixe:** `%` INTEGER · `&` LONG · `!` SINGLE · `#` DOUBLE ·
|
||||
`$` STRING · `@` CURRENCY. `name`, `name%`, `name$` sind
|
||||
**verschiedene Variablen**.
|
||||
- **Keywords** sind reserviert und case-insensitiv; die IDE normalisiert
|
||||
auf Großschreibung.
|
||||
- **Numerische Literale:** dezimal (`123`, `1.5`, `1.5E3`, `1D3` für DOUBLE),
|
||||
hexadezimal `&HFF`, oktal `&O777`; Suffixe wie bei Variablen (`10%`, `10&`,
|
||||
`1.5#`, `2.5@`). Ohne Suffix: kleinster passender Typ (Ganzzahl → INTEGER,
|
||||
sonst SINGLE/DOUBLE je nach Präzision; TODO: exakte Regel testen).
|
||||
- **String-Literale:** `"…"`; doppeltes `""` ergibt ein Anführungszeichen.
|
||||
|
||||
## 2. Typsystem
|
||||
|
||||
| Typ | Suffix | Repräsentation | Wertebereich |
|
||||
|---|---|---|---|
|
||||
| INTEGER | `%` | i16 | −32 768 … 32 767 |
|
||||
| LONG | `&` | i32 | −2 147 483 648 … 2 147 483 647 |
|
||||
| SINGLE | `!` | f32 | ±3.4E38 |
|
||||
| DOUBLE | `#` | f64 | ±1.8E308 |
|
||||
| STRING | `$` | dynamischer Unicode-String | Länge 0 … 32 767 Zeichen |
|
||||
| STRING * n | — | fester String, n Zeichen | nur in `TYPE`/`DIM` |
|
||||
| CURRENCY | `@` | i64, Festkomma ×10 000 | ±922 337 203 685 477.5807 |
|
||||
|
||||
- **Benutzerdefinierte Typen:** `TYPE name … END TYPE` mit Elementen fester
|
||||
Größe (numerische Typen, `STRING * n`, verschachtelte TYPEs). Kein
|
||||
dynamischer STRING in TYPE.
|
||||
- **Implizite Deklaration:** Erstverwendung deklariert die Variable. Ohne
|
||||
Suffix gilt der Standardtyp SINGLE, änderbar per
|
||||
`DEFINT/DEFLNG/DEFSNG/DEFDBL/DEFSTR/DEFCUR a–z` (buchstabenbereichsweise,
|
||||
wirkt pro Modul/Prozedur ab Deklaration).
|
||||
- `OPTION EXPLICIT` gibt es im Vorbild **nicht** — TODO: als opt-in
|
||||
Erweiterung anbieten?
|
||||
- **Arrays:** `DIM a(10)`, `DIM a(1 TO 10, 0 TO 5)`. Untergrenze standardmäßig
|
||||
0, per `OPTION BASE 1` änderbar. `$STATIC`/`$DYNAMIC` bzw. Kontext bestimmen
|
||||
statisch/dynamisch; `REDIM` (dynamisch, löscht Inhalt), `ERASE`
|
||||
(reinitialisiert statisch / gibt dynamisch frei). Max. 8 Dimensionen
|
||||
(TODO: prüfen). `LBOUND`/`UBOUND` liefern Grenzen.
|
||||
- **Konvertierung:** implizit zwischen numerischen Typen mit Rundung
|
||||
(Banker's Rounding bei `CINT`/`CLNG` und Zuweisung an Ganzzahl); Überlauf
|
||||
→ Fehler 6. Keine implizite Konvertierung Zahl ↔ String (Fehler 13,
|
||||
Type mismatch).
|
||||
|
||||
## 3. Deklarationen und Sichtbarkeit
|
||||
|
||||
- `DIM [SHARED] var[(dims)] [AS typ]` — `AS`-Klausel: INTEGER, LONG, SINGLE,
|
||||
DOUBLE, STRING, STRING * n, CURRENCY, benutzerdefinierter Typ.
|
||||
- `COMMON [SHARED] [/blockname/] liste` — modulübergreifend (Kette
|
||||
CHAIN-kompatibel im Vorbild; TODO: Relevanz ohne CHAIN klären).
|
||||
- `SHARED` (in Prozedur): Zugriff auf Modulebene-Variablen.
|
||||
- `STATIC` (in Prozedur): Variablen behalten Werte zwischen Aufrufen;
|
||||
`STATIC`-Attribut an `SUB`/`FUNCTION` macht alle lokalen Variablen statisch.
|
||||
- `CONST name = ausdruck` — Konstanten (konstante Ausdrücke zur Compilezeit).
|
||||
- `DECLARE SUB/FUNCTION name (parameter)` — Prototyp; die IDE erzeugt sie
|
||||
automatisch beim Speichern.
|
||||
|
||||
## 4. Operatoren (nach Priorität, hoch → niedrig)
|
||||
|
||||
1. `^` (Potenz)
|
||||
2. unäres `-`
|
||||
3. `*`, `/` (Fließkommadivision)
|
||||
4. `\` (Ganzzahldivision; Operanden werden gerundet auf INTEGER/LONG)
|
||||
5. `MOD` (Ganzzahlrest, Vorzeichen wie Dividend)
|
||||
6. `+`, `-` (`+` auch String-Verkettung)
|
||||
7. Vergleiche `= <> < > <= >=` (Zahlen und Strings; Strings
|
||||
codepoint-weise — Abweichung, s. u.)
|
||||
8. `NOT`
|
||||
9. `AND`
|
||||
10. `OR`
|
||||
11. `XOR`
|
||||
12. `EQV`
|
||||
13. `IMP`
|
||||
|
||||
Logische Operatoren sind **bitweise** auf Ganzzahlen; Vergleichsergebnis ist
|
||||
INTEGER −1 (wahr) / 0 (falsch).
|
||||
|
||||
## 5. Kontrollfluss
|
||||
|
||||
- `IF b THEN … [ELSE …]` (einzeilig) und Block-`IF … THEN / ELSEIF / ELSE /
|
||||
END IF`
|
||||
- `SELECT CASE ausdruck` mit `CASE wert`, `CASE a TO b`, `CASE IS >= x`,
|
||||
`CASE ELSE`
|
||||
- `FOR i = a TO b [STEP s] … NEXT [i]` (Grenzen werden einmal ausgewertet;
|
||||
Schleifenvariable numerisch)
|
||||
- `DO [WHILE|UNTIL b] … LOOP [WHILE|UNTIL b]`, `WHILE … WEND`
|
||||
- `EXIT FOR / EXIT DO / EXIT SUB / EXIT FUNCTION / EXIT DEF`
|
||||
- `GOTO ziel`, `GOSUB ziel` / `RETURN [ziel]`
|
||||
- `ON n GOTO liste`, `ON n GOSUB liste` (berechneter Sprung, 1-basiert;
|
||||
0 oder > Anzahl: kein Sprung; negativ/>255: Fehler 5)
|
||||
- `END` (Programmende), `STOP` (Unterbrechung → im IDE-Kontext Debugger),
|
||||
`SYSTEM` (Programmende, im Vorbild „zurück zu DOS")
|
||||
- `SLEEP [sekunden]`, `DO EVENTS`/`DOEVENTS` (Ereignisse verarbeiten —
|
||||
zentral für Forms)
|
||||
|
||||
## 6. Prozeduren
|
||||
|
||||
- `SUB name (p1 AS t, p2(), …) [STATIC] … END SUB`; Aufruf `CALL name(args)`
|
||||
oder `name args` (ohne Klammern).
|
||||
- `FUNCTION name (…) [STATIC] … name = wert … END FUNCTION`; Typ über Suffix
|
||||
oder `DEF…`-Regel.
|
||||
- **Parameterübergabe standardmäßig BYREF.** Klammern um ein Argument
|
||||
(`CALL f((x))`) erzwingen Wertübergabe. `BYVAL` nur in `DECLARE` für
|
||||
externe Routinen (entfällt bei uns; TODO: `BYVAL` allgemein erlauben?).
|
||||
- Arrays werden mit `name()` übergeben, TYPEs BYREF.
|
||||
- Rekursion erlaubt (außer bei `STATIC`-Semantik-Konflikten).
|
||||
- `DEF FNname (args) = ausdruck` und Block-`DEF FN … END DEF`; Aufruf
|
||||
`FNname(…)`. Modulweit, kein eigener Namensraum.
|
||||
|
||||
## 7. Fehlerbehandlung
|
||||
|
||||
- `ON ERROR GOTO label` (aktiviert Handler), `ON ERROR GOTO 0` (deaktiviert;
|
||||
in einem aktiven Handler: Fehler weiterreichen → Programmabbruch),
|
||||
`ON ERROR RESUME NEXT` — TODO: prüfen, ob das Vorbild das kennt
|
||||
(QB-Familie: nein; Forms-Dialekt: ja?).
|
||||
- Im Handler: `RESUME` (fehlerauslösende Anweisung wiederholen),
|
||||
`RESUME NEXT`, `RESUME label`.
|
||||
- `ERR` (Code), `ERL` (Zeilennummer, nur numerische Zeilennummern!),
|
||||
`ERROR n` (Fehler auslösen).
|
||||
- Fehler im aktiven Handler → sofortiger Abbruch. Fehler ohne Handler →
|
||||
Abbruch mit Meldung „Fehlertext in Zeile n" bzw. Debugger in der IDE.
|
||||
- Fehlerkatalog: siehe `tb-runtime/src/errors.rs` (implementiert).
|
||||
|
||||
## 8. Ereignis-Traps (klassisch, ohne Forms)
|
||||
|
||||
`ON TIMER(n) GOSUB label` + `TIMER ON/OFF/STOP`; analog `ON KEY(n)`,
|
||||
`ON PLAY`, `ON COM(n)`, `ON PEN`, `ON STRIG(n)`. Für Terminal Basic relevant:
|
||||
`TIMER` und `KEY`; Rest: Nicht-Ziel (siehe Abweichungen).
|
||||
|
||||
## 9. Konsolen-E/A
|
||||
|
||||
- `PRINT [#n,] liste` — Trennzeichen `;` (direkt anschließend) und `,`
|
||||
(nächste 14-Zeichen-Druckzone). Zahlen: führendes Leerzeichen bzw. `-`,
|
||||
nachgestelltes Leerzeichen. Abschluss ohne `;`/`,` → Zeilenumbruch.
|
||||
- `PRINT USING "format"; liste` — Formatzeichen: `#` Ziffer, `.` Dezimalpunkt,
|
||||
`,` Tausendertrennung, `+`/`-` Vorzeichen, `$$` Währung, `**` Füllsterne,
|
||||
`^^^^` Exponent, `&` String ganz, `!` erstes Zeichen, `\ \` n Zeichen,
|
||||
`_` Literal-Escape.
|
||||
- `INPUT ["prompt"{;|,}] var, …` (mit `;` vor Prompt: kein „? ");
|
||||
`LINE INPUT` (ganze Zeile in String).
|
||||
- `INKEY$` (nicht blockierend; "" wenn leer; erweiterte Tasten:
|
||||
2-Zeichen-Sequenz `CHR$(0)+code` im Vorbild — Abweichung s. u.),
|
||||
`INPUT$(n [,#f])`.
|
||||
- `LOCATE [zeile][,spalte][,cursor an/aus][,start,ende]`, `CSRLIN`, `POS(0)`.
|
||||
- `COLOR [vg][,hg]` (vg 0–31: 16–31 = blinkend; hg 0–7), `CLS`,
|
||||
`WIDTH` (80/40 — wir: nur 80), `VIEW PRINT oben TO unten` (Scrollbereich).
|
||||
- `TAB(n)`, `SPC(n)` in PRINT-Listen.
|
||||
- `BEEP`, `SOUND freq, dauer` (Terminal-Bell / Nicht-Ziel, s. Abweichungen),
|
||||
`PLAY` (Nicht-Ziel).
|
||||
- `KEY n, text$` / `KEY LIST` / `KEY ON/OFF` (Funktionstasten-Makros +
|
||||
Statuszeile) — TODO: Umfang klären.
|
||||
|
||||
## 10. Datei-E/A
|
||||
|
||||
- `OPEN datei$ [FOR modus] [ACCESS zugriff] [lock] AS [#]n [LEN=reclen]`
|
||||
Modi: `INPUT`, `OUTPUT`, `APPEND` (sequenziell), `RANDOM` (Standard),
|
||||
`BINARY`.
|
||||
- `CLOSE [#n, …]`, `RESET` (alle schließen).
|
||||
- Sequenziell: `PRINT #`, `PRINT # USING`, `WRITE #` (CSV-artig, Strings in
|
||||
`"…"`), `INPUT #`, `LINE INPUT #`, `EOF(n)`.
|
||||
- Random: `FIELD #n, breite AS var$…` (klassisch) **und** `GET/PUT #n
|
||||
[,satznr] [,var]` mit TYPE-Variablen; `LSET`/`RSET` für Feldpuffer.
|
||||
- Binary: `GET/PUT #n, [pos], var`, `SEEK #n, pos` / `SEEK(n)`,
|
||||
`LOC(n)`, `LOF(n)`.
|
||||
- Verwaltung: `NAME alt$ AS neu$`, `KILL datei$`, `FILES [muster$]`,
|
||||
`CHDIR`, `MKDIR`, `RMDIR`, `FILEATTR`, `FREEFILE`.
|
||||
- Pfade: plattformneutral; `/` und `\` werden akzeptiert.
|
||||
|
||||
## 11. Eingebaute Funktionen (Katalog)
|
||||
|
||||
**Strings:** `LEN`, `LEFT$`, `RIGHT$`, `MID$` (Funktion **und** Anweisung),
|
||||
`INSTR([start,] s$, such$)`, `UCASE$`, `LCASE$`, `LTRIM$`, `RTRIM$`,
|
||||
`SPACE$`, `STRING$(n, zeichen|code)`, `ASC`, `CHR$`, `STR$`, `VAL`,
|
||||
`HEX$`, `OCT$`, `LSET`/`RSET` (Anweisungen).
|
||||
|
||||
**Mathematik:** `ABS`, `SGN`, `INT` (abrunden), `FIX` (Richtung 0), `SQR`,
|
||||
`EXP`, `LOG`, `SIN`, `COS`, `TAN`, `ATN`, `RND[(n)]`, `RANDOMIZE [saat]`
|
||||
(kompatibler PRNG! → Testkorpus), `CINT`, `CLNG`, `CSNG`, `CDBL`, `CCUR`.
|
||||
|
||||
**Datum/Zeit:** `DATE$` (Funktion und Anweisung — Setzen: Nicht-Ziel),
|
||||
`TIME$`, `TIMER` (Sekunden seit Mitternacht, SINGLE).
|
||||
|
||||
**Sonstiges:** `LBOUND`, `UBOUND`, `FRE(…)` (freier Speicher — liefert bei
|
||||
uns Pseudowerte), `VARPTR`/`VARSEG`/`SADD`/`PEEK`/`POKE` → Nicht-Ziel
|
||||
(Fehler 73), `ENVIRON$`, `COMMAND$`, `SHELL [cmd$]`.
|
||||
|
||||
**DATA:** `DATA konstanten`, `READ var, …`, `RESTORE [label]`.
|
||||
|
||||
## 12. Forms-Anbindung (Details in forms-referenz.md)
|
||||
|
||||
`form.eigenschaft = wert`, `form!steuerelement.eigenschaft`,
|
||||
Ereignisprozeduren `SUB name_Ereignis (…)`, `LOAD`/`UNLOAD form`,
|
||||
`form.SHOW [modal]`, `form.HIDE`, `MSGBOX`/`INPUTBOX$`-Funktionen,
|
||||
`DOEVENTS`, `SCREEN`-Objekt (aktives Formular/Steuerelement).
|
||||
|
||||
---
|
||||
|
||||
## Abweichungen vom Vorbild (beschlossen)
|
||||
|
||||
1. **UTF-8/Unicode statt CP437** (2026-09-01). Konsequenzen:
|
||||
- STRING ist eine Folge von Unicode-Zeichen (Codepoints); `LEN` zählt
|
||||
Zeichen, nicht Bytes. `ASC`/`CHR$` arbeiten auf Codepoints
|
||||
(`CHR$(9731)` = „☃"). `ASC("")` bleibt Fehler 5.
|
||||
- String-Vergleich codepoint-weise (keine CP437-Sortierung).
|
||||
- Der Bildschirmpuffer speichert Unicode-Zeichen; Rahmen werden mit
|
||||
Unicode-Box-Drawing gezeichnet.
|
||||
- Zeichen mit Darstellungsbreite ≠ 1: offen (siehe PLAN.md).
|
||||
2. **Keine Hardware-Nähe:** `PEEK`/`POKE`/`INP`/`OUT`/`CALL ABSOLUTE`/
|
||||
Interrupts lösen Fehler 73 (Feature unavailable) aus.
|
||||
3. **`INKEY$` für erweiterte Tasten** liefert weiterhin
|
||||
`CHR$(0) + code`-Sequenzen mit den klassischen Scancodes (F1 = `CHR$(0)+";"`
|
||||
usw.), damit bestehender Code funktioniert. Zusätzliche moderne Tasten:
|
||||
TODO.
|
||||
4. **Kein `CHAIN`/Overlay-Mechanismus**; `SHELL` startet die System-Shell.
|
||||
5. **Grafik-Anweisungen** (`SCREEN n>0`, `PSET`, `LINE`, `CIRCLE`, `PAINT`,
|
||||
`DRAW`) sind Nicht-Ziel (Fehler 73) — das Vorbild war im Forms-Modus
|
||||
ebenfalls textonly.
|
||||
6. **`PLAY`/`SOUND`**: `BEEP` = Terminal-Bell; Rest Nicht-Ziel (Fehler 73).
|
||||
7. **`WIDTH 40`** wird nicht unterstützt (nur 80×25).
|
||||
85
docs/tbvm-design.md
Normal file
85
docs/tbvm-design.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# TBVM — Designentwurf
|
||||
|
||||
Eigene Stack-basierte Bytecode-VM (Entscheidung siehe PLAN.md, 0.2).
|
||||
Die VM ist in `tbdos` (IDE) und in von `tbdosc` erzeugte Executables
|
||||
eingebettet — identischer Code, identisches Verhalten, schneller Turnaround.
|
||||
|
||||
## Werte-Modell
|
||||
|
||||
Tagged Enum, kein NaN-Boxing (Einfachheit und Debugbarkeit vor Mikro-
|
||||
performance):
|
||||
|
||||
```rust
|
||||
enum Value {
|
||||
Int(i16), // INTEGER
|
||||
Long(i32), // LONG
|
||||
Single(f32), // SINGLE
|
||||
Double(f64), // DOUBLE
|
||||
Currency(i64), // CURRENCY, Festkomma ×10 000
|
||||
Str(Rc<str>), // STRING (immutabel geteilt; Copy-on-Write bei MID$-Anweisung)
|
||||
// Arrays und TYPE-Instanzen leben im Heap-Bereich der VM,
|
||||
// Variablen-Slots referenzieren sie per Handle (Rc<RefCell<…>>).
|
||||
}
|
||||
```
|
||||
|
||||
- Kein GC nötig: der Dialekt kennt keine Zyklen (keine Objektreferenzen in
|
||||
TYPEs, keine Closures) → `Rc` genügt.
|
||||
- Feste Strings (`STRING * n`) und TYPE-Records werden als eigene
|
||||
Speicherobjekte mit fester Zeichen-/Elementstruktur geführt.
|
||||
|
||||
## Bytecode
|
||||
|
||||
- Stack-Maschine, Opcodes 1 Byte + Operanden variabler Länge (u8/u16/u32,
|
||||
little-endian).
|
||||
- **Modul-Struktur:** Konstantenpool (Strings, Zahlen), Typtabelle
|
||||
(TYPE-Layouts), Prozedurtabelle (Signatur, Locals-Anzahl, Code-Offset),
|
||||
globale Slots, DATA-Segment (für READ/RESTORE), Zeilentabelle.
|
||||
- **Zeilentabelle:** Abbildung Code-Offset → (Moduldatei, Zeile). Grundlage
|
||||
für `ERL`, Fehlermeldungen, Breakpoints und Einzelschritt.
|
||||
- **Aufrufkonventionen:**
|
||||
- SUB/FUNCTION: eigener Frame (Locals-Slots, Operandenstack-Basis);
|
||||
BYREF-Parameter als Referenz-Slots (Handle auf Variablen-Slot).
|
||||
- GOSUB: **kein** Frame — Rücksprungadresse auf separatem GOSUB-Stack im
|
||||
aktuellen Frame (RETURN prüft diesen zuerst).
|
||||
- **Fehlerbehandlung:** Pro Frame ein Handler-Zustand (`ON ERROR GOTO x`).
|
||||
Laufzeitfehler → VM sucht aktiven Handler im aktuellen Modulkontext
|
||||
(Vorbild: Handler sind modul-/prozedurlokal, TODO: exakte Scoping-Regel
|
||||
testen), setzt `ERR`/`ERL`, springt. `RESUME` nutzt gemerkten
|
||||
Anweisungs-Offset.
|
||||
|
||||
## Ausführungsmodell / Unterbrechbarkeit
|
||||
|
||||
- Die Interpreterschleife läuft in **Ticks**: nach jeder Anweisung (Grenze
|
||||
aus der Zeilentabelle) prüft sie ein Flag-Set: Breakpoint? Einzelschritt?
|
||||
Strg+Untbr? Ereignis-Queue nicht leer und Zustellung erlaubt?
|
||||
- Ereigniszustellung (Forms, Timer) erfolgt kooperativ: nur an
|
||||
Zustellpunkten (`DOEVENTS`, `SLEEP`, blockierende Eingabe, Ende einer
|
||||
Ereignisprozedur) — wie im Vorbild.
|
||||
- Die VM ist eine gewöhnliche zustandsbehaftete Struktur, `step()`-basiert;
|
||||
die einbettende Schleife (IDE-Debugger oder Runner) treibt sie. Kein
|
||||
eigener Thread nötig; Terminal-Events werden zwischen Ticks gepollt.
|
||||
|
||||
## `.tbc`-Container (Entwurf)
|
||||
|
||||
```
|
||||
Magic "TBC\0" · Formatversion u16 · Flags
|
||||
Abschnittstabelle: [ (Kennung, Offset, Länge) ]
|
||||
Abschnitte: CONSTS, TYPES, PROCS, CODE, DATA, LINES, FORMS (serialisierte .FRM-Beschreibungen)
|
||||
```
|
||||
|
||||
Serialisierung mit einfachem eigenem Writer/Reader (kein serde nötig,
|
||||
Format bleibt stabil und dokumentiert).
|
||||
|
||||
## Eigenständige Executables
|
||||
|
||||
`tbdosc build --exe` kopiert den vorkompilierten Runner (dieselbe
|
||||
tb-vm/tb-runtime/tb-ui-Bibliothek wie die IDE) und hängt das `.tbc` als
|
||||
Ressource an (Anhängen ans Binary + Fußzeile mit Offset/Magic; portabel für
|
||||
alle drei Plattformen). Alternative — `include_bytes!` + Cargo-Build beim
|
||||
Nutzer — verworfen: erfordert Rust-Toolchain beim Anwender.
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
- Opcode-Satz konkret ausformulieren (mit Phase 2)
|
||||
- Zahlenkonvertierungs-Matrix (implizite Casts, Rundung, Überlauf) als Tabelle
|
||||
- Verhalten von `STOP`/`CONT` im Runner (ohne IDE): Abbruch mit Meldung?
|
||||
20
tests/compat/README.md
Normal file
20
tests/compat/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Kompatibilitäts-Testkorpus
|
||||
|
||||
Kleine Referenzprogramme mit dokumentierter erwarteter Ausgabe. Jede
|
||||
`name.bas` hat eine `name.out` mit der exakten Konsolenausgabe (UTF-8,
|
||||
LF-Zeilenenden). **Achtung:** PRINT gibt Zahlen mit führendem Leerzeichen
|
||||
(bzw. `-`) und nachgestelltem Leerzeichen aus — auch am Zeilenende. Die
|
||||
`.out`-Dateien enthalten daher signifikante Leerzeichen am Zeilenende;
|
||||
Editoren dürfen sie nicht wegtrimmen; `.gitattributes` schützt die Dateien
|
||||
vor Zeilenenden-Konvertierung.
|
||||
|
||||
Ab Phase 2 führt ein Test-Harness (`cargo test`) jede Datei per
|
||||
`tbdosc run` aus und vergleicht die Ausgabe. Bis dahin dienen die
|
||||
`.out`-Dateien als festgehaltene Verhaltensspezifikation — wo möglich am
|
||||
Vorbild (Emulator) verifiziert, sonst nach Referenzlage rekonstruiert
|
||||
und mit `TODO verify` im Programmkopf markiert.
|
||||
|
||||
Konventionen:
|
||||
- Nur Konsolen-E/A (keine Forms) bis Phase 4
|
||||
- Kein Zufall/Zeit, außer der Test fixiert die Saat (`RANDOMIZE` mit
|
||||
Konstante) bzw. prüft nur Invarianten
|
||||
3
tests/compat/hello.bas
Normal file
3
tests/compat/hello.bas
Normal file
@@ -0,0 +1,3 @@
|
||||
' Minimalfall: PRINT mit String und Zeilenumbruch
|
||||
PRINT "Hallo, Welt!"
|
||||
END
|
||||
1
tests/compat/hello.out
Normal file
1
tests/compat/hello.out
Normal file
@@ -0,0 +1 @@
|
||||
Hallo, Welt!
|
||||
26
tests/compat/kontrollfluss.bas
Normal file
26
tests/compat/kontrollfluss.bas
Normal file
@@ -0,0 +1,26 @@
|
||||
' FOR/NEXT, DO/LOOP, SELECT CASE, GOSUB
|
||||
FOR i% = 1 TO 3
|
||||
PRINT "for"; i%
|
||||
NEXT i%
|
||||
|
||||
n% = 3
|
||||
DO WHILE n% > 0
|
||||
PRINT "do"; n%
|
||||
n% = n% - 1
|
||||
LOOP
|
||||
|
||||
FOR i% = 1 TO 3
|
||||
SELECT CASE i%
|
||||
CASE 1: PRINT "eins"
|
||||
CASE 2 TO 3: PRINT "zwei-drei"
|
||||
CASE ELSE: PRINT "nie"
|
||||
END SELECT
|
||||
NEXT
|
||||
|
||||
GOSUB Unterprogramm
|
||||
PRINT "ende"
|
||||
END
|
||||
|
||||
Unterprogramm:
|
||||
PRINT "gosub"
|
||||
RETURN
|
||||
11
tests/compat/kontrollfluss.out
Normal file
11
tests/compat/kontrollfluss.out
Normal file
@@ -0,0 +1,11 @@
|
||||
for 1
|
||||
for 2
|
||||
for 3
|
||||
do 3
|
||||
do 2
|
||||
do 1
|
||||
eins
|
||||
zwei-drei
|
||||
zwei-drei
|
||||
gosub
|
||||
ende
|
||||
6
tests/compat/printzahlen.bas
Normal file
6
tests/compat/printzahlen.bas
Normal file
@@ -0,0 +1,6 @@
|
||||
' PRINT-Formatierung von Zahlen: führendes Leerzeichen (bzw. -),
|
||||
' nachgestelltes Leerzeichen; Semikolon = direkt anschließen.
|
||||
PRINT 1; 2; 3
|
||||
PRINT -1; -2
|
||||
PRINT 1.5; -2.5
|
||||
END
|
||||
3
tests/compat/printzahlen.out
Normal file
3
tests/compat/printzahlen.out
Normal file
@@ -0,0 +1,3 @@
|
||||
1 2 3
|
||||
-1 -2
|
||||
1.5 -2.5
|
||||
5
tests/compat/printzonen.bas
Normal file
5
tests/compat/printzonen.bas
Normal file
@@ -0,0 +1,5 @@
|
||||
' Komma = Sprung zur nächsten 14-Zeichen-Druckzone
|
||||
PRINT "a", "b", "c"
|
||||
PRINT "1234567890123", "x"
|
||||
PRINT "12345678901234", "x"
|
||||
END
|
||||
3
tests/compat/printzonen.out
Normal file
3
tests/compat/printzonen.out
Normal file
@@ -0,0 +1,3 @@
|
||||
a b c
|
||||
1234567890123 x
|
||||
12345678901234 x
|
||||
12
tests/compat/strings.bas
Normal file
12
tests/compat/strings.bas
Normal file
@@ -0,0 +1,12 @@
|
||||
' Stringfunktionen; LEN zählt Zeichen (UTF-8-Abweichung dokumentiert)
|
||||
s$ = "Terminal Basic"
|
||||
PRINT LEN(s$)
|
||||
PRINT LEFT$(s$, 8)
|
||||
PRINT RIGHT$(s$, 5)
|
||||
PRINT MID$(s$, 10, 3)
|
||||
PRINT INSTR(s$, "Basic")
|
||||
PRINT UCASE$("äöü grüße")
|
||||
PRINT STRING$(5, "*"); SPACE$(3); "|"
|
||||
PRINT CHR$(65); ASC("A")
|
||||
PRINT STR$(42); "|"; VAL(" 12.5abc")
|
||||
END
|
||||
9
tests/compat/strings.out
Normal file
9
tests/compat/strings.out
Normal file
@@ -0,0 +1,9 @@
|
||||
14
|
||||
Terminal
|
||||
Basic
|
||||
Bas
|
||||
10
|
||||
ÄÖÜ GRÜSSE
|
||||
***** |
|
||||
A 65
|
||||
42| 12.5
|
||||
Reference in New Issue
Block a user