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:
@@ -218,9 +218,12 @@ impl<'a> P<'a> {
|
||||
}
|
||||
TokenKind::Kw(Kw::Line) => {
|
||||
self.advance();
|
||||
if self.expect_kw(Kw::Input, "INPUT") {
|
||||
if self.k() == TokenKind::Kw(Kw::Input) {
|
||||
self.advance();
|
||||
self.parse_input(true, pos)
|
||||
} else {
|
||||
// Grafikform LINE (x1,y1)-(x2,y2): deklariertes Non-Feature.
|
||||
self.err("Feature unavailable");
|
||||
self.sync();
|
||||
None
|
||||
}
|
||||
@@ -474,28 +477,40 @@ impl<'a> P<'a> {
|
||||
}
|
||||
TokenKind::Kw(Kw::Data) => {
|
||||
self.advance();
|
||||
// Der Lexer liefert den Rohtext; hier wird nur an Kommas
|
||||
// außerhalb von Anführungszeichen getrennt. Unquotierte
|
||||
// Elemente verlieren nur den Leerraum an den Rändern.
|
||||
let roh = match self.k() {
|
||||
TokenKind::DataRaw(t) => {
|
||||
self.advance();
|
||||
t
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
let mut items = Vec::new();
|
||||
let mut cur = String::new();
|
||||
while !self.at_stmt_end() {
|
||||
match self.k() {
|
||||
TokenKind::Comma => {
|
||||
items.push(cur.trim().to_string());
|
||||
cur = String::new();
|
||||
let mut in_quote = false;
|
||||
let mut quotiert = false;
|
||||
for c in roh.chars() {
|
||||
match c {
|
||||
'"' if !in_quote && cur.trim().is_empty() => {
|
||||
in_quote = true;
|
||||
quotiert = true;
|
||||
cur.clear();
|
||||
}
|
||||
TokenKind::Str(s) => cur.push_str(&s),
|
||||
TokenKind::Num(n) => cur.push_str(&num_text(n)),
|
||||
TokenKind::Ident { name, suffix } => {
|
||||
cur.push_str(&name);
|
||||
if let Some(sfx) = suffix {
|
||||
cur.push(sfx.as_char());
|
||||
}
|
||||
'"' if in_quote => in_quote = false,
|
||||
',' if !in_quote => {
|
||||
items.push(if quotiert {
|
||||
std::mem::take(&mut cur)
|
||||
} else {
|
||||
std::mem::take(&mut cur).trim().to_string()
|
||||
});
|
||||
quotiert = false;
|
||||
}
|
||||
TokenKind::Minus => cur.push('-'),
|
||||
_ => {}
|
||||
_ => cur.push(c),
|
||||
}
|
||||
self.advance();
|
||||
}
|
||||
items.push(cur.trim().to_string());
|
||||
items.push(if quotiert { cur } else { cur.trim().to_string() });
|
||||
Some(Stmt::Data { items, pos })
|
||||
}
|
||||
TokenKind::Kw(Kw::Read) => {
|
||||
@@ -1401,6 +1416,14 @@ impl<'a> P<'a> {
|
||||
|
||||
fn parse_def_fn(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||
self.advance(); // DEF
|
||||
// `DEF SEG` ist Segmentadressierung und damit deklariertes
|
||||
// Non-Feature — an der Syntax erkennbar, weil `DEF` sonst nur
|
||||
// `DEF FNname` einleitet.
|
||||
if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "SEG") {
|
||||
self.err("Feature unavailable");
|
||||
self.sync();
|
||||
return None;
|
||||
}
|
||||
let (name, suffix) = match self.k() {
|
||||
TokenKind::Ident { name, suffix } if name.starts_with("FN") => {
|
||||
self.advance();
|
||||
@@ -1710,6 +1733,23 @@ impl<'a> P<'a> {
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> Option<Expr> {
|
||||
// `SEEK(n)` ist zugleich Funktion und Anweisung; in Ausdrucksposition
|
||||
// gilt die Funktionsform.
|
||||
if self.k() == TokenKind::Kw(Kw::Seek) && self.k_at(1) == TokenKind::LParen {
|
||||
let pos = self.pos();
|
||||
self.advance(); // SEEK
|
||||
self.advance(); // (
|
||||
let arg = self.parse_expr()?;
|
||||
if !self.eat(&TokenKind::RParen) {
|
||||
self.err("Expected: )");
|
||||
}
|
||||
return Some(Expr::Name {
|
||||
name: "SEEK".into(),
|
||||
suffix: None,
|
||||
args: Some(vec![arg]),
|
||||
pos,
|
||||
});
|
||||
}
|
||||
match self.k() {
|
||||
TokenKind::Num(n) => {
|
||||
self.advance();
|
||||
@@ -1743,16 +1783,6 @@ impl<'a> P<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn num_text(n: NumValue) -> String {
|
||||
match n {
|
||||
NumValue::Int(v) => v.to_string(),
|
||||
NumValue::Long(v) => v.to_string(),
|
||||
NumValue::Single(v) => v.to_string(),
|
||||
NumValue::Double(v) => v.to_string(),
|
||||
NumValue::Currency(v) => format!("{}", v as f64 / 10_000.0),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user