Phase 4: Steuerelemente implementieren
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user