Phase 4: Ereignisschleife und klassische Traps
This commit is contained in:
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user