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:
2026-09-01 16:36:45 +02:00
parent 5821ace0cf
commit 7e6fa6ea88
24 changed files with 1148 additions and 25 deletions

View File

@@ -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");
}
}