Phase 4: Steuerelemente implementieren

This commit is contained in:
2026-09-05 12:49:46 +02:00
parent 1cb6ae8fd9
commit 19804e0e2d
36 changed files with 6472 additions and 689 deletions

View File

@@ -302,6 +302,7 @@ instrs! {
0xA4 RetGosub;
0xA5 RetGosubTo(a: u32);
0xA6 OnJump(a: u16, b: bool); // Sprungtabelle, gosub?
0xA7 Run(a: u8); // 0 = ohne Ziel, 1 = Zeile, 2 = Datei
// 0xB0 — Prozeduren und Builtins
0xB0 Call(a: u16, b: u8);
@@ -316,6 +317,9 @@ instrs! {
0xB9 ObjectLoad(a: u16, b: bool, c: bool); // unload?, Index liegt auf Stack?
0xBA LoadDynamicObjectProperty(a: u16);
0xBB StoreDynamicObjectProperty(a: u16);
0xBC LoadObjectIndexedProperty(a: u16, b: u16);
0xBD ObjectMethodFn(a: u16, b: u16, c: u8);
0xBE StoreObjectIndexedProperty(a: u16, b: u16);
// 0xE0 — Ereignis-Traps (Sprachreferenz §8); Kennung vom Stack
0xE0 TrapDefine(a: u8, b: u32); // Quellenart, Sprungziel

View File

@@ -20,6 +20,10 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
string_ids: HashMap::new(),
jump_tables: Vec::new(),
modul_label_pc: Vec::new(),
initialize_main: hir
.objects
.iter()
.any(|object| object.class == tb_frontend::forms::ObjectClass::Form),
};
let mut procs = Vec::new();
for proc in &hir.procs {
@@ -29,7 +33,7 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
name: hir.name.clone(),
option_base: hir.option_base,
strings: cg.strings,
globals_init: hir.globals.iter().map(|g| slot_init(g)).collect(),
globals_init: hir.globals.iter().map(slot_init).collect(),
global_names: hir.globals.iter().map(|g| g.name.clone()).collect(),
udts: hir
.udts
@@ -85,6 +89,9 @@ struct Codegen {
/// `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>>,
/// Formulare müssen erst nach den globalen DIM-Anweisungen Ereignisse
/// zustellen; reine Textprogramme behalten ihre bisherigen Grenzen.
initialize_main: bool,
}
struct ProcCtx {
@@ -160,7 +167,30 @@ impl Codegen {
fixups: Vec::new(),
table_fixups: Vec::new(),
};
if proc.kind == hir::HProcKind::Main {
let declarations = proc
.body
.iter()
.filter(|stmt| matches!(stmt.kind, HStmtKind::Dim { redim: false, .. }))
.collect::<Vec<_>>();
for stmt in declarations {
self.stmt_inner(&mut ctx, proc, stmt, false);
}
if self.initialize_main
|| proc
.body
.iter()
.any(|stmt| matches!(stmt.kind, HStmtKind::Dim { redim: false, .. }))
{
ctx.emit(Instr::Stmt(0));
}
}
for stmt in &proc.body {
if proc.kind == hir::HProcKind::Main
&& matches!(stmt.kind, HStmtKind::Dim { redim: false, .. })
{
continue;
}
self.stmt(&mut ctx, proc, stmt);
}
// Rumpfende
@@ -234,12 +264,17 @@ impl Codegen {
// ---- Anweisungen -------------------------------------------------------
fn stmt(&mut self, ctx: &mut ProcCtx, proc: &hir::HProc, stmt: &HStmt) {
self.stmt_inner(ctx, proc, stmt, true);
}
fn stmt_inner(&mut self, ctx: &mut ProcCtx, proc: &hir::HProc, stmt: &HStmt, boundary: bool) {
match &stmt.kind {
HStmtKind::Label(l) => {
ctx.bind(*l);
return;
}
_ => ctx.emit(Instr::Stmt(stmt.line)),
_ if boundary => ctx.emit(Instr::Stmt(stmt.line)),
_ => {}
}
match &stmt.kind {
HStmtKind::Label(_) => unreachable!(),
@@ -263,6 +298,23 @@ impl Codegen {
index.is_some(),
));
}
HStmtKind::SetObjectIndexedProperty {
object,
object_index,
property,
index,
value,
} => {
if let Some(object_index) = object_index {
self.expr(ctx, object_index);
}
self.expr(ctx, index);
self.expr(ctx, value);
ctx.emit(Instr::StoreObjectIndexedProperty(
*object,
*property | if object_index.is_some() { 0x8000 } else { 0 },
));
}
HStmtKind::SetDynamicObjectProperty {
object,
property,
@@ -275,13 +327,21 @@ impl Codegen {
}
HStmtKind::ObjectMethod {
object,
index,
method,
args,
} => {
if let Some(index) = index {
self.expr(ctx, index);
}
for arg in args {
self.expr(ctx, arg);
}
ctx.emit(Instr::ObjectMethod(*object, *method, args.len() as u8));
ctx.emit(Instr::ObjectMethod(
*object,
*method,
args.len() as u8 | if index.is_some() { 0x80 } else { 0 },
));
}
HStmtKind::ObjectLoad {
object,
@@ -509,6 +569,18 @@ impl Codegen {
None => ctx.emit(Instr::RetGosub),
Some(l) => ctx.emit_jump(Instr::RetGosubTo(0), *l),
},
HStmtKind::Run { target, string } => {
if let Some(target) = target {
self.expr(ctx, target);
}
ctx.emit(Instr::Run(if target.is_none() {
0
} else if *string {
2
} else {
1
}));
}
HStmtKind::ExitProc => self.emit_proc_exit(ctx, proc),
HStmtKind::CallSub { proc: id, args } => {
for a in args {
@@ -533,6 +605,9 @@ impl Codegen {
}
let id = builtin_id(*b);
ctx.emit(Instr::CallBuiltin(id, args.len() as u8));
if matches!(b, Builtin::MsgBox) {
ctx.emit(Instr::Pop);
}
if builtin_returns_value(*b) {
ctx.emit(Instr::Pop);
}
@@ -725,6 +800,41 @@ impl Codegen {
index.is_some(),
));
}
HExpr::ObjectIndexedProperty {
object,
object_index,
property,
index,
..
} => {
if let Some(object_index) = object_index {
self.expr(ctx, object_index);
}
self.expr(ctx, index);
ctx.emit(Instr::LoadObjectIndexedProperty(
*object,
*property | if object_index.is_some() { 0x8000 } else { 0 },
));
}
HExpr::ObjectMethodCall {
object,
index,
method,
args,
..
} => {
if let Some(index) = index {
self.expr(ctx, index);
}
for arg in args {
self.expr(ctx, arg);
}
ctx.emit(Instr::ObjectMethodFn(
*object,
*method,
args.len() as u8 | if index.is_some() { 0x80 } else { 0 },
));
}
HExpr::DynamicObjectProperty { object, property } => {
self.expr(ctx, object);
let property = self.pool(property);
@@ -1173,6 +1283,9 @@ fn builtin_id(b: Builtin) -> u16 {
Builtin::Width => ids::WIDTH,
Builtin::ViewPrint => ids::VIEW_PRINT,
Builtin::ScreenStmt => ids::SCREEN_STMT,
Builtin::GraphicsLine => ids::GRAPHICS_LINE,
Builtin::GraphicsPaint => ids::GRAPHICS_PAINT,
Builtin::GraphicsView => ids::GRAPHICS_VIEW,
Builtin::KeyAssign => ids::KEY_ASSIGN,
Builtin::KeyList => ids::KEY_LIST,
Builtin::KeyDisplay => ids::KEY_DISPLAY,
@@ -1277,6 +1390,10 @@ fn builtin_id(b: Builtin) -> u16 {
Builtin::IsamSavepoint => ids::ISAM_SAVEPOINT,
Builtin::IsamSetmem => ids::ISAM_SETMEM,
Builtin::IsamBof => ids::ISAM_BOF,
Builtin::MsgBox => ids::MSGBOX,
Builtin::InputBoxS => ids::INPUTBOX_S,
Builtin::ClipboardAdd => ids::CLIPBOARD_ADD,
Builtin::ClipboardGet => ids::CLIPBOARD_GET,
}
}

View File

@@ -10,11 +10,11 @@
//! - `GOSUB`-Stack pro Frame; `RETURN` ohne GOSUB → Fehler 3.
use crate::bytecode::{CmpOp, CompiledModule, Instr};
use std::collections::HashSet;
use std::collections::{HashSet, VecDeque};
use std::rc::Rc;
use tb_runtime::builtins::{builtin_table, RtState};
use tb_runtime::builtins::{builtin_table, ids, RtState};
use tb_runtime::errors::RuntimeError;
use tb_runtime::host::Host;
use tb_runtime::host::{Ereignis, Host};
use tb_runtime::traps::Quelle;
use tb_runtime::value::{self, default_value, ArrayObj, RecordObj, TypeInit, Value, VarRef};
use tb_ui::forms::{FormEvent, FormsModel, PropertyValue, ShowResult};
@@ -37,6 +37,10 @@ pub enum RunEvent {
Interrupted {
line: u32,
},
Restart {
program: Option<String>,
line: Option<u32>,
},
/// Unbehandelter Laufzeitfehler.
Error {
code: u16,
@@ -106,6 +110,7 @@ pub struct Vm {
tick_zaehler: u32,
breakpoints: HashSet<u32>,
data_ptr: usize,
start_pc: Option<usize>,
pub forms: FormsModel,
}
@@ -166,6 +171,7 @@ impl Vm {
tick_zaehler: 0,
breakpoints: HashSet::new(),
data_ptr: 0,
start_pc: None,
forms,
module,
};
@@ -176,6 +182,24 @@ impl Vm {
vm
}
pub fn start_at_line(&mut self, line: u32) -> Result<(), RuntimeError> {
let target = self.module.procs[0]
.code
.iter()
.position(|instruction| matches!(instruction, Instr::Stmt(found) if *found == line));
let target = target.ok_or(RuntimeError(8))?;
if self.module.procs[0]
.code
.iter()
.any(|instruction| matches!(instruction, Instr::Stmt(0)))
{
self.start_pc = Some(target);
} else {
self.frames[0].pc = target;
}
Ok(())
}
fn push_frame(&mut self, proc: usize, argc: usize) {
// Argumente liegen zuoberst auf dem Stack (links → rechts).
let locals_base = self.locals.len();
@@ -204,6 +228,238 @@ impl Vm {
});
}
fn external_arg(&self, value: &Value) -> Result<Value, RuntimeError> {
match value {
Value::Ref(reference) => self.read_ref(reference),
value => Ok(value.clone()),
}
}
fn external_string(&self, value: &Value) -> Result<String, RuntimeError> {
match self.external_arg(value)? {
Value::Str(text) => Ok(text.to_string()),
_ => Err(RuntimeError::TYPE_MISMATCH),
}
}
fn external_set(
&mut self,
args: &[Value],
index: usize,
value: Value,
) -> Result<(), RuntimeError> {
match args.get(index) {
Some(Value::Ref(reference)) => self.write_ref(reference, value),
_ => Ok(()),
}
}
fn dialog_integer(&self, value: &Value) -> Result<i32, RuntimeError> {
match self.external_arg(value)? {
Value::Int(value) => Ok(value as i32),
Value::Lng(value) => Ok(value),
_ => Err(RuntimeError::TYPE_MISMATCH),
}
}
fn take_dialog_input(&mut self) -> VecDeque<Ereignis> {
let mut events = VecDeque::new();
events.extend(
self.rt
.tasten
.drain(..)
.map(|(key, shift)| Ereignis::Taste(key, shift)),
);
events.extend(self.rt.maus.drain(..).map(Ereignis::Maus));
events
}
fn restore_dialog_input(&mut self, events: VecDeque<Ereignis>) {
for event in events {
match event {
Ereignis::Taste(key, shift) => self.rt.tasten.push_back((key, shift)),
Ereignis::Maus(event) => self.rt.maus.push_back(event),
Ereignis::Abbruch => self.rt.abbruch = true,
Ereignis::Ende => self.rt.ende = true,
Ereignis::Signal(number) => {
if number == 1
&& !self
.rt
.traps
.melden(tb_runtime::traps::Quelle::Signal(number))
{
self.rt.abbruch = true;
}
}
Ereignis::Groesse { cols, rows } => self.rt.screen.resize(cols, rows),
}
}
}
fn forms_dialog(
&mut self,
id: u16,
args: &[Value],
host: &mut dyn Host,
) -> Result<Option<Value>, RuntimeError> {
match id {
ids::MSGBOX => {
let text =
self.external_string(args.first().ok_or(RuntimeError::TYPE_MISMATCH)?)?;
let kind = args
.get(1)
.map_or(Ok(0), |value| self.dialog_integer(value))?;
let title = args
.get(2)
.map_or_else(|| Ok(String::new()), |value| self.external_string(value))?;
let mut queued = self.take_dialog_input();
let result = tb_ui::forms::msgbox_dialog(
&mut self.rt.screen,
host,
&mut queued,
&text,
kind,
&title,
);
self.restore_dialog_input(queued);
result.map(|value| Some(Value::Int(value)))
}
ids::INPUTBOX_S => {
let prompt =
self.external_string(args.first().ok_or(RuntimeError::TYPE_MISMATCH)?)?;
let title = args
.get(1)
.map_or_else(|| Ok(String::new()), |value| self.external_string(value))?;
let initial = args
.get(2)
.map_or_else(|| Ok(String::new()), |value| self.external_string(value))?;
let position = match (args.get(3), args.get(4)) {
(None, None) => None,
(Some(x), Some(y)) => Some((self.dialog_integer(x)?, self.dialog_integer(y)?)),
_ => return Err(RuntimeError::TYPE_MISMATCH),
};
let mut queued = self.take_dialog_input();
let result = tb_ui::forms::inputbox_dialog(
&mut self.rt.screen,
host,
&mut queued,
&prompt,
&title,
&initial,
position,
);
self.restore_dialog_input(queued);
result.map(|value| Some(Value::Str(Rc::from(value))))
}
_ => Err(RuntimeError::FEATURE_UNAVAILABLE),
}
}
fn external_dialog(
&mut self,
proc: usize,
argc: usize,
host: &mut dyn Host,
) -> Result<bool, RuntimeError> {
if !matches!(self.module.procs[proc].code.as_slice(), [Instr::RetProc]) {
return Ok(false);
}
let name = self.module.procs[proc].name.to_ascii_uppercase();
if !matches!(
name.as_str(),
"CMNDLGREGISTER"
| "CMNDLGCLOSE"
| "ABOUT"
| "FILEOPEN"
| "FILESAVE"
| "FILEPRINT"
| "FINDTEXT"
| "CHANGETEXT"
| "COLORPALETTE"
) {
return Ok(false);
}
let mut args = self.stack.split_off(self.stack.len().saturating_sub(argc));
match name.as_str() {
"CMNDLGREGISTER" => self.external_set(&args, 0, Value::Int(-1))?,
"CMNDLGCLOSE" => {}
"ABOUT" => {
let dialog = vec![
self.external_arg(&args[0])?,
Value::Lng(0),
Value::Str(Rc::from("About")),
];
self.forms_dialog(ids::MSGBOX, &dialog, host)?;
}
"FILEOPEN" | "FILESAVE" => {
let file = self.external_string(&args[0])?;
let path = self.external_string(&args[1])?;
let title = if name == "FILEOPEN" {
"Open file"
} else {
"Save file"
};
let default = if path.is_empty() {
file
} else {
format!("{path}{sep}{file}", sep = std::path::MAIN_SEPARATOR)
};
let dialog = vec![
Value::Str(Rc::from(title)),
Value::Str(Rc::from(title)),
Value::Str(Rc::from(default)),
];
let result = self
.forms_dialog(ids::INPUTBOX_S, &dialog, host)?
.unwrap_or(Value::Str(Rc::from("")));
let Value::Str(result) = result else {
return Err(RuntimeError::TYPE_MISMATCH);
};
let result = result.to_string();
if result.is_empty() {
self.external_set(&args, 7, Value::Int(-1))?;
} else {
let (path, file) = result
.rsplit_once(['/', '\\'])
.map_or(("", result.as_str()), |(path, file)| (path, file));
self.external_set(&args, 0, Value::Str(Rc::from(file)))?;
self.external_set(&args, 1, Value::Str(Rc::from(path)))?;
self.external_set(&args, 7, Value::Int(0))?;
}
}
"FILEPRINT" => {
self.external_set(&args, 0, Value::Int(1))?;
self.external_set(&args, 3, Value::Int(0))?;
}
"FINDTEXT" | "CHANGETEXT" => {
let dialog = vec![
Value::Str(Rc::from(if name == "FINDTEXT" {
"Find text"
} else {
"Replacement text"
})),
Value::Str(Rc::from("Find")),
self.external_arg(&args[usize::from(name == "CHANGETEXT")])?,
];
let result = self
.forms_dialog(ids::INPUTBOX_S, &dialog, host)?
.unwrap_or(Value::Str(Rc::from("")));
let target = usize::from(name == "CHANGETEXT");
self.external_set(&args, target, result.clone())?;
let empty = matches!(&result, Value::Str(text) if text.is_empty());
self.external_set(&args, args.len() - 1, Value::Int(-i16::from(empty)))?;
}
"COLORPALETTE" => {
let color = self.external_arg(&args[0])?;
self.external_set(&args, 0, color)?;
self.external_set(&args, 3, Value::Int(0))?;
}
_ => unreachable!(),
}
args.clear();
Ok(true)
}
pub fn queue_form_event(&mut self, event: FormEvent) {
self.forms.queue(event);
}
@@ -221,6 +477,17 @@ impl Vm {
}
}
fn form_arg(v: Value) -> Result<PropertyValue, RuntimeError> {
Ok(match v {
Value::Int(v) => PropertyValue::Integer(v as i32),
Value::Lng(v) => PropertyValue::Integer(v),
Value::Sng(v) => PropertyValue::Single(v),
Value::Str(v) => PropertyValue::String(v.to_string()),
Value::Obj(object, index) => PropertyValue::Object(Some((object, index))),
_ => return Err(RuntimeError::TYPE_MISMATCH),
})
}
fn property_value(
&self,
object: u16,
@@ -322,36 +589,37 @@ impl Vm {
Ok(())
}
fn forms_zustellen(&mut self) -> bool {
fn tick(&mut self, host: &mut dyn Host) {
self.forms.render(&mut self.rt.screen);
if self.forms.menu_is_open() {
host.present(&self.rt.screen);
self.rt.pump(host, false);
} else {
self.rt.tick(host);
}
}
fn forms_zustellen(&mut self, host: &mut dyn Host) -> bool {
self.forms
.resize(self.rt.screen.cols(), self.rt.screen.rows());
while let Some(m) = self.rt.maus.pop_front() {
if let Some(object) = self.forms.active_form() {
let name = match m.art {
tb_runtime::host::MausArt::Druck => "MOUSEDOWN",
tb_runtime::host::MausArt::Loslassen => "MOUSEUP",
tb_runtime::host::MausArt::Bewegung => "MOUSEMOVE",
};
self.forms.queue(FormEvent {
object,
array_index: None,
name: name.into(),
args: vec![
PropertyValue::Integer(m.taste as i32),
PropertyValue::Integer(m.shift as i32),
PropertyValue::Single(m.spalte as f32),
PropertyValue::Single(m.zeile as f32),
],
});
self.forms.timers(host.jetzt_ms());
if self.forms.active_form().is_some() {
while let Some((key, shift)) = self.rt.tasten.pop_front() {
self.forms.handle_key(&key, shift);
}
}
let now_ms = host.jetzt_ms();
while let Some(m) = self.rt.maus.pop_front() {
self.forms.handle_mouse_at(m, now_ms);
}
self.forms.render(&mut self.rt.screen);
self.dispatch_next_form_event()
}
/// Anstehendes Ereignis zustellen, falls eines fällig ist. Liefert
/// `true`, wenn ein Handler aufgesetzt wurde.
fn zustellen(&mut self, host: &mut dyn Host) -> bool {
if !self.rt.traps.aktiv() {
if self.forms.menu_is_open() || !self.rt.traps.aktiv() {
return false;
}
let jetzt = host.jetzt_ms();
@@ -377,7 +645,7 @@ impl Vm {
// virtuelle nie vorrückt.
let real_start = std::time::Instant::now();
loop {
self.rt.tick(host);
self.tick(host);
if self.zustellen(host) {
return;
}
@@ -531,14 +799,41 @@ impl Vm {
// ---- Hauptschleife --------------------------------------------------------
/// Hält modellose Formulare nach dem Ende des Modulrumpfs bedienbar.
/// Ereignisprozeduren laufen weiter auf derselben VM und können das
/// Formular schließen, ein anderes Programm starten oder einen Fehler
/// auslösen.
pub fn run_visible_forms(&mut self, host: &mut dyn Host) -> RunEvent {
while self.forms.has_visible_forms() {
self.tick(host);
let dispatched = self.forms_zustellen(host);
if dispatched {
match self.run(host) {
RunEvent::Ended => {}
event => return event,
}
}
if self.rt.ende {
return RunEvent::Ended;
}
if self.rt.abbruch {
return RunEvent::Interrupted {
line: self.current_line(),
};
}
std::thread::sleep(std::time::Duration::from_millis(1));
}
RunEvent::Ended
}
pub fn run(&mut self, host: &mut dyn Host) -> RunEvent {
loop {
if let Some(form) = self.frames.last().and_then(|f| f.waiting_form) {
if !self.forms.is_visible(form) {
self.frames.last_mut().unwrap().waiting_form = None;
} else {
self.rt.tick(host);
self.forms_zustellen();
self.tick(host);
self.forms_zustellen(host);
if self.rt.ende {
return RunEvent::Ended;
}
@@ -818,6 +1113,11 @@ impl Vm {
use Instr as I;
match instr {
I::Stmt(line) => {
if line == 0 {
if let Some(target) = self.start_pc.take() {
self.frames[0].pc = target;
}
}
let f = self.frames.last_mut().unwrap();
f.line = line;
f.last_stmt_pc = pc;
@@ -826,11 +1126,12 @@ impl Vm {
// 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.forms.render(&mut self.rt.screen);
if self.rt.screen.ist_veraendert() || self.tick_zaehler.is_multiple_of(1024) {
self.tick(host);
self.rt.screen.veraenderung_quittieren();
}
if self.forms_zustellen() {
if self.forms_zustellen(host) {
return Ok(Flow::Normal);
}
// Ereigniszustellung (design.md, D1): nur wenn überhaupt
@@ -841,7 +1142,7 @@ impl Vm {
// Auflösung eines Zeit-Traps ist damit 64 Anweisungen;
// reicht das nicht, wird daraus ein eigener Zähler je
// Trap.
if self.tick_zaehler % 64 == 0 {
if self.tick_zaehler.is_multiple_of(64) {
let jetzt = host.jetzt_ms();
self.rt.traps.zeit_pruefen(jetzt);
}
@@ -901,8 +1202,8 @@ impl Vm {
// bekommt seinen eigenen Stapelabschnitt darüber und
// lässt den Wert unberührt.
self.stack.push(Value::Int(0));
self.rt.tick(host);
self.forms_zustellen();
self.tick(host);
self.forms_zustellen(host);
self.zustellen(host);
Ok(Flow::Normal)
}
@@ -1051,6 +1352,56 @@ impl Vm {
self.forms.set_at(object, index, property, value)?;
Ok(Flow::Normal)
}
I::LoadObjectIndexedProperty(object, property) => {
let index = self.pop_i32()?;
let object_index = if property & 0x8000 != 0 {
Some(self.pop_i32()?)
} else {
None
};
let value =
self.forms
.get_indexed_at(object, object_index, property & 0x7fff, index)?;
self.push(Self::form_value(value));
Ok(Flow::Normal)
}
I::StoreObjectIndexedProperty(object, property) => {
let value = self.pop_i32()?;
let index = self.pop_i32()?;
let object_index = if property & 0x8000 != 0 {
Some(self.pop_i32()?)
} else {
None
};
self.forms
.set_indexed_at(object, object_index, property & 0x7fff, index, value)?;
Ok(Flow::Normal)
}
I::ObjectMethodFn(object, method, argc) => {
let indexed = argc & 0x80 != 0;
let argc = argc & 0x7f;
let class = self
.module
.objects
.get(object as usize)
.ok_or(RuntimeError(420))?
.class;
let name = *tb_frontend::forms::methods(class)
.get(method as usize)
.ok_or(RuntimeError(421))?;
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(Self::form_arg(self.pop()?)?);
}
args.reverse();
let index = if indexed { Some(self.pop_i32()?) } else { None };
let value = self
.forms
.object_method_at(object, index, name, args)?
.ok_or(RuntimeError(421))?;
self.push(Self::form_value(value));
Ok(Flow::Normal)
}
I::TypeOf(class) => {
let matches = match self.pop()? {
Value::Obj(object, _) => self
@@ -1064,6 +1415,8 @@ impl Vm {
Ok(Flow::Normal)
}
I::ObjectMethod(object, method, argc) => {
let indexed = argc & 0x80 != 0;
let argc = argc & 0x7f;
let class = self
.module
.objects
@@ -1093,6 +1446,7 @@ impl Vm {
args.push(self.pop()?);
}
args.reverse();
let index = if indexed { Some(self.pop_i32()?) } else { None };
if resumed_show && !self.forms.is_loaded(object) {
return Ok(Flow::Normal);
}
@@ -1118,13 +1472,26 @@ impl Vm {
(tb_frontend::forms::ObjectClass::Form, "UNLOAD") => {
self.request_unload(object)?
}
(tb_frontend::forms::ObjectClass::Form, "PRINTFORM") => {
self.forms.render(&mut self.rt.screen);
self.rt
.print
.drucker
.push_str(&tb_runtime::snapshot::text(&self.rt.screen));
}
(tb_frontend::forms::ObjectClass::Screen, "SHOW") => {
self.forms.screen_show(true)
}
(tb_frontend::forms::ObjectClass::Screen, "HIDE") => {
self.forms.screen_show(false)
}
_ => return Err(RuntimeError::FEATURE_UNAVAILABLE),
_ => {
let args = args
.into_iter()
.map(Self::form_arg)
.collect::<Result<Vec<_>, _>>()?;
self.forms.object_method_at(object, index, name, args)?;
}
}
self.dispatch_next_form_event();
Ok(Flow::Normal)
@@ -1318,7 +1685,7 @@ impl Vm {
s.chars().take(n).collect()
} else {
let mut t = s.to_string();
t.extend(std::iter::repeat(' ').take(n - len));
t.extend(std::iter::repeat_n(' ', n - len));
t
};
self.push(Value::Str(Rc::from(fixed.as_str())));
@@ -1714,9 +2081,27 @@ impl Vm {
}
Ok(Flow::Normal)
}
I::Run(kind) => {
let (program, line) = match kind {
0 => (None, None),
1 => {
let line = self.pop_i32()?;
if line < 0 {
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
}
(None, Some(line as u32))
}
2 => (Some(self.pop_str()?.to_string()), None),
_ => return Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
};
Ok(Flow::Event(RunEvent::Restart { program, line }))
}
// ---- Prozeduren ----
I::Call(proc, argc) => {
if self.external_dialog(proc as usize, argc as usize, host)? {
return Ok(Flow::Normal);
}
self.push_frame(proc as usize, argc as usize);
Ok(Flow::Normal)
}
@@ -1754,6 +2139,12 @@ impl Vm {
args.push(self.pop()?);
}
args.reverse();
if matches!(id, ids::MSGBOX | ids::INPUTBOX_S) {
if let Some(value) = self.forms_dialog(id, &args, host)? {
self.push(value);
}
return Ok(Flow::Normal);
}
let f = builtin_table()[id as usize];
match f(&mut self.rt, host, &mut args) {
Ok(Some(v)) => {
@@ -2228,9 +2619,7 @@ fn cur_mul(a: i64, b: i64) -> Result<i64, RuntimeError> {
let r = p.rem_euclid(10_000);
let rounded = if r > 5_000 {
q + 1
} else if r < 5_000 {
q
} else if q % 2 == 0 {
} else if r < 5_000 || q % 2 == 0 {
q
} else {
q + 1