Phase 3 (Kern): Laufzeitbibliothek, Bildschirm und Datei-E/A
Setzt den OpenSpec-Change phase-3-runtime-bildschirm um (77/77 Aufgaben). Abdeckung laut Inventar: 195 implementiert, 37 offen, 53 Non-Feature. Vollstaendigkeits-Inventar - docs/inventar.md mit 285 Eintraegen aus den Themenlisten von bas7advr.hlp und qb45advr.hlp, je mit Status und Fundstelle - crates/tb-frontend/tests/inventar.rs haelt die Tabelle in beide Richtungen gegen den Code; der Abdeckungsstand kann nicht veralten Bruchschritt (Puffer, Host, Korpus) - Cell/TextScreen ziehen ratatui-frei nach tb-runtime::screen; tb-ui behaelt Farbabbildung, Widget und den neuen Terminal-Host - Host wechselt vom Zeichenstrom auf Anzeige des Zellenpuffers plus Ereignisse (Taste, Groessenaenderung, Abbruch) - Korpusvergleich auf getrimmten Bildschirm-Snapshot; die acht bestehenden Sollausgaben blieben dabei unveraendert Groessenunabhaengigkeit - 80x25 ist nirgends mehr eine feste Grenze; Groessenaenderungen waehrend der Ausfuehrung werden zugestellt (Inhalt oben links erhalten, Cursor und VIEW PRINT geklemmt) - Korpusnachweis: dasselbe Programm bei 80x25 und 120x40 mit je eigener Sollausgabe, plus ein Programm mit Groessenwechsel mitten im Lauf Bibliothek - Breite Unicode-Zeichen belegen zwei Zellen (Cursor, POS, Randumbruch) - Bildschirm: CLS, COLOR, LOCATE, WIDTH, VIEW PRINT, SCREEN, CSRLIN, POS - Tastatur: INKEY$, INPUT$, Funktionstasten-Makros (KEY n / LIST / ON) - PRINT USING, LPRINT USING, FORMAT$, SetFormatCC - Mathematik mit kompatiblem PRNG (gleiche Saat, gleiche Folge) - Datum und Zeit mit Serienwerten ab 1899 - Finanzmathematik: FV#, PV#, Pmt#, IPmt#, PPmt#, NPer#, Rate#, NPV#, IRR#, MIRR#, SLN#, SYD#, DDB# - Datei-E/A: sequenziell, RANDOM (Recordpuffer und UDT-Variablen), BINARY, FIELD/LSET/RSET, Statusfunktionen, Dateisystem, MK$/CV - System: ENVIRON, FRE, CLEAR, TRON/TROFF, STACK, ERDEV, ERR-Anweisung Altlasten aus Phase 2 - ON ERROR GOTO auf Modulebene ist aus Prozeduren ansprechbar (prozeduruebergreifender Fixup im Codegenerator) - DATA behaelt seinen Rohtext (Gross-/Kleinschreibung, innerer Leerraum) - Die vier TODO-verify-Zellen der Konvertierungsmatrix sind aufgeloest Vom Inventar aufgedeckte Fehler - Zwoelf Non-Features wies der Compiler entgegen der Phase-1-Spec nicht ab (CALLS, SSEG, POINT, VIEW, COM, PEN, STRIG, STICK, die String*-Routinen, LINE und OPEN "COMn:") -- geschlossen - Drei Gruppen fehlten im urspruenglichen Umfang: Finanzmathematik, Record-Konvertierung, Rest der Dateisystemfunktionen - bas7advr.hlp allein ist keine vollstaendige Quelle; das Inventar bildet die Vereinigung mit qb45advr.hlp Neue Changes - phase-3-isam: schliesst Phase 3 ab (Speicherschicht redb) - phase-3-ortszeit: zieht die UTC-Abweichung zurueck Neue Abhaengigkeit: unicode-width. Dokumentiert: sprachreferenz.md und tbvm-design.md sind TODO-frei, docs/bibliothek.md neu, PLAN.md fortgeschrieben. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -316,11 +316,18 @@ instrs! {
|
||||
0xC8 RaiseError; // Code vom Stack (ERROR n)
|
||||
0xC9 LoadErr;
|
||||
0xCA LoadErl;
|
||||
0xCB SetErr; // ERR = n (setzt den Code, löst nichts aus)
|
||||
|
||||
// 0xD0 — DATA und Eingabe
|
||||
0xD0 ReadData(a: u8); // nächstes DATA-Element; 0 = String, 1 = Zahl (DOUBLE)
|
||||
0xD1 Restore(a: u32);
|
||||
0xD2 Input(a: u8, b: bool, c: u16, d: bool); // argc, line_mode, prompt (0xFFFF=ohne), '?'
|
||||
0xD3 InputFile(a: u8, b: bool); // argc, line_mode — Dateinummer liegt unter den Referenzen
|
||||
// put?, mit Recordnummer?, Feldart (0 = ohne Variable, s. `Feldart`),
|
||||
// Zusatz (UDT-Index bzw. Länge fester Strings)
|
||||
0xD4 GetPut(a: bool, b: bool, c: u8, d: u16);
|
||||
0xD5 Field(a: u8); // Feldzahl; Stack: Dateinummer, dann (Länge, Referenz)*
|
||||
0xD6 LsetRset(a: bool); // rset?; Stack: Referenz, Wert // argc, line_mode — Dateinummer liegt unter den Referenzen
|
||||
}
|
||||
|
||||
// ---- Modulstruktur ------------------------------------------------------------
|
||||
|
||||
@@ -19,6 +19,7 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
|
||||
strings: Vec::new(),
|
||||
string_ids: HashMap::new(),
|
||||
jump_tables: Vec::new(),
|
||||
modul_label_pc: Vec::new(),
|
||||
};
|
||||
let mut procs = Vec::new();
|
||||
for proc in &hir.procs {
|
||||
@@ -74,6 +75,10 @@ struct Codegen {
|
||||
strings: Vec<Rc<str>>,
|
||||
string_ids: HashMap<String, u16>,
|
||||
jump_tables: Vec<Vec<u32>>,
|
||||
/// Sprungziele des Modulrumpfs (Prozedur 0). Ein modulweites
|
||||
/// `ON ERROR GOTO` aus einer Prozedur zeigt dorthin; da Prozedur 0
|
||||
/// zuerst übersetzt wird, stehen die Positionen rechtzeitig fest.
|
||||
modul_label_pc: Vec<Option<u32>>,
|
||||
}
|
||||
|
||||
struct ProcCtx {
|
||||
@@ -137,9 +142,26 @@ impl Codegen {
|
||||
// Rumpfende
|
||||
self.emit_proc_exit(&mut ctx, proc);
|
||||
|
||||
// Prozedur 0 (Modulrumpf) gibt ihre Sprungziele weiter.
|
||||
if proc.kind == hir::HProcKind::Main {
|
||||
self.modul_label_pc = ctx.label_pc.clone();
|
||||
}
|
||||
|
||||
// Fixups patchen
|
||||
for (idx, label) in std::mem::take(&mut ctx.fixups) {
|
||||
let pc = ctx.label_pc[label as usize].expect("Label ohne Position");
|
||||
// Modulweites `ON ERROR GOTO` in einer Prozedur: das Label lebt
|
||||
// im Modulrumpf, nicht im eigenen.
|
||||
let modulweit = proc.kind != hir::HProcKind::Main
|
||||
&& matches!(ctx.code[idx], Instr::OnErrorGoto(_));
|
||||
let pc = if modulweit {
|
||||
self.modul_label_pc
|
||||
.get(label as usize)
|
||||
.copied()
|
||||
.flatten()
|
||||
.expect("Modul-Label ohne Position")
|
||||
} else {
|
||||
ctx.label_pc[label as usize].expect("Label ohne Position")
|
||||
};
|
||||
match &mut ctx.code[idx] {
|
||||
Instr::Jump(t)
|
||||
| Instr::JumpIfFalse(t)
|
||||
@@ -224,7 +246,57 @@ impl Codegen {
|
||||
ctx.emit(Instr::CallBuiltin(ids::PRINT_NEWLINE, 0));
|
||||
}
|
||||
}
|
||||
HStmtKind::Input { line_mode, prompt, question, targets } => {
|
||||
HStmtKind::SetErr(e) => {
|
||||
self.expr(ctx, e);
|
||||
ctx.emit(Instr::SetErr);
|
||||
}
|
||||
HStmtKind::Field { file, fields } => {
|
||||
self.expr(ctx, file);
|
||||
for (len, place) in fields {
|
||||
self.expr(ctx, len);
|
||||
self.make_ref(ctx, place);
|
||||
}
|
||||
ctx.emit(Instr::Field(fields.len() as u8));
|
||||
}
|
||||
HStmtKind::LsetRset { rset, target, value } => {
|
||||
self.make_ref(ctx, target);
|
||||
self.expr(ctx, value);
|
||||
ctx.emit(Instr::LsetRset(*rset));
|
||||
}
|
||||
HStmtKind::GetPut { put, file, recnum, var } => {
|
||||
self.expr(ctx, file);
|
||||
if let Some(r) = recnum {
|
||||
self.expr(ctx, r);
|
||||
}
|
||||
let (art, zusatz) = match var {
|
||||
None => (0u8, 0u16),
|
||||
Some(v) => {
|
||||
self.make_ref(ctx, v);
|
||||
match &v.ty {
|
||||
HTy::Num(NumTy::Int) => (1, 0),
|
||||
HTy::Num(NumTy::Lng) => (2, 0),
|
||||
HTy::Num(NumTy::Sng) => (3, 0),
|
||||
HTy::Num(NumTy::Dbl) => (4, 0),
|
||||
HTy::Num(NumTy::Cur) => (5, 0),
|
||||
HTy::FixedStr(n) => (6, *n as u16),
|
||||
HTy::Udt(i) => (7, *i),
|
||||
// Variable Strings: Länge erst zur Laufzeit.
|
||||
HTy::Str => (8, 0),
|
||||
}
|
||||
}
|
||||
};
|
||||
ctx.emit(Instr::GetPut(*put, recnum.is_some(), art, zusatz));
|
||||
}
|
||||
HStmtKind::Input { file, line_mode, prompt, question, targets } => {
|
||||
if let Some(f) = file {
|
||||
// Dateinummer zuerst, dann die Referenzen darüber.
|
||||
self.expr(ctx, f);
|
||||
for t in targets {
|
||||
self.make_ref(ctx, t);
|
||||
}
|
||||
ctx.emit(Instr::InputFile(targets.len() as u8, *line_mode));
|
||||
return;
|
||||
}
|
||||
for t in targets {
|
||||
self.make_ref(ctx, t);
|
||||
}
|
||||
@@ -901,9 +973,90 @@ fn builtin_id(b: Builtin) -> u16 {
|
||||
Builtin::PrintTab => ids::PRINT_TAB,
|
||||
Builtin::PrintSpc => ids::PRINT_SPC,
|
||||
Builtin::PrintNewline => ids::PRINT_NEWLINE,
|
||||
Builtin::PrintUsing => ids::PRINT_USING,
|
||||
Builtin::FormatS => ids::FORMAT_S,
|
||||
Builtin::SetFormatCc => ids::SET_FORMAT_CC,
|
||||
Builtin::Cls => ids::CLS,
|
||||
Builtin::Color => ids::COLOR,
|
||||
Builtin::Locate => ids::LOCATE,
|
||||
Builtin::Width => ids::WIDTH,
|
||||
Builtin::ViewPrint => ids::VIEW_PRINT,
|
||||
Builtin::ScreenStmt => ids::SCREEN_STMT,
|
||||
Builtin::KeyAssign => ids::KEY_ASSIGN,
|
||||
Builtin::KeyList => ids::KEY_LIST,
|
||||
Builtin::KeyDisplay => ids::KEY_DISPLAY,
|
||||
Builtin::Csrlin => ids::CSRLIN,
|
||||
Builtin::PosFn => ids::POS_FN,
|
||||
Builtin::ScreenFn => ids::SCREEN_FN,
|
||||
Builtin::InkeyS => ids::INKEY_S,
|
||||
Builtin::InputS => ids::INPUT_S,
|
||||
Builtin::EnvironS => ids::ENVIRON_S,
|
||||
Builtin::EnvironSet => ids::ENVIRON_SET,
|
||||
Builtin::Fre => ids::FRE,
|
||||
Builtin::Clear => ids::CLEAR,
|
||||
Builtin::Tron => ids::TRON,
|
||||
Builtin::Troff => ids::TROFF,
|
||||
Builtin::StackFn => ids::STACK_FN,
|
||||
Builtin::StackStmt => ids::STACK_STMT,
|
||||
Builtin::Erdev => ids::ERDEV,
|
||||
Builtin::ErdevS => ids::ERDEV_S,
|
||||
Builtin::Open => ids::OPEN,
|
||||
Builtin::Close => ids::CLOSE,
|
||||
Builtin::CloseAll => ids::CLOSE_ALL,
|
||||
Builtin::PrintZiel => ids::PRINT_ZIEL,
|
||||
Builtin::WriteFile => ids::WRITE_FILE,
|
||||
Builtin::EofF => ids::EOF_F,
|
||||
Builtin::LofF => ids::LOF_F,
|
||||
Builtin::LocF => ids::LOC_F,
|
||||
Builtin::SeekF => ids::SEEK_F,
|
||||
Builtin::SeekStmt => ids::SEEK_STMT,
|
||||
Builtin::Freefile => ids::FREEFILE,
|
||||
Builtin::Fileattr => ids::FILEATTR,
|
||||
Builtin::LockStmt => ids::LOCK_STMT,
|
||||
Builtin::Kill => ids::KILL,
|
||||
Builtin::NameStmt => ids::NAME_STMT,
|
||||
Builtin::Files => ids::FILES,
|
||||
Builtin::Chdir => ids::CHDIR,
|
||||
Builtin::Chdrive => ids::CHDRIVE,
|
||||
Builtin::Mkdir => ids::MKDIR,
|
||||
Builtin::Rmdir => ids::RMDIR,
|
||||
Builtin::CurdirS => ids::CURDIR_S,
|
||||
Builtin::DirS => ids::DIR_S,
|
||||
Builtin::Lpos => ids::LPOS,
|
||||
Builtin::ShellStmt => ids::SHELL_STMT,
|
||||
Builtin::ShellFn => ids::SHELL_FN,
|
||||
Builtin::MkS => ids::MK_S,
|
||||
Builtin::CvF => ids::CV_F,
|
||||
Builtin::Fv => ids::FV,
|
||||
Builtin::Pv => ids::PV,
|
||||
Builtin::Pmt => ids::PMT,
|
||||
Builtin::NPer => ids::NPER,
|
||||
Builtin::IPmt => ids::IPMT,
|
||||
Builtin::PPmt => ids::PPMT,
|
||||
Builtin::Rate => ids::RATE,
|
||||
Builtin::Npv => ids::NPV,
|
||||
Builtin::Irr => ids::IRR,
|
||||
Builtin::Mirr => ids::MIRR,
|
||||
Builtin::Sln => ids::SLN,
|
||||
Builtin::Syd => ids::SYD,
|
||||
Builtin::Ddb => ids::DDB,
|
||||
Builtin::Timer => ids::TIMER,
|
||||
Builtin::DateS => ids::DATE_S,
|
||||
Builtin::TimeS => ids::TIME_S,
|
||||
Builtin::DateSet => ids::DATE_SET,
|
||||
Builtin::TimeSet => ids::TIME_SET,
|
||||
Builtin::Now => ids::NOW,
|
||||
Builtin::DateSerial => ids::DATE_SERIAL,
|
||||
Builtin::TimeSerial => ids::TIME_SERIAL,
|
||||
Builtin::DateValue => ids::DATE_VALUE,
|
||||
Builtin::TimeValue => ids::TIME_VALUE,
|
||||
Builtin::DayF => ids::DAY_F,
|
||||
Builtin::MonthF => ids::MONTH_F,
|
||||
Builtin::YearF => ids::YEAR_F,
|
||||
Builtin::WeekdayF => ids::WEEKDAY_F,
|
||||
Builtin::HourF => ids::HOUR_F,
|
||||
Builtin::MinuteF => ids::MINUTE_F,
|
||||
Builtin::SecondF => ids::SECOND_F,
|
||||
Builtin::CommandS => ids::COMMAND_S,
|
||||
Builtin::Doevents => ids::DOEVENTS,
|
||||
Builtin::Sleep => ids::SLEEP,
|
||||
|
||||
@@ -71,6 +71,8 @@ pub struct Vm {
|
||||
resume_pc: usize,
|
||||
// Steuerung
|
||||
flags: u32,
|
||||
/// Zählt Anweisungsgrenzen für die regelmäßige Ereignisabholung.
|
||||
tick_zaehler: u32,
|
||||
breakpoints: HashSet<u32>,
|
||||
data_ptr: usize,
|
||||
}
|
||||
@@ -99,6 +101,7 @@ impl Vm {
|
||||
in_handler: false,
|
||||
resume_pc: 0,
|
||||
flags: 0,
|
||||
tick_zaehler: 0,
|
||||
breakpoints: HashSet::new(),
|
||||
data_ptr: 0,
|
||||
module,
|
||||
@@ -508,6 +511,15 @@ impl Vm {
|
||||
let f = self.frames.last_mut().unwrap();
|
||||
f.line = line;
|
||||
f.last_stmt_pc = pc;
|
||||
// Zustellpunkt: anzeigen, wenn sich der Bildschirm geändert
|
||||
// hat, und regelmäßig Ereignisse abholen. Das ist keine
|
||||
// Debugger-Funktion — ohne sie sähe niemand die Ausgabe und
|
||||
// Größenänderungen kämen nie an.
|
||||
self.tick_zaehler = self.tick_zaehler.wrapping_add(1);
|
||||
if self.rt.screen.ist_veraendert() || self.tick_zaehler % 1024 == 0 {
|
||||
self.rt.tick(host);
|
||||
self.rt.screen.veraenderung_quittieren();
|
||||
}
|
||||
if self.flags != 0 {
|
||||
if self.flags & F_STEP != 0 {
|
||||
return Ok(Flow::Event(RunEvent::Stepped { line }));
|
||||
@@ -515,7 +527,7 @@ impl Vm {
|
||||
if self.flags & F_BREAK != 0 && self.breakpoints.contains(&line) {
|
||||
return Ok(Flow::Event(RunEvent::Breakpoint { line }));
|
||||
}
|
||||
if self.flags & F_POLL != 0 && host.interrupted() {
|
||||
if self.flags & F_POLL != 0 && self.rt.abbruch {
|
||||
return Ok(Flow::Event(RunEvent::Interrupted { line }));
|
||||
}
|
||||
}
|
||||
@@ -1204,6 +1216,30 @@ impl Vm {
|
||||
self.do_input(argc, line_mode, prompt, question, host)?;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::InputFile(argc, line_mode) => {
|
||||
self.do_input_file(argc, line_mode)?;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::GetPut(put, mit_nr, art, zusatz) => {
|
||||
self.do_get_put(put, mit_nr, art, zusatz)?;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::SetErr => {
|
||||
let n = self.pop_i32()?;
|
||||
if !(0..=32767).contains(&n) {
|
||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||
}
|
||||
self.err = n as u16;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::Field(anzahl) => {
|
||||
self.do_field(anzahl)?;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::LsetRset(rset) => {
|
||||
self.do_lset_rset(rset)?;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,15 +1283,14 @@ impl Vm {
|
||||
loop {
|
||||
if prompt != 0xFFFF {
|
||||
let p = self.module.strings[prompt as usize].clone();
|
||||
self.rt.print.write(host, &p);
|
||||
self.rt.screen.print(&p);
|
||||
}
|
||||
if question {
|
||||
self.rt.print.write(host, "? ");
|
||||
self.rt.screen.print("? ");
|
||||
}
|
||||
let Some(mut line) = host.read_line() else {
|
||||
let Some(mut line) = self.rt.read_line(host) else {
|
||||
return Err(RuntimeError(62)); // Input past end of file
|
||||
};
|
||||
self.rt.print.col = 0;
|
||||
if line_mode {
|
||||
let r = refs.first().ok_or(RuntimeError(51))?;
|
||||
self.write_ref(r, Value::Str(Rc::from(line.as_str())))?;
|
||||
@@ -1264,7 +1299,7 @@ impl Vm {
|
||||
// Felder trennen (Quotes respektieren).
|
||||
let fields = split_input_fields(&line);
|
||||
if fields.len() != refs.len() {
|
||||
self.rt.print.write(host, "Redo from start\n");
|
||||
self.rt.screen.print("Redo from start\n");
|
||||
line.clear();
|
||||
continue;
|
||||
}
|
||||
@@ -1282,7 +1317,7 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
self.rt.print.write(host, "Redo from start\n");
|
||||
self.rt.screen.print("Redo from start\n");
|
||||
continue;
|
||||
}
|
||||
for (r, v) in refs.iter().zip(vals) {
|
||||
@@ -1292,6 +1327,230 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// `FIELD #n, laenge AS var$, …` — Recordpuffer aufteilen. Die
|
||||
/// Feldvariablen werden gemerkt: `GET` frischt sie aus dem Puffer auf,
|
||||
/// `LSET`/`RSET` schreiben zugleich in den Puffer.
|
||||
fn do_field(&mut self, anzahl: u8) -> Result<(), RuntimeError> {
|
||||
let mut paare = Vec::with_capacity(anzahl as usize);
|
||||
for _ in 0..anzahl {
|
||||
let r = match self.pop()? {
|
||||
Value::Ref(r) => r,
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
};
|
||||
let laenge = self.pop_i32()?;
|
||||
paare.push((laenge, r));
|
||||
}
|
||||
paare.reverse();
|
||||
let nummer = self.pop_i32()?;
|
||||
let datei = self.rt.dateien.get(nummer)?;
|
||||
datei.felder.clear();
|
||||
let mut start = 0usize;
|
||||
for (laenge, r) in paare {
|
||||
if laenge < 0 {
|
||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||
}
|
||||
let n = laenge as usize;
|
||||
if start + n * tb_runtime::fileio::UTF32_BREITE > datei.puffer.len() {
|
||||
return Err(RuntimeError(50)); // FIELD overflow
|
||||
}
|
||||
datei.felder.push(tb_runtime::fileio::Feld {
|
||||
referenz: r,
|
||||
start,
|
||||
laenge: n,
|
||||
});
|
||||
start += n * tb_runtime::fileio::UTF32_BREITE;
|
||||
}
|
||||
// Feldvariablen sofort aus dem Puffer belegen.
|
||||
self.felder_auffrischen(nummer)
|
||||
}
|
||||
|
||||
/// Feldvariablen einer Datei aus dem Recordpuffer neu belegen.
|
||||
fn felder_auffrischen(&mut self, nummer: i32) -> Result<(), RuntimeError> {
|
||||
let datei = self.rt.dateien.get(nummer)?;
|
||||
let werte: Vec<(VarRef, String)> = datei
|
||||
.felder
|
||||
.iter()
|
||||
.map(|f| {
|
||||
(
|
||||
f.referenz.clone(),
|
||||
tb_runtime::fileio::feld_lesen(&datei.puffer, f.start, f.laenge),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
for (r, t) in werte {
|
||||
self.write_ref(&r, Value::Str(Rc::from(t.as_str())))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `LSET`/`RSET` — bündig zuweisen; bei einem `FIELD`-Feld zusätzlich in
|
||||
/// den Recordpuffer schreiben.
|
||||
fn do_lset_rset(&mut self, rset: bool) -> Result<(), RuntimeError> {
|
||||
let wert = self.pop()?;
|
||||
let ziel = match self.pop()? {
|
||||
Value::Ref(r) => r,
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
};
|
||||
let Value::Str(text) = &wert else {
|
||||
// UDT-Zuweisung: unverändert durchreichen (Vorbild).
|
||||
self.write_ref(&ziel, wert)?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Gehört das Ziel zu einem FIELD-Puffer?
|
||||
let treffer = self.rt.dateien.feld_finden(&ziel);
|
||||
match treffer {
|
||||
Some((nummer, start, laenge)) => {
|
||||
let datei = self.rt.dateien.get(nummer)?;
|
||||
tb_runtime::fileio::feld_setzen(
|
||||
&mut datei.puffer,
|
||||
start,
|
||||
laenge,
|
||||
text,
|
||||
rset,
|
||||
);
|
||||
let neu = tb_runtime::fileio::feld_lesen(&datei.puffer, start, laenge);
|
||||
self.write_ref(&ziel, Value::Str(Rc::from(neu.as_str())))?;
|
||||
}
|
||||
None => {
|
||||
// Ohne Feldbindung wirkt LSET/RSET auf die Stringlänge des
|
||||
// bisherigen Werts (Vorbild).
|
||||
let alt = match self.read_ref(&ziel)? {
|
||||
Value::Str(s) => s.chars().count(),
|
||||
_ => text.chars().count(),
|
||||
};
|
||||
let mut puffer = vec![0u8; alt * tb_runtime::fileio::UTF32_BREITE];
|
||||
tb_runtime::fileio::feld_setzen(&mut puffer, 0, alt, text, rset);
|
||||
let neu = tb_runtime::fileio::feld_lesen(&puffer, 0, alt);
|
||||
self.write_ref(&ziel, Value::Str(Rc::from(neu.as_str())))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `GET`/`PUT` — Datensatz (RANDOM) bzw. Bytes (BINARY) übertragen.
|
||||
fn do_get_put(
|
||||
&mut self,
|
||||
put: bool,
|
||||
mit_nr: bool,
|
||||
art: u8,
|
||||
zusatz: u16,
|
||||
) -> Result<(), RuntimeError> {
|
||||
use tb_runtime::fileio as fio;
|
||||
let ziel = if art != 0 {
|
||||
match self.pop()? {
|
||||
Value::Ref(r) => Some(r),
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let nr = if mit_nr { Some(self.pop_i32()?) } else { None };
|
||||
let nummer = self.pop_i32()?;
|
||||
|
||||
let udts = self.module.udts.clone();
|
||||
let datei = self.rt.dateien.get(nummer)?;
|
||||
let binaer = datei.modus == fio::Modus::Binary;
|
||||
// Ohne Nummer gilt die Position nach der letzten Operation.
|
||||
let pos = match nr {
|
||||
Some(n) if n >= 1 => n as u64,
|
||||
Some(_) => return Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
|
||||
None => datei.position,
|
||||
};
|
||||
|
||||
// Typ der Zielvariablen bestimmt die übertragene Länge.
|
||||
let typ = match art {
|
||||
0 => None,
|
||||
1 => Some(TypeInit::Int),
|
||||
2 => Some(TypeInit::Lng),
|
||||
3 => Some(TypeInit::Sng),
|
||||
4 => Some(TypeInit::Dbl),
|
||||
5 => Some(TypeInit::Cur),
|
||||
6 => Some(TypeInit::FixedStr(zusatz as u32)),
|
||||
7 => Some(TypeInit::Udt(zusatz)),
|
||||
// Variabler String: aktuelle Zeichenzahl bestimmt die Länge.
|
||||
_ => {
|
||||
let laenge = match self.read_ref(ziel.as_ref().unwrap())? {
|
||||
Value::Str(s) => s.chars().count() as u32,
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
};
|
||||
Some(TypeInit::FixedStr(laenge))
|
||||
}
|
||||
};
|
||||
|
||||
if binaer {
|
||||
let Some(t) = typ else {
|
||||
// Ohne Variable überträgt BINARY nichts.
|
||||
return Ok(());
|
||||
};
|
||||
let n = fio::breite(&t, &udts).ok_or(RuntimeError::TYPE_MISMATCH)?;
|
||||
if put {
|
||||
let wert = self.read_ref(ziel.as_ref().unwrap())?;
|
||||
let mut puffer = vec![0u8; n];
|
||||
fio::wert_schreiben(&mut puffer, 0, &wert, &t, &udts)?;
|
||||
self.rt.dateien.get(nummer)?.bytes_schreiben(pos, &puffer)?;
|
||||
} else {
|
||||
let puffer = self.rt.dateien.get(nummer)?.bytes_lesen(pos, n)?;
|
||||
let (v, _) = fio::wert_lesen(&puffer, 0, &t, &udts)?;
|
||||
self.write_ref(ziel.as_ref().unwrap(), v)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// RANDOM: über den Recordpuffer.
|
||||
if put {
|
||||
if let (Some(r), Some(t)) = (&ziel, &typ) {
|
||||
let wert = self.read_ref(r)?;
|
||||
let datei = self.rt.dateien.get(nummer)?;
|
||||
let mut puffer = std::mem::take(&mut datei.puffer);
|
||||
puffer.iter_mut().for_each(|b| *b = b' ');
|
||||
let ergebnis = fio::wert_schreiben(&mut puffer, 0, &wert, t, &udts);
|
||||
self.rt.dateien.get(nummer)?.puffer = puffer;
|
||||
ergebnis?;
|
||||
}
|
||||
self.rt.dateien.get(nummer)?.record_schreiben(pos)?;
|
||||
} else {
|
||||
self.rt.dateien.get(nummer)?.record_lesen(pos)?;
|
||||
self.felder_auffrischen(nummer)?;
|
||||
if let (Some(r), Some(t)) = (&ziel, &typ) {
|
||||
let puffer = self.rt.dateien.get(nummer)?.puffer.clone();
|
||||
let (v, _) = fio::wert_lesen(&puffer, 0, t, &udts)?;
|
||||
self.write_ref(r, v)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `INPUT #n` / `LINE INPUT #n` — Werte aus einer sequenziellen Datei.
|
||||
fn do_input_file(&mut self, argc: u8, line_mode: bool) -> Result<(), RuntimeError> {
|
||||
let mut refs = Vec::with_capacity(argc as usize);
|
||||
for _ in 0..argc {
|
||||
match self.pop()? {
|
||||
Value::Ref(r) => refs.push(r),
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
}
|
||||
}
|
||||
refs.reverse();
|
||||
let nummer = self.pop_i32()?;
|
||||
|
||||
let Some(zeile) = self.rt.dateien.get(nummer)?.zeile_lesen()? else {
|
||||
return Err(RuntimeError(62)); // Input past end of file
|
||||
};
|
||||
if line_mode {
|
||||
let r = refs.first().ok_or(RuntimeError(51))?;
|
||||
self.write_ref(r, Value::Str(Rc::from(zeile.as_str())))?;
|
||||
return Ok(());
|
||||
}
|
||||
let felder = tb_runtime::fileio::felder_trennen(&zeile);
|
||||
for (i, r) in refs.iter().enumerate() {
|
||||
let text = felder.get(i).map(String::as_str).unwrap_or("");
|
||||
let ziel = self.read_ref(r)?;
|
||||
let wert = input_value(&ziel, text).ok_or(RuntimeError(64))?; // Bad file name
|
||||
self.write_ref(r, wert)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Typisierte Binäroperationen mit Überlaufprüfung.
|
||||
fn bin_i16(&mut self, f: impl Fn(i16, i16) -> Option<i16>) -> Result<Flow, RuntimeError> {
|
||||
let b = self.pop_i16()?;
|
||||
@@ -1473,3 +1732,4 @@ fn strict_number(t: &str) -> Option<f64> {
|
||||
let cleaned = t.replace(['d', 'D'], "E").replace('e', "E");
|
||||
cleaned.parse::<f64>().ok()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user