Phase 4: Ereignisschleife und klassische Traps

This commit is contained in:
2026-09-04 15:56:47 +02:00
parent 060674b466
commit 5cf5a6582c
40 changed files with 4024 additions and 176 deletions

View File

@@ -233,9 +233,7 @@ fn builtin_fn(name: &str) -> Option<(u8, u8, &'static [ArgK], RetK)> {
"NOW" => (0, 0, &[], Db),
"DATESERIAL" | "TIMESERIAL" => (3, 3, &[N, N, N], Db),
"DATEVALUE" | "TIMEVALUE" => (1, 1, &[S], Db),
"DAY" | "MONTH" | "YEAR" | "WEEKDAY" | "HOUR" | "MINUTE" | "SECOND" => {
(1, 1, &[N], I)
}
"DAY" | "MONTH" | "YEAR" | "WEEKDAY" | "HOUR" | "MINUTE" | "SECOND" => (1, 1, &[N], I),
"FORMAT$" => (1, 2, &[A, S], St),
"ERR" | "ERL" => (0, 0, &[], L),
"FRE" => (1, 1, &[A], L),
@@ -253,9 +251,7 @@ fn builtin_fn(name: &str) -> Option<(u8, u8, &'static [ArgK], RetK)> {
"FV" | "FV#" | "PV" | "PV#" | "PMT" | "PMT#" | "NPER" | "NPER#" => {
(5, 5, &[N, N, N, N, N], Db)
}
"IPMT" | "IPMT#" | "PPMT" | "PPMT#" | "RATE" | "RATE#" => {
(6, 6, &[N, N, N, N, N, N], Db)
}
"IPMT" | "IPMT#" | "PPMT" | "PPMT#" | "RATE" | "RATE#" => (6, 6, &[N, N, N, N, N, N], Db),
"NPV" | "NPV#" | "IRR" | "IRR#" => (2, 2, &[N, A], Db),
"MIRR" | "MIRR#" => (3, 3, &[A, N, N], Db),
"SLN" | "SLN#" => (3, 3, &[N, N, N], Db),
@@ -636,9 +632,9 @@ impl Sema {
self.prescan(&a.body, scope, module_data);
}
}
Stmt::For { body, .. }
| Stmt::DoLoop { body, .. }
| Stmt::While { body, .. } => self.prescan(body, scope, module_data),
Stmt::For { body, .. } | Stmt::DoLoop { body, .. } | Stmt::While { body, .. } => {
self.prescan(body, scope, module_data)
}
_ => {}
}
}
@@ -880,21 +876,25 @@ impl Sema {
Expr::CurrencyLit(v) => Some(ConstVal::Num(*v as f64 / 10_000.0)),
Expr::StrLit(s) => Some(ConstVal::Str(s.clone())),
Expr::Paren(e) => self.fold_const(e),
Expr::Name { name, args: None, .. } => {
self.consts.get(name).and_then(|(_, v)| v.clone())
}
Expr::Unary { op: UnOp::Neg, operand, .. } => {
match self.fold_const(operand)? {
ConstVal::Num(n) => Some(ConstVal::Num(-n)),
ConstVal::Str(_) => None,
}
}
Expr::Unary { op: UnOp::Not, operand, .. } => {
match self.fold_const(operand)? {
ConstVal::Num(n) => Some(ConstVal::Num(!(n as i64) as f64)),
ConstVal::Str(_) => None,
}
}
Expr::Name {
name, args: None, ..
} => self.consts.get(name).and_then(|(_, v)| v.clone()),
Expr::Unary {
op: UnOp::Neg,
operand,
..
} => match self.fold_const(operand)? {
ConstVal::Num(n) => Some(ConstVal::Num(-n)),
ConstVal::Str(_) => None,
},
Expr::Unary {
op: UnOp::Not,
operand,
..
} => match self.fold_const(operand)? {
ConstVal::Num(n) => Some(ConstVal::Num(!(n as i64) as f64)),
ConstVal::Str(_) => None,
},
Expr::Binary { op, lhs, rhs, .. } => {
let l = self.fold_const(lhs)?;
let r = self.fold_const(rhs)?;
@@ -1513,25 +1513,41 @@ impl Sema {
Some(t) => {
self.check_label_exists(t, scope, *pos);
match t {
LabelRef::Name(n) => {
self.data_marks_name.get(n).copied().unwrap_or(0)
}
LabelRef::Line(n) => {
self.data_marks_line.get(n).copied().unwrap_or(0)
}
LabelRef::Name(n) => self.data_marks_name.get(n).copied().unwrap_or(0),
LabelRef::Line(n) => self.data_marks_line.get(n).copied().unwrap_or(0),
}
}
};
self.push(out, scope, HStmtKind::Restore(idx));
}
Stmt::DefFn { name, suffix, params, body, pos } => {
Stmt::DefFn {
name,
suffix,
params,
body,
pos,
} => {
self.lower_def_fn(name, suffix, params, None, Some(body), scope, *pos);
}
Stmt::DefFnBlock { name, suffix, params, body, pos } => {
Stmt::DefFnBlock {
name,
suffix,
params,
body,
pos,
} => {
self.lower_def_fn(name, suffix, params, Some(body), None, scope, *pos);
}
// ---- Datei-E/A (Grammatik Phase 1, Laufzeit Phase 3) ----
Stmt::Open { file, mode, isam, number, len, pos, .. } => {
Stmt::Open {
file,
mode,
isam,
number,
len,
pos,
..
} => {
let (fe, _) = self.want_str(file, scope);
self.reject_com_device(file, *pos);
let (ne, nt) = self.want_num(number, scope);
@@ -1779,12 +1795,67 @@ impl Sema {
self.push(
out,
scope,
HStmtKind::BuiltinStmt { b: Builtin::ViewPrint, args: a },
HStmtKind::BuiltinStmt {
b: Builtin::ViewPrint,
args: a,
},
);
}
Stmt::EventControl { device, index, action, pos } => {
Stmt::TrapDef {
device,
index,
target,
pos,
} => {
let Some(art) = trap_art(device) else {
// COM/PEN/PLAY/STRIG sind Non-Feature — namentlich.
self.err(*pos, "Feature unavailable");
return;
};
// Wertebereich prüfen, soweit die Kennung konstant ist.
if let Some(i) = index {
if let Some(n) = const_zahl(i) {
if !trap_bereich_ok(art, n) {
self.err(*pos, trap_bereich_text(art));
}
}
} else if art != TRAP_UEVENT {
self.err(*pos, trap_bereich_text(art));
}
if index.is_some() && art == TRAP_UEVENT {
self.err(*pos, "ON UEVENT nimmt keine Kennung");
}
let hindex = match index {
Some(i) => self.lower_num_as(i, scope, NumTy::Lng),
None => HExpr::Lng(0),
};
// `GOSUB 0` schaltet den Trap ab und meint nicht Zeile 0.
let ziel = if matches!(target, LabelRef::Line(0)) {
None
} else {
match self.label_id(target, scope, *pos) {
Some(id) => Some(id),
None => return,
}
};
self.push(
out,
scope,
HStmtKind::TrapDef {
art,
index: hindex,
ziel,
},
);
}
Stmt::EventControl {
device,
index,
action,
pos,
} => {
match device.as_str() {
"TIMER" | "KEY" | "UEVENT" | "EVENT" => {}
"TIMER" | "KEY" | "UEVENT" | "SIGNAL" | "EVENT" => {}
_ => self.err(*pos, "Feature unavailable"),
}
if let Some(i) = index {
@@ -1805,7 +1876,45 @@ impl Sema {
);
return;
}
self.push(out, scope, HStmtKind::Unsupported("Ereignissteuerung"));
if device == "EVENT" {
// Das Vorbild kennt nur ON und OFF — kein STOP.
if *action == EventAction::Stop {
self.err(*pos, "EVENT kennt nur ON und OFF");
return;
}
let an = *action == EventAction::On;
self.push(out, scope, HStmtKind::EventSwitch(an));
return;
}
let Some(art) = trap_art(device) else {
self.err(*pos, "Feature unavailable");
return;
};
if let Some(i) = index {
if let Some(n) = const_zahl(i) {
if !trap_bereich_ok(art, n) {
self.err(*pos, trap_bereich_text(art));
}
}
}
let hindex = match index {
Some(i) => self.lower_num_as(i, scope, NumTy::Lng),
None => HExpr::Lng(0),
};
let zustand = match action {
EventAction::On => 0,
EventAction::Off => 1,
EventAction::Stop => 2,
};
self.push(
out,
scope,
HStmtKind::TrapSet {
art,
index: hindex,
zustand,
},
);
}
}
}
@@ -2343,10 +2452,22 @@ impl Sema {
let lowered = self.check_and_lower_builtin_args(name, args, min, max, spec, scope, pos);
match name {
"BEEP" => self.push(out, scope, HStmtKind::BuiltinStmt { b: Builtin::Beep, args: vec![] }),
"DOEVENTS" => {
self.push(out, scope, HStmtKind::BuiltinStmt { b: Builtin::Doevents, args: vec![] })
}
"BEEP" => self.push(
out,
scope,
HStmtKind::BuiltinStmt {
b: Builtin::Beep,
args: vec![],
},
),
"DOEVENTS" => self.push(
out,
scope,
HStmtKind::BuiltinStmt {
b: Builtin::Doevents,
args: vec![],
},
),
"RANDOMIZE" => {
let a = lowered
.into_iter()
@@ -2494,19 +2615,29 @@ impl Sema {
self.push(out, scope, HStmtKind::BuiltinStmt { b, args: a });
}
// ---- ISAM ------------------------------------------------------
"CREATEINDEX" | "DELETEINDEX" | "SETINDEX" | "INSERT" | "RETRIEVE"
| "UPDATE" | "DELETE" | "DELETETABLE" | "MOVEFIRST" | "MOVELAST"
| "MOVENEXT" | "MOVEPREVIOUS" | "SEEKEQ" | "SEEKGT" | "SEEKGE"
| "BEGINTRANS" | "COMMITTRANS" | "ROLLBACK" => {
"CREATEINDEX" | "DELETEINDEX" | "SETINDEX" | "INSERT" | "RETRIEVE" | "UPDATE"
| "DELETE" | "DELETETABLE" | "MOVEFIRST" | "MOVELAST" | "MOVENEXT" | "MOVEPREVIOUS"
| "SEEKEQ" | "SEEKGT" | "SEEKGE" | "BEGINTRANS" | "COMMITTRANS" | "ROLLBACK" => {
self.lower_isam_stmt(name, args, lowered, scope, out)
}
"SETUEVENT" => self.push(
out,
scope,
HStmtKind::BuiltinStmt {
b: Builtin::SetUEvent,
args: vec![],
},
),
// Bildschirm-/Datei-/System-Anweisungen späterer Phasen.
_ => self.push(out, scope, HStmtKind::Unsupported(match name {
"RUN" => "RUN",
"MSGBOX" => "MSGBOX",
"SETUEVENT" => "SETUEVENT",
_ => "Anweisung",
})),
_ => self.push(
out,
scope,
HStmtKind::Unsupported(match name {
"RUN" => "RUN",
"MSGBOX" => "MSGBOX",
_ => "Anweisung",
}),
),
}
}
@@ -2897,9 +3028,12 @@ impl Sema {
let (re, rt) = self.lower_expr(rhs, scope);
self.lower_binary(*op, le, lt, re, rt, *pos)
}
Expr::Name { name, suffix, args, pos } => {
self.lower_name_expr(name, suffix, args, *pos, scope)
}
Expr::Name {
name,
suffix,
args,
pos,
} => self.lower_name_expr(name, suffix, args, *pos, scope),
}
}
@@ -3742,6 +3876,7 @@ fn stmt_pos(stmt: &Stmt) -> SourcePos {
| Stmt::LockStmt { pos, .. }
| Stmt::NameStmt { pos, .. }
| Stmt::ViewPrint { pos, .. }
| Stmt::TrapDef { pos, .. }
| Stmt::EventControl { pos, .. }
| Stmt::Include { pos, .. }
| Stmt::MetaArrays { pos, .. }
@@ -4082,3 +4217,52 @@ mod tests {
);
}
}
// ---- Ereignis-Traps (Sprachreferenz §8) -------------------------------------
pub const TRAP_KEY: u8 = 0;
pub const TRAP_TIMER: u8 = 1;
pub const TRAP_UEVENT: u8 = 2;
pub const TRAP_SIGNAL: u8 = 3;
/// Quellenname → Art. `None` für die Non-Features.
fn trap_art(device: &str) -> Option<u8> {
Some(match device {
"KEY" => TRAP_KEY,
"TIMER" => TRAP_TIMER,
"UEVENT" => TRAP_UEVENT,
"SIGNAL" => TRAP_SIGNAL,
_ => return None,
})
}
/// Konstanter Zahlenwert eines Ausdrucks, soweit direkt ablesbar.
fn const_zahl(e: &Expr) -> Option<i64> {
match e {
Expr::IntLit(n) => Some(*n as i64),
Expr::LongLit(n) => Some(*n as i64),
Expr::SingleLit(n) => Some(*n as i64),
Expr::DoubleLit(n) => Some(*n as i64),
Expr::Paren(inner) => const_zahl(inner),
_ => None,
}
}
/// Wertebereiche nach der Original-Hilfe (Belege in `umfang-und-form.md`).
fn trap_bereich_ok(art: u8, n: i64) -> bool {
match art {
TRAP_KEY => matches!(n, 0..=25 | 30 | 31),
TRAP_TIMER => (1..=86_400).contains(&n),
TRAP_SIGNAL => (1..=2).contains(&n),
_ => true,
}
}
fn trap_bereich_text(art: u8) -> &'static str {
match art {
TRAP_KEY => "ON KEY: Kennung 0, 1-25 oder 30-31 erwartet",
TRAP_TIMER => "ON TIMER: Intervall 1 bis 86400 Sekunden erwartet",
TRAP_SIGNAL => "ON SIGNAL: Kennung 1 (SIGINT) oder 2 (SIGTERM) erwartet",
_ => "Kennung erwartet",
}
}