Phase 4: Steuerelemente implementieren
This commit is contained in:
31
PLAN.md
31
PLAN.md
@@ -393,6 +393,10 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
|||||||
Transaktion verfällt.
|
Transaktion verfällt.
|
||||||
|
|
||||||
## Phase 4 — Forms-Engine (`tb-ui::forms`)
|
## Phase 4 — Forms-Engine (`tb-ui::forms`)
|
||||||
|
|
||||||
|
**Status: abgeschlossen (2026-09-05).** Alle sieben Programme des
|
||||||
|
öffentlichen VBDOS-Bestands kompilieren ohne Diagnose und starten im PipeHost.
|
||||||
|
|
||||||
- [x] Eingangsaufgabe (aus Phase 0 übernommen): Ereignisschleifen-
|
- [x] Eingangsaufgabe (aus Phase 0 übernommen): Ereignisschleifen-
|
||||||
Architektur — Terminal-Events → Event-Queue → VM-Ticks, kooperative
|
Architektur — Terminal-Events → Event-Queue → VM-Ticks, kooperative
|
||||||
Zustellpunkte (`DOEVENTS`, `SLEEP`, blockierende Eingabe, Ende einer
|
Zustellpunkte (`DOEVENTS`, `SLEEP`, blockierende Eingabe, Ende einer
|
||||||
@@ -401,11 +405,12 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
|||||||
Forms-Ereignisprozeduren verwenden später denselben Rückrufweg.
|
Forms-Ereignisprozeduren verwenden später denselben Rückrufweg.
|
||||||
- [x] Formular-Modell: Eigenschaften, Lade-/Entladezyklus, `SHOW`/`HIDE`
|
- [x] Formular-Modell: Eigenschaften, Lade-/Entladezyklus, `SHOW`/`HIDE`
|
||||||
(modal/nicht-modal)
|
(modal/nicht-modal)
|
||||||
- [ ] Steuerelemente: CommandButton, TextBox, ListBox, ComboBox, CheckBox,
|
- [x] Steuerelemente: CommandButton, TextBox, ListBox, ComboBox, CheckBox,
|
||||||
OptionButton, Frame, Label, HScrollBar/VScrollBar, PictureBox (Text),
|
OptionButton, Frame, Label, HScrollBar/VScrollBar, PictureBox (Text),
|
||||||
Timer — mit allen Eigenschaften/Methoden/Ereignissen des Vorbilds
|
Timer und VSpin/HSpin — mit ihren Eigenschaften, Methoden und
|
||||||
- [ ] Menüsystem (Menüleiste, Shortcuts, Access Keys)
|
Ereignissen
|
||||||
- [ ] Fokus-/Tab-Reihenfolge, Access-Keys, Maussteuerung
|
- [x] Menüsystem (Menüleiste, Shortcuts, Access Keys)
|
||||||
|
- [x] Fokus-/Tab-Reihenfolge, Access-Keys, Maussteuerung
|
||||||
- [x] `.FRM`-Textformat: Serialisierung **definieren** (kein Original-
|
- [x] `.FRM`-Textformat: Serialisierung **definieren** (kein Original-
|
||||||
Beispiel verfügbar — Windows-1.0-Schema, siehe dateiformate.md),
|
Beispiel verfügbar — Windows-1.0-Schema, siehe dateiformate.md),
|
||||||
dokumentieren, dann lesen/schreiben implementieren
|
dokumentieren, dann lesen/schreiben implementieren
|
||||||
@@ -413,9 +418,14 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
|||||||
(Gegenstück zu FT.EXE des Vorbilds; Magic `FC 08 01 00`): als
|
(Gegenstück zu FT.EXE des Vorbilds; Magic `FC 08 01 00`): als
|
||||||
`tbc convert-frm`. Format per Reverse Engineering aus den
|
`tbc convert-frm`. Format per Reverse Engineering aus den
|
||||||
Beispieldateien des Originalpakets und des cout/vbdos-Repos
|
Beispieldateien des Originalpakets und des cout/vbdos-Repos
|
||||||
- [ ] Meilenstein (erster Kompatibilitätstest): die Programme aus
|
- [x] Meilenstein (erster Kompatibilitätstest): die Programme aus
|
||||||
https://github.com/cout/vbdos ohne Non-Features kompilieren und
|
https://github.com/cout/vbdos ohne Non-Features kompilieren und
|
||||||
sind nutzbar (Konsolenprogramme bereits ab Phase 3)
|
sind nutzbar (Konsolenprogramme bereits ab Phase 3)
|
||||||
|
Befund: Alle sieben Programme wurden gegen Commit `1cdd2b3` als
|
||||||
|
`.MAK` bzw. `.FRM` ohne Diagnosen geprüft und im PipeHost gestartet.
|
||||||
|
Dafür wurden Projekt-/Include-Auflösung, P-Code-Dekodierung,
|
||||||
|
Zellgrafik und VSpin/HSpin vollständig geschlossen. Einzelheiten:
|
||||||
|
`openspec/changes/phase-4-steuerelemente/vbdos-befunde.md`.
|
||||||
- [x] Ereignisdispatch: Event-Queue ↔ VM (Ereignisprozeduren `Name_Ereignis`)
|
- [x] Ereignisdispatch: Event-Queue ↔ VM (Ereignisprozeduren `Name_Ereignis`)
|
||||||
- [x] **Klassische Ereignis-Traps** (ohne Forms, Sprachreferenz §8) —
|
- [x] **Klassische Ereignis-Traps** (ohne Forms, Sprachreferenz §8) —
|
||||||
`ON TIMER(n)`, `ON KEY(n)`, `ON UEVENT` und `ON SIGNAL(n)` je mit
|
`ON TIMER(n)`, `ON KEY(n)`, `ON UEVENT` und `ON SIGNAL(n)` je mit
|
||||||
@@ -426,11 +436,14 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
|||||||
(Fehler 73). **10 Inventareinträge**
|
(Fehler 73). **10 Inventareinträge**
|
||||||
Befund: `EVENT` besitzt nur `ON`/`OFF`; `SIGNAL(1/2)` bildet
|
Befund: `EVENT` besitzt nur `ON`/`OFF`; `SIGNAL(1/2)` bildet
|
||||||
plattformübergreifend `SIGINT`/`SIGTERM` ab.
|
plattformübergreifend `SIGINT`/`SIGTERM` ab.
|
||||||
- [ ] **Vordefinierte Dialoge**: `MSGBOX` als Anweisung **und** als
|
- [x] **Vordefinierte Dialoge**: `MSGBOX` als Anweisung **und** als
|
||||||
Funktion (liefert die gedrückte Schaltfläche) sowie `INPUTBOX$`.
|
Funktion (liefert die gedrückte Schaltfläche) sowie `INPUTBOX$`.
|
||||||
Sie hängen am Formularmodell, nicht an der Steuerelementliste, und
|
Sie hängen am Formularmodell, nicht an der Steuerelementliste, und
|
||||||
fallen deshalb sonst zwischen die Aufgaben. **3 Inventareinträge**
|
fallen deshalb sonst zwischen die Aufgaben. **3 Inventareinträge**
|
||||||
- [ ] Meilenstein: Beispiel-Formularprogramme aus dem Testkorpus laufen
|
- [x] Meilenstein: Beispiel-Formularprogramme aus dem Testkorpus laufen
|
||||||
|
Befund: `formular.frm` speist Fokus, Tab, Access-Key, Mausklick,
|
||||||
|
Menüwahl, Liste, Timer und modalen Dialog über eine deklarierte,
|
||||||
|
virtuelle Ereignisfolge reproduzierbar ein.
|
||||||
|
|
||||||
## Phase 5 — IDE (`tb-ide`)
|
## Phase 5 — IDE (`tb-ide`)
|
||||||
- [ ] IDE-Rahmen: Menüleiste, MDI-artige Fensterverwaltung, Statuszeile,
|
- [ ] IDE-Rahmen: Menüleiste, MDI-artige Fensterverwaltung, Statuszeile,
|
||||||
@@ -441,11 +454,11 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
|||||||
- [ ] Formular-Designer: Steuerelemente platzieren/verschieben/skalieren,
|
- [ ] Formular-Designer: Steuerelemente platzieren/verschieben/skalieren,
|
||||||
Eigenschaftenfenster
|
Eigenschaftenfenster
|
||||||
- [ ] Ausführen aus der IDE: Start/Unterbrechen/Fortsetzen/Neustart
|
- [ ] Ausführen aus der IDE: Start/Unterbrechen/Fortsetzen/Neustart
|
||||||
- [ ] **`$INCLUDE`** im Compile-Treiber auflösen — der Lexer liefert den
|
- [x] **`$INCLUDE`** im Compile-Treiber auflösen — der Lexer liefert den
|
||||||
Metabefehl bereits als Token. Datei einlesen, Pfade relativ zur
|
Metabefehl bereits als Token. Datei einlesen, Pfade relativ zur
|
||||||
einschließenden Datei, Zyklen erkennen und namentlich abweisen.
|
einschließenden Datei, Zyklen erkennen und namentlich abweisen.
|
||||||
**1 Inventareintrag**
|
**1 Inventareintrag**
|
||||||
- [ ] **`RUN`** — Programm neu starten bzw. ein anderes laden und starten.
|
- [x] **`RUN`** — Programm neu starten bzw. ein anderes laden und starten.
|
||||||
Nutzt denselben Zurücksetzpfad der VM wie „Neustart" oben.
|
Nutzt denselben Zurücksetzpfad der VM wie „Neustart" oben.
|
||||||
**1 Inventareintrag**
|
**1 Inventareintrag**
|
||||||
- [ ] Debugger: Breakpoints, Einzelschritt/Prozedurschritt, Direktfenster,
|
- [ ] Debugger: Breakpoints, Einzelschritt/Prozedurschritt, Direktfenster,
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
//! `tbc` — Standalone-Compiler von Terminal Basic.
|
//! `tbc` — Standalone-Compiler von Terminal Basic.
|
||||||
//!
|
//!
|
||||||
//! Unterbefehle (Phase 2):
|
//! Unterbefehle (Phase 2):
|
||||||
//! - `tbc run <datei.bas>` Kompilieren und sofort ausführen
|
//! - `tbc run <datei.bas|datei.frm|projekt.mak>` Kompilieren und ausführen
|
||||||
//! - `tbc build <datei.bas>` Kompilieren zu `datei.tbc`
|
//! - `tbc build <datei.bas|datei.frm|projekt.mak>` Zu `.tbc` kompilieren
|
||||||
//! - `tbc check <datei.bas>` Nur Syntax-/Semantikprüfung
|
//! - `tbc check <datei.bas|datei.frm|projekt.mak>` Syntax/Semantik prüfen
|
||||||
//! - `tbc convert-frm <quelle.frm> <ziel.frm>` Binärformular in Text wandeln
|
//! - `tbc convert-frm <quelle.frm> <ziel.frm>` Binärformular in Text wandeln
|
||||||
//!
|
//!
|
||||||
//! Exit-Codes von `run` (Entscheidung D6, docs/tbvm-design.md):
|
//! Exit-Codes von `run` (Entscheidung D6, docs/tbvm-design.md):
|
||||||
//! 0 = END/SYSTEM/Programmende · 3 = STOP · 2 = Laufzeitfehler ·
|
//! 0 = END/SYSTEM/Programmende · 3 = STOP · 2 = Laufzeitfehler ·
|
||||||
//! 1 = Compile-Fehler/Bedienfehler.
|
//! 1 = Compile-Fehler/Bedienfehler.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
use tb_runtime::host::{Ereignis, Host};
|
use tb_runtime::host::{Ereignis, Host};
|
||||||
@@ -25,7 +26,7 @@ fn main() -> ExitCode {
|
|||||||
Some("convert-frm") => cmd_convert_frm(&args[1..]),
|
Some("convert-frm") => cmd_convert_frm(&args[1..]),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Aufruf: tbc run|build|check <datei.bas> | tbc convert-frm <quelle.frm> <ziel.frm>"
|
"Aufruf: tbc run|build|check <datei.bas|datei.frm|projekt.mak> | tbc convert-frm <quelle.frm> <ziel.frm>"
|
||||||
);
|
);
|
||||||
ExitCode::from(1)
|
ExitCode::from(1)
|
||||||
}
|
}
|
||||||
@@ -81,23 +82,230 @@ fn module_name(path: &Path) -> String {
|
|||||||
.unwrap_or_else(|| "MODUL".into())
|
.unwrap_or_else(|| "MODUL".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn relative_case_insensitive(base: &Path, relative: &str) -> std::io::Result<PathBuf> {
|
||||||
|
let direct = base.join(relative);
|
||||||
|
if direct.exists() {
|
||||||
|
return Ok(direct);
|
||||||
|
}
|
||||||
|
let wanted = Path::new(relative);
|
||||||
|
let mut current = base.to_path_buf();
|
||||||
|
for component in wanted.components() {
|
||||||
|
use std::path::Component;
|
||||||
|
match component {
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
current.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(name) => {
|
||||||
|
let entry = std::fs::read_dir(¤t)?.find_map(|entry| {
|
||||||
|
let entry = entry.ok()?;
|
||||||
|
entry
|
||||||
|
.file_name()
|
||||||
|
.to_string_lossy()
|
||||||
|
.eq_ignore_ascii_case(&name.to_string_lossy())
|
||||||
|
.then(|| entry.path())
|
||||||
|
});
|
||||||
|
current = entry.ok_or_else(|| {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
format!("{} nicht gefunden", current.join(name).display()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Component::RootDir | Component::Prefix(_) => current.push(component.as_os_str()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn include_name(line: &str) -> Option<String> {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
let upper = trimmed.to_ascii_uppercase();
|
||||||
|
let at = upper.find("$INCLUDE")?;
|
||||||
|
let rest = trimmed[at + "$INCLUDE".len()..].trim_start();
|
||||||
|
let rest = rest.strip_prefix(':')?.trim_start();
|
||||||
|
let rest = rest.strip_prefix('\'')?;
|
||||||
|
Some(rest.split('\'').next().unwrap_or_default().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_includes(path: &Path, source: &str, stack: &mut Vec<PathBuf>) -> Result<String, String> {
|
||||||
|
let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
|
||||||
|
if stack.contains(&canonical) {
|
||||||
|
return Err(format!("{}: zyklisches $INCLUDE", path.display()));
|
||||||
|
}
|
||||||
|
stack.push(canonical);
|
||||||
|
let mut out = String::new();
|
||||||
|
for line in source.split_inclusive('\n') {
|
||||||
|
if let Some(name) = include_name(line) {
|
||||||
|
if name.is_empty() {
|
||||||
|
return Err(format!("{}: leeres $INCLUDE", path.display()));
|
||||||
|
}
|
||||||
|
let included =
|
||||||
|
relative_case_insensitive(path.parent().unwrap_or(Path::new(".")), &name)
|
||||||
|
.map_err(|error| format!("{}: {error}", path.display()))?;
|
||||||
|
let text = std::fs::read_to_string(&included)
|
||||||
|
.map_err(|error| format!("{}: {error}", included.display()))?;
|
||||||
|
out.push_str(&expand_includes(&included, &text, stack)?);
|
||||||
|
if !out.ends_with('\n') {
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.push_str(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_form(path: &Path) -> Result<tb_ui::frm::FormFile, String> {
|
||||||
|
let bytes = std::fs::read(path).map_err(|error| format!("{}: {error}", path.display()))?;
|
||||||
|
if bytes.starts_with(b"VERSION ") || bytes.starts_with(b"Version ") {
|
||||||
|
let source = std::str::from_utf8(&bytes)
|
||||||
|
.map_err(|_| format!("{}: ungültige Textkodierung", path.display()))?;
|
||||||
|
let mut form = tb_ui::frm::read_text(&path.display().to_string(), source)
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
form.code = expand_includes(path, &form.code, &mut Vec::new())?;
|
||||||
|
Ok(form)
|
||||||
|
} else {
|
||||||
|
tb_ui::frm::read_binary(&path.display().to_string(), &bytes)
|
||||||
|
.map(|read| read.form)
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ProjectSymbols {
|
||||||
|
constants: HashSet<String>,
|
||||||
|
types: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_project_source(target: &mut String, source: &str, symbols: &mut ProjectSymbols) {
|
||||||
|
let mut in_proc = false;
|
||||||
|
let mut skip_type = false;
|
||||||
|
for line in source.split_inclusive('\n') {
|
||||||
|
let upper = line.trim().to_ascii_uppercase();
|
||||||
|
if skip_type {
|
||||||
|
if upper == "END TYPE" {
|
||||||
|
skip_type = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !in_proc {
|
||||||
|
if let Some(name) = upper
|
||||||
|
.strip_prefix("TYPE ")
|
||||||
|
.and_then(|rest| rest.split_whitespace().next())
|
||||||
|
{
|
||||||
|
if !symbols.types.insert(name.to_string()) {
|
||||||
|
skip_type = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(rest) = upper.strip_prefix("CONST ") {
|
||||||
|
let names = rest
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|part| part.split('=').next())
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !names.is_empty() && names.iter().all(|name| symbols.constants.contains(*name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
symbols
|
||||||
|
.constants
|
||||||
|
.extend(names.into_iter().map(str::to_string));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.push_str(line);
|
||||||
|
if upper.starts_with("SUB ")
|
||||||
|
|| upper.starts_with("FUNCTION ")
|
||||||
|
|| upper.starts_with("STATIC SUB ")
|
||||||
|
|| upper.starts_with("STATIC FUNCTION ")
|
||||||
|
{
|
||||||
|
in_proc = true;
|
||||||
|
} else if matches!(upper.as_str(), "END SUB" | "END FUNCTION") {
|
||||||
|
in_proc = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !target.ends_with('\n') {
|
||||||
|
target.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_sources(path: &Path) -> Result<(String, Vec<tb_ui::frm::FormFile>), String> {
|
||||||
|
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
|
||||||
|
let paths = if extension.eq_ignore_ascii_case("mak") {
|
||||||
|
let project = std::fs::read_to_string(path)
|
||||||
|
.map_err(|error| format!("{}: {error}", path.display()))?;
|
||||||
|
let base = path.parent().unwrap_or(Path::new("."));
|
||||||
|
project
|
||||||
|
.lines()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.map(|line| {
|
||||||
|
relative_case_insensitive(base, line)
|
||||||
|
.map_err(|error| format!("{}: {error}", path.display()))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
|
} else {
|
||||||
|
vec![path.to_path_buf()]
|
||||||
|
};
|
||||||
|
let mut source = String::new();
|
||||||
|
let mut forms = Vec::new();
|
||||||
|
let mut symbols = ProjectSymbols::default();
|
||||||
|
for member in paths {
|
||||||
|
if member
|
||||||
|
.extension()
|
||||||
|
.and_then(|ext| ext.to_str())
|
||||||
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("frm"))
|
||||||
|
{
|
||||||
|
let form = read_form(&member)?;
|
||||||
|
append_project_source(&mut source, &form.code, &mut symbols);
|
||||||
|
forms.push(form);
|
||||||
|
} else {
|
||||||
|
let text = std::fs::read_to_string(&member)
|
||||||
|
.map_err(|error| format!("{}: {error}", member.display()))?;
|
||||||
|
let expanded = expand_includes(&member, &text, &mut Vec::new())?;
|
||||||
|
append_project_source(&mut source, &expanded, &mut symbols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((source, forms))
|
||||||
|
}
|
||||||
|
|
||||||
fn compile(
|
fn compile(
|
||||||
path_arg: Option<&String>,
|
path_arg: Option<&String>,
|
||||||
) -> Result<(PathBuf, tb_vm::bytecode::CompiledModule), ExitCode> {
|
) -> Result<
|
||||||
|
(
|
||||||
|
PathBuf,
|
||||||
|
tb_vm::bytecode::CompiledModule,
|
||||||
|
Vec<tb_ui::frm::FormFile>,
|
||||||
|
),
|
||||||
|
ExitCode,
|
||||||
|
> {
|
||||||
let Some(path) = path_arg else {
|
let Some(path) = path_arg else {
|
||||||
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
|
eprintln!("Aufruf: tbc run|build|check <datei.bas|datei.frm|projekt.mak>");
|
||||||
return Err(ExitCode::from(1));
|
return Err(ExitCode::from(1));
|
||||||
};
|
};
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
let source = match std::fs::read_to_string(&path) {
|
let (source, forms) = match input_sources(&path) {
|
||||||
Ok(s) => s,
|
Ok(input) => input,
|
||||||
Err(e) => {
|
Err(error) => {
|
||||||
eprintln!("{}: {e}", path.display());
|
eprintln!("{error}");
|
||||||
return Err(ExitCode::from(1));
|
return Err(ExitCode::from(1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match tb_vm::compile_source(&module_name(&path), &source) {
|
let compiled = if forms.is_empty() {
|
||||||
Ok(m) => Ok((path, m)),
|
tb_vm::compile_source(&module_name(&path), &source)
|
||||||
|
} else {
|
||||||
|
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||||
|
for form in &forms {
|
||||||
|
for object in form.catalog().objects {
|
||||||
|
catalog.objects.push(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tb_vm::compile_source_with_forms(&forms[0].root.name, &source, &catalog)
|
||||||
|
};
|
||||||
|
match compiled {
|
||||||
|
Ok(m) => Ok((path, m, forms)),
|
||||||
Err(diags) => {
|
Err(diags) => {
|
||||||
for d in &diags {
|
for d in &diags {
|
||||||
eprintln!("{}:{d}", path.display());
|
eprintln!("{}:{d}", path.display());
|
||||||
@@ -116,7 +324,7 @@ fn cmd_check(args: &[String]) -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_build(args: &[String]) -> ExitCode {
|
fn cmd_build(args: &[String]) -> ExitCode {
|
||||||
let (path, module) = match compile(args.first()) {
|
let (path, module, _) = match compile(args.first()) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(code) => return code,
|
Err(code) => return code,
|
||||||
};
|
};
|
||||||
@@ -134,36 +342,20 @@ fn cmd_build(args: &[String]) -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_run(args: &[String]) -> ExitCode {
|
fn cmd_run(args: &[String]) -> ExitCode {
|
||||||
let (_path, module) = match compile(args.first()) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(code) => return code,
|
|
||||||
};
|
|
||||||
let mut vm = Vm::new(module);
|
|
||||||
vm.rt.command = args[1..].join(" ");
|
|
||||||
// Im Rohmodus ist Strg+C kein Signal mehr — der Abbruch kommt als
|
|
||||||
// Ereignis und muss den Lauf beenden können.
|
|
||||||
vm.set_poll_interrupt(true);
|
|
||||||
// Der Rückfall auf UTC wird einmal beim Start gemeldet, nicht je
|
|
||||||
// Zeitabfrage — ein stiller Wechsel wäre ein Verstoß gegen den
|
|
||||||
// Guiding Principle.
|
|
||||||
if vm.rt.zeitzone == tb_runtime::datetime::Zeitzone::Unbekannt {
|
|
||||||
eprintln!("Zeitzone nicht ermittelbar — Zeitfunktionen rechnen in UTC.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ohne Terminal (Pipe, Skript, CI) läuft das Programm im PipeHost:
|
// Ohne Terminal (Pipe, Skript, CI) läuft das Programm im PipeHost:
|
||||||
// Eingabe zeilenweise von stdin, Ausgabe am Ende als Snapshot.
|
// Eingabe zeilenweise von stdin, Ausgabe am Ende als Snapshot.
|
||||||
let ereignis = match TerminalHost::new() {
|
let result = match TerminalHost::new() {
|
||||||
Ok(mut host) => {
|
Ok(mut host) => {
|
||||||
// Der Bildschirm folgt der Terminalgröße von Beginn an (80×25 ist
|
let size = host.groesse().ok();
|
||||||
// stets nur die untere Schranke, nie eine feste Größe).
|
let result = run_chain(args, &mut host, size);
|
||||||
if let Ok((cols, rows)) = host.groesse() {
|
|
||||||
vm.rt.screen.resize(cols, rows);
|
|
||||||
}
|
|
||||||
let e = vm.run(&mut host);
|
|
||||||
drop(host); // Alternativschirm verlassen, bevor gedruckt wird
|
drop(host); // Alternativschirm verlassen, bevor gedruckt wird
|
||||||
e
|
result
|
||||||
}
|
}
|
||||||
Err(_) => vm.run(&mut PipeHost::new()),
|
Err(_) => run_chain(args, &mut PipeHost::new(), None),
|
||||||
|
};
|
||||||
|
let (ereignis, vm) = match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(code) => return code,
|
||||||
};
|
};
|
||||||
print!("{}", tb_runtime::snapshot::text(&vm.rt.screen));
|
print!("{}", tb_runtime::snapshot::text(&vm.rt.screen));
|
||||||
// `LPRINT` sammelt im Druckerpuffer; am Programmende geht er in die
|
// `LPRINT` sammelt im Druckerpuffer; am Programmende geht er in die
|
||||||
@@ -197,6 +389,90 @@ fn cmd_run(args: &[String]) -> ExitCode {
|
|||||||
}
|
}
|
||||||
// Ohne Debugger-Flags treten diese Ereignisse nicht auf.
|
// Ohne Debugger-Flags treten diese Ereignisse nicht auf.
|
||||||
RunEvent::Breakpoint { .. } | RunEvent::Stepped { .. } => ExitCode::from(2),
|
RunEvent::Breakpoint { .. } | RunEvent::Stepped { .. } => ExitCode::from(2),
|
||||||
|
RunEvent::Restart { .. } => unreachable!("RUN wird vom Runner aufgelöst"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_target(current: &Path, program: &str) -> Result<PathBuf, String> {
|
||||||
|
let base = current.parent().unwrap_or(Path::new("."));
|
||||||
|
if Path::new(program).extension().is_some() {
|
||||||
|
return relative_case_insensitive(base, program).map_err(|error| error.to_string());
|
||||||
|
}
|
||||||
|
for extension in ["bas", "frm", "mak"] {
|
||||||
|
let candidate = format!("{program}.{extension}");
|
||||||
|
if let Ok(path) = relative_case_insensitive(base, &candidate) {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(format!("{program}: Programm nicht gefunden"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_chain(
|
||||||
|
args: &[String],
|
||||||
|
host: &mut dyn Host,
|
||||||
|
size: Option<(usize, usize)>,
|
||||||
|
) -> Result<(RunEvent, Vm), ExitCode> {
|
||||||
|
let Some(first) = args.first() else {
|
||||||
|
eprintln!("Aufruf: tbc run <datei.bas|datei.frm|projekt.mak>");
|
||||||
|
return Err(ExitCode::from(1));
|
||||||
|
};
|
||||||
|
let mut current = PathBuf::from(first);
|
||||||
|
let mut start_line = None;
|
||||||
|
let command = args[1..].join(" ");
|
||||||
|
loop {
|
||||||
|
let current_arg = current.display().to_string();
|
||||||
|
let (path, module, forms) = compile(Some(¤t_arg))?;
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
if !forms.is_empty() {
|
||||||
|
let applied = forms.iter().try_for_each(|form| form.apply(&mut vm.forms));
|
||||||
|
let first = vm
|
||||||
|
.forms
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.position(|object| {
|
||||||
|
object
|
||||||
|
.description
|
||||||
|
.name
|
||||||
|
.eq_ignore_ascii_case(&forms[0].root.name)
|
||||||
|
})
|
||||||
|
.unwrap() as u16;
|
||||||
|
if let Err(error) = applied.and_then(|_| vm.forms.show(first, false).map(|_| ())) {
|
||||||
|
eprintln!("Runtime error {}: {}", error.0, error);
|
||||||
|
return Err(ExitCode::from(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(line) = start_line.take() {
|
||||||
|
if let Err(error) = vm.start_at_line(line) {
|
||||||
|
eprintln!("Runtime error {}: {}", error.0, error);
|
||||||
|
return Err(ExitCode::from(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((cols, rows)) = size {
|
||||||
|
vm.rt.screen.resize(cols, rows);
|
||||||
|
}
|
||||||
|
vm.rt.command.clone_from(&command);
|
||||||
|
vm.set_poll_interrupt(true);
|
||||||
|
if vm.rt.zeitzone == tb_runtime::datetime::Zeitzone::Unbekannt {
|
||||||
|
eprintln!("Zeitzone nicht ermittelbar — Zeitfunktionen rechnen in UTC.");
|
||||||
|
}
|
||||||
|
let event = vm.run(host);
|
||||||
|
let event = if event == RunEvent::Ended && vm.forms.has_visible_forms() {
|
||||||
|
vm.run_visible_forms(host)
|
||||||
|
} else {
|
||||||
|
event
|
||||||
|
};
|
||||||
|
match event {
|
||||||
|
RunEvent::Restart { program, line } => {
|
||||||
|
if let Some(program) = program {
|
||||||
|
current = run_target(&path, &program).map_err(|error| {
|
||||||
|
eprintln!("{error}");
|
||||||
|
ExitCode::from(1)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
start_line = line;
|
||||||
|
}
|
||||||
|
event => return Ok((event, vm)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,11 +487,30 @@ struct PipeHost {
|
|||||||
|
|
||||||
impl PipeHost {
|
impl PipeHost {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
PipeHost {
|
let mut host = PipeHost {
|
||||||
puffer: std::collections::VecDeque::new(),
|
puffer: std::collections::VecDeque::new(),
|
||||||
eof: false,
|
eof: false,
|
||||||
start: std::time::Instant::now(),
|
start: std::time::Instant::now(),
|
||||||
|
};
|
||||||
|
use std::io::{IsTerminal, Read};
|
||||||
|
let mut stdin = std::io::stdin();
|
||||||
|
if !stdin.is_terminal() {
|
||||||
|
let mut input = String::new();
|
||||||
|
let _ = stdin.read_to_string(&mut input);
|
||||||
|
for line in input.split_inclusive('\n') {
|
||||||
|
for character in line.trim_end_matches(['\r', '\n']).chars() {
|
||||||
|
host.puffer
|
||||||
|
.push_back(Ereignis::Taste(character.to_string(), 0));
|
||||||
}
|
}
|
||||||
|
host.puffer.push_back(Ereignis::Taste(
|
||||||
|
tb_runtime::host::taste::ENTER.to_string(),
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
host.puffer.push_back(Ereignis::Ende);
|
||||||
|
host.eof = true;
|
||||||
|
}
|
||||||
|
host
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Eine Zeile von stdin in Tastendrücke zerlegen.
|
/// Eine Zeile von stdin in Tastendrücke zerlegen.
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
//! nur die untere Schranke, nie eine feste Größe, und der Korpus muss das
|
//! nur die untere Schranke, nie eine feste Größe, und der Korpus muss das
|
||||||
//! nachweisen können.
|
//! nachweisen können.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use tb_runtime::host::CaptureHost;
|
use tb_runtime::host::{CaptureHost, Host};
|
||||||
use tb_vm::interp::{RunEvent, Vm};
|
use tb_vm::interp::{RunEvent, Vm};
|
||||||
|
|
||||||
fn compat_dir() -> PathBuf {
|
fn compat_dir() -> PathBuf {
|
||||||
@@ -100,10 +101,14 @@ fn resize_direktive(name: &str, src: &str) -> Option<(usize, (usize, usize))> {
|
|||||||
/// `<F1>`…`<F12>`, sonst je ein Zeichen; `+` davor bedeutet Umschalt,
|
/// `<F1>`…`<F12>`, sonst je ein Zeichen; `+` davor bedeutet Umschalt,
|
||||||
/// `^` Strg, `%` Alt — gebraucht für die benutzerdefinierten Trap-Tasten.
|
/// `^` Strg, `%` Alt — gebraucht für die benutzerdefinierten Trap-Tasten.
|
||||||
fn tasten_direktive(name: &str, src: &str) -> Vec<tb_runtime::host::Ereignis> {
|
fn tasten_direktive(name: &str, src: &str) -> Vec<tb_runtime::host::Ereignis> {
|
||||||
use tb_runtime::host::{taste, umschalt, Ereignis};
|
|
||||||
let Some(wert) = direktive(src, "tb-keys:") else {
|
let Some(wert) = direktive(src, "tb-keys:") else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
tastenfolge(name, &wert)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tastenfolge(name: &str, wert: &str) -> Vec<tb_runtime::host::Ereignis> {
|
||||||
|
use tb_runtime::host::{taste, umschalt, Ereignis};
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let zeichen: Vec<char> = wert.chars().collect();
|
let zeichen: Vec<char> = wert.chars().collect();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@@ -143,6 +148,21 @@ fn tasten_direktive(name: &str, src: &str) -> Vec<tb_runtime::host::Ereignis> {
|
|||||||
"LEFT" => 0x4B,
|
"LEFT" => 0x4B,
|
||||||
"RIGHT" => 0x4D,
|
"RIGHT" => 0x4D,
|
||||||
"DOWN" => 0x50,
|
"DOWN" => 0x50,
|
||||||
|
"ENTER" => {
|
||||||
|
out.push(Ereignis::Taste(taste::ENTER.into(), shift));
|
||||||
|
i += ende + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
"ESC" => {
|
||||||
|
out.push(Ereignis::Taste(taste::ESC.into(), shift));
|
||||||
|
i += ende + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
"TAB" => {
|
||||||
|
out.push(Ereignis::Taste(taste::TAB.into(), shift));
|
||||||
|
i += ende + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
other => panic!("{name}: tb-keys kennt `<{other}>` nicht"),
|
other => panic!("{name}: tb-keys kennt `<{other}>` nicht"),
|
||||||
};
|
};
|
||||||
out.push(Ereignis::Taste(taste::sonder(code), shift));
|
out.push(Ereignis::Taste(taste::sonder(code), shift));
|
||||||
@@ -155,6 +175,98 @@ fn tasten_direktive(name: &str, src: &str) -> Vec<tb_runtime::host::Ereignis> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Ereignisschritt {
|
||||||
|
Ereignis(tb_runtime::host::Ereignis),
|
||||||
|
Zeit(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ereignis_direktive(name: &str, src: &str) -> Vec<Ereignisschritt> {
|
||||||
|
use tb_runtime::host::{Ereignis, MausArt, MausEreignis};
|
||||||
|
let Some(wert) = direktive(src, "tb-events:") else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for teil in wert.split('|').map(str::trim) {
|
||||||
|
if let Some(keys) = teil.strip_prefix("key:") {
|
||||||
|
out.extend(
|
||||||
|
tastenfolge(name, keys)
|
||||||
|
.into_iter()
|
||||||
|
.map(Ereignisschritt::Ereignis),
|
||||||
|
);
|
||||||
|
} else if let Some(ms) = teil.strip_prefix("time:") {
|
||||||
|
let ms = ms.trim().strip_suffix("ms").unwrap_or(ms.trim());
|
||||||
|
out.push(Ereignisschritt::Zeit(
|
||||||
|
ms.parse()
|
||||||
|
.unwrap_or_else(|_| panic!("{name}: ungültige Zeit {ms:?}")),
|
||||||
|
));
|
||||||
|
} else if let Some(mouse) = teil.strip_prefix("mouse:") {
|
||||||
|
let fields: Vec<_> = mouse.split(',').map(str::trim).collect();
|
||||||
|
let [kind, button, shift, row, col] = fields.as_slice() else {
|
||||||
|
panic!("{name}: mouse erwartet art,taste,shift,zeile,spalte");
|
||||||
|
};
|
||||||
|
let art = match *kind {
|
||||||
|
"down" => MausArt::Druck,
|
||||||
|
"up" => MausArt::Loslassen,
|
||||||
|
"move" => MausArt::Bewegung,
|
||||||
|
_ => panic!("{name}: unbekannte Mausart {kind:?}"),
|
||||||
|
};
|
||||||
|
let parse = |value: &str| {
|
||||||
|
value
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap_or_else(|_| panic!("{name}: ungültige Zahl {value:?}"))
|
||||||
|
};
|
||||||
|
out.push(Ereignisschritt::Ereignis(Ereignis::Maus(MausEreignis {
|
||||||
|
art,
|
||||||
|
taste: parse(button) as u8,
|
||||||
|
shift: parse(shift) as u8,
|
||||||
|
zeile: parse(row),
|
||||||
|
spalte: parse(col),
|
||||||
|
})));
|
||||||
|
} else {
|
||||||
|
panic!("{name}: unbekannter tb-events-Schritt {teil:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EreignisHost {
|
||||||
|
inner: CaptureHost,
|
||||||
|
schritte: VecDeque<Ereignisschritt>,
|
||||||
|
pause: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tb_runtime::host::Host for EreignisHost {
|
||||||
|
fn present(&mut self, screen: &tb_runtime::screen::TextScreen) {
|
||||||
|
self.inner.present(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_event(&mut self, blockierend: bool) -> Option<tb_runtime::host::Ereignis> {
|
||||||
|
if !blockierend && self.pause {
|
||||||
|
self.pause = false;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
match self.schritte.pop_front() {
|
||||||
|
Some(Ereignisschritt::Zeit(ms)) => {
|
||||||
|
self.inner.uhr_vorruecken(ms);
|
||||||
|
if !blockierend {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Ereignisschritt::Ereignis(event)) => {
|
||||||
|
self.pause = true;
|
||||||
|
return Some(event);
|
||||||
|
}
|
||||||
|
None => return self.inner.next_event(blockierend),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn jetzt_ms(&mut self) -> u64 {
|
||||||
|
self.inner.jetzt_ms()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Virtuelle Uhr: `' tb-clock: <n>ms/Zustellpunkt`. Ohne die Direktive
|
/// Virtuelle Uhr: `' tb-clock: <n>ms/Zustellpunkt`. Ohne die Direktive
|
||||||
/// steht die Uhr des Testhosts still — ein Programm mit Zeit-Traps liefe
|
/// steht die Uhr des Testhosts still — ein Programm mit Zeit-Traps liefe
|
||||||
/// dann endlos. Der Harness weist es deshalb ab, statt auf die Systemuhr
|
/// dann endlos. Der Harness weist es deshalb ab, statt auf die Systemuhr
|
||||||
@@ -242,45 +354,77 @@ impl Drop for TempVerzeichnis {
|
|||||||
|
|
||||||
/// Korpusdatei ausführen und den Bildschirm-Snapshot liefern.
|
/// Korpusdatei ausführen und den Bildschirm-Snapshot liefern.
|
||||||
fn run_corpus_file(path: &Path, groesse: Option<(usize, usize)>) -> String {
|
fn run_corpus_file(path: &Path, groesse: Option<(usize, usize)>) -> String {
|
||||||
let src = std::fs::read_to_string(path).unwrap();
|
let raw = std::fs::read_to_string(path).unwrap();
|
||||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||||
let modul_name = path.file_stem().unwrap().to_string_lossy().to_uppercase();
|
let form = if path
|
||||||
let (cols, rows) = groesse.unwrap_or_else(|| screen_groessen(&name, &src)[0]);
|
.extension()
|
||||||
let module = tb_vm::compile_source(&modul_name, &src)
|
.and_then(|ext| ext.to_str())
|
||||||
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("frm"))
|
||||||
|
{
|
||||||
|
Some(tb_ui::frm::read_text(&name, &raw).unwrap_or_else(|error| panic!("{error}")))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let src = form
|
||||||
|
.as_ref()
|
||||||
|
.map_or(raw.as_str(), |form| form.code.as_str());
|
||||||
|
let modul_name = form.as_ref().map_or_else(
|
||||||
|
|| path.file_stem().unwrap().to_string_lossy().to_uppercase(),
|
||||||
|
|form| form.root.name.to_uppercase(),
|
||||||
|
);
|
||||||
|
let (cols, rows) = groesse.unwrap_or_else(|| screen_groessen(&name, src)[0]);
|
||||||
|
let module = match &form {
|
||||||
|
Some(form) => tb_vm::compile_source_with_forms(&modul_name, src, &form.catalog()),
|
||||||
|
None => tb_vm::compile_source(&modul_name, src),
|
||||||
|
}
|
||||||
.unwrap_or_else(|d| panic!("{}: Compile-Fehler: {d:?}", path.display()));
|
.unwrap_or_else(|d| panic!("{}: Compile-Fehler: {d:?}", path.display()));
|
||||||
let mut vm = Vm::new(module);
|
let mut vm = Vm::new(module);
|
||||||
|
if let Some(form) = &form {
|
||||||
|
form.apply(&mut vm.forms).unwrap();
|
||||||
|
vm.forms.show(0, false).unwrap();
|
||||||
|
}
|
||||||
vm.rt.screen.resize(cols, rows);
|
vm.rt.screen.resize(cols, rows);
|
||||||
if let Some(z) = tz_direktive(&name, &src) {
|
if let Some(z) = tz_direktive(&name, src) {
|
||||||
vm.rt.zeitzone = z;
|
vm.rt.zeitzone = z;
|
||||||
}
|
}
|
||||||
// `' tb-tempdir` — das Programm arbeitet mit Dateien.
|
// `' tb-tempdir` — das Programm arbeitet mit Dateien.
|
||||||
let _temp = direktive(&src, "tb-tempdir").map(|_| TempVerzeichnis::neu(&name));
|
let _temp = direktive(src, "tb-tempdir").map(|_| TempVerzeichnis::neu(&name));
|
||||||
let mut host = CaptureHost::default();
|
let mut host = CaptureHost::default();
|
||||||
if let Some((nach, (c, r))) = resize_direktive(&name, &src) {
|
if let Some((nach, (c, r))) = resize_direktive(&name, src) {
|
||||||
host.ereignis_nach(
|
host.ereignis_nach(
|
||||||
nach,
|
nach,
|
||||||
tb_runtime::host::Ereignis::Groesse { cols: c, rows: r },
|
tb_runtime::host::Ereignis::Groesse { cols: c, rows: r },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for t in tasten_direktive(&name, &src) {
|
for t in tasten_direktive(&name, src) {
|
||||||
host.ereignis(t);
|
host.ereignis(t);
|
||||||
}
|
}
|
||||||
let schritt = clock_schritt(&name, &src);
|
let schritt = clock_schritt(&name, src);
|
||||||
|
let schritte = ereignis_direktive(&name, src);
|
||||||
|
if !schritte.is_empty() {
|
||||||
|
assert!(
|
||||||
|
schritt.is_none(),
|
||||||
|
"{name}: tb-events: time und tb-clock nicht mischen"
|
||||||
|
);
|
||||||
|
let mut host = EreignisHost {
|
||||||
|
inner: host,
|
||||||
|
schritte: schritte.into(),
|
||||||
|
pause: false,
|
||||||
|
};
|
||||||
|
return run_to_snapshot(path, &mut vm, &mut host);
|
||||||
|
}
|
||||||
if let Some(schritt_ms) = schritt {
|
if let Some(schritt_ms) = schritt {
|
||||||
let mut host = UhrHost {
|
let mut host = UhrHost {
|
||||||
inner: host,
|
inner: host,
|
||||||
schritt_ms,
|
schritt_ms,
|
||||||
};
|
};
|
||||||
return match vm.run(&mut host) {
|
return run_to_snapshot(path, &mut vm, &mut host);
|
||||||
RunEvent::Ended => tb_runtime::snapshot::snapshot(&vm.rt.screen),
|
|
||||||
other => panic!(
|
|
||||||
"{}: unerwartetes Laufzeitende {other:?}\nBildschirm bisher:\n{}",
|
|
||||||
path.display(),
|
|
||||||
tb_runtime::snapshot::snapshot(&vm.rt.screen)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
match vm.run(&mut host) {
|
run_to_snapshot(path, &mut vm, &mut host)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_to_snapshot(path: &Path, vm: &mut Vm, host: &mut dyn Host) -> String {
|
||||||
|
match vm.run(host) {
|
||||||
RunEvent::Ended => tb_runtime::snapshot::snapshot(&vm.rt.screen),
|
RunEvent::Ended => tb_runtime::snapshot::snapshot(&vm.rt.screen),
|
||||||
other => panic!(
|
other => panic!(
|
||||||
"{}: unerwartetes Laufzeitende {other:?}\nBildschirm bisher:\n{}",
|
"{}: unerwartetes Laufzeitende {other:?}\nBildschirm bisher:\n{}",
|
||||||
@@ -290,6 +434,21 @@ fn run_corpus_file(path: &Path, groesse: Option<(usize, usize)>) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn source_for_directives(path: &Path) -> String {
|
||||||
|
let raw = std::fs::read_to_string(path).unwrap();
|
||||||
|
if path
|
||||||
|
.extension()
|
||||||
|
.and_then(|ext| ext.to_str())
|
||||||
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("frm"))
|
||||||
|
{
|
||||||
|
tb_ui::frm::read_text(&path.display().to_string(), &raw)
|
||||||
|
.unwrap()
|
||||||
|
.code
|
||||||
|
} else {
|
||||||
|
raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Snapshots vergleichen: erst das Textbild, dann die Attributebene.
|
/// Snapshots vergleichen: erst das Textbild, dann die Attributebene.
|
||||||
/// Bei Abweichung im Text zählt die Zeile, bei Attributen Zeile und Spalte.
|
/// Bei Abweichung im Text zählt die Zeile, bei Attributen Zeile und Spalte.
|
||||||
fn assert_output_matches(file: &str, want: &str, got: &str) {
|
fn assert_output_matches(file: &str, want: &str, got: &str) {
|
||||||
@@ -365,12 +524,16 @@ fn korpus_laeuft_mit_korrekter_ausgabe() {
|
|||||||
let mut entries: Vec<PathBuf> = std::fs::read_dir(&dir)
|
let mut entries: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||||
.expect("tests/compat fehlt")
|
.expect("tests/compat fehlt")
|
||||||
.map(|e| e.unwrap().path())
|
.map(|e| e.unwrap().path())
|
||||||
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("bas"))
|
.filter(|p| {
|
||||||
|
p.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.is_some_and(|ext| matches!(ext, "bas" | "frm"))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
entries.sort();
|
entries.sort();
|
||||||
for path in entries {
|
for path in entries {
|
||||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||||
let src = std::fs::read_to_string(&path).unwrap();
|
let src = source_for_directives(&path);
|
||||||
let groessen = screen_groessen(&name, &src);
|
let groessen = screen_groessen(&name, &src);
|
||||||
for (cols, rows) in &groessen {
|
for (cols, rows) in &groessen {
|
||||||
// Bei mehreren Größen trägt jede ihre eigene Sollausgabe.
|
// Bei mehreren Größen trägt jede ihre eigene Sollausgabe.
|
||||||
@@ -406,6 +569,25 @@ fn uhr_direktive_wird_gelesen() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ereignisfolge_liefert_taste_maus_und_zeit_in_reihenfolge() {
|
||||||
|
let steps = ereignis_direktive(
|
||||||
|
"form.frm",
|
||||||
|
"' tb-events: key:%o | mouse:down,1,0,5,7 | time:250ms | key:<ENTER>",
|
||||||
|
);
|
||||||
|
assert_eq!(steps.len(), 4);
|
||||||
|
assert!(
|
||||||
|
matches!(&steps[0], Ereignisschritt::Ereignis(tb_runtime::host::Ereignis::Taste(key, shift)) if key == "o" && *shift == 4)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(&steps[1], Ereignisschritt::Ereignis(tb_runtime::host::Ereignis::Maus(mouse)) if mouse.zeile == 5 && mouse.spalte == 7)
|
||||||
|
);
|
||||||
|
assert!(matches!(steps[2], Ereignisschritt::Zeit(250)));
|
||||||
|
assert!(
|
||||||
|
matches!(&steps[3], Ereignisschritt::Ereignis(tb_runtime::host::Ereignis::Taste(key, _)) if key == tb_runtime::host::taste::ENTER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "deklariert aber keinen Zeitverlauf")]
|
#[should_panic(expected = "deklariert aber keinen Zeitverlauf")]
|
||||||
fn zeittrap_ohne_uhr_direktive_wird_abgewiesen() {
|
fn zeittrap_ohne_uhr_direktive_wird_abgewiesen() {
|
||||||
@@ -467,6 +649,40 @@ fn tbc_build_erzeugt_tbc() {
|
|||||||
let _ = std::fs::remove_file(&tbc);
|
let _ = std::fs::remove_file(&tbc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tbc_loest_include_projekt_und_run_datei_auf() {
|
||||||
|
let exe = env!("CARGO_BIN_EXE_tbc");
|
||||||
|
let dir = std::env::temp_dir().join(format!("tb_project_{}", std::process::id()));
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::write(dir.join("werte.bi"), "CONST N = 7\n").unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
dir.join("main.bas"),
|
||||||
|
"'$INCLUDE: 'WERTE.BI'\nDECLARE SUB Ausgabe\nAusgabe\nEND\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
std::fs::write(dir.join("lib.bas"), "SUB Ausgabe\nPRINT N\nEND SUB\n").unwrap();
|
||||||
|
std::fs::write(dir.join("app.mak"), "MAIN.BAS\nLIB.BAS\n").unwrap();
|
||||||
|
let project = Command::new(exe)
|
||||||
|
.args(["run"])
|
||||||
|
.arg(dir.join("app.mak"))
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(project.status.success(), "{project:?}");
|
||||||
|
assert_eq!(String::from_utf8_lossy(&project.stdout), " 7 \n");
|
||||||
|
|
||||||
|
std::fs::write(dir.join("start.bas"), "RUN \"ZIEL\"\n").unwrap();
|
||||||
|
std::fs::write(dir.join("ziel.bas"), "PRINT \"weiter\"\nEND\n").unwrap();
|
||||||
|
let chained = Command::new(exe)
|
||||||
|
.args(["run"])
|
||||||
|
.arg(dir.join("start.bas"))
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(chained.status.success(), "{chained:?}");
|
||||||
|
assert_eq!(String::from_utf8_lossy(&chained.stdout), "weiter\n");
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Harness meldet Abweichungen benannt (Spec kompat-testkorpus) ----------
|
// ---- Harness meldet Abweichungen benannt (Spec kompat-testkorpus) ----------
|
||||||
|
|
||||||
fn meldung(want: &str, got: &str) -> String {
|
fn meldung(want: &str, got: &str) -> String {
|
||||||
@@ -537,12 +753,16 @@ fn erzeuge_sollausgaben() {
|
|||||||
let mut entries: Vec<PathBuf> = std::fs::read_dir(&dir)
|
let mut entries: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||||
.expect("tests/compat fehlt")
|
.expect("tests/compat fehlt")
|
||||||
.map(|e| e.unwrap().path())
|
.map(|e| e.unwrap().path())
|
||||||
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("bas"))
|
.filter(|p| {
|
||||||
|
p.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.is_some_and(|ext| matches!(ext, "bas" | "frm"))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
entries.sort();
|
entries.sort();
|
||||||
for path in entries {
|
for path in entries {
|
||||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||||
let src = std::fs::read_to_string(&path).unwrap();
|
let src = source_for_directives(&path);
|
||||||
let groessen = screen_groessen(&name, &src);
|
let groessen = screen_groessen(&name, &src);
|
||||||
for (cols, rows) in &groessen {
|
for (cols, rows) in &groessen {
|
||||||
let out_path = if groessen.len() == 1 {
|
let out_path = if groessen.len() == 1 {
|
||||||
|
|||||||
@@ -480,6 +480,26 @@ pub enum Stmt {
|
|||||||
bottom: Option<Expr>,
|
bottom: Option<Expr>,
|
||||||
pos: SourcePos,
|
pos: SourcePos,
|
||||||
},
|
},
|
||||||
|
GraphicsLine {
|
||||||
|
from: Option<(Expr, Expr)>,
|
||||||
|
to: (Expr, Expr),
|
||||||
|
relative: bool,
|
||||||
|
color: Option<Expr>,
|
||||||
|
fill: u8,
|
||||||
|
pos: SourcePos,
|
||||||
|
},
|
||||||
|
GraphicsPaint {
|
||||||
|
point: (Expr, Expr),
|
||||||
|
paint: Option<Expr>,
|
||||||
|
border: Option<Expr>,
|
||||||
|
pos: SourcePos,
|
||||||
|
},
|
||||||
|
GraphicsView {
|
||||||
|
rect: Option<(Expr, Expr, Expr, Expr)>,
|
||||||
|
fill: Option<Expr>,
|
||||||
|
border: Option<Expr>,
|
||||||
|
pos: SourcePos,
|
||||||
|
},
|
||||||
/// `ON TIMER(n&) GOSUB ziel`, `ON KEY(n%) GOSUB ziel`,
|
/// `ON TIMER(n&) GOSUB ziel`, `ON KEY(n%) GOSUB ziel`,
|
||||||
/// `ON UEVENT GOSUB ziel`, `ON SIGNAL(n%) GOSUB ziel` — die
|
/// `ON UEVENT GOSUB ziel`, `ON SIGNAL(n%) GOSUB ziel` — die
|
||||||
/// klassischen Ereignis-Traps (Sprachreferenz §8). `GOSUB 0` schaltet
|
/// klassischen Ereignis-Traps (Sprachreferenz §8). `GOSUB 0` schaltet
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ pub enum ObjectClass {
|
|||||||
Timer,
|
Timer,
|
||||||
VScrollBar,
|
VScrollBar,
|
||||||
Screen,
|
Screen,
|
||||||
|
Spin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectClass {
|
impl ObjectClass {
|
||||||
pub const ALL: [Self; 18] = [
|
pub const ALL: [Self; 19] = [
|
||||||
Self::Form,
|
Self::Form,
|
||||||
Self::CheckBox,
|
Self::CheckBox,
|
||||||
Self::ComboBox,
|
Self::ComboBox,
|
||||||
@@ -43,6 +44,7 @@ impl ObjectClass {
|
|||||||
Self::Timer,
|
Self::Timer,
|
||||||
Self::VScrollBar,
|
Self::VScrollBar,
|
||||||
Self::Screen,
|
Self::Screen,
|
||||||
|
Self::Spin,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
@@ -65,6 +67,7 @@ impl ObjectClass {
|
|||||||
Self::Timer => "TIMER",
|
Self::Timer => "TIMER",
|
||||||
Self::VScrollBar => "VSCROLLBAR",
|
Self::VScrollBar => "VSCROLLBAR",
|
||||||
Self::Screen => "SCREEN",
|
Self::Screen => "SCREEN",
|
||||||
|
Self::Spin => "SPIN",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +91,7 @@ impl ObjectClass {
|
|||||||
Self::Timer => "Timer",
|
Self::Timer => "Timer",
|
||||||
Self::VScrollBar => "VScrollBar",
|
Self::VScrollBar => "VScrollBar",
|
||||||
Self::Screen => "Screen",
|
Self::Screen => "Screen",
|
||||||
|
Self::Spin => "Spin",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +299,7 @@ pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
|
|||||||
string("TEXT", ""),
|
string("TEXT", ""),
|
||||||
]),
|
]),
|
||||||
CheckBox => p.extend([string("CAPTION", ""), range("VALUE", 0, 0, 2)]),
|
CheckBox => p.extend([string("CAPTION", ""), range("VALUE", 0, 0, 2)]),
|
||||||
OptionButton => p.extend([string("CAPTION", ""), range("VALUE", 0, -1, 0)]),
|
OptionButton => p.extend([string("CAPTION", ""), range("VALUE", 0, -1, 1)]),
|
||||||
Frame => p.push(string("CAPTION", "")),
|
Frame => p.push(string("CAPTION", "")),
|
||||||
Label => p.extend([
|
Label => p.extend([
|
||||||
range("ALIGNMENT", 0, 0, 2),
|
range("ALIGNMENT", 0, 0, 2),
|
||||||
@@ -311,6 +315,21 @@ pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
|
|||||||
range("SMALLCHANGE", 1, 1, 32767),
|
range("SMALLCHANGE", 1, 1, 32767),
|
||||||
range("VALUE", 0, -32768, 32767),
|
range("VALUE", 0, -32768, 32767),
|
||||||
]),
|
]),
|
||||||
|
Spin => {
|
||||||
|
p.extend([
|
||||||
|
ro("BORDERSTYLE", PropertyType::Integer),
|
||||||
|
range("INTERVAL", 250, 0, 65535),
|
||||||
|
range("MIN", 0, -32768, 32767),
|
||||||
|
range("MAX", 32767, -32768, 32767),
|
||||||
|
range("STYLE", 0, 0, 1),
|
||||||
|
range("VALUE", 0, -32768, 32767),
|
||||||
|
]);
|
||||||
|
for name in ["HEIGHT", "WIDTH"] {
|
||||||
|
if let Some(property) = p.iter_mut().find(|property| property.name == name) {
|
||||||
|
property.writable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
PictureBox => p.extend([
|
PictureBox => p.extend([
|
||||||
boolp("AUTOREDRAW", false),
|
boolp("AUTOREDRAW", false),
|
||||||
range("BORDERSTYLE", 1, 0, 2),
|
range("BORDERSTYLE", 1, 0, 2),
|
||||||
@@ -337,10 +356,26 @@ pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
|
|||||||
boolp("SEPARATOR", false),
|
boolp("SEPARATOR", false),
|
||||||
string("TAG", ""),
|
string("TAG", ""),
|
||||||
boolp("VISIBLE", true),
|
boolp("VISIBLE", true),
|
||||||
|
string("SHORTCUT", ""),
|
||||||
|
]),
|
||||||
|
DirListBox => p.extend([
|
||||||
|
ro("LIST", PropertyType::String),
|
||||||
|
ro("LISTCOUNT", PropertyType::Integer),
|
||||||
|
int("LISTINDEX", -1),
|
||||||
|
string("PATH", ""),
|
||||||
|
string("TEXT", ""),
|
||||||
|
]),
|
||||||
|
DriveListBox => p.extend([
|
||||||
|
ro("LIST", PropertyType::String),
|
||||||
|
ro("LISTCOUNT", PropertyType::Integer),
|
||||||
|
int("LISTINDEX", -1),
|
||||||
|
string("DRIVE", ""),
|
||||||
|
string("TEXT", ""),
|
||||||
]),
|
]),
|
||||||
DirListBox => p.extend([string("PATH", ""), string("TEXT", "")]),
|
|
||||||
DriveListBox => p.extend([string("DRIVE", ""), string("TEXT", "")]),
|
|
||||||
FileListBox => p.extend([
|
FileListBox => p.extend([
|
||||||
|
ro("LIST", PropertyType::String),
|
||||||
|
ro("LISTCOUNT", PropertyType::Integer),
|
||||||
|
int("LISTINDEX", -1),
|
||||||
string("FILENAME", ""),
|
string("FILENAME", ""),
|
||||||
string("PATH", ""),
|
string("PATH", ""),
|
||||||
string("PATTERN", "*.*"),
|
string("PATTERN", "*.*"),
|
||||||
@@ -413,6 +448,7 @@ pub fn methods(class: ObjectClass) -> &'static [&'static str] {
|
|||||||
&["DRAG", "MOVE", "REFRESH", "SETFOCUS"]
|
&["DRAG", "MOVE", "REFRESH", "SETFOCUS"]
|
||||||
}
|
}
|
||||||
Frame | Label | HScrollBar | VScrollBar => &["DRAG", "MOVE", "REFRESH"],
|
Frame | Label | HScrollBar | VScrollBar => &["DRAG", "MOVE", "REFRESH"],
|
||||||
|
Spin => &["DRAG", "REFRESH", "SETFOCUS"],
|
||||||
PictureBox => &[
|
PictureBox => &[
|
||||||
"CLS",
|
"CLS",
|
||||||
"DRAG",
|
"DRAG",
|
||||||
@@ -429,11 +465,7 @@ pub fn methods(class: ObjectClass) -> &'static [&'static str] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn method_is_implemented(class: ObjectClass, name: &str) -> bool {
|
pub fn method_is_implemented(class: ObjectClass, name: &str) -> bool {
|
||||||
matches!(
|
methods(class).contains(&name)
|
||||||
(class, name),
|
|
||||||
(ObjectClass::Form, "HIDE" | "LOAD" | "SHOW" | "UNLOAD")
|
|
||||||
| (ObjectClass::Screen, "HIDE" | "SHOW")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn method_arity(class: ObjectClass, name: &str) -> Option<(usize, usize)> {
|
pub fn method_arity(class: ObjectClass, name: &str) -> Option<(usize, usize)> {
|
||||||
@@ -441,6 +473,22 @@ pub fn method_arity(class: ObjectClass, name: &str) -> Option<(usize, usize)> {
|
|||||||
(ObjectClass::Form, "SHOW") => Some((0, 1)),
|
(ObjectClass::Form, "SHOW") => Some((0, 1)),
|
||||||
(ObjectClass::Form, "HIDE" | "LOAD" | "UNLOAD")
|
(ObjectClass::Form, "HIDE" | "LOAD" | "UNLOAD")
|
||||||
| (ObjectClass::Screen, "HIDE" | "SHOW") => Some((0, 0)),
|
| (ObjectClass::Screen, "HIDE" | "SHOW") => Some((0, 0)),
|
||||||
|
(_, "REFRESH" | "SETFOCUS" | "CLS" | "PRINTFORM") => Some((0, 0)),
|
||||||
|
(_, "DRAG") => Some((1, 1)),
|
||||||
|
(_, "MOVE") => Some((2, 4)),
|
||||||
|
(ObjectClass::ListBox | ObjectClass::ComboBox, "ADDITEM") => Some((1, 2)),
|
||||||
|
(ObjectClass::ListBox | ObjectClass::ComboBox, "REMOVEITEM") => Some((1, 1)),
|
||||||
|
(ObjectClass::Form | ObjectClass::PictureBox, "PRINT") => Some((0, usize::MAX)),
|
||||||
|
(ObjectClass::Form | ObjectClass::PictureBox, "TEXTWIDTH" | "TEXTHEIGHT") => Some((1, 1)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method_return_type(class: ObjectClass, name: &str) -> Option<PropertyType> {
|
||||||
|
match (class, name) {
|
||||||
|
(ObjectClass::Form | ObjectClass::PictureBox, "TEXTWIDTH" | "TEXTHEIGHT") => {
|
||||||
|
Some(PropertyType::Integer)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,6 +606,16 @@ pub fn events(class: ObjectClass) -> &'static [&'static str] {
|
|||||||
"KEYUP",
|
"KEYUP",
|
||||||
"LOSTFOCUS",
|
"LOSTFOCUS",
|
||||||
],
|
],
|
||||||
|
Spin => &[
|
||||||
|
"CUSTOM",
|
||||||
|
"DRAGDROP",
|
||||||
|
"DRAGOVER",
|
||||||
|
"GOTFOCUS",
|
||||||
|
"KEYDOWN",
|
||||||
|
"KEYPRESS",
|
||||||
|
"KEYUP",
|
||||||
|
"LOSTFOCUS",
|
||||||
|
],
|
||||||
PictureBox => &[
|
PictureBox => &[
|
||||||
"CLICK",
|
"CLICK",
|
||||||
"DBLCLICK",
|
"DBLCLICK",
|
||||||
@@ -608,6 +666,7 @@ pub fn event_params(event: &str) -> Option<&'static [(&'static str, EventParamTy
|
|||||||
("STATE", Integer),
|
("STATE", Integer),
|
||||||
],
|
],
|
||||||
"UNLOAD" => &[("CANCEL", Integer)],
|
"UNLOAD" => &[("CANCEL", Integer)],
|
||||||
|
"CUSTOM" => &[("EVENTTYPE", Integer)],
|
||||||
"CLICK" | "DBLCLICK" | "CHANGE" | "DROPDOWN" | "GOTFOCUS" | "LOSTFOCUS" | "LOAD"
|
"CLICK" | "DBLCLICK" | "CHANGE" | "DROPDOWN" | "GOTFOCUS" | "LOSTFOCUS" | "LOAD"
|
||||||
| "PAINT" | "RESIZE" | "TIMER" | "PATHCHANGE" | "PATTERNCHANGE" => &[],
|
| "PAINT" | "RESIZE" | "TIMER" | "PATHCHANGE" | "PATTERNCHANGE" => &[],
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@@ -652,4 +711,18 @@ impl FormCatalog {
|
|||||||
.find(|(_, o)| o.name.eq_ignore_ascii_case(name))
|
.find(|(_, o)| o.name.eq_ignore_ascii_case(name))
|
||||||
.map(|(i, o)| (i as u16, o))
|
.map(|(i, o)| (i as u16, o))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn belongs_to(&self, object: &FormObject, form: &str) -> bool {
|
||||||
|
let mut parent = object.parent_form.as_deref();
|
||||||
|
for _ in 0..self.objects.len() {
|
||||||
|
let Some(name) = parent else { return false };
|
||||||
|
if name.eq_ignore_ascii_case(form) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
parent = self
|
||||||
|
.find(name)
|
||||||
|
.and_then(|(_, object)| object.parent_form.as_deref());
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,6 +369,13 @@ pub enum Builtin {
|
|||||||
IsamSavepoint,
|
IsamSavepoint,
|
||||||
IsamSetmem,
|
IsamSetmem,
|
||||||
IsamBof,
|
IsamBof,
|
||||||
|
MsgBox,
|
||||||
|
InputBoxS,
|
||||||
|
ClipboardAdd,
|
||||||
|
ClipboardGet,
|
||||||
|
GraphicsLine,
|
||||||
|
GraphicsPaint,
|
||||||
|
GraphicsView,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -386,6 +393,20 @@ pub enum HExpr {
|
|||||||
property: u16,
|
property: u16,
|
||||||
ty: HTy,
|
ty: HTy,
|
||||||
},
|
},
|
||||||
|
ObjectIndexedProperty {
|
||||||
|
object: u16,
|
||||||
|
object_index: Option<Box<HExpr>>,
|
||||||
|
property: u16,
|
||||||
|
index: Box<HExpr>,
|
||||||
|
ty: HTy,
|
||||||
|
},
|
||||||
|
ObjectMethodCall {
|
||||||
|
object: u16,
|
||||||
|
index: Option<Box<HExpr>>,
|
||||||
|
method: u16,
|
||||||
|
args: Vec<HExpr>,
|
||||||
|
ty: HTy,
|
||||||
|
},
|
||||||
DynamicObjectProperty {
|
DynamicObjectProperty {
|
||||||
object: Box<HExpr>,
|
object: Box<HExpr>,
|
||||||
property: String,
|
property: String,
|
||||||
@@ -511,6 +532,13 @@ pub enum HStmtKind {
|
|||||||
property: u16,
|
property: u16,
|
||||||
value: HExpr,
|
value: HExpr,
|
||||||
},
|
},
|
||||||
|
SetObjectIndexedProperty {
|
||||||
|
object: u16,
|
||||||
|
object_index: Option<HExpr>,
|
||||||
|
property: u16,
|
||||||
|
index: HExpr,
|
||||||
|
value: HExpr,
|
||||||
|
},
|
||||||
SetDynamicObjectProperty {
|
SetDynamicObjectProperty {
|
||||||
object: HExpr,
|
object: HExpr,
|
||||||
property: String,
|
property: String,
|
||||||
@@ -518,6 +546,7 @@ pub enum HStmtKind {
|
|||||||
},
|
},
|
||||||
ObjectMethod {
|
ObjectMethod {
|
||||||
object: u16,
|
object: u16,
|
||||||
|
index: Option<HExpr>,
|
||||||
method: u16,
|
method: u16,
|
||||||
args: Vec<HExpr>,
|
args: Vec<HExpr>,
|
||||||
},
|
},
|
||||||
@@ -592,6 +621,10 @@ pub enum HStmtKind {
|
|||||||
targets: Vec<LabelId>,
|
targets: Vec<LabelId>,
|
||||||
},
|
},
|
||||||
ReturnGosub(Option<LabelId>),
|
ReturnGosub(Option<LabelId>),
|
||||||
|
Run {
|
||||||
|
target: Option<HExpr>,
|
||||||
|
string: bool,
|
||||||
|
},
|
||||||
/// Ereignis-Trap erklären (Sprachreferenz §8). `art` ist die Quelle
|
/// Ereignis-Trap erklären (Sprachreferenz §8). `art` ist die Quelle
|
||||||
/// (0 KEY, 1 TIMER, 2 UEVENT, 3 SIGNAL), `index` ihre Kennung bzw. bei
|
/// (0 KEY, 1 TIMER, 2 UEVENT, 3 SIGNAL), `index` ihre Kennung bzw. bei
|
||||||
/// `TIMER` das Intervall in Sekunden. `ziel = None` = `GOSUB 0`.
|
/// `TIMER` das Intervall in Sekunden. `ziel = None` = `GOSUB 0`.
|
||||||
|
|||||||
@@ -750,8 +750,8 @@ mod tests {
|
|||||||
assert_eq!(kinds("40000")[0], TokenKind::Num(NumValue::Long(40000)));
|
assert_eq!(kinds("40000")[0], TokenKind::Num(NumValue::Long(40000)));
|
||||||
assert_eq!(kinds("1.5")[0], TokenKind::Num(NumValue::Single(1.5)));
|
assert_eq!(kinds("1.5")[0], TokenKind::Num(NumValue::Single(1.5)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
kinds("3.14159265")[0],
|
kinds("1.23456789")[0],
|
||||||
TokenKind::Num(NumValue::Double(3.14159265))
|
TokenKind::Num(NumValue::Double(1.23456789))
|
||||||
);
|
);
|
||||||
assert_eq!(kinds("1E3")[0], TokenKind::Num(NumValue::Single(1000.0)));
|
assert_eq!(kinds("1E3")[0], TokenKind::Num(NumValue::Single(1000.0)));
|
||||||
assert_eq!(kinds("1D3")[0], TokenKind::Num(NumValue::Double(1000.0)));
|
assert_eq!(kinds("1D3")[0], TokenKind::Num(NumValue::Double(1000.0)));
|
||||||
|
|||||||
@@ -28,7 +28,18 @@ pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput {
|
|||||||
match p.k() {
|
match p.k() {
|
||||||
TokenKind::Eof => break,
|
TokenKind::Eof => break,
|
||||||
TokenKind::Kw(Kw::Sub) | TokenKind::Kw(Kw::Function) => {
|
TokenKind::Kw(Kw::Sub) | TokenKind::Kw(Kw::Function) => {
|
||||||
if let Some(proc) = p.parse_proc() {
|
if let Some(proc) = p.parse_proc(false) {
|
||||||
|
procs.push(proc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenKind::Kw(Kw::Static)
|
||||||
|
if matches!(
|
||||||
|
p.k_at(1),
|
||||||
|
TokenKind::Kw(Kw::Sub) | TokenKind::Kw(Kw::Function)
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
p.advance();
|
||||||
|
if let Some(proc) = p.parse_proc(true) {
|
||||||
procs.push(proc);
|
procs.push(proc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,10 +255,7 @@ impl<'a> P<'a> {
|
|||||||
self.advance();
|
self.advance();
|
||||||
self.parse_input(true, pos)
|
self.parse_input(true, pos)
|
||||||
} else {
|
} else {
|
||||||
// Grafikform LINE (x1,y1)-(x2,y2): deklariertes Non-Feature.
|
self.parse_graphics_line(pos)
|
||||||
self.err("Feature unavailable");
|
|
||||||
self.sync();
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Kw(Kw::If) => self.parse_if(pos),
|
TokenKind::Kw(Kw::If) => self.parse_if(pos),
|
||||||
@@ -350,12 +358,8 @@ impl<'a> P<'a> {
|
|||||||
TokenKind::Kw(Kw::Erase) => {
|
TokenKind::Kw(Kw::Erase) => {
|
||||||
self.advance();
|
self.advance();
|
||||||
let mut names = Vec::new();
|
let mut names = Vec::new();
|
||||||
loop {
|
while let Some(e) = self.parse_name_only() {
|
||||||
if let Some(e) = self.parse_name_only() {
|
|
||||||
names.push(e);
|
names.push(e);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !self.eat(&TokenKind::Comma) {
|
if !self.eat(&TokenKind::Comma) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -548,12 +552,8 @@ impl<'a> P<'a> {
|
|||||||
TokenKind::Kw(Kw::Read) => {
|
TokenKind::Kw(Kw::Read) => {
|
||||||
self.advance();
|
self.advance();
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
loop {
|
while let Some(e) = self.parse_name_ref() {
|
||||||
if let Some(e) = self.parse_name_ref() {
|
|
||||||
vars.push(e);
|
vars.push(e);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !self.eat(&TokenKind::Comma) {
|
if !self.eat(&TokenKind::Comma) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -733,6 +733,14 @@ impl<'a> P<'a> {
|
|||||||
}
|
}
|
||||||
Some(Stmt::ViewPrint { top, bottom, pos })
|
Some(Stmt::ViewPrint { top, bottom, pos })
|
||||||
}
|
}
|
||||||
|
TokenKind::Ident {
|
||||||
|
ref name,
|
||||||
|
suffix: None,
|
||||||
|
} if name == "VIEW" => self.parse_graphics_view(pos),
|
||||||
|
TokenKind::Ident {
|
||||||
|
ref name,
|
||||||
|
suffix: None,
|
||||||
|
} if name == "PAINT" => self.parse_graphics_paint(pos),
|
||||||
TokenKind::Ident {
|
TokenKind::Ident {
|
||||||
ref name,
|
ref name,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
@@ -832,6 +840,121 @@ impl<'a> P<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_graphics_point(&mut self) -> Option<(Expr, Expr)> {
|
||||||
|
if !self.eat(&TokenKind::LParen) {
|
||||||
|
self.err("Expected: (");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let x = self.parse_expr()?;
|
||||||
|
if !self.eat(&TokenKind::Comma) {
|
||||||
|
self.err("Expected: ,");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let y = self.parse_expr()?;
|
||||||
|
if !self.eat(&TokenKind::RParen) {
|
||||||
|
self.err("Expected: )");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_graphics_line(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||||
|
let from = if self.eat(&TokenKind::Minus) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let point = self.parse_graphics_point()?;
|
||||||
|
if !self.eat(&TokenKind::Minus) {
|
||||||
|
self.err("Expected: -");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(point)
|
||||||
|
};
|
||||||
|
let relative = self.eat_kw(Kw::Step);
|
||||||
|
let to = self.parse_graphics_point()?;
|
||||||
|
let mut color = None;
|
||||||
|
let mut fill = 0;
|
||||||
|
if self.eat(&TokenKind::Comma) {
|
||||||
|
if !matches!(
|
||||||
|
self.k(),
|
||||||
|
TokenKind::Comma | TokenKind::Colon | TokenKind::Eol | TokenKind::Eof
|
||||||
|
) {
|
||||||
|
color = Some(self.parse_expr()?);
|
||||||
|
}
|
||||||
|
if self.eat(&TokenKind::Comma) {
|
||||||
|
if let TokenKind::Ident { name, .. } = self.k() {
|
||||||
|
fill = match name.as_str() {
|
||||||
|
"B" => 1,
|
||||||
|
"BF" => 2,
|
||||||
|
_ => {
|
||||||
|
self.err("Expected: B or BF");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Stmt::GraphicsLine {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
relative,
|
||||||
|
color,
|
||||||
|
fill,
|
||||||
|
pos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_graphics_paint(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||||
|
self.advance();
|
||||||
|
let point = self.parse_graphics_point()?;
|
||||||
|
let paint = self
|
||||||
|
.eat(&TokenKind::Comma)
|
||||||
|
.then(|| self.parse_expr())
|
||||||
|
.flatten();
|
||||||
|
let border = self
|
||||||
|
.eat(&TokenKind::Comma)
|
||||||
|
.then(|| self.parse_expr())
|
||||||
|
.flatten();
|
||||||
|
Some(Stmt::GraphicsPaint {
|
||||||
|
point,
|
||||||
|
paint,
|
||||||
|
border,
|
||||||
|
pos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_graphics_view(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||||
|
self.advance();
|
||||||
|
if self.at_stmt_end() {
|
||||||
|
return Some(Stmt::GraphicsView {
|
||||||
|
rect: None,
|
||||||
|
fill: None,
|
||||||
|
border: None,
|
||||||
|
pos,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let (x1, y1) = self.parse_graphics_point()?;
|
||||||
|
if !self.eat(&TokenKind::Minus) {
|
||||||
|
self.err("Expected: -");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (x2, y2) = self.parse_graphics_point()?;
|
||||||
|
let fill = self
|
||||||
|
.eat(&TokenKind::Comma)
|
||||||
|
.then(|| self.parse_expr())
|
||||||
|
.flatten();
|
||||||
|
let border = self
|
||||||
|
.eat(&TokenKind::Comma)
|
||||||
|
.then(|| self.parse_expr())
|
||||||
|
.flatten();
|
||||||
|
Some(Stmt::GraphicsView {
|
||||||
|
rect: Some((x1, y1, x2, y2)),
|
||||||
|
fill,
|
||||||
|
border,
|
||||||
|
pos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_print(&mut self, pos: SourcePos, printer: bool) -> Option<Stmt> {
|
fn parse_print(&mut self, pos: SourcePos, printer: bool) -> Option<Stmt> {
|
||||||
self.advance(); // PRINT bzw. LPRINT
|
self.advance(); // PRINT bzw. LPRINT
|
||||||
let file = if !printer && self.eat(&TokenKind::Hash) {
|
let file = if !printer && self.eat(&TokenKind::Hash) {
|
||||||
@@ -1056,12 +1179,8 @@ impl<'a> P<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
loop {
|
while let Some(e) = self.parse_name_ref() {
|
||||||
if let Some(e) = self.parse_name_ref() {
|
|
||||||
vars.push(e);
|
vars.push(e);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !self.eat(&TokenKind::Comma) {
|
if !self.eat(&TokenKind::Comma) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1384,11 +1503,8 @@ impl<'a> P<'a> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let mut targets = Vec::new();
|
let mut targets = Vec::new();
|
||||||
loop {
|
while let Some(t) = self.parse_label_ref() {
|
||||||
match self.parse_label_ref() {
|
targets.push(t);
|
||||||
Some(t) => targets.push(t),
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
if !self.eat(&TokenKind::Comma) {
|
if !self.eat(&TokenKind::Comma) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1432,11 +1548,7 @@ impl<'a> P<'a> {
|
|||||||
let dims = if self.eat(&TokenKind::LParen) {
|
let dims = if self.eat(&TokenKind::LParen) {
|
||||||
let mut ds = Vec::new();
|
let mut ds = Vec::new();
|
||||||
if !self.eat(&TokenKind::RParen) {
|
if !self.eat(&TokenKind::RParen) {
|
||||||
loop {
|
while let Some(a) = self.parse_expr() {
|
||||||
let a = match self.parse_expr() {
|
|
||||||
Some(e) => e,
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
if self.eat_kw(Kw::To) {
|
if self.eat_kw(Kw::To) {
|
||||||
match self.parse_expr() {
|
match self.parse_expr() {
|
||||||
Some(b) => ds.push((Some(a), b)),
|
Some(b) => ds.push((Some(a), b)),
|
||||||
@@ -1602,8 +1714,7 @@ impl<'a> P<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
if self.eat(&TokenKind::LParen) {
|
if self.eat(&TokenKind::LParen) && !self.eat(&TokenKind::RParen) {
|
||||||
if !self.eat(&TokenKind::RParen) {
|
|
||||||
loop {
|
loop {
|
||||||
match self.k() {
|
match self.k() {
|
||||||
TokenKind::Ident {
|
TokenKind::Ident {
|
||||||
@@ -1640,7 +1751,6 @@ impl<'a> P<'a> {
|
|||||||
}
|
}
|
||||||
self.eat(&TokenKind::RParen);
|
self.eat(&TokenKind::RParen);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Some(ProcSig {
|
Some(ProcSig {
|
||||||
kind,
|
kind,
|
||||||
name,
|
name,
|
||||||
@@ -1649,10 +1759,10 @@ impl<'a> P<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_proc(&mut self) -> Option<Proc> {
|
fn parse_proc(&mut self, leading_static: bool) -> Option<Proc> {
|
||||||
let pos = self.pos();
|
let pos = self.pos();
|
||||||
let sig = self.parse_proc_sig()?;
|
let sig = self.parse_proc_sig()?;
|
||||||
let is_static = self.eat_kw(Kw::Static);
|
let is_static = leading_static || self.eat_kw(Kw::Static);
|
||||||
let end_kw = match sig.kind {
|
let end_kw = match sig.kind {
|
||||||
ProcKind::Sub => Kw::Sub,
|
ProcKind::Sub => Kw::Sub,
|
||||||
ProcKind::Function => Kw::Function,
|
ProcKind::Function => Kw::Function,
|
||||||
@@ -1697,8 +1807,7 @@ impl<'a> P<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
if self.eat(&TokenKind::LParen) {
|
if self.eat(&TokenKind::LParen) && !self.eat(&TokenKind::RParen) {
|
||||||
if !self.eat(&TokenKind::RParen) {
|
|
||||||
loop {
|
loop {
|
||||||
match self.k() {
|
match self.k() {
|
||||||
TokenKind::Ident {
|
TokenKind::Ident {
|
||||||
@@ -1724,7 +1833,6 @@ impl<'a> P<'a> {
|
|||||||
}
|
}
|
||||||
self.eat(&TokenKind::RParen);
|
self.eat(&TokenKind::RParen);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if self.eat(&TokenKind::Eq) {
|
if self.eat(&TokenKind::Eq) {
|
||||||
let body = self.parse_expr()?;
|
let body = self.parse_expr()?;
|
||||||
Some(Stmt::DefFn {
|
Some(Stmt::DefFn {
|
||||||
@@ -1818,8 +1926,8 @@ impl<'a> P<'a> {
|
|||||||
other => Expr::Paren(Box::new(other)),
|
other => Expr::Paren(Box::new(other)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if !self.at_stmt_end() && call_args.is_empty() {
|
if !self.at_stmt_end() && (call_args.is_empty() || name.contains('.')) {
|
||||||
call_args = self.parse_arg_list_to_stmt_end();
|
call_args.extend(self.parse_arg_list_to_stmt_end());
|
||||||
}
|
}
|
||||||
Some(Stmt::Call {
|
Some(Stmt::Call {
|
||||||
name,
|
name,
|
||||||
@@ -1842,7 +1950,7 @@ impl<'a> P<'a> {
|
|||||||
match self.k() {
|
match self.k() {
|
||||||
TokenKind::Ident { mut name, suffix } => {
|
TokenKind::Ident { mut name, suffix } => {
|
||||||
self.advance();
|
self.advance();
|
||||||
let args = if self.eat(&TokenKind::LParen) {
|
let mut args = if self.eat(&TokenKind::LParen) {
|
||||||
let a = self.parse_arg_list(&TokenKind::RParen);
|
let a = self.parse_arg_list(&TokenKind::RParen);
|
||||||
self.eat(&TokenKind::RParen);
|
self.eat(&TokenKind::RParen);
|
||||||
Some(a)
|
Some(a)
|
||||||
@@ -1858,6 +1966,11 @@ impl<'a> P<'a> {
|
|||||||
name.push('.');
|
name.push('.');
|
||||||
name.push_str(&member);
|
name.push_str(&member);
|
||||||
self.advance();
|
self.advance();
|
||||||
|
if self.eat(&TokenKind::LParen) {
|
||||||
|
let member_args = self.parse_arg_list(&TokenKind::RParen);
|
||||||
|
self.eat(&TokenKind::RParen);
|
||||||
|
args.get_or_insert_with(Vec::new).extend(member_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => self.err("Expected: property"),
|
_ => self.err("Expected: property"),
|
||||||
}
|
}
|
||||||
@@ -1935,6 +2048,7 @@ impl<'a> P<'a> {
|
|||||||
args.push(Expr::Missing);
|
args.push(Expr::Missing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
self.eat(&TokenKind::Hash);
|
||||||
match self.parse_expr() {
|
match self.parse_expr() {
|
||||||
Some(e) => args.push(e),
|
Some(e) => args.push(e),
|
||||||
None => break,
|
None => break,
|
||||||
@@ -1985,11 +2099,7 @@ impl<'a> P<'a> {
|
|||||||
|
|
||||||
fn parse_bin(&mut self, min_prec: u8) -> Option<Expr> {
|
fn parse_bin(&mut self, min_prec: u8) -> Option<Expr> {
|
||||||
let mut lhs = self.parse_prefix()?;
|
let mut lhs = self.parse_prefix()?;
|
||||||
loop {
|
while let Some((prec, op)) = self.infix_op() {
|
||||||
let (prec, op) = match self.infix_op() {
|
|
||||||
Some(x) => x,
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
if prec < min_prec {
|
if prec < min_prec {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -131,12 +131,12 @@ fn stand() -> Stand {
|
|||||||
let unsup_fn = abschnitt(
|
let unsup_fn = abschnitt(
|
||||||
&q,
|
&q,
|
||||||
"// --- Spätere Phasen: dokumentiert",
|
"// --- Spätere Phasen: dokumentiert",
|
||||||
"(HExpr::Unsupported(name)",
|
"(HExpr::Unsupported(\"Funktion\")",
|
||||||
);
|
);
|
||||||
let unsup_stmt = abschnitt(
|
let unsup_stmt = abschnitt(
|
||||||
&q,
|
&q,
|
||||||
"// Bildschirm-/Datei-/System-Anweisungen späterer Phasen.",
|
"// Bildschirm-/Datei-/System-Anweisungen späterer Phasen.",
|
||||||
r#"_ => "Anweisung""#,
|
r#"HStmtKind::Unsupported("Anweisung")"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut signaturen = literale(fn_tab);
|
let mut signaturen = literale(fn_tab);
|
||||||
@@ -470,7 +470,6 @@ fn dokumentierte_elemente_werden_namentlich_abgewiesen() {
|
|||||||
("Y = POINT(1, 2)", "POINT", "Feature unavailable"),
|
("Y = POINT(1, 2)", "POINT", "Feature unavailable"),
|
||||||
("CALLS Foo", "CALLS", "Feature unavailable"),
|
("CALLS Foo", "CALLS", "Feature unavailable"),
|
||||||
// Non-Feature über die Syntax (teilt das Schlüsselwort)
|
// Non-Feature über die Syntax (teilt das Schlüsselwort)
|
||||||
("LINE (1, 1)-(2, 2)", "LINE", "Feature unavailable"),
|
|
||||||
(
|
(
|
||||||
"GET (1, 1)-(2, 2), A",
|
"GET (1, 1)-(2, 2), A",
|
||||||
"GET (Grafik)",
|
"GET (Grafik)",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ pub struct RtState {
|
|||||||
rng: u32,
|
rng: u32,
|
||||||
rnd_last: f32,
|
rnd_last: f32,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
|
pub clipboard: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RtState {
|
impl Default for RtState {
|
||||||
@@ -93,6 +94,7 @@ impl Default for RtState {
|
|||||||
rng: 0x50000,
|
rng: 0x50000,
|
||||||
rnd_last: 0.0,
|
rnd_last: 0.0,
|
||||||
command: String::new(),
|
command: String::new(),
|
||||||
|
clipboard: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,7 +440,14 @@ pub mod ids {
|
|||||||
pub const ISAM_BOF: u16 = 147;
|
pub const ISAM_BOF: u16 = 147;
|
||||||
/// `SetUEvent` — loest das benutzerdefinierte Ereignis aus (§8).
|
/// `SetUEvent` — loest das benutzerdefinierte Ereignis aus (§8).
|
||||||
pub const SETUEVENT: u16 = 148;
|
pub const SETUEVENT: u16 = 148;
|
||||||
pub const COUNT: u16 = 149;
|
pub const MSGBOX: u16 = 149;
|
||||||
|
pub const INPUTBOX_S: u16 = 150;
|
||||||
|
pub const CLIPBOARD_ADD: u16 = 151;
|
||||||
|
pub const CLIPBOARD_GET: u16 = 152;
|
||||||
|
pub const GRAPHICS_LINE: u16 = 153;
|
||||||
|
pub const GRAPHICS_PAINT: u16 = 154;
|
||||||
|
pub const GRAPHICS_VIEW: u16 = 155;
|
||||||
|
pub const COUNT: u16 = 156;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch-Tabelle in Index-Reihenfolge.
|
/// Dispatch-Tabelle in Index-Reihenfolge.
|
||||||
@@ -594,11 +603,35 @@ pub fn builtin_table() -> &'static [BuiltinFn] {
|
|||||||
bi_isam_setmem,
|
bi_isam_setmem,
|
||||||
bi_isam_bof,
|
bi_isam_bof,
|
||||||
bi_setuevent,
|
bi_setuevent,
|
||||||
|
bi_msgbox,
|
||||||
|
bi_inputbox,
|
||||||
|
bi_clipboard_add,
|
||||||
|
bi_clipboard_get,
|
||||||
|
bi_graphics_line,
|
||||||
|
bi_graphics_paint,
|
||||||
|
bi_graphics_view,
|
||||||
];
|
];
|
||||||
debug_assert_eq!(TABLE.len(), ids::COUNT as usize);
|
debug_assert_eq!(TABLE.len(), ids::COUNT as usize);
|
||||||
TABLE
|
TABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bi_clipboard_add(
|
||||||
|
st: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
a: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
st.clipboard.push_str(&arg_str(a, 0)?);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bi_clipboard_get(
|
||||||
|
st: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
_: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
Ok(Some(Value::Str(Rc::from(st.clipboard.as_str()))))
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Argument-Hilfen --------------------------------------------------------
|
// ---- Argument-Hilfen --------------------------------------------------------
|
||||||
|
|
||||||
fn arg_str(args: &[Value], i: usize) -> Result<Rc<str>, RuntimeError> {
|
fn arg_str(args: &[Value], i: usize) -> Result<Rc<str>, RuntimeError> {
|
||||||
@@ -1881,17 +1914,90 @@ fn bi_view_print(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bi_screen_stmt(
|
fn bi_screen_stmt(
|
||||||
_: &mut RtState,
|
st: &mut RtState,
|
||||||
_: &mut dyn Host,
|
_: &mut dyn Host,
|
||||||
a: &mut [Value],
|
a: &mut [Value],
|
||||||
) -> Result<Option<Value>, RuntimeError> {
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
// `SCREEN modus[, farbschalter[, aktiv[, sichtbar]]]`. Nur der Textmodus 0
|
// Die Terminaldarstellung bildet die historischen Pixelmodi auf Zellen ab;
|
||||||
// existiert; Grafikmodi sind deklariertes Non-Feature. Die übrigen
|
// Seitenargumente haben im einzelnen Puffer keine zusätzliche Wirkung.
|
||||||
// Argumente betreffen Grafikseiten und bleiben folgenlos.
|
|
||||||
match opt(a, 0)? {
|
match opt(a, 0)? {
|
||||||
None | Some(0) => Ok(None),
|
None | Some(0..=13) => {
|
||||||
Some(_) => Err(RuntimeError(73)), // Advanced feature unavailable
|
st.screen.graphics_view(None).unwrap();
|
||||||
|
st.screen.cls();
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
Some(_) => Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bi_graphics_line(
|
||||||
|
st: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
a: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
let mut from = (arg_i32(a, 0)?, arg_i32(a, 1)?);
|
||||||
|
if from.0 == i32::MIN {
|
||||||
|
from = st.screen.graphics_position();
|
||||||
|
}
|
||||||
|
let mut to = (arg_i32(a, 2)?, arg_i32(a, 3)?);
|
||||||
|
if arg_i32(a, 5)? != 0 {
|
||||||
|
to.0 += from.0;
|
||||||
|
to.1 += from.1;
|
||||||
|
}
|
||||||
|
let color = match arg_i32(a, 4)? {
|
||||||
|
-1 => i32::from(st.screen.fg),
|
||||||
|
color @ 0..=15 => color,
|
||||||
|
_ => return Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
|
||||||
|
};
|
||||||
|
st.screen
|
||||||
|
.graphics_line(from, to, color, arg_i32(a, 6)? as u8);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bi_graphics_paint(
|
||||||
|
st: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
a: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
let color = match a.get(2) {
|
||||||
|
Some(Value::Int(v)) => i32::from(*v),
|
||||||
|
Some(Value::Lng(v)) => *v,
|
||||||
|
Some(Value::Str(_)) | None => i32::from(st.screen.fg),
|
||||||
|
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||||
|
};
|
||||||
|
if !(0..=15).contains(&color) {
|
||||||
|
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||||
|
}
|
||||||
|
st.screen
|
||||||
|
.graphics_paint(arg_i32(a, 0)?, arg_i32(a, 1)?, color);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bi_graphics_view(
|
||||||
|
st: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
a: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
let x1 = arg_i32(a, 0)?;
|
||||||
|
if x1 < 0 {
|
||||||
|
st.screen.graphics_view(None).unwrap();
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let view = (x1, arg_i32(a, 1)?, arg_i32(a, 2)?, arg_i32(a, 3)?);
|
||||||
|
st.screen
|
||||||
|
.graphics_view(Some(view))
|
||||||
|
.map_err(|_| RuntimeError::ILLEGAL_FUNCTION_CALL)?;
|
||||||
|
let fill = arg_i32(a, 4)?;
|
||||||
|
if fill >= 0 {
|
||||||
|
st.screen
|
||||||
|
.graphics_line((view.0, view.1), (view.2, view.3), fill, 2);
|
||||||
|
}
|
||||||
|
let border = arg_i32(a, 5)?;
|
||||||
|
if border >= 0 {
|
||||||
|
st.screen
|
||||||
|
.graphics_line((view.0, view.1), (view.2, view.3), border, 1);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Nummer eines Funktionstasten-Makros → Index 0–11.
|
/// Nummer eines Funktionstasten-Makros → Index 0–11.
|
||||||
@@ -2016,6 +2122,14 @@ fn bi_input_s(
|
|||||||
if n < 0 {
|
if n < 0 {
|
||||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||||
}
|
}
|
||||||
|
if a.len() > 1 {
|
||||||
|
let nummer = arg_i32(a, 1)?;
|
||||||
|
let datei = st.dateien.get(nummer)?;
|
||||||
|
let bytes = datei.bytes_lesen(datei.position, n as usize)?;
|
||||||
|
return Ok(Some(Value::Str(Rc::from(
|
||||||
|
String::from_utf8_lossy(&bytes).as_ref(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
// Blockierend, ohne Echo auf dem Bildschirm.
|
// Blockierend, ohne Echo auf dem Bildschirm.
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
@@ -2546,6 +2660,22 @@ fn bi_isam_bof(
|
|||||||
Ok(Some(Value::Int(if st.isam.bof(n) { -1 } else { 0 })))
|
Ok(Some(Value::Int(if st.isam.bof(n) { -1 } else { 0 })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bi_msgbox(
|
||||||
|
_: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
_: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
Err(RuntimeError::FEATURE_UNAVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bi_inputbox(
|
||||||
|
_: &mut RtState,
|
||||||
|
_: &mut dyn Host,
|
||||||
|
_: &mut [Value],
|
||||||
|
) -> Result<Option<Value>, RuntimeError> {
|
||||||
|
Err(RuntimeError::FEATURE_UNAVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -2635,7 +2765,7 @@ mod tests {
|
|||||||
h.ereignis(Ereignis::Signal(1));
|
h.ereignis(Ereignis::Signal(1));
|
||||||
st.pump(&mut h, false);
|
st.pump(&mut h, false);
|
||||||
assert_eq!(st.traps.naechstes(), Some((Quelle::Signal(1), 22)));
|
assert_eq!(st.traps.naechstes(), Some((Quelle::Signal(1), 22)));
|
||||||
assert!(matches!(h.next_event(false), None));
|
assert!(h.next_event(false).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ use unicode_width::UnicodeWidthChar;
|
|||||||
pub const MIN_COLS: usize = 80;
|
pub const MIN_COLS: usize = 80;
|
||||||
pub const MIN_ROWS: usize = 25;
|
pub const MIN_ROWS: usize = 25;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ScreenError;
|
||||||
|
|
||||||
/// Eine Bildschirmzelle: Zeichen plus klassisches Farbattribut.
|
/// Eine Bildschirmzelle: Zeichen plus klassisches Farbattribut.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
@@ -78,6 +81,9 @@ pub struct TextScreen {
|
|||||||
/// Seit der letzten Anzeige verändert? Der Host wird nur dann zum
|
/// Seit der letzten Anzeige verändert? Der Host wird nur dann zum
|
||||||
/// Neuzeichnen aufgefordert.
|
/// Neuzeichnen aufgefordert.
|
||||||
veraendert: bool,
|
veraendert: bool,
|
||||||
|
graphic_x: i32,
|
||||||
|
graphic_y: i32,
|
||||||
|
graphic_view: Option<(i32, i32, i32, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextScreen {
|
impl Default for TextScreen {
|
||||||
@@ -110,6 +116,9 @@ impl TextScreen {
|
|||||||
view_full: true,
|
view_full: true,
|
||||||
belegt: vec![None; rows],
|
belegt: vec![None; rows],
|
||||||
veraendert: true,
|
veraendert: true,
|
||||||
|
graphic_x: 0,
|
||||||
|
graphic_y: 0,
|
||||||
|
graphic_view: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +155,8 @@ impl TextScreen {
|
|||||||
}
|
}
|
||||||
self.cells = cells;
|
self.cells = cells;
|
||||||
let mut belegt = vec![None; rows];
|
let mut belegt = vec![None; rows];
|
||||||
for r in 0..self.rows.min(rows) {
|
for (r, value) in belegt.iter_mut().enumerate().take(self.rows.min(rows)) {
|
||||||
belegt[r] = self.belegt[r].map(|c| c.min(cols - 1));
|
*value = self.belegt[r].map(|c| c.min(cols - 1));
|
||||||
}
|
}
|
||||||
self.belegt = belegt;
|
self.belegt = belegt;
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
@@ -225,9 +234,9 @@ impl TextScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `LOCATE zeile, spalte` (1-basiert); außerhalb → Err (Fehler 5).
|
/// `LOCATE zeile, spalte` (1-basiert); außerhalb → Err (Fehler 5).
|
||||||
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ()> {
|
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ScreenError> {
|
||||||
if row < 1 || row > self.rows || col < 1 || col > self.cols {
|
if row < 1 || row > self.rows || col < 1 || col > self.cols {
|
||||||
return Err(());
|
return Err(ScreenError);
|
||||||
}
|
}
|
||||||
self.cur_row = row - 1;
|
self.cur_row = row - 1;
|
||||||
// Auf eine Fortsetzungszelle zu zeigen bedeutet den Zeichenanfang.
|
// Auf eine Fortsetzungszelle zu zeigen bedeutet den Zeichenanfang.
|
||||||
@@ -251,9 +260,9 @@ impl TextScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `VIEW PRINT oben TO unten` (1-basiert).
|
/// `VIEW PRINT oben TO unten` (1-basiert).
|
||||||
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ()> {
|
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ScreenError> {
|
||||||
if top < 1 || bottom > self.rows || top > bottom {
|
if top < 1 || bottom > self.rows || top > bottom {
|
||||||
return Err(());
|
return Err(ScreenError);
|
||||||
}
|
}
|
||||||
self.view_top = top - 1;
|
self.view_top = top - 1;
|
||||||
self.view_bottom = bottom - 1;
|
self.view_bottom = bottom - 1;
|
||||||
@@ -265,6 +274,122 @@ impl TextScreen {
|
|||||||
self.cells[(row - 1) * self.cols + (col - 1)]
|
self.cells[(row - 1) * self.cols + (col - 1)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn graphics_position(&self) -> (i32, i32) {
|
||||||
|
(self.graphic_x, self.graphic_y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_view(&mut self, view: Option<(i32, i32, i32, i32)>) -> Result<(), ScreenError> {
|
||||||
|
if view.is_some_and(|(x1, y1, x2, y2)| x1 < 0 || y1 < 0 || x1 > x2 || y1 > y2) {
|
||||||
|
return Err(ScreenError);
|
||||||
|
}
|
||||||
|
self.graphic_view = view;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn graphics_cell(&mut self, x: i32, y: i32, color: i32) {
|
||||||
|
if x < 0
|
||||||
|
|| y < 0
|
||||||
|
|| self
|
||||||
|
.graphic_view
|
||||||
|
.is_some_and(|(x1, y1, x2, y2)| x < x1 || x > x2 || y < y1 || y > y2)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (col, row) = (x as usize / 8, y as usize / 8);
|
||||||
|
if col >= self.cols || row >= self.rows {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.cells[row * self.cols + col] = Cell {
|
||||||
|
ch: if color == 0 { ' ' } else { '█' },
|
||||||
|
fg: color.clamp(0, 15) as u8,
|
||||||
|
bg: self.bg,
|
||||||
|
fortsetzung: false,
|
||||||
|
};
|
||||||
|
self.belegt[row] = Some(self.belegt[row].map_or(col, |last| last.max(col)));
|
||||||
|
self.veraendert = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_line(&mut self, from: (i32, i32), to: (i32, i32), color: i32, fill: u8) {
|
||||||
|
let (x1, y1) = from;
|
||||||
|
let (x2, y2) = to;
|
||||||
|
if fill == 2 {
|
||||||
|
for y in (y1.min(y2)..=y1.max(y2)).step_by(8) {
|
||||||
|
for x in (x1.min(x2)..=x1.max(x2)).step_by(8) {
|
||||||
|
self.graphics_cell(x, y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if fill == 1 {
|
||||||
|
self.graphics_segment(x1, y1, x2, y1, color);
|
||||||
|
self.graphics_segment(x2, y1, x2, y2, color);
|
||||||
|
self.graphics_segment(x2, y2, x1, y2, color);
|
||||||
|
self.graphics_segment(x1, y2, x1, y1, color);
|
||||||
|
} else {
|
||||||
|
self.graphics_segment(x1, y1, x2, y2, color);
|
||||||
|
}
|
||||||
|
self.graphic_x = x2;
|
||||||
|
self.graphic_y = y2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn graphics_segment(&mut self, mut x: i32, mut y: i32, x2: i32, y2: i32, color: i32) {
|
||||||
|
let dx = (x2 - x).abs();
|
||||||
|
let sx = if x < x2 { 1 } else { -1 };
|
||||||
|
let dy = -(y2 - y).abs();
|
||||||
|
let sy = if y < y2 { 1 } else { -1 };
|
||||||
|
let mut error = dx + dy;
|
||||||
|
loop {
|
||||||
|
self.graphics_cell(x, y, color);
|
||||||
|
if x == x2 && y == y2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let twice = error * 2;
|
||||||
|
if twice >= dy {
|
||||||
|
error += dy;
|
||||||
|
x += sx;
|
||||||
|
}
|
||||||
|
if twice <= dx {
|
||||||
|
error += dx;
|
||||||
|
y += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_paint(&mut self, x: i32, y: i32, color: i32) {
|
||||||
|
let (start_col, start_row) = (x.max(0) as usize / 8, y.max(0) as usize / 8);
|
||||||
|
if start_col >= self.cols || start_row >= self.rows {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let target = self.cells[start_row * self.cols + start_col].ch;
|
||||||
|
let replacement = if color == 0 { ' ' } else { '█' };
|
||||||
|
if target == replacement {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut pending = vec![(start_col, start_row)];
|
||||||
|
while let Some((col, row)) = pending.pop() {
|
||||||
|
let px = col as i32 * 8;
|
||||||
|
let py = row as i32 * 8;
|
||||||
|
if col >= self.cols
|
||||||
|
|| row >= self.rows
|
||||||
|
|| self.cells[row * self.cols + col].ch != target
|
||||||
|
|| self
|
||||||
|
.graphic_view
|
||||||
|
.is_some_and(|(x1, y1, x2, y2)| px < x1 || px > x2 || py < y1 || py > y2)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.graphics_cell(px, py, color);
|
||||||
|
if col > 0 {
|
||||||
|
pending.push((col - 1, row));
|
||||||
|
}
|
||||||
|
if row > 0 {
|
||||||
|
pending.push((col, row - 1));
|
||||||
|
}
|
||||||
|
pending.push((col + 1, row));
|
||||||
|
pending.push((col, row + 1));
|
||||||
|
}
|
||||||
|
self.graphic_x = x;
|
||||||
|
self.graphic_y = y;
|
||||||
|
}
|
||||||
|
|
||||||
/// Text an der Cursorposition ausgeben: Umbruch am rechten Rand,
|
/// Text an der Cursorposition ausgeben: Umbruch am rechten Rand,
|
||||||
/// Scrollen am unteren Rand des Scrollbereichs. `\n` bricht um,
|
/// Scrollen am unteren Rand des Scrollbereichs. `\n` bricht um,
|
||||||
/// `\r` setzt an den Zeilenanfang.
|
/// `\r` setzt an den Zeilenanfang.
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ fn tausender_gruppieren(s: &str) -> String {
|
|||||||
let z: Vec<char> = s.chars().collect();
|
let z: Vec<char> = s.chars().collect();
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
for (i, c) in z.iter().enumerate() {
|
for (i, c) in z.iter().enumerate() {
|
||||||
if i > 0 && (z.len() - i) % 3 == 0 {
|
if i > 0 && (z.len() - i).is_multiple_of(3) {
|
||||||
out.push(',');
|
out.push(',');
|
||||||
}
|
}
|
||||||
out.push(*c);
|
out.push(*c);
|
||||||
@@ -433,7 +433,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn numerisches_feld_mit_nachkommastellen() {
|
fn numerisches_feld_mit_nachkommastellen() {
|
||||||
assert_eq!(u("###.##", &[z(3.14159)]), " 3.14");
|
assert_eq!(u("###.##", &[z(3.126)]), " 3.13");
|
||||||
assert_eq!(u("##.##", &[z(-1.5)]), "-1.50");
|
assert_eq!(u("##.##", &[z(-1.5)]), "-1.50");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -199,9 +199,7 @@ pub fn cur_to_i64(c: i64) -> i64 {
|
|||||||
// r in 0..10000; runde halb-zu-gerade
|
// r in 0..10000; runde halb-zu-gerade
|
||||||
if r > 5_000 {
|
if r > 5_000 {
|
||||||
q + 1
|
q + 1
|
||||||
} else if r < 5_000 {
|
} else if r < 5_000 || q % 2 == 0 {
|
||||||
q
|
|
||||||
} else if q % 2 == 0 {
|
|
||||||
q
|
q
|
||||||
} else {
|
} else {
|
||||||
q + 1
|
q + 1
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,11 @@
|
|||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use tb_frontend::forms::{self, ObjectClass, PropertyDefault, PropertySpec, PropertyType};
|
use tb_frontend::forms::{
|
||||||
|
self, FormCatalog, ObjectClass, PropertyDefault, PropertySpec, PropertyType,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::forms::PropertyValue;
|
use crate::forms::{FormsModel, PropertyValue};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FormNode {
|
pub struct FormNode {
|
||||||
@@ -46,6 +48,67 @@ impl FormFile {
|
|||||||
original: None,
|
original: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn catalog(&self) -> FormCatalog {
|
||||||
|
fn add(node: &FormNode, parent: Option<&str>, catalog: &mut FormCatalog) {
|
||||||
|
if let Some((id, _)) = catalog.find(&node.name) {
|
||||||
|
catalog.objects[id as usize].array = true;
|
||||||
|
} else {
|
||||||
|
let array = forms::property(node.class, "INDEX")
|
||||||
|
.and_then(|(property, _)| node.properties.get(&property))
|
||||||
|
.is_some_and(
|
||||||
|
|value| matches!(value, PropertyValue::Integer(index) if *index != 0),
|
||||||
|
);
|
||||||
|
catalog.add(&node.name, node.class, parent, array);
|
||||||
|
}
|
||||||
|
for child in &node.children {
|
||||||
|
add(child, Some(&node.name), catalog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut catalog = FormCatalog::default();
|
||||||
|
add(&self.root, None, &mut catalog);
|
||||||
|
catalog
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&self, model: &mut FormsModel) -> Result<(), tb_runtime::errors::RuntimeError> {
|
||||||
|
fn apply_node(
|
||||||
|
node: &FormNode,
|
||||||
|
model: &mut FormsModel,
|
||||||
|
menu_depth: usize,
|
||||||
|
) -> Result<(), tb_runtime::errors::RuntimeError> {
|
||||||
|
let menu_depth = if node.class == ObjectClass::Menu {
|
||||||
|
menu_depth + 1
|
||||||
|
} else {
|
||||||
|
menu_depth
|
||||||
|
};
|
||||||
|
if menu_depth > 6 {
|
||||||
|
return Err(tb_runtime::errors::RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||||
|
}
|
||||||
|
let object = model
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.position(|candidate| candidate.description.name.eq_ignore_ascii_case(&node.name))
|
||||||
|
.ok_or(tb_runtime::errors::RuntimeError(420))? as u16;
|
||||||
|
let index = forms::property(node.class, "INDEX")
|
||||||
|
.and_then(|(property, _)| node.properties.get(&property))
|
||||||
|
.and_then(|value| match value {
|
||||||
|
PropertyValue::Integer(value) => Some(*value),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
if index != 0 && !model.is_loaded_at(object, Some(index)) {
|
||||||
|
model.load_design_array(object, index)?;
|
||||||
|
}
|
||||||
|
for (property, value) in &node.properties {
|
||||||
|
model.set_initial_at(object, Some(index), *property, value.clone())?;
|
||||||
|
}
|
||||||
|
for child in &node.children {
|
||||||
|
apply_node(child, model, menu_depth)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
apply_node(&self.root, model, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -444,7 +507,7 @@ fn read_symbol_table(bytes: &[u8], mut at: usize) -> Result<Vec<BinarySymbol>, u
|
|||||||
let reference = read_u16(bytes, at).ok_or(at)?;
|
let reference = read_u16(bytes, at).ok_or(at)?;
|
||||||
let class_id = bytes[at + 2] & 0x7f;
|
let class_id = bytes[at + 2] & 0x7f;
|
||||||
let length = bytes[at + 3] as usize;
|
let length = bytes[at + 3] as usize;
|
||||||
let class = ObjectClass::from_id(class_id).ok_or(at + 2)?;
|
let mut class = ObjectClass::from_id(class_id).ok_or(at + 2)?;
|
||||||
if length == 0 || length > 40 || at + 4 + length > bytes.len() {
|
if length == 0 || length > 40 || at + 4 + length > bytes.len() {
|
||||||
return Err(at + 3);
|
return Err(at + 3);
|
||||||
}
|
}
|
||||||
@@ -459,12 +522,18 @@ fn read_symbol_table(bytes: &[u8], mut at: usize) -> Result<Vec<BinarySymbol>, u
|
|||||||
if symbols.is_empty() && class != ObjectClass::Form {
|
if symbols.is_empty() && class != ObjectClass::Form {
|
||||||
return Err(at + 2);
|
return Err(at + 2);
|
||||||
}
|
}
|
||||||
|
let name = String::from_utf8_lossy(name).into_owned();
|
||||||
|
if class == ObjectClass::Screen
|
||||||
|
&& matches!(name.to_ascii_uppercase().as_str(), "VSPIN" | "HSPIN")
|
||||||
|
{
|
||||||
|
class = ObjectClass::Spin;
|
||||||
|
}
|
||||||
symbols.push(BinarySymbol {
|
symbols.push(BinarySymbol {
|
||||||
offset: at,
|
offset: at,
|
||||||
class,
|
class,
|
||||||
name: String::from_utf8_lossy(name).into_owned(),
|
name,
|
||||||
is_array: bytes[at + 2] & 0x80 != 0,
|
is_array: bytes[at + 2] & 0x80 != 0,
|
||||||
unsupported: class_id == ObjectClass::Screen.id(),
|
unsupported: class_id == ObjectClass::Screen.id() && class != ObjectClass::Spin,
|
||||||
});
|
});
|
||||||
at += 4 + length;
|
at += 4 + length;
|
||||||
if reference == 0 {
|
if reference == 0 {
|
||||||
@@ -501,7 +570,7 @@ fn record_len(class: ObjectClass) -> usize {
|
|||||||
ObjectClass::PictureBox => 31,
|
ObjectClass::PictureBox => 31,
|
||||||
ObjectClass::TextBox => 34,
|
ObjectClass::TextBox => 34,
|
||||||
ObjectClass::Menu | ObjectClass::Frame => 26,
|
ObjectClass::Menu | ObjectClass::Frame => 26,
|
||||||
ObjectClass::Screen => 80,
|
ObjectClass::Screen | ObjectClass::Spin => 80,
|
||||||
_ => 28,
|
_ => 28,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,7 +591,7 @@ fn record_lengths(class: ObjectClass) -> &'static [usize] {
|
|||||||
ObjectClass::OptionButton => &[21, 28],
|
ObjectClass::OptionButton => &[21, 28],
|
||||||
ObjectClass::PictureBox => &[31],
|
ObjectClass::PictureBox => &[31],
|
||||||
ObjectClass::HScrollBar | ObjectClass::VScrollBar => &[32],
|
ObjectClass::HScrollBar | ObjectClass::VScrollBar => &[32],
|
||||||
ObjectClass::Screen => &[73, 80],
|
ObjectClass::Screen | ObjectClass::Spin => &[73, 80],
|
||||||
ObjectClass::Timer => &[21, 28],
|
ObjectClass::Timer => &[21, 28],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,7 +696,12 @@ fn walk_object_records(
|
|||||||
let next_symbol = bytes[header] as usize;
|
let next_symbol = bytes[header] as usize;
|
||||||
if next_symbol >= symbols.len()
|
if next_symbol >= symbols.len()
|
||||||
|| bytes[header + 2] != 0
|
|| bytes[header + 2] != 0
|
||||||
|| bytes[header + 1] & 0x7f != symbols[next_symbol].class.id()
|
|| bytes[header + 1] & 0x7f
|
||||||
|
!= if symbols[next_symbol].class == ObjectClass::Spin {
|
||||||
|
ObjectClass::Screen.id()
|
||||||
|
} else {
|
||||||
|
symbols[next_symbol].class.id()
|
||||||
|
}
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -978,7 +1052,7 @@ fn assign_strings(
|
|||||||
) -> Result<Vec<BinaryWarning>, FormError> {
|
) -> Result<Vec<BinaryWarning>, FormError> {
|
||||||
let mut warnings = records
|
let mut warnings = records
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|record| symbols[record.symbol].class == ObjectClass::Screen)
|
.filter(|record| symbols[record.symbol].unsupported)
|
||||||
.map(|record| BinaryWarning {
|
.map(|record| BinaryWarning {
|
||||||
offset: 0x20 + record.start,
|
offset: 0x20 + record.start,
|
||||||
name: format!("{}.CustomControl", symbols[record.symbol].name),
|
name: format!("{}.CustomControl", symbols[record.symbol].name),
|
||||||
@@ -1063,10 +1137,9 @@ fn assign_strings(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for record in records
|
for record in records.iter().filter(|record| {
|
||||||
.iter()
|
symbols[record.symbol].unsupported || symbols[record.symbol].class == ObjectClass::Spin
|
||||||
.filter(|record| symbols[record.symbol].unsupported)
|
}) {
|
||||||
{
|
|
||||||
for offset in [17, 28, 37, 39, 53, 55, 64, 69] {
|
for offset in [17, 28, 37, 39, 53, 55, 64, 69] {
|
||||||
let Some(pointer) =
|
let Some(pointer) =
|
||||||
record_u16(bytes, record.start, record.end, offset).filter(|pointer| *pointer != 0)
|
record_u16(bytes, record.start, record.end, offset).filter(|pointer| *pointer != 0)
|
||||||
@@ -1319,6 +1392,35 @@ mod tests {
|
|||||||
assert_eq!(write_text(&form), EXAMPLE);
|
assert_eq!(write_text(&form), EXAMPLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_form_control_arrays_share_one_catalog_object_and_keep_indices() {
|
||||||
|
let form = read_text(
|
||||||
|
"array.frm",
|
||||||
|
"VERSION 1.00\nBegin Form Form1\n Begin CommandButton Command1\n Index = 0\n Caption = \"null\"\n End\n Begin CommandButton Command1\n Index = 1\n Caption = \"eins\"\n End\nEnd\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let catalog = form.catalog();
|
||||||
|
assert_eq!(catalog.objects.len(), 2);
|
||||||
|
assert!(catalog.objects[1].array);
|
||||||
|
let mut model = FormsModel::new(catalog.objects, 80, 25);
|
||||||
|
form.apply(&mut model).unwrap();
|
||||||
|
let caption = forms::property(ObjectClass::CommandButton, "CAPTION")
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
assert_eq!(
|
||||||
|
model.get_at(1, Some(0), caption).unwrap(),
|
||||||
|
PropertyValue::String("null".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.get_at(1, Some(1), caption).unwrap(),
|
||||||
|
PropertyValue::String("eins".into())
|
||||||
|
);
|
||||||
|
model.show(0, false).unwrap();
|
||||||
|
model.events.clear();
|
||||||
|
model.object_method(1, "SETFOCUS", vec![]).unwrap();
|
||||||
|
assert_eq!(model.next_event().unwrap().array_index, Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_version_class_property_value_and_open_block_with_location() {
|
fn rejects_version_class_property_value_and_open_block_with_location() {
|
||||||
let cases = [
|
let cases = [
|
||||||
@@ -1502,6 +1604,18 @@ mod tests {
|
|||||||
assert_eq!(failure.name, "New.HEIGHT");
|
assert_eq!(failure.name, "New.HEIGHT");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spin_symbol_wird_als_bedienbares_control_erkannt() {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
bytes.extend_from_slice(&[1, 0, ObjectClass::Form.id(), 5]);
|
||||||
|
bytes.extend_from_slice(b"Form1");
|
||||||
|
bytes.extend_from_slice(&[0, 0, ObjectClass::Screen.id(), 5]);
|
||||||
|
bytes.extend_from_slice(b"VSpin");
|
||||||
|
let symbols = read_symbol_table(&bytes, 0).unwrap();
|
||||||
|
assert_eq!(symbols[1].class, ObjectClass::Spin);
|
||||||
|
assert!(!symbols[1].unsupported);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unsupported_custom_control_is_named_and_not_emitted_as_screen() {
|
fn unsupported_custom_control_is_named_and_not_emitted_as_screen() {
|
||||||
let symbols = vec![
|
let symbols = vec![
|
||||||
@@ -1515,7 +1629,7 @@ mod tests {
|
|||||||
BinarySymbol {
|
BinarySymbol {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
class: ObjectClass::Screen,
|
class: ObjectClass::Screen,
|
||||||
name: "VSpin".into(),
|
name: "Custom1".into(),
|
||||||
is_array: false,
|
is_array: false,
|
||||||
unsupported: true,
|
unsupported: true,
|
||||||
},
|
},
|
||||||
@@ -1569,7 +1683,7 @@ mod tests {
|
|||||||
let root = build_tree(decoded).unwrap();
|
let root = build_tree(decoded).unwrap();
|
||||||
|
|
||||||
assert_eq!(warnings[0].offset, 0x46);
|
assert_eq!(warnings[0].offset, 0x46);
|
||||||
assert_eq!(warnings[0].name, "VSpin.CustomControl");
|
assert_eq!(warnings[0].name, "Custom1.CustomControl");
|
||||||
assert!(root.children.is_empty());
|
assert!(root.children.is_empty());
|
||||||
|
|
||||||
let mut strings_with_orphan = (100..=108)
|
let mut strings_with_orphan = (100..=108)
|
||||||
|
|||||||
@@ -1317,7 +1317,7 @@ fn token_def(code: u16) -> Option<TokenDef> {
|
|||||||
}),
|
}),
|
||||||
0x138 => Some(TokenDef {
|
0x138 => Some(TokenDef {
|
||||||
len: 0,
|
len: 0,
|
||||||
rules: &["expr::=MKS$({0})"],
|
rules: &["expr::=MKS$({expr:0})", "{0}"],
|
||||||
}),
|
}),
|
||||||
0x139 => Some(TokenDef {
|
0x139 => Some(TokenDef {
|
||||||
len: 0,
|
len: 0,
|
||||||
@@ -1682,7 +1682,7 @@ fn token_def(code: u16) -> Option<TokenDef> {
|
|||||||
}),
|
}),
|
||||||
0x1b2 => Some(TokenDef {
|
0x1b2 => Some(TokenDef {
|
||||||
len: 0,
|
len: 0,
|
||||||
rules: &["expr::=MSGBOX({2}, {1}, {0})"],
|
rules: &["expr::=STR$({expr:0})"],
|
||||||
}),
|
}),
|
||||||
0x1b3 => Some(TokenDef {
|
0x1b3 => Some(TokenDef {
|
||||||
len: 0,
|
len: 0,
|
||||||
@@ -2421,6 +2421,23 @@ impl<'a> Decoder<'a> {
|
|||||||
.ok_or_else(|| self.fail(self.at, "P-Code", "Tokendaten abgeschnitten"))?
|
.ok_or_else(|| self.fail(self.at, "P-Code", "Tokendaten abgeschnitten"))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
self.at += (len + 1) & !1;
|
self.at += (len + 1) & !1;
|
||||||
|
if matches!(self.pcode, 0x065 | 0x066) {
|
||||||
|
let operands = usize::from(self.pcode == 0x066);
|
||||||
|
let split = self.stack.len().saturating_sub(operands);
|
||||||
|
if split > 0
|
||||||
|
&& self.stack[split - 1].kind != "newline"
|
||||||
|
&& !self.stack[split - 1].text.ends_with(" THEN ")
|
||||||
|
&& !self.stack[split - 1].text.ends_with(" ELSE ")
|
||||||
|
{
|
||||||
|
self.stack.insert(
|
||||||
|
split,
|
||||||
|
Item {
|
||||||
|
kind: "newline".into(),
|
||||||
|
text: "\r\u{1}".into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if matches!(self.pcode, 0x1c5 | 0x1c6) {
|
if matches!(self.pcode, 0x1c5 | 0x1c6) {
|
||||||
let first = self
|
let first = self
|
||||||
.stack
|
.stack
|
||||||
@@ -2634,6 +2651,26 @@ mod tests {
|
|||||||
assert_eq!(decoder.array(&array, false).unwrap(), "Oldcontents()");
|
assert_eq!(decoder.array(&array, false).unwrap(), "Oldcontents()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn next_trennt_eine_vorangehende_anweisung_auch_ohne_newline_token() {
|
||||||
|
let (sym, ids) = symbols(&["i"]);
|
||||||
|
let mut code = Vec::new();
|
||||||
|
code.extend_from_slice(&0u16.to_le_bytes());
|
||||||
|
code.extend_from_slice(&0x00au16.to_le_bytes());
|
||||||
|
code.extend_from_slice(&9u16.to_le_bytes());
|
||||||
|
code.extend_from_slice(&[0, 0]);
|
||||||
|
code.extend_from_slice(b"PRINT 1");
|
||||||
|
code.push(0);
|
||||||
|
code.extend_from_slice(&(0x0400u16 | 0x00b).to_le_bytes());
|
||||||
|
code.extend_from_slice(&ids[0].to_le_bytes());
|
||||||
|
code.extend_from_slice(&0x066u16.to_le_bytes());
|
||||||
|
code.extend_from_slice(&[0xff, 0xff, 1, 0]);
|
||||||
|
code.extend_from_slice(&0u16.to_le_bytes());
|
||||||
|
code.extend_from_slice(&8u16.to_le_bytes());
|
||||||
|
let (decoded, _) = Decoder::new("test.frm", &sym, &code, 0).run().unwrap();
|
||||||
|
assert!(decoded.contains("PRINT 1\nNEXT i%\n"), "{decoded:?}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decodes_defint_and_vbdos_isam_tokens() {
|
fn decodes_defint_and_vbdos_isam_tokens() {
|
||||||
let mut decoder = Decoder::new("test.frm", &[], &[], 0);
|
let mut decoder = Decoder::new("test.frm", &[], &[], 0);
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ instrs! {
|
|||||||
0xA4 RetGosub;
|
0xA4 RetGosub;
|
||||||
0xA5 RetGosubTo(a: u32);
|
0xA5 RetGosubTo(a: u32);
|
||||||
0xA6 OnJump(a: u16, b: bool); // Sprungtabelle, gosub?
|
0xA6 OnJump(a: u16, b: bool); // Sprungtabelle, gosub?
|
||||||
|
0xA7 Run(a: u8); // 0 = ohne Ziel, 1 = Zeile, 2 = Datei
|
||||||
|
|
||||||
// 0xB0 — Prozeduren und Builtins
|
// 0xB0 — Prozeduren und Builtins
|
||||||
0xB0 Call(a: u16, b: u8);
|
0xB0 Call(a: u16, b: u8);
|
||||||
@@ -316,6 +317,9 @@ instrs! {
|
|||||||
0xB9 ObjectLoad(a: u16, b: bool, c: bool); // unload?, Index liegt auf Stack?
|
0xB9 ObjectLoad(a: u16, b: bool, c: bool); // unload?, Index liegt auf Stack?
|
||||||
0xBA LoadDynamicObjectProperty(a: u16);
|
0xBA LoadDynamicObjectProperty(a: u16);
|
||||||
0xBB StoreDynamicObjectProperty(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 — Ereignis-Traps (Sprachreferenz §8); Kennung vom Stack
|
||||||
0xE0 TrapDefine(a: u8, b: u32); // Quellenart, Sprungziel
|
0xE0 TrapDefine(a: u8, b: u32); // Quellenart, Sprungziel
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
|
|||||||
string_ids: HashMap::new(),
|
string_ids: HashMap::new(),
|
||||||
jump_tables: Vec::new(),
|
jump_tables: Vec::new(),
|
||||||
modul_label_pc: 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();
|
let mut procs = Vec::new();
|
||||||
for proc in &hir.procs {
|
for proc in &hir.procs {
|
||||||
@@ -29,7 +33,7 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
|
|||||||
name: hir.name.clone(),
|
name: hir.name.clone(),
|
||||||
option_base: hir.option_base,
|
option_base: hir.option_base,
|
||||||
strings: cg.strings,
|
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(),
|
global_names: hir.globals.iter().map(|g| g.name.clone()).collect(),
|
||||||
udts: hir
|
udts: hir
|
||||||
.udts
|
.udts
|
||||||
@@ -85,6 +89,9 @@ struct Codegen {
|
|||||||
/// `ON ERROR GOTO` aus einer Prozedur zeigt dorthin; da Prozedur 0
|
/// `ON ERROR GOTO` aus einer Prozedur zeigt dorthin; da Prozedur 0
|
||||||
/// zuerst übersetzt wird, stehen die Positionen rechtzeitig fest.
|
/// zuerst übersetzt wird, stehen die Positionen rechtzeitig fest.
|
||||||
modul_label_pc: Vec<Option<u32>>,
|
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 {
|
struct ProcCtx {
|
||||||
@@ -160,7 +167,30 @@ impl Codegen {
|
|||||||
fixups: Vec::new(),
|
fixups: Vec::new(),
|
||||||
table_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 {
|
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);
|
self.stmt(&mut ctx, proc, stmt);
|
||||||
}
|
}
|
||||||
// Rumpfende
|
// Rumpfende
|
||||||
@@ -234,12 +264,17 @@ impl Codegen {
|
|||||||
// ---- Anweisungen -------------------------------------------------------
|
// ---- Anweisungen -------------------------------------------------------
|
||||||
|
|
||||||
fn stmt(&mut self, ctx: &mut ProcCtx, proc: &hir::HProc, stmt: &HStmt) {
|
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 {
|
match &stmt.kind {
|
||||||
HStmtKind::Label(l) => {
|
HStmtKind::Label(l) => {
|
||||||
ctx.bind(*l);
|
ctx.bind(*l);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ => ctx.emit(Instr::Stmt(stmt.line)),
|
_ if boundary => ctx.emit(Instr::Stmt(stmt.line)),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
match &stmt.kind {
|
match &stmt.kind {
|
||||||
HStmtKind::Label(_) => unreachable!(),
|
HStmtKind::Label(_) => unreachable!(),
|
||||||
@@ -263,6 +298,23 @@ impl Codegen {
|
|||||||
index.is_some(),
|
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 {
|
HStmtKind::SetDynamicObjectProperty {
|
||||||
object,
|
object,
|
||||||
property,
|
property,
|
||||||
@@ -275,13 +327,21 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
HStmtKind::ObjectMethod {
|
HStmtKind::ObjectMethod {
|
||||||
object,
|
object,
|
||||||
|
index,
|
||||||
method,
|
method,
|
||||||
args,
|
args,
|
||||||
} => {
|
} => {
|
||||||
|
if let Some(index) = index {
|
||||||
|
self.expr(ctx, index);
|
||||||
|
}
|
||||||
for arg in args {
|
for arg in args {
|
||||||
self.expr(ctx, arg);
|
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 {
|
HStmtKind::ObjectLoad {
|
||||||
object,
|
object,
|
||||||
@@ -509,6 +569,18 @@ impl Codegen {
|
|||||||
None => ctx.emit(Instr::RetGosub),
|
None => ctx.emit(Instr::RetGosub),
|
||||||
Some(l) => ctx.emit_jump(Instr::RetGosubTo(0), *l),
|
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::ExitProc => self.emit_proc_exit(ctx, proc),
|
||||||
HStmtKind::CallSub { proc: id, args } => {
|
HStmtKind::CallSub { proc: id, args } => {
|
||||||
for a in args {
|
for a in args {
|
||||||
@@ -533,6 +605,9 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
let id = builtin_id(*b);
|
let id = builtin_id(*b);
|
||||||
ctx.emit(Instr::CallBuiltin(id, args.len() as u8));
|
ctx.emit(Instr::CallBuiltin(id, args.len() as u8));
|
||||||
|
if matches!(b, Builtin::MsgBox) {
|
||||||
|
ctx.emit(Instr::Pop);
|
||||||
|
}
|
||||||
if builtin_returns_value(*b) {
|
if builtin_returns_value(*b) {
|
||||||
ctx.emit(Instr::Pop);
|
ctx.emit(Instr::Pop);
|
||||||
}
|
}
|
||||||
@@ -725,6 +800,41 @@ impl Codegen {
|
|||||||
index.is_some(),
|
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 } => {
|
HExpr::DynamicObjectProperty { object, property } => {
|
||||||
self.expr(ctx, object);
|
self.expr(ctx, object);
|
||||||
let property = self.pool(property);
|
let property = self.pool(property);
|
||||||
@@ -1173,6 +1283,9 @@ fn builtin_id(b: Builtin) -> u16 {
|
|||||||
Builtin::Width => ids::WIDTH,
|
Builtin::Width => ids::WIDTH,
|
||||||
Builtin::ViewPrint => ids::VIEW_PRINT,
|
Builtin::ViewPrint => ids::VIEW_PRINT,
|
||||||
Builtin::ScreenStmt => ids::SCREEN_STMT,
|
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::KeyAssign => ids::KEY_ASSIGN,
|
||||||
Builtin::KeyList => ids::KEY_LIST,
|
Builtin::KeyList => ids::KEY_LIST,
|
||||||
Builtin::KeyDisplay => ids::KEY_DISPLAY,
|
Builtin::KeyDisplay => ids::KEY_DISPLAY,
|
||||||
@@ -1277,6 +1390,10 @@ fn builtin_id(b: Builtin) -> u16 {
|
|||||||
Builtin::IsamSavepoint => ids::ISAM_SAVEPOINT,
|
Builtin::IsamSavepoint => ids::ISAM_SAVEPOINT,
|
||||||
Builtin::IsamSetmem => ids::ISAM_SETMEM,
|
Builtin::IsamSetmem => ids::ISAM_SETMEM,
|
||||||
Builtin::IsamBof => ids::ISAM_BOF,
|
Builtin::IsamBof => ids::ISAM_BOF,
|
||||||
|
Builtin::MsgBox => ids::MSGBOX,
|
||||||
|
Builtin::InputBoxS => ids::INPUTBOX_S,
|
||||||
|
Builtin::ClipboardAdd => ids::CLIPBOARD_ADD,
|
||||||
|
Builtin::ClipboardGet => ids::CLIPBOARD_GET,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
//! - `GOSUB`-Stack pro Frame; `RETURN` ohne GOSUB → Fehler 3.
|
//! - `GOSUB`-Stack pro Frame; `RETURN` ohne GOSUB → Fehler 3.
|
||||||
|
|
||||||
use crate::bytecode::{CmpOp, CompiledModule, Instr};
|
use crate::bytecode::{CmpOp, CompiledModule, Instr};
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashSet, VecDeque};
|
||||||
use std::rc::Rc;
|
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::errors::RuntimeError;
|
||||||
use tb_runtime::host::Host;
|
use tb_runtime::host::{Ereignis, Host};
|
||||||
use tb_runtime::traps::Quelle;
|
use tb_runtime::traps::Quelle;
|
||||||
use tb_runtime::value::{self, default_value, ArrayObj, RecordObj, TypeInit, Value, VarRef};
|
use tb_runtime::value::{self, default_value, ArrayObj, RecordObj, TypeInit, Value, VarRef};
|
||||||
use tb_ui::forms::{FormEvent, FormsModel, PropertyValue, ShowResult};
|
use tb_ui::forms::{FormEvent, FormsModel, PropertyValue, ShowResult};
|
||||||
@@ -37,6 +37,10 @@ pub enum RunEvent {
|
|||||||
Interrupted {
|
Interrupted {
|
||||||
line: u32,
|
line: u32,
|
||||||
},
|
},
|
||||||
|
Restart {
|
||||||
|
program: Option<String>,
|
||||||
|
line: Option<u32>,
|
||||||
|
},
|
||||||
/// Unbehandelter Laufzeitfehler.
|
/// Unbehandelter Laufzeitfehler.
|
||||||
Error {
|
Error {
|
||||||
code: u16,
|
code: u16,
|
||||||
@@ -106,6 +110,7 @@ pub struct Vm {
|
|||||||
tick_zaehler: u32,
|
tick_zaehler: u32,
|
||||||
breakpoints: HashSet<u32>,
|
breakpoints: HashSet<u32>,
|
||||||
data_ptr: usize,
|
data_ptr: usize,
|
||||||
|
start_pc: Option<usize>,
|
||||||
pub forms: FormsModel,
|
pub forms: FormsModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +171,7 @@ impl Vm {
|
|||||||
tick_zaehler: 0,
|
tick_zaehler: 0,
|
||||||
breakpoints: HashSet::new(),
|
breakpoints: HashSet::new(),
|
||||||
data_ptr: 0,
|
data_ptr: 0,
|
||||||
|
start_pc: None,
|
||||||
forms,
|
forms,
|
||||||
module,
|
module,
|
||||||
};
|
};
|
||||||
@@ -176,6 +182,24 @@ impl Vm {
|
|||||||
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) {
|
fn push_frame(&mut self, proc: usize, argc: usize) {
|
||||||
// Argumente liegen zuoberst auf dem Stack (links → rechts).
|
// Argumente liegen zuoberst auf dem Stack (links → rechts).
|
||||||
let locals_base = self.locals.len();
|
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) {
|
pub fn queue_form_event(&mut self, event: FormEvent) {
|
||||||
self.forms.queue(event);
|
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(
|
fn property_value(
|
||||||
&self,
|
&self,
|
||||||
object: u16,
|
object: u16,
|
||||||
@@ -322,36 +589,37 @@ impl Vm {
|
|||||||
Ok(())
|
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
|
self.forms
|
||||||
.resize(self.rt.screen.cols(), self.rt.screen.rows());
|
.resize(self.rt.screen.cols(), self.rt.screen.rows());
|
||||||
|
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() {
|
while let Some(m) = self.rt.maus.pop_front() {
|
||||||
if let Some(object) = self.forms.active_form() {
|
self.forms.handle_mouse_at(m, now_ms);
|
||||||
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.render(&mut self.rt.screen);
|
||||||
self.dispatch_next_form_event()
|
self.dispatch_next_form_event()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Anstehendes Ereignis zustellen, falls eines fällig ist. Liefert
|
/// Anstehendes Ereignis zustellen, falls eines fällig ist. Liefert
|
||||||
/// `true`, wenn ein Handler aufgesetzt wurde.
|
/// `true`, wenn ein Handler aufgesetzt wurde.
|
||||||
fn zustellen(&mut self, host: &mut dyn Host) -> bool {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
let jetzt = host.jetzt_ms();
|
let jetzt = host.jetzt_ms();
|
||||||
@@ -377,7 +645,7 @@ impl Vm {
|
|||||||
// virtuelle nie vorrückt.
|
// virtuelle nie vorrückt.
|
||||||
let real_start = std::time::Instant::now();
|
let real_start = std::time::Instant::now();
|
||||||
loop {
|
loop {
|
||||||
self.rt.tick(host);
|
self.tick(host);
|
||||||
if self.zustellen(host) {
|
if self.zustellen(host) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -531,14 +799,41 @@ impl Vm {
|
|||||||
|
|
||||||
// ---- Hauptschleife --------------------------------------------------------
|
// ---- 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 {
|
pub fn run(&mut self, host: &mut dyn Host) -> RunEvent {
|
||||||
loop {
|
loop {
|
||||||
if let Some(form) = self.frames.last().and_then(|f| f.waiting_form) {
|
if let Some(form) = self.frames.last().and_then(|f| f.waiting_form) {
|
||||||
if !self.forms.is_visible(form) {
|
if !self.forms.is_visible(form) {
|
||||||
self.frames.last_mut().unwrap().waiting_form = None;
|
self.frames.last_mut().unwrap().waiting_form = None;
|
||||||
} else {
|
} else {
|
||||||
self.rt.tick(host);
|
self.tick(host);
|
||||||
self.forms_zustellen();
|
self.forms_zustellen(host);
|
||||||
if self.rt.ende {
|
if self.rt.ende {
|
||||||
return RunEvent::Ended;
|
return RunEvent::Ended;
|
||||||
}
|
}
|
||||||
@@ -818,6 +1113,11 @@ impl Vm {
|
|||||||
use Instr as I;
|
use Instr as I;
|
||||||
match instr {
|
match instr {
|
||||||
I::Stmt(line) => {
|
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();
|
let f = self.frames.last_mut().unwrap();
|
||||||
f.line = line;
|
f.line = line;
|
||||||
f.last_stmt_pc = pc;
|
f.last_stmt_pc = pc;
|
||||||
@@ -826,11 +1126,12 @@ impl Vm {
|
|||||||
// Debugger-Funktion — ohne sie sähe niemand die Ausgabe und
|
// Debugger-Funktion — ohne sie sähe niemand die Ausgabe und
|
||||||
// Größenänderungen kämen nie an.
|
// Größenänderungen kämen nie an.
|
||||||
self.tick_zaehler = self.tick_zaehler.wrapping_add(1);
|
self.tick_zaehler = self.tick_zaehler.wrapping_add(1);
|
||||||
if self.rt.screen.ist_veraendert() || self.tick_zaehler % 1024 == 0 {
|
self.forms.render(&mut self.rt.screen);
|
||||||
self.rt.tick(host);
|
if self.rt.screen.ist_veraendert() || self.tick_zaehler.is_multiple_of(1024) {
|
||||||
|
self.tick(host);
|
||||||
self.rt.screen.veraenderung_quittieren();
|
self.rt.screen.veraenderung_quittieren();
|
||||||
}
|
}
|
||||||
if self.forms_zustellen() {
|
if self.forms_zustellen(host) {
|
||||||
return Ok(Flow::Normal);
|
return Ok(Flow::Normal);
|
||||||
}
|
}
|
||||||
// Ereigniszustellung (design.md, D1): nur wenn überhaupt
|
// Ereigniszustellung (design.md, D1): nur wenn überhaupt
|
||||||
@@ -841,7 +1142,7 @@ impl Vm {
|
|||||||
// Auflösung eines Zeit-Traps ist damit 64 Anweisungen;
|
// Auflösung eines Zeit-Traps ist damit 64 Anweisungen;
|
||||||
// reicht das nicht, wird daraus ein eigener Zähler je
|
// reicht das nicht, wird daraus ein eigener Zähler je
|
||||||
// Trap.
|
// Trap.
|
||||||
if self.tick_zaehler % 64 == 0 {
|
if self.tick_zaehler.is_multiple_of(64) {
|
||||||
let jetzt = host.jetzt_ms();
|
let jetzt = host.jetzt_ms();
|
||||||
self.rt.traps.zeit_pruefen(jetzt);
|
self.rt.traps.zeit_pruefen(jetzt);
|
||||||
}
|
}
|
||||||
@@ -901,8 +1202,8 @@ impl Vm {
|
|||||||
// bekommt seinen eigenen Stapelabschnitt darüber und
|
// bekommt seinen eigenen Stapelabschnitt darüber und
|
||||||
// lässt den Wert unberührt.
|
// lässt den Wert unberührt.
|
||||||
self.stack.push(Value::Int(0));
|
self.stack.push(Value::Int(0));
|
||||||
self.rt.tick(host);
|
self.tick(host);
|
||||||
self.forms_zustellen();
|
self.forms_zustellen(host);
|
||||||
self.zustellen(host);
|
self.zustellen(host);
|
||||||
Ok(Flow::Normal)
|
Ok(Flow::Normal)
|
||||||
}
|
}
|
||||||
@@ -1051,6 +1352,56 @@ impl Vm {
|
|||||||
self.forms.set_at(object, index, property, value)?;
|
self.forms.set_at(object, index, property, value)?;
|
||||||
Ok(Flow::Normal)
|
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) => {
|
I::TypeOf(class) => {
|
||||||
let matches = match self.pop()? {
|
let matches = match self.pop()? {
|
||||||
Value::Obj(object, _) => self
|
Value::Obj(object, _) => self
|
||||||
@@ -1064,6 +1415,8 @@ impl Vm {
|
|||||||
Ok(Flow::Normal)
|
Ok(Flow::Normal)
|
||||||
}
|
}
|
||||||
I::ObjectMethod(object, method, argc) => {
|
I::ObjectMethod(object, method, argc) => {
|
||||||
|
let indexed = argc & 0x80 != 0;
|
||||||
|
let argc = argc & 0x7f;
|
||||||
let class = self
|
let class = self
|
||||||
.module
|
.module
|
||||||
.objects
|
.objects
|
||||||
@@ -1093,6 +1446,7 @@ impl Vm {
|
|||||||
args.push(self.pop()?);
|
args.push(self.pop()?);
|
||||||
}
|
}
|
||||||
args.reverse();
|
args.reverse();
|
||||||
|
let index = if indexed { Some(self.pop_i32()?) } else { None };
|
||||||
if resumed_show && !self.forms.is_loaded(object) {
|
if resumed_show && !self.forms.is_loaded(object) {
|
||||||
return Ok(Flow::Normal);
|
return Ok(Flow::Normal);
|
||||||
}
|
}
|
||||||
@@ -1118,13 +1472,26 @@ impl Vm {
|
|||||||
(tb_frontend::forms::ObjectClass::Form, "UNLOAD") => {
|
(tb_frontend::forms::ObjectClass::Form, "UNLOAD") => {
|
||||||
self.request_unload(object)?
|
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") => {
|
(tb_frontend::forms::ObjectClass::Screen, "SHOW") => {
|
||||||
self.forms.screen_show(true)
|
self.forms.screen_show(true)
|
||||||
}
|
}
|
||||||
(tb_frontend::forms::ObjectClass::Screen, "HIDE") => {
|
(tb_frontend::forms::ObjectClass::Screen, "HIDE") => {
|
||||||
self.forms.screen_show(false)
|
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();
|
self.dispatch_next_form_event();
|
||||||
Ok(Flow::Normal)
|
Ok(Flow::Normal)
|
||||||
@@ -1318,7 +1685,7 @@ impl Vm {
|
|||||||
s.chars().take(n).collect()
|
s.chars().take(n).collect()
|
||||||
} else {
|
} else {
|
||||||
let mut t = s.to_string();
|
let mut t = s.to_string();
|
||||||
t.extend(std::iter::repeat(' ').take(n - len));
|
t.extend(std::iter::repeat_n(' ', n - len));
|
||||||
t
|
t
|
||||||
};
|
};
|
||||||
self.push(Value::Str(Rc::from(fixed.as_str())));
|
self.push(Value::Str(Rc::from(fixed.as_str())));
|
||||||
@@ -1714,9 +2081,27 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
Ok(Flow::Normal)
|
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 ----
|
// ---- Prozeduren ----
|
||||||
I::Call(proc, argc) => {
|
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);
|
self.push_frame(proc as usize, argc as usize);
|
||||||
Ok(Flow::Normal)
|
Ok(Flow::Normal)
|
||||||
}
|
}
|
||||||
@@ -1754,6 +2139,12 @@ impl Vm {
|
|||||||
args.push(self.pop()?);
|
args.push(self.pop()?);
|
||||||
}
|
}
|
||||||
args.reverse();
|
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];
|
let f = builtin_table()[id as usize];
|
||||||
match f(&mut self.rt, host, &mut args) {
|
match f(&mut self.rt, host, &mut args) {
|
||||||
Ok(Some(v)) => {
|
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 r = p.rem_euclid(10_000);
|
||||||
let rounded = if r > 5_000 {
|
let rounded = if r > 5_000 {
|
||||||
q + 1
|
q + 1
|
||||||
} else if r < 5_000 {
|
} else if r < 5_000 || q % 2 == 0 {
|
||||||
q
|
|
||||||
} else if q % 2 == 0 {
|
|
||||||
q
|
q
|
||||||
} else {
|
} else {
|
||||||
q + 1
|
q + 1
|
||||||
|
|||||||
@@ -38,13 +38,11 @@ fn err_code(src: &str) -> u16 {
|
|||||||
/// Verzeichnis, das beim Verlassen samt Inhalt verschwindet. Programme
|
/// Verzeichnis, das beim Verlassen samt Inhalt verschwindet. Programme
|
||||||
/// hinterlassen dadurch nichts im Projektbaum.
|
/// hinterlassen dadurch nichts im Projektbaum.
|
||||||
struct TempVerzeichnis {
|
struct TempVerzeichnis {
|
||||||
vorher: std::path::PathBuf,
|
|
||||||
dir: std::path::PathBuf,
|
dir: std::path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TempVerzeichnis {
|
impl TempVerzeichnis {
|
||||||
fn neu(name: &str) -> TempVerzeichnis {
|
fn neu(name: &str) -> TempVerzeichnis {
|
||||||
let vorher = std::env::current_dir().unwrap();
|
|
||||||
let dir = std::env::temp_dir().join(format!(
|
let dir = std::env::temp_dir().join(format!(
|
||||||
"tb_vm_{name}_{}_{:?}",
|
"tb_vm_{name}_{}_{:?}",
|
||||||
std::process::id(),
|
std::process::id(),
|
||||||
@@ -52,14 +50,16 @@ impl TempVerzeichnis {
|
|||||||
));
|
));
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
TempVerzeichnis { dir }
|
||||||
TempVerzeichnis { vorher, dir }
|
}
|
||||||
|
|
||||||
|
fn pfad(&self, name: &str) -> String {
|
||||||
|
self.dir.join(name).to_string_lossy().replace('"', "\"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TempVerzeichnis {
|
impl Drop for TempVerzeichnis {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let _ = std::env::set_current_dir(&self.vorher);
|
|
||||||
let _ = std::fs::remove_dir_all(&self.dir);
|
let _ = std::fs::remove_dir_all(&self.dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +71,48 @@ fn print_hallo_welt() {
|
|||||||
assert_eq!(out("PRINT \"Hallo, Welt!\"\nEND"), "Hallo, Welt!\n");
|
assert_eq!(out("PRINT \"Hallo, Welt!\"\nEND"), "Hallo, Welt!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn grafik_line_paint_und_view_zeichnen_in_den_zellenpuffer() {
|
||||||
|
assert_eq!(
|
||||||
|
out("SCREEN 2\nVIEW (0,0)-(31,15),0,1\nLINE (0,0)-(15,7),1,BF\nPAINT (24,8),1\nEND"),
|
||||||
|
"████\n████\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_gibt_datei_oder_zeile_an_den_runner_weiter() {
|
||||||
|
assert_eq!(
|
||||||
|
run("RUN \"next\"").0,
|
||||||
|
RunEvent::Restart {
|
||||||
|
program: Some("next".into()),
|
||||||
|
line: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run("RUN 100").0,
|
||||||
|
RunEvent::Restart {
|
||||||
|
program: None,
|
||||||
|
line: Some(100)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn common_dialog_registrierung_setzt_byref_erfolg() {
|
||||||
|
assert_eq!(
|
||||||
|
out("DECLARE SUB CmnDlgRegister(ok AS INTEGER)\nCmnDlgRegister ok%\nPRINT ok%\nEND"),
|
||||||
|
"-1 \n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn len_liefert_die_feste_satzbreite_eines_udt() {
|
||||||
|
assert_eq!(
|
||||||
|
out("TYPE Satz\nText AS STRING * 3\nZahl AS INTEGER\nEND TYPE\nDIM Wert AS Satz\nPRINT LEN(Wert)"),
|
||||||
|
" 14 \n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn banker_rounding_cint() {
|
fn banker_rounding_cint() {
|
||||||
// Spec-Szenario: PRINT CINT(0.5); CINT(1.5); CINT(2.5) → " 0 2 2 "
|
// Spec-Szenario: PRINT CINT(0.5); CINT(1.5); CINT(2.5) → " 0 2 2 "
|
||||||
@@ -486,31 +528,19 @@ fn print_zonen_und_tab() {
|
|||||||
assert_eq!(out("PRINT \"a\"; SPC(3); \"b\""), "a b\n");
|
assert_eq!(out("PRINT \"a\"; SPC(3); \"b\""), "a b\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unsupported_feature_fehler_73() {
|
|
||||||
// Dokumentiert, aber noch offen: `RUN` kommt mit Phase 5
|
|
||||||
// → Laufzeitfehler 73 mit dem Katalogtext des Vorbilds (VBDOS).
|
|
||||||
// (`SETUEVENT` stand hier bis zum Change `phase-4-ereignisschleife`.)
|
|
||||||
let (ev, _) = run("RUN");
|
|
||||||
match ev {
|
|
||||||
RunEvent::Error { code, message, .. } => {
|
|
||||||
assert_eq!(code, 73);
|
|
||||||
assert_eq!(message, "Feature unavailable");
|
|
||||||
}
|
|
||||||
other => panic!("{other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Aufgabe 2.4 des Changes `phase-3-isam`: `OPEN … FOR ISAM` senkt nicht
|
/// Aufgabe 2.4 des Changes `phase-3-isam`: `OPEN … FOR ISAM` senkt nicht
|
||||||
/// mehr auf den „nicht verfügbar"-Marker ab, sondern arbeitet. Der Lauf
|
/// mehr auf den „nicht verfügbar"-Marker ab, sondern arbeitet. Der Lauf
|
||||||
/// findet in einem temporären Verzeichnis statt und lässt nichts zurück.
|
/// findet in einem temporären Verzeichnis statt und lässt nichts zurück.
|
||||||
#[test]
|
#[test]
|
||||||
fn open_for_isam_endet_nicht_mehr_mit_fehler_73() {
|
fn open_for_isam_endet_nicht_mehr_mit_fehler_73() {
|
||||||
let _dir = TempVerzeichnis::neu("open_isam");
|
let dir = TempVerzeichnis::neu("open_isam");
|
||||||
let (ev, ausgabe) = run("TYPE T\n f AS INTEGER\nEND TYPE\n\
|
let (ev, ausgabe) = run(&format!(
|
||||||
OPEN \"db.isam\" FOR ISAM T \"Tab\" AS #1\n\
|
"TYPE T\n f AS INTEGER\nEND TYPE\n\
|
||||||
|
OPEN \"{}\" FOR ISAM T \"Tab\" AS #1\n\
|
||||||
PRINT \"offen\"\n\
|
PRINT \"offen\"\n\
|
||||||
CLOSE #1");
|
CLOSE #1",
|
||||||
|
dir.pfad("db.isam")
|
||||||
|
));
|
||||||
assert_eq!(ev, RunEvent::Ended, "unerwartetes Ende: {ev:?}\n{ausgabe}");
|
assert_eq!(ev, RunEvent::Ended, "unerwartetes Ende: {ev:?}\n{ausgabe}");
|
||||||
assert!(ausgabe.contains("offen"), "{ausgabe}");
|
assert!(ausgabe.contains("offen"), "{ausgabe}");
|
||||||
}
|
}
|
||||||
@@ -594,17 +624,10 @@ fn color_prueft_wertebereich() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn screen_anweisung_nur_textmodus() {
|
fn screen_anweisung_bildet_grafikmodi_auf_den_zellenpuffer_ab() {
|
||||||
// Literaler Grafikmodus schon zur Compile-Zeit.
|
|
||||||
let d = tb_vm::compile_source("T", "SCREEN 1").unwrap_err();
|
|
||||||
assert!(
|
|
||||||
d.iter().any(|x| x.message.contains("Feature unavailable")),
|
|
||||||
"{d:?}"
|
|
||||||
);
|
|
||||||
// Textmodus ist folgenlos zulässig.
|
|
||||||
assert_eq!(out("SCREEN 0\nPRINT \"ok\""), "ok\n");
|
assert_eq!(out("SCREEN 0\nPRINT \"ok\""), "ok\n");
|
||||||
// Berechneter Modus erst zur Laufzeit.
|
assert_eq!(out("SCREEN 13\nPRINT \"ok\""), "ok\n");
|
||||||
assert_eq!(err_code("m% = 2\nSCREEN m%"), 73);
|
assert_eq!(err_code("m% = 14\nSCREEN m%"), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -644,6 +667,32 @@ fn input_s_liest_genau_n_zeichen_ohne_echo() {
|
|||||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen), "xyz\n");
|
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen), "xyz\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_s_liest_aus_einer_binaerdatei() {
|
||||||
|
let tmp = TempVerzeichnis::neu("input_s_datei");
|
||||||
|
let pfad = tmp.pfad("daten.bin");
|
||||||
|
std::fs::write(&pfad, b"abcdef").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
out(&format!(
|
||||||
|
"OPEN \"{pfad}\" FOR BINARY AS #1\na$ = INPUT$(3, #1)\nPRINT a$\nCLOSE #1"
|
||||||
|
)),
|
||||||
|
"abc\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clipboard_und_printer_objekte_haben_laufzeitwirkung() {
|
||||||
|
let module = tb_vm::compile_source(
|
||||||
|
"T",
|
||||||
|
"CLIPBOARD.ADDITEM \"abc\"\nPRINT CLIPBOARD.GETTEXT\nPRINTER.PRINT \"Seite\"\nPRINTER.NEWPAGE\nPRINTER.ENDDOC",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||||
|
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen), "abc\n");
|
||||||
|
assert_eq!(vm.rt.print.drucker, "Seite\n\u{c}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cls_setzt_cursor_zurueck() {
|
fn cls_setzt_cursor_zurueck() {
|
||||||
assert_eq!(out("PRINT \"weg\"\nCLS\nPRINT \"neu\""), "neu\n");
|
assert_eq!(out("PRINT \"weg\"\nCLS\nPRINT \"neu\""), "neu\n");
|
||||||
@@ -1136,6 +1185,14 @@ fn objektzugriff_laeuft_ueber_objekt_und_eigenschaftsindex() {
|
|||||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "hallo");
|
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "hallo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn screen_controlpanel_hat_einen_indexierten_laufzeitwert() {
|
||||||
|
assert_eq!(
|
||||||
|
out("SCREEN.CONTROLPANEL(5) = 3\nPRINT SCREEN.CONTROLPANEL(5)"),
|
||||||
|
" 3 \n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn steuerarray_eigenschaften_und_objektparameter_funktionieren() {
|
fn steuerarray_eigenschaften_und_objektparameter_funktionieren() {
|
||||||
let mut vm = form_vm(
|
let mut vm = form_vm(
|
||||||
@@ -1175,6 +1232,31 @@ fn modales_show_setzt_nach_unload_fort() {
|
|||||||
assert!(host.presents >= 2);
|
assert!(host.presents >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modelloses_formular_bleibt_nach_programmende_bedienbar() {
|
||||||
|
let mut vm = form_vm(
|
||||||
|
"DIM SHARED gesehen%\nEND\n\
|
||||||
|
SUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\n\
|
||||||
|
SHARED gesehen%\ngesehen% = 1\nUNLOAD Form1\nEND SUB",
|
||||||
|
);
|
||||||
|
vm.forms.show(0, false).unwrap();
|
||||||
|
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||||
|
let mut host = CaptureHost::default();
|
||||||
|
host.ereignis_nach(
|
||||||
|
1,
|
||||||
|
tb_runtime::host::Ereignis::Maus(tb_runtime::host::MausEreignis {
|
||||||
|
art: tb_runtime::host::MausArt::Druck,
|
||||||
|
taste: 1,
|
||||||
|
shift: 0,
|
||||||
|
zeile: 1,
|
||||||
|
spalte: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert_eq!(vm.run_visible_forms(&mut host), RunEvent::Ended);
|
||||||
|
assert!(matches!(vm.inspect("gesehen"), Some(Value::Int(1))));
|
||||||
|
assert!(!vm.forms.has_visible_forms());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fehlende_ereignisprozedur_verfaellt_und_typeof_prueft_klasse() {
|
fn fehlende_ereignisprozedur_verfaellt_und_typeof_prueft_klasse() {
|
||||||
let mut vm = form_vm("Form1.Show\nIF TYPEOF Text1 IS TextBox THEN PRINT \"ja\"");
|
let mut vm = form_vm("Form1.Show\nIF TYPEOF Text1 IS TextBox THEN PRINT \"ja\"");
|
||||||
@@ -1278,3 +1360,153 @@ fn objektcode_erzeugt_keine_zusaetzlichen_zustellopcodes() {
|
|||||||
.count();
|
.count();
|
||||||
assert_eq!(n, 1, "nur das explizite DOEVENTS ist ein Zustellopcode");
|
assert_eq!(n, 1, "nur das explizite DOEVENTS ist ein Zustellopcode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn listenmethoden_indexeigenschaft_und_picture_messung_laufen_in_der_vm() {
|
||||||
|
use tb_frontend::forms::ObjectClass;
|
||||||
|
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||||
|
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||||
|
catalog.add("List1", ObjectClass::ListBox, Some("Form1"), false);
|
||||||
|
catalog.add("Picture1", ObjectClass::PictureBox, Some("Form1"), false);
|
||||||
|
let module = tb_vm::compile_source_with_forms(
|
||||||
|
"FORM1",
|
||||||
|
"List1.Sorted = -1\nList1.ADDITEM \"b\"\nList1.ADDITEM \"a\"\nPRINT List1.List(0)\nPRINT List1.ListCount\nPRINT Picture1.TEXTWIDTH(\"abc\")",
|
||||||
|
&catalog,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||||
|
assert_eq!(
|
||||||
|
tb_runtime::snapshot::text(&vm.rt.screen).trim(),
|
||||||
|
"a\n 2 \n 3"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn listenmethoden_und_indexeigenschaft_laufen_auf_control_arrays() {
|
||||||
|
use tb_frontend::forms::ObjectClass;
|
||||||
|
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||||
|
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||||
|
catalog.add("List1", ObjectClass::ListBox, Some("Form1"), true);
|
||||||
|
let module = tb_vm::compile_source_with_forms(
|
||||||
|
"FORM1",
|
||||||
|
"LOAD List1(1)\nList1(1).ADDITEM \"x\"\nPRINT List1(1).List(0)",
|
||||||
|
&catalog,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||||
|
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn msgbox_und_inputbox_sind_keine_unsupported_opcodes_mehr() {
|
||||||
|
use tb_runtime::host::{taste, Ereignis};
|
||||||
|
let module = tb_vm::compile_source(
|
||||||
|
"DIALOG",
|
||||||
|
"r% = MSGBOX(\"Weiter?\", 4, \"Frage\")\ns$ = INPUTBOX$(\"Name\")\nPRINT r%; s$",
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||||
|
assert!(!module.procs[0]
|
||||||
|
.code
|
||||||
|
.iter()
|
||||||
|
.any(|instruction| matches!(instruction, tb_vm::bytecode::Instr::Unsupported(_))));
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
let mut host = CaptureHost::default();
|
||||||
|
for key in [taste::ENTER, "A", taste::ENTER] {
|
||||||
|
host.ereignis(Ereignis::Taste(key.into(), 0));
|
||||||
|
}
|
||||||
|
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||||
|
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "6 A");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_button_hat_vorrang_und_beendet_modales_formular() {
|
||||||
|
use tb_frontend::forms::ObjectClass;
|
||||||
|
use tb_runtime::host::{taste, Ereignis};
|
||||||
|
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||||
|
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||||
|
catalog.add("Command1", ObjectClass::CommandButton, Some("Form1"), false);
|
||||||
|
let module = tb_vm::compile_source_with_forms(
|
||||||
|
"FORM1",
|
||||||
|
"DIM SHARED gesehen%\nForm1.Show 1\nPRINT gesehen%\nEND\nSUB Command1_Click()\nSHARED gesehen%\ngesehen% = 1\nUNLOAD Form1\nEND SUB",
|
||||||
|
&catalog,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
let default = tb_frontend::forms::property(ObjectClass::CommandButton, "DEFAULT")
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
vm.forms
|
||||||
|
.set(1, default, tb_ui::forms::PropertyValue::Boolean(true))
|
||||||
|
.unwrap();
|
||||||
|
let mut host = CaptureHost::default();
|
||||||
|
host.ereignis(Ereignis::Taste(taste::ENTER.into(), 0));
|
||||||
|
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||||
|
assert!(matches!(vm.inspect("gesehen"), Some(Value::Int(1))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn printform_schreibt_das_textformular_in_den_druckerkanal() {
|
||||||
|
use tb_frontend::forms::ObjectClass;
|
||||||
|
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||||
|
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||||
|
let module = tb_vm::compile_source_with_forms("FORM1", "Form1.Show\nForm1.PRINTFORM", &catalog)
|
||||||
|
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
for (name, value) in [("WIDTH", 12), ("HEIGHT", 4)] {
|
||||||
|
let property = tb_frontend::forms::property(ObjectClass::Form, name)
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
vm.forms
|
||||||
|
.set_initial(0, property, tb_ui::forms::PropertyValue::Integer(value))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let caption = tb_frontend::forms::property(ObjectClass::Form, "CAPTION")
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
vm.forms
|
||||||
|
.set_initial(
|
||||||
|
0,
|
||||||
|
caption,
|
||||||
|
tb_ui::forms::PropertyValue::String("Druck".into()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||||
|
assert!(vm.rt.print.drucker.contains("Druck"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn klassischer_timer_trap_ruht_solange_das_menu_offen_ist() {
|
||||||
|
use tb_frontend::forms::ObjectClass;
|
||||||
|
use tb_runtime::host::umschalt;
|
||||||
|
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||||
|
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||||
|
catalog.add("mnuDatei", ObjectClass::Menu, Some("Form1"), false);
|
||||||
|
let module = tb_vm::compile_source_with_forms(
|
||||||
|
"FORM1",
|
||||||
|
"DIM SHARED n%\nON TIMER(1) GOSUB Tick\nTIMER ON\nForm1.Show\nSTOP\nDOEVENTS\nSTOP\nDOEVENTS\nPRINT n%\nEND\nTick:\nn% = n% + 1\nRETURN",
|
||||||
|
&catalog,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||||
|
let mut vm = Vm::new(module);
|
||||||
|
let caption = tb_frontend::forms::property(ObjectClass::Menu, "CAPTION")
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
vm.forms
|
||||||
|
.set_initial(
|
||||||
|
1,
|
||||||
|
caption,
|
||||||
|
tb_ui::forms::PropertyValue::String("&Datei".into()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut host = CaptureHost::default();
|
||||||
|
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
|
||||||
|
assert!(vm.forms.handle_key("d", umschalt::ALT));
|
||||||
|
host.uhr_vorruecken(1_500);
|
||||||
|
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
|
||||||
|
assert!(matches!(vm.inspect("n"), Some(Value::Int(0))));
|
||||||
|
assert!(vm.forms.handle_key(tb_runtime::host::taste::ESC, 0));
|
||||||
|
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||||
|
assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
|
||||||
|
}
|
||||||
|
|||||||
@@ -127,7 +127,10 @@ zum Abbruch; eine Ausgabedatei wird erst nach erfolgreichem Lesen angelegt.
|
|||||||
|
|
||||||
Reine Textdatei ohne Kopfzeile, **eine Projektdatei pro Zeile**
|
Reine Textdatei ohne Kopfzeile, **eine Projektdatei pro Zeile**
|
||||||
(`.BAS`/`.FRM`; belegt durch die Originalbeispiele). Terminal Basic
|
(`.BAS`/`.FRM`; belegt durch die Originalbeispiele). Terminal Basic
|
||||||
akzeptiert zusätzlich Kommentarzeilen mit `'`.
|
akzeptiert zusätzlich Kommentarzeilen mit `'`. `tbc check`, `build` und
|
||||||
|
`run` lösen die Einträge relativ zur `.MAK`-Datei und ohne Beachtung der
|
||||||
|
DOS-Großschreibung auf; Formularobjekte aller `.FRM`-Einträge stehen dem
|
||||||
|
gemeinsamen Modulverbund zur Verfügung.
|
||||||
|
|
||||||
## Record-Dateien (`GET`/`PUT`, `OPEN … FOR RANDOM`)
|
## Record-Dateien (`GET`/`PUT`, `OPEN … FOR RANDOM`)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Maus-Koordinaten in Ereignissen sind SINGLE, aber in Zellen.
|
|||||||
## Steuerelemente (exakte Klassennamen)
|
## Steuerelemente (exakte Klassennamen)
|
||||||
|
|
||||||
`CheckBox, ComboBox, CommandButton, DirListBox, DriveListBox, FileListBox,
|
`CheckBox, ComboBox, CommandButton, DirListBox, DriveListBox, FileListBox,
|
||||||
Frame, HScrollBar, Label, ListBox, Menu, OptionButton, PictureBox, TextBox,
|
Frame, HScrollBar, Label, ListBox, Menu, OptionButton, PictureBox, Spin, TextBox,
|
||||||
Timer, VScrollBar` — alle außer `Menu` über die Toolbox platzierbar
|
Timer, VScrollBar` — alle außer `Menu` über die Toolbox platzierbar
|
||||||
(Menüs entstehen im Menu Design Window). Default-Namen: `Check1`, `Combo1`,
|
(Menüs entstehen im Menu Design Window). Default-Namen: `Check1`, `Combo1`,
|
||||||
`Command1`, `Dir1`, `Drive1`, `File1`, `Frame1`, `HScroll1`, `Label1`,
|
`Command1`, `Dir1`, `Drive1`, `File1`, `Frame1`, `HScroll1`, `Label1`,
|
||||||
@@ -83,6 +83,11 @@ LostFocus (**kein** separates Scroll-Ereignis). Eigenschaften: zusätzlich
|
|||||||
Attached (VBDOS-Spezifikum: am Formularrand angedockt; Laufzeit read-only),
|
Attached (VBDOS-Spezifikum: am Formularrand angedockt; Laufzeit read-only),
|
||||||
LargeChange, Min, Max, SmallChange, Value.
|
LargeChange, Min, Max, SmallChange, Value.
|
||||||
|
|
||||||
|
**Spin** — Ereignisse: Custom(EventType), Drag*, GotFocus, Key*, LostFocus.
|
||||||
|
`Style` wählt vertikal/horizontal, `Interval` steuert die Wiederholung; `Value`
|
||||||
|
läuft zyklisch zwischen `Min` und `Max`. `BorderStyle`, `Width` und `Height`
|
||||||
|
sind zur Laufzeit schreibgeschützt.
|
||||||
|
|
||||||
**PictureBox** — Text-Zeichenfläche (PRINT/CLS aufs Control) und Container
|
**PictureBox** — Text-Zeichenfläche (PRINT/CLS aufs Control) und Container
|
||||||
für OptionButton-Gruppen. Ereignisse: Click, DblClick, Drag*, GotFocus,
|
für OptionButton-Gruppen. Ereignisse: Click, DblClick, Drag*, GotFocus,
|
||||||
Key*, LostFocus, Mouse*, Paint. Methoden: CLS, DRAG, MOVE, PRINT, REFRESH,
|
Key*, LostFocus, Mouse*, Paint. Methoden: CLS, DRAG, MOVE, PRINT, REFRESH,
|
||||||
@@ -93,7 +98,7 @@ TabStop, AutoRedraw, CurrentX, CurrentY, ScaleHeight, ScaleWidth.
|
|||||||
Index, Interval (0 = aus … 65 535 ms), Parent, Tag.
|
Index, Interval (0 = aus … 65 535 ms), Parent, Tag.
|
||||||
|
|
||||||
**Menu** — Ereignis Click; Eigenschaften Caption, Checked, CtlName, Enabled,
|
**Menu** — Ereignis Click; Eigenschaften Caption, Checked, CtlName, Enabled,
|
||||||
Index, Parent, Separator, Tag, Visible (Shortcut nur zur Entwurfszeit).
|
Index, Parent, Separator, Shortcut, Tag, Visible.
|
||||||
|
|
||||||
**DirListBox/DriveListBox/FileListBox** — Datei-Browser-Controls
|
**DirListBox/DriveListBox/FileListBox** — Datei-Browser-Controls
|
||||||
(Path/Drive/FileName/Pattern/Archive/Hidden/Normal/ReadOnly/System;
|
(Path/Drive/FileName/Pattern/Archive/Hidden/Normal/ReadOnly/System;
|
||||||
@@ -124,6 +129,61 @@ sie plattformneutral nach (Laufwerksliste → Wurzeln/Mounts).
|
|||||||
| Text (TextBox/ComboBox) | — | Entwurfszeit = CtlName |
|
| Text (TextBox/ComboBox) | — | Entwurfszeit = CtlName |
|
||||||
| ListIndex | −1 = keine Auswahl | −1 |
|
| ListIndex | −1 = keine Auswahl | −1 |
|
||||||
|
|
||||||
|
## Zeichenbilder und Zustände
|
||||||
|
|
||||||
|
Die Originalquellen belegen Aufbau und Rahmenformen, enthalten aber keine
|
||||||
|
vollständige Screenshot-Reihe für jede Klasse und jeden Zustand. Die folgende
|
||||||
|
einheitliche Umsetzung ist deshalb **unsere Festlegung**: normal = die Werte
|
||||||
|
aus `ForeColor`/`BackColor`, fokussiert = Farbe 15 auf 1, deaktiviert = Farbe
|
||||||
|
8 auf 0. `&` kennzeichnet
|
||||||
|
nur den Access-Key und wird nicht ausgegeben. Die Beispiele zeigen jeweils
|
||||||
|
normal · fokussiert · deaktiviert; Inhalt wird auf die verfügbare Breite
|
||||||
|
gekürzt und der Rest mit Leerzeichen gefüllt.
|
||||||
|
|
||||||
|
| Klasse | normal | fokussiert | deaktiviert |
|
||||||
|
|---|---|---|---|
|
||||||
|
| CommandButton, Höhe 1 | `<OK>` | `<OK>` | `<OK>` |
|
||||||
|
| CommandButton, Höhe 2 | `┌OK──┐` / `└────┘` | ebenso | ebenso |
|
||||||
|
| CommandButton, Höhe ≥ 3 | `┌────┐` / `│ OK │` / `└────┘` | ebenso | ebenso |
|
||||||
|
| Label | `Text` | `Text` | `Text` |
|
||||||
|
| Frame | `┌ Titel ─┐` / `│ │` / `└────────┘` | ebenso | ebenso |
|
||||||
|
| CheckBox | `[ ] Text` | `[ ] Text` | `[ ] Text` |
|
||||||
|
| CheckBox, gewählt/grau | `[x] Text` / `[-] Text` | ebenso | ebenso |
|
||||||
|
| OptionButton | `( ) Text` | `( ) Text` | `( ) Text` |
|
||||||
|
| OptionButton, gewählt | `(•) Text` | `(•) Text` | `(•) Text` |
|
||||||
|
| TextBox | `┌──────┐` / `│Text │` / `└──────┘` | ebenso, Cursor an `SelStart` | ebenso, ohne Cursor |
|
||||||
|
| ListBox | `┌──────┐` / `│eins │` / `│zwei │` / `└──────┘` | ebenso, aktuelle Zeile in Fokusfarben | ebenso |
|
||||||
|
| ComboBox, Dropdown | `[Text ▼]` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| ComboBox, Simple | `[Text ▼]` / `┌──────┐` / `│eins │` / `└──────┘` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| HScrollBar | `◄──□──►` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| VScrollBar | `▲` / `│` / `□` / `│` / `▼` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| Spin | `▲` / `▼` oder `◄ ►` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| PictureBox | `┌──────┐` / `│Text │` / `└──────┘` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| DirListBox | `┌──────┐` / `│[dir] │` / `└──────┘` | ebenso, aktuelle Zeile in Fokusfarben | ebenso |
|
||||||
|
| DriveListBox | `[ / ▼]` | ebenso in Fokusfarben | ebenso |
|
||||||
|
| FileListBox | `┌──────┐` / `│a.bas │` / `└──────┘` | ebenso, aktuelle Zeile in Fokusfarben | ebenso |
|
||||||
|
| Menu | ` Datei Bearbeiten ` | gewählter Titel/Eintrag in Fokusfarben | Text in Farbe 8 |
|
||||||
|
| Timer | keine sichtbaren Zellen | keine sichtbaren Zellen | keine sichtbaren Zellen |
|
||||||
|
|
||||||
|
`BorderStyle = 0` unterdrückt den Rahmen von Label, TextBox und PictureBox;
|
||||||
|
`BorderStyle = 2` nutzt `╔═╗║╚╝`. Scrollbars erscheinen nur in den von
|
||||||
|
`ScrollBars` genannten Richtungen. Ein unsichtbares Steuerelement zeichnet
|
||||||
|
keine Zelle.
|
||||||
|
|
||||||
|
## Festgelegte Reihenfolgen
|
||||||
|
|
||||||
|
Die folgenden Regeln sind **unsere Festlegung**, nicht nachgewiesenes
|
||||||
|
Referenzverhalten:
|
||||||
|
|
||||||
|
- **Z-Reihenfolge:** Erzeugungsreihenfolge; später erzeugte Elemente liegen
|
||||||
|
oben. Bei Steuerelement-Arrays entscheidet bei sonst gleicher Position der
|
||||||
|
aufsteigende Index, sodass der höchste Index oben liegt. Gezeichnet wird von
|
||||||
|
unten nach oben, die Trefferprüfung läuft umgekehrt.
|
||||||
|
- **Timer-Reihenfolge:** Sind mehrere Timer am selben Zustellpunkt fällig,
|
||||||
|
werden ihre Ereignisse in aufsteigender, ASCII-unabhängig
|
||||||
|
großgeschriebener `CtlName`-Reihenfolge zugestellt. Mehrere verstrichene
|
||||||
|
Intervalle desselben Timers bleiben in zeitlicher Reihenfolge davor.
|
||||||
|
|
||||||
**Keine Default-Eigenschaften:** `Text1 = "x"` gibt es nicht — Zugriff
|
**Keine Default-Eigenschaften:** `Text1 = "x"` gibt es nicht — Zugriff
|
||||||
immer explizit (`Text1.Text`). „Default" ist nur die CommandButton-
|
immer explizit (`Text1.Text`). „Default" ist nur die CommandButton-
|
||||||
Eigenschaft (Enter-Taste); „Cancel" analog für Esc.
|
Eigenschaft (Enter-Taste); „Cancel" analog für Esc.
|
||||||
@@ -188,8 +248,4 @@ Menü-Steuerelement-Arrays sind möglich.
|
|||||||
|
|
||||||
## Offene Detailfragen
|
## Offene Detailfragen
|
||||||
|
|
||||||
- Exakte Optik je Steuerelement-Zustand (normal/fokussiert/deaktiviert):
|
|
||||||
aus den Original-Screenshots ableiten (Phase 4)
|
|
||||||
- Z-Reihenfolge/Überlappung: Verhalten bei überlappenden Controls
|
|
||||||
- Timer-Reihenfolge bei mehreren gleichzeitig fälligen Timern
|
|
||||||
- `.FRM`-Textformat: exakte Serialisierung (siehe dateiformate.md)
|
- `.FRM`-Textformat: exakte Serialisierung (siehe dateiformate.md)
|
||||||
|
|||||||
171
docs/inventar.md
171
docs/inventar.md
@@ -22,7 +22,7 @@ namentlich abgewiesen. `Non-Feature` = abgelehnt, gelistet unter
|
|||||||
**Fundstelle.** Bei `implementiert` das Modul, bei `Non-Feature` der
|
**Fundstelle.** Bei `implementiert` das Modul, bei `Non-Feature` der
|
||||||
Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
||||||
|
|
||||||
**Abdeckung.** implementiert 689 · offen 61 · Non-Feature 53 · gesamt 803
|
**Abdeckung.** implementiert 794 · offen 0 · Non-Feature 50 · gesamt 844
|
||||||
|
|
||||||
| Name | Art | Gruppe | Status | Fundstelle | Quelle |
|
| Name | Art | Gruppe | Status | Fundstelle | Quelle |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
@@ -164,14 +164,14 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `FORMAT$` | Funktion | Formatierung | implementiert | tb-runtime::using | bas7advr |
|
| `FORMAT$` | Funktion | Formatierung | implementiert | tb-runtime::using | bas7advr |
|
||||||
| `PRINT USING` | Anweisung | Formatierung | implementiert | tb-runtime::using | bas7advr |
|
| `PRINT USING` | Anweisung | Formatierung | implementiert | tb-runtime::using | bas7advr |
|
||||||
| `SetFormatCC` | Routine | Formatierung | implementiert | tb-runtime::using | bas7advr |
|
| `SetFormatCC` | Routine | Formatierung | implementiert | tb-runtime::using | bas7advr |
|
||||||
| `INPUTBOX$` | Funktion | Forms | offen | - | sprachreferenz |
|
| `INPUTBOX$` | Funktion | Forms | implementiert | tb-runtime::builtins | sprachreferenz |
|
||||||
| `MSGBOX` | Anweisung | Forms | offen | - | sprachreferenz |
|
| `MSGBOX` | Anweisung | Forms | implementiert | tb-runtime::builtins | sprachreferenz |
|
||||||
| `MSGBOX` | Funktion | Forms | offen | - | sprachreferenz |
|
| `MSGBOX` | Funktion | Forms | implementiert | tb-runtime::builtins | sprachreferenz |
|
||||||
| `CIRCLE` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `CIRCLE` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `DRAW` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `DRAW` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `GET (Grafik)` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `GET (Grafik)` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `LINE` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `LINE` | Anweisung | Grafik | implementiert | tb-runtime::screen | bas7advr |
|
||||||
| `PAINT` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PAINT` | Anweisung | Grafik | implementiert | tb-runtime::screen | bas7advr |
|
||||||
| `PALETTE` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PALETTE` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `PCOPY` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PCOPY` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `PMAP` | Funktion | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PMAP` | Funktion | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
@@ -179,7 +179,7 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `PRESET` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PRESET` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `PSET` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PSET` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `PUT (Grafik)` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `PUT (Grafik)` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `VIEW` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `VIEW` | Anweisung | Grafik | implementiert | tb-runtime::screen | bas7advr |
|
||||||
| `WINDOW` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `WINDOW` | Anweisung | Grafik | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `ABSOLUTE` | Routine | Hardware | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `ABSOLUTE` | Routine | Hardware | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
| `BLOAD` | Anweisung | Hardware | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
| `BLOAD` | Anweisung | Hardware | Non-Feature | sprachreferenz.md#abweichungen | bas7advr |
|
||||||
@@ -255,7 +255,7 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `SQR` | Funktion | Mathematik | implementiert | tb-runtime::math | qb45advr |
|
| `SQR` | Funktion | Mathematik | implementiert | tb-runtime::math | qb45advr |
|
||||||
| `TAN` | Funktion | Mathematik | implementiert | tb-runtime::math | bas7advr |
|
| `TAN` | Funktion | Mathematik | implementiert | tb-runtime::math | bas7advr |
|
||||||
| `$DYNAMIC` | Metabefehl | Metabefehl | implementiert | tb-frontend::lexer | bas7advr |
|
| `$DYNAMIC` | Metabefehl | Metabefehl | implementiert | tb-frontend::lexer | bas7advr |
|
||||||
| `$INCLUDE` | Metabefehl | Metabefehl | offen | - | bas7advr |
|
| `$INCLUDE` | Metabefehl | Metabefehl | implementiert | tb-cli | bas7advr |
|
||||||
| `$STATIC` | Metabefehl | Metabefehl | implementiert | tb-frontend::lexer | bas7advr |
|
| `$STATIC` | Metabefehl | Metabefehl | implementiert | tb-frontend::lexer | bas7advr |
|
||||||
| `CALL` | Anweisung | Prozeduren | implementiert | tb-frontend::sema | bas7advr |
|
| `CALL` | Anweisung | Prozeduren | implementiert | tb-frontend::sema | bas7advr |
|
||||||
| `DECLARE` | Anweisung | Prozeduren | implementiert | tb-frontend::sema | bas7advr |
|
| `DECLARE` | Anweisung | Prozeduren | implementiert | tb-frontend::sema | bas7advr |
|
||||||
@@ -301,7 +301,7 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `ERDEV` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
| `ERDEV` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
||||||
| `ERDEV$` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
| `ERDEV$` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
||||||
| `FRE` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
| `FRE` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
||||||
| `RUN` | Anweisung | System | offen | - | bas7advr |
|
| `RUN` | Anweisung | System | implementiert | tb-cli | bas7advr |
|
||||||
| `SLEEP` | Anweisung | System | implementiert | tb-runtime::builtins | bas7advr |
|
| `SLEEP` | Anweisung | System | implementiert | tb-runtime::builtins | bas7advr |
|
||||||
| `STACK` | Anweisung | System | implementiert | tb-runtime::builtins | bas7advr |
|
| `STACK` | Anweisung | System | implementiert | tb-runtime::builtins | bas7advr |
|
||||||
| `STACK` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
| `STACK` | Funktion | System | implementiert | tb-runtime::builtins | bas7advr |
|
||||||
@@ -341,17 +341,17 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `FORM.SCALEHEIGHT` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.SCALEHEIGHT` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.SCALEWIDTH` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.SCALEWIDTH` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.WINDOWSTATE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.WINDOWSTATE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.CLS` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.CLS` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.DRAG` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.DRAG` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.HIDE` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.HIDE` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.LOAD` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.LOAD` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.MOVE` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.MOVE` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.PRINT` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.PRINT` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.PRINTFORM` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.PRINTFORM` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.REFRESH` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.REFRESH` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.SHOW` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.SHOW` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.TEXTHEIGHT` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.TEXTHEIGHT` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.TEXTWIDTH` | Methode | Forms/FORM | offen | - | forms-referenz |
|
| `FORM.TEXTWIDTH` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM.UNLOAD` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
| `FORM.UNLOAD` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FORM_CLICK` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
|
| `FORM_CLICK` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `FORM_DBLCLICK` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
|
| `FORM_DBLCLICK` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -387,10 +387,10 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `CHECKBOX.DRAGMODE` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `CHECKBOX.DRAGMODE` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX.CAPTION` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `CHECKBOX.CAPTION` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX.VALUE` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `CHECKBOX.VALUE` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX.DRAG` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
|
| `CHECKBOX.DRAG` | Methode | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX.MOVE` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
|
| `CHECKBOX.MOVE` | Methode | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX.REFRESH` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
|
| `CHECKBOX.REFRESH` | Methode | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX.SETFOCUS` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
|
| `CHECKBOX.SETFOCUS` | Methode | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `CHECKBOX_CLICK` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `CHECKBOX_CLICK` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `CHECKBOX_DRAGDROP` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `CHECKBOX_DRAGDROP` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `CHECKBOX_DRAGOVER` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `CHECKBOX_DRAGOVER` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -424,12 +424,12 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `COMBOBOX.SELTEXT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `COMBOBOX.SELTEXT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.STYLE` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `COMBOBOX.STYLE` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.TEXT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `COMBOBOX.TEXT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.ADDITEM` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
|
| `COMBOBOX.ADDITEM` | Methode | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.DRAG` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
|
| `COMBOBOX.DRAG` | Methode | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.MOVE` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
|
| `COMBOBOX.MOVE` | Methode | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.REFRESH` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
|
| `COMBOBOX.REFRESH` | Methode | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.REMOVEITEM` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
|
| `COMBOBOX.REMOVEITEM` | Methode | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX.SETFOCUS` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
|
| `COMBOBOX.SETFOCUS` | Methode | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMBOBOX_CHANGE` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `COMBOBOX_CHANGE` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `COMBOBOX_CLICK` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `COMBOBOX_CLICK` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `COMBOBOX_DBLCLICK` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `COMBOBOX_DBLCLICK` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -463,10 +463,10 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `COMMANDBUTTON.CAPTION` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `COMMANDBUTTON.CAPTION` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON.DEFAULT` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `COMMANDBUTTON.DEFAULT` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON.VALUE` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `COMMANDBUTTON.VALUE` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON.DRAG` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
|
| `COMMANDBUTTON.DRAG` | Methode | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON.MOVE` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
|
| `COMMANDBUTTON.MOVE` | Methode | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON.REFRESH` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
|
| `COMMANDBUTTON.REFRESH` | Methode | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON.SETFOCUS` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
|
| `COMMANDBUTTON.SETFOCUS` | Methode | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `COMMANDBUTTON_CLICK` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
| `COMMANDBUTTON_CLICK` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `COMMANDBUTTON_DRAGDROP` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
| `COMMANDBUTTON_DRAGDROP` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `COMMANDBUTTON_DRAGOVER` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
| `COMMANDBUTTON_DRAGOVER` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -489,6 +489,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `DIRLISTBOX.TABINDEX` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DIRLISTBOX.TABINDEX` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DIRLISTBOX.TABSTOP` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DIRLISTBOX.TABSTOP` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DIRLISTBOX.CTLNAME` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DIRLISTBOX.CTLNAME` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `DIRLISTBOX.LIST` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `DIRLISTBOX.LISTCOUNT` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `DIRLISTBOX.LISTINDEX` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DIRLISTBOX.PATH` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DIRLISTBOX.PATH` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DIRLISTBOX.TEXT` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DIRLISTBOX.TEXT` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DIRLISTBOX_CHANGE` | Ereignis | Forms/DIRLISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `DIRLISTBOX_CHANGE` | Ereignis | Forms/DIRLISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -509,6 +512,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `DRIVELISTBOX.TABINDEX` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DRIVELISTBOX.TABINDEX` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DRIVELISTBOX.TABSTOP` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DRIVELISTBOX.TABSTOP` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DRIVELISTBOX.CTLNAME` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DRIVELISTBOX.CTLNAME` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `DRIVELISTBOX.LIST` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `DRIVELISTBOX.LISTCOUNT` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `DRIVELISTBOX.LISTINDEX` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DRIVELISTBOX.DRIVE` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DRIVELISTBOX.DRIVE` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DRIVELISTBOX.TEXT` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `DRIVELISTBOX.TEXT` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `DRIVELISTBOX_CHANGE` | Ereignis | Forms/DRIVELISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `DRIVELISTBOX_CHANGE` | Ereignis | Forms/DRIVELISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -526,6 +532,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `FILELISTBOX.TABINDEX` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `FILELISTBOX.TABINDEX` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FILELISTBOX.TABSTOP` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `FILELISTBOX.TABSTOP` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FILELISTBOX.CTLNAME` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `FILELISTBOX.CTLNAME` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `FILELISTBOX.LIST` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `FILELISTBOX.LISTCOUNT` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `FILELISTBOX.LISTINDEX` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FILELISTBOX.FILENAME` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `FILELISTBOX.FILENAME` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FILELISTBOX.PATH` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `FILELISTBOX.PATH` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FILELISTBOX.PATTERN` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `FILELISTBOX.PATTERN` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
@@ -554,9 +563,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `FRAME.CTLNAME` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
| `FRAME.CTLNAME` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.DRAGMODE` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
| `FRAME.DRAGMODE` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.CAPTION` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
| `FRAME.CAPTION` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.DRAG` | Methode | Forms/FRAME | offen | - | forms-referenz |
|
| `FRAME.DRAG` | Methode | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.MOVE` | Methode | Forms/FRAME | offen | - | forms-referenz |
|
| `FRAME.MOVE` | Methode | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.REFRESH` | Methode | Forms/FRAME | offen | - | forms-referenz |
|
| `FRAME.REFRESH` | Methode | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME_DRAGDROP` | Ereignis | Forms/FRAME | implementiert | tb-vm::interp | forms-referenz |
|
| `FRAME_DRAGDROP` | Ereignis | Forms/FRAME | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `FRAME_DRAGOVER` | Ereignis | Forms/FRAME | implementiert | tb-vm::interp | forms-referenz |
|
| `FRAME_DRAGOVER` | Ereignis | Forms/FRAME | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `HSCROLLBAR.BACKCOLOR` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `HSCROLLBAR.BACKCOLOR` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
@@ -581,9 +590,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `HSCROLLBAR.MAX` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `HSCROLLBAR.MAX` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `HSCROLLBAR.SMALLCHANGE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `HSCROLLBAR.SMALLCHANGE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `HSCROLLBAR.VALUE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `HSCROLLBAR.VALUE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `HSCROLLBAR.DRAG` | Methode | Forms/HSCROLLBAR | offen | - | forms-referenz |
|
| `HSCROLLBAR.DRAG` | Methode | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `HSCROLLBAR.MOVE` | Methode | Forms/HSCROLLBAR | offen | - | forms-referenz |
|
| `HSCROLLBAR.MOVE` | Methode | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `HSCROLLBAR.REFRESH` | Methode | Forms/HSCROLLBAR | offen | - | forms-referenz |
|
| `HSCROLLBAR.REFRESH` | Methode | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `HSCROLLBAR_CHANGE` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `HSCROLLBAR_CHANGE` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `HSCROLLBAR_DRAGDROP` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `HSCROLLBAR_DRAGDROP` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `HSCROLLBAR_DRAGOVER` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `HSCROLLBAR_DRAGOVER` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -611,9 +620,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `LABEL.AUTOSIZE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
| `LABEL.AUTOSIZE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.BORDERSTYLE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
| `LABEL.BORDERSTYLE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.CAPTION` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
| `LABEL.CAPTION` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.DRAG` | Methode | Forms/LABEL | offen | - | forms-referenz |
|
| `LABEL.DRAG` | Methode | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.MOVE` | Methode | Forms/LABEL | offen | - | forms-referenz |
|
| `LABEL.MOVE` | Methode | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.REFRESH` | Methode | Forms/LABEL | offen | - | forms-referenz |
|
| `LABEL.REFRESH` | Methode | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL_CHANGE` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
|
| `LABEL_CHANGE` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `LABEL_CLICK` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
|
| `LABEL_CLICK` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `LABEL_DBLCLICK` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
|
| `LABEL_DBLCLICK` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -643,12 +652,12 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `LISTBOX.LISTINDEX` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `LISTBOX.LISTINDEX` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.SORTED` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `LISTBOX.SORTED` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.TEXT` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `LISTBOX.TEXT` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.ADDITEM` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
|
| `LISTBOX.ADDITEM` | Methode | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.DRAG` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
|
| `LISTBOX.DRAG` | Methode | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.MOVE` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
|
| `LISTBOX.MOVE` | Methode | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.REFRESH` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
|
| `LISTBOX.REFRESH` | Methode | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.REMOVEITEM` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
|
| `LISTBOX.REMOVEITEM` | Methode | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX.SETFOCUS` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
|
| `LISTBOX.SETFOCUS` | Methode | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LISTBOX_CLICK` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `LISTBOX_CLICK` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `LISTBOX_DBLCLICK` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `LISTBOX_DBLCLICK` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `LISTBOX_DRAGDROP` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `LISTBOX_DRAGDROP` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -670,6 +679,7 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `MENU.SEPARATOR` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
| `MENU.SEPARATOR` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `MENU.TAG` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
| `MENU.TAG` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `MENU.VISIBLE` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
| `MENU.VISIBLE` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `MENU.SHORTCUT` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `MENU_CLICK` | Ereignis | Forms/MENU | implementiert | tb-vm::interp | forms-referenz |
|
| `MENU_CLICK` | Ereignis | Forms/MENU | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `OPTIONBUTTON.BACKCOLOR` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `OPTIONBUTTON.BACKCOLOR` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.ENABLED` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `OPTIONBUTTON.ENABLED` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
@@ -689,10 +699,10 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `OPTIONBUTTON.DRAGMODE` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `OPTIONBUTTON.DRAGMODE` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.CAPTION` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `OPTIONBUTTON.CAPTION` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.VALUE` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
| `OPTIONBUTTON.VALUE` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.DRAG` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
|
| `OPTIONBUTTON.DRAG` | Methode | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.MOVE` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
|
| `OPTIONBUTTON.MOVE` | Methode | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.REFRESH` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
|
| `OPTIONBUTTON.REFRESH` | Methode | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON.SETFOCUS` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
|
| `OPTIONBUTTON.SETFOCUS` | Methode | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `OPTIONBUTTON_CLICK` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
| `OPTIONBUTTON_CLICK` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `OPTIONBUTTON_DBLCLICK` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
| `OPTIONBUTTON_DBLCLICK` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `OPTIONBUTTON_DRAGDROP` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
| `OPTIONBUTTON_DRAGDROP` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -724,14 +734,14 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `PICTUREBOX.CURRENTY` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `PICTUREBOX.CURRENTY` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.SCALEHEIGHT` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `PICTUREBOX.SCALEHEIGHT` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.SCALEWIDTH` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `PICTUREBOX.SCALEWIDTH` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.CLS` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.CLS` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.DRAG` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.DRAG` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.MOVE` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.MOVE` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.PRINT` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.PRINT` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.REFRESH` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.REFRESH` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.SETFOCUS` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.SETFOCUS` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.TEXTHEIGHT` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.TEXTHEIGHT` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.TEXTWIDTH` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
|
| `PICTUREBOX.TEXTWIDTH` | Methode | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX_CLICK` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `PICTUREBOX_CLICK` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `PICTUREBOX_DBLCLICK` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `PICTUREBOX_DBLCLICK` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `PICTUREBOX_DRAGDROP` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `PICTUREBOX_DRAGDROP` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -768,10 +778,10 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `TEXTBOX.SELSTART` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `TEXTBOX.SELSTART` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX.SELTEXT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `TEXTBOX.SELTEXT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX.TEXT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
| `TEXTBOX.TEXT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX.DRAG` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
|
| `TEXTBOX.DRAG` | Methode | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX.MOVE` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
|
| `TEXTBOX.MOVE` | Methode | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX.REFRESH` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
|
| `TEXTBOX.REFRESH` | Methode | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX.SETFOCUS` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
|
| `TEXTBOX.SETFOCUS` | Methode | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `TEXTBOX_CHANGE` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `TEXTBOX_CHANGE` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `TEXTBOX_DRAGDROP` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `TEXTBOX_DRAGDROP` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `TEXTBOX_DRAGOVER` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
|
| `TEXTBOX_DRAGOVER` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -809,9 +819,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `VSCROLLBAR.MAX` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `VSCROLLBAR.MAX` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `VSCROLLBAR.SMALLCHANGE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `VSCROLLBAR.SMALLCHANGE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `VSCROLLBAR.VALUE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
| `VSCROLLBAR.VALUE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `VSCROLLBAR.DRAG` | Methode | Forms/VSCROLLBAR | offen | - | forms-referenz |
|
| `VSCROLLBAR.DRAG` | Methode | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `VSCROLLBAR.MOVE` | Methode | Forms/VSCROLLBAR | offen | - | forms-referenz |
|
| `VSCROLLBAR.MOVE` | Methode | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `VSCROLLBAR.REFRESH` | Methode | Forms/VSCROLLBAR | offen | - | forms-referenz |
|
| `VSCROLLBAR.REFRESH` | Methode | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `VSCROLLBAR_CHANGE` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `VSCROLLBAR_CHANGE` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `VSCROLLBAR_DRAGDROP` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `VSCROLLBAR_DRAGDROP` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `VSCROLLBAR_DRAGOVER` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `VSCROLLBAR_DRAGOVER` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
@@ -820,6 +830,37 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
|||||||
| `VSCROLLBAR_KEYPRESS` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `VSCROLLBAR_KEYPRESS` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `VSCROLLBAR_KEYUP` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `VSCROLLBAR_KEYUP` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `VSCROLLBAR_LOSTFOCUS` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
| `VSCROLLBAR_LOSTFOCUS` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN.BACKCOLOR` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.BORDERSTYLE` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.CTLNAME` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.ENABLED` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.HEIGHT` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.INDEX` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.INTERVAL` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.LEFT` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.MAX` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.MIN` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.MOUSEPOINTER` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.PARENT` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.STYLE` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.TABINDEX` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.TABSTOP` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.TAG` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.TOP` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.VALUE` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.VISIBLE` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.WIDTH` | Eigenschaft | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.DRAG` | Methode | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.REFRESH` | Methode | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN.SETFOCUS` | Methode | Forms/SPIN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
| `SPIN_CUSTOM` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_DRAGDROP` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_DRAGOVER` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_GOTFOCUS` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_KEYDOWN` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_KEYPRESS` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_KEYUP` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
|
| `SPIN_LOSTFOCUS` | Ereignis | Forms/SPIN | implementiert | tb-vm::interp | forms-referenz |
|
||||||
| `SCREEN.ACTIVECONTROL` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
|
| `SCREEN.ACTIVECONTROL` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `SCREEN.ACTIVEFORM` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
|
| `SCREEN.ACTIVEFORM` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `SCREEN.AUTOREDRAW` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
|
| `SCREEN.AUTOREDRAW` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ stehen am Ende dieses Dokuments; nur diese Liste rechtfertigt Abweisungen.
|
|||||||
- `CONST name = ausdruck` — Konstanten (konstante Ausdrücke zur Compilezeit).
|
- `CONST name = ausdruck` — Konstanten (konstante Ausdrücke zur Compilezeit).
|
||||||
- `DECLARE SUB/FUNCTION name (parameter)` — Prototyp; die IDE erzeugt sie
|
- `DECLARE SUB/FUNCTION name (parameter)` — Prototyp; die IDE erzeugt sie
|
||||||
automatisch beim Speichern.
|
automatisch beim Speichern.
|
||||||
|
- `'$INCLUDE: 'datei'` — bindet eine Textquelle relativ zur einschließenden
|
||||||
|
Datei ein; Namen werden wie unter DOS ohne Beachtung der Großschreibung
|
||||||
|
aufgelöst, Zyklen werden namentlich abgewiesen.
|
||||||
|
|
||||||
## 4. Operatoren (nach Priorität, hoch → niedrig)
|
## 4. Operatoren (nach Priorität, hoch → niedrig)
|
||||||
|
|
||||||
@@ -148,6 +151,9 @@ INTEGER −1 (wahr) / 0 (falsch).
|
|||||||
als Direktmodus-Befehl der IDE (Phase 5) und ist nicht kompilierbar.
|
als Direktmodus-Befehl der IDE (Phase 5) und ist nicht kompilierbar.
|
||||||
- `SLEEP [sekunden]`, `DO EVENTS`/`DOEVENTS` (Ereignisse verarbeiten —
|
- `SLEEP [sekunden]`, `DO EVENTS`/`DOEVENTS` (Ereignisse verarbeiten —
|
||||||
zentral für Forms)
|
zentral für Forms)
|
||||||
|
- `RUN` startet das aktuelle Programm neu; `RUN zeile` startet es nach den
|
||||||
|
globalen Deklarationen an der angegebenen Zeile, `RUN datei$` lädt relativ
|
||||||
|
zur laufenden Quelle eine `.BAS`-, `.FRM`- oder `.MAK`-Datei neu.
|
||||||
|
|
||||||
## 6. Prozeduren
|
## 6. Prozeduren
|
||||||
|
|
||||||
@@ -517,16 +523,12 @@ Dialoge:
|
|||||||
Kombinationen) liefern nichts — eine eigene Kodierung dafür wäre eine
|
Kombinationen) liefern nichts — eine eigene Kodierung dafür wäre eine
|
||||||
Erweiterung und gehört in Stufe 2.
|
Erweiterung und gehört in Stufe 2.
|
||||||
4. **Kein `CHAIN`/Overlay-Mechanismus**; `SHELL` startet die System-Shell.
|
4. **Kein `CHAIN`/Overlay-Mechanismus**; `SHELL` startet die System-Shell.
|
||||||
5. **Grafik-Anweisungen** (`SCREEN n>0`, `PSET`, `PRESET`, `LINE`,
|
5. **Grafik im Zellenpuffer:** `SCREEN 0` bis `SCREEN 13`, `LINE` (auch
|
||||||
`CIRCLE`, `PAINT`, `DRAW`, Grafik-`GET`/`PUT`, `PALETTE`, `WINDOW`,
|
`B`/`BF` und `STEP`), `PAINT` und `VIEW` werden ausgeführt. Historische
|
||||||
`VIEW`, `PMAP`, `PCOPY`, `POINT`) sind Nicht-Ziel und werden **zur
|
Pixelkoordinaten werden dabei in 8×8-Blöcken auf Terminalzellen
|
||||||
Compile-Zeit** mit „Feature unavailable" abgewiesen. Die Formen, die
|
abgebildet. `PSET`, `PRESET`, `CIRCLE`, `DRAW`, Grafik-`GET`/`PUT`,
|
||||||
sich ein Schlüsselwort mit einer unterstützten Anweisung teilen, erkennt
|
`PALETTE`, `WINDOW`, `PMAP`, `PCOPY` und `POINT` bleiben Nicht-Ziel und
|
||||||
der Compiler an ihrer Syntax: `LINE` ohne `INPUT`, `GET`/`PUT` mit
|
werden zur Compile-Zeit mit „Feature unavailable" abgewiesen.
|
||||||
`(`, `VIEW` ohne `PRINT`. Hinweis: das Vorbild **hatte** die
|
|
||||||
Grafikmodi 0–13, aber nicht gleichzeitig mit Forms (dort Fehler 272
|
|
||||||
„Invalid when forms are showing") — wir übernehmen nur den
|
|
||||||
Forms-/Textmodus-Zweig.
|
|
||||||
6. **`PLAY`/`SOUND`**: `BEEP` = Terminal-Bell; Rest Nicht-Ziel (Fehler 73).
|
6. **`PLAY`/`SOUND`**: `BEEP` = Terminal-Bell; Rest Nicht-Ziel (Fehler 73).
|
||||||
5a. **`LPRINT` schreibt in eine Datei** (2026-09-03): einen
|
5a. **`LPRINT` schreibt in eine Datei** (2026-09-03): einen
|
||||||
plattformübergreifenden Druckerkanal gibt es nicht. Der Druckerpuffer
|
plattformübergreifenden Druckerkanal gibt es nicht. Der Druckerpuffer
|
||||||
@@ -576,3 +578,13 @@ Dialoge:
|
|||||||
werden nicht abgebildet. Unter Windows ist `SIGTERM` von außen nicht
|
werden nicht abgebildet. Unter Windows ist `SIGTERM` von außen nicht
|
||||||
zustellbar — `SIGNAL(2)` bleibt dort wirkungslos. Trifft `SIGINT` ohne aktiven Trap
|
zustellbar — `SIGNAL(2)` bleibt dort wirkungslos. Trifft `SIGINT` ohne aktiven Trap
|
||||||
ein, bleibt es beim bisherigen Abbruch.
|
ein, bleibt es beim bisherigen Abbruch.
|
||||||
|
|
||||||
|
9. **Forms-Darstellung und Dateisystem-Steuerelemente** (2026-09-05):
|
||||||
|
Controls zeichnen ausschließlich in den Unicode-Zellenpuffer. Mangels
|
||||||
|
vollständiger Original-Screenshots gelten die Zeichenbilder und
|
||||||
|
Fokus-/Deaktiviert-Farben aus `forms-referenz.md` als unsere Festlegung.
|
||||||
|
Dasselbe gilt für die Z-Reihenfolge (Erzeugungsreihenfolge, später liegt
|
||||||
|
oben) und gleichzeitig fällige Timer (aufsteigend nach `CtlName`).
|
||||||
|
`DirListBox` und `FileListBox` verwenden native Pfade und `*`/`?`-Muster;
|
||||||
|
`DriveListBox` zeigt unter Unix `/` und unter macOS zusätzlich Mounts aus
|
||||||
|
`/Volumes` statt DOS-Laufwerksbuchstaben.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Siehe proposal.md — Why. Dieser Change setzt auf drei fertigen Stücken
|
Siehe proposal.md — Why. Dieser Change setzt auf drei fertigen Stücken
|
||||||
auf: dem Zellenpuffer aus Phase 3, der Ereignisschleife samt Mausquelle
|
auf: dem Zellenpuffer aus Phase 3, der Ereignisschleife samt Mausquelle
|
||||||
(`phase-4-ereignisschleife`) und dem Objektmodell
|
(`phase-4-ereignisschleife`) und dem Objektmodell
|
||||||
(`phase-4-objektmodell`). Er ist der breiteste Change der Phase — 15
|
(`phase-4-objektmodell`). Er ist der breiteste Change der Phase — 16
|
||||||
Klassen, Menüs, Dialoge —, aber der mit den wenigsten offenen
|
Klassen, Menüs, Dialoge —, aber der mit den wenigsten offenen
|
||||||
Architekturfragen.
|
Architekturfragen.
|
||||||
|
|
||||||
@@ -94,9 +94,16 @@ Einhängepunkte der Plattform; DirListBox und FileListBox arbeiten auf
|
|||||||
Pfaden. `Pattern` bleibt das Muster des Vorbilds. Die Abweichung wird in
|
Pfaden. `Pattern` bleibt das Muster des Vorbilds. Die Abweichung wird in
|
||||||
der Sprachreferenz festgehalten.
|
der Sprachreferenz festgehalten.
|
||||||
|
|
||||||
|
### D7 — Korpusbefunde werden im gemeinsamen Ausführungspfad geschlossen
|
||||||
|
|
||||||
|
Die VBDOS-Projekte laufen durch denselben CLI-, Frontend-, VM- und
|
||||||
|
Zellenpufferpfad wie eigene Programme. Projekt- und Include-Auflösung,
|
||||||
|
VSpin/HSpin, Common-Dialog-Routinen und die tatsächlich verwendete Grafik
|
||||||
|
werden deshalb dort implementiert und getestet, nicht im Korpus umgangen.
|
||||||
|
|
||||||
## Risks / Trade-offs
|
## Risks / Trade-offs
|
||||||
|
|
||||||
- **Breite statt Tiefe** → 15 Klassen sind viel Fläche für Flüchtigkeit;
|
- **Breite statt Tiefe** → 16 Klassen sind viel Fläche für Flüchtigkeit;
|
||||||
jede Klasse bekommt ihren eigenen Korpusnachweis, nicht nur einen
|
jede Klasse bekommt ihren eigenen Korpusnachweis, nicht nur einen
|
||||||
gemeinsamen Sammeltest.
|
gemeinsamen Sammeltest.
|
||||||
- **Vollständiges Neuzeichnen bei großen Terminals** → bei 200×60 sind es
|
- **Vollständiges Neuzeichnen bei großen Terminals** → bei 200×60 sind es
|
||||||
@@ -109,6 +116,4 @@ der Sprachreferenz festgehalten.
|
|||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
- Ob die PictureBox als Container für OptionButton-Gruppen dieselbe
|
Keine.
|
||||||
Fokuslogik wie der Frame braucht, zeigt der erste Korpusnachweis mit
|
|
||||||
gruppierten Optionsfeldern.
|
|
||||||
@@ -18,7 +18,7 @@ zwischen die Aufgaben.
|
|||||||
- **Steuerelementklassen mit Verhalten**: CommandButton, TextBox,
|
- **Steuerelementklassen mit Verhalten**: CommandButton, TextBox,
|
||||||
ListBox, ComboBox, CheckBox, OptionButton, Frame, Label, HScrollBar,
|
ListBox, ComboBox, CheckBox, OptionButton, Frame, Label, HScrollBar,
|
||||||
VScrollBar, PictureBox, Timer sowie die Dateisystem-Steuerelemente
|
VScrollBar, PictureBox, Timer sowie die Dateisystem-Steuerelemente
|
||||||
DirListBox, DriveListBox und FileListBox — je mit Darstellung,
|
DirListBox, DriveListBox, FileListBox und VSpin/HSpin — je mit Darstellung,
|
||||||
Zuständen (normal, fokussiert, deaktiviert) und ihren Methoden.
|
Zuständen (normal, fokussiert, deaktiviert) und ihren Methoden.
|
||||||
- **Darstellung im Zellenpuffer**: Steuerelemente zeichnen in den
|
- **Darstellung im Zellenpuffer**: Steuerelemente zeichnen in den
|
||||||
vorhandenen Textbildschirm; es entsteht keine zweite Zeichenschicht.
|
vorhandenen Textbildschirm; es entsteht keine zweite Zeichenschicht.
|
||||||
@@ -43,10 +43,13 @@ zwischen die Aufgaben.
|
|||||||
Positionierung in Zeichen.
|
Positionierung in Zeichen.
|
||||||
- **Timer als Steuerelement**: `Interval` 0–65 535 ms auf der Zeitquelle
|
- **Timer als Steuerelement**: `Interval` 0–65 535 ms auf der Zeitquelle
|
||||||
der Ereignisschleife, Ereignis `Timer`.
|
der Ereignisschleife, Ereignis `Timer`.
|
||||||
|
- **VBDOS-Kompatibilität**: `.MAK`- und `$INCLUDE`-Auflösung, `RUN`,
|
||||||
|
VSpin/HSpin, die verwendete Zellgrafik (`SCREEN`, `LINE`, `PAINT`, `VIEW`)
|
||||||
|
und die von den Beispielen benutzten Common-Dialog-Routinen.
|
||||||
|
|
||||||
**Non-Goals:** Der Formular-Designer und die Projektverwaltung (Phase 5);
|
**Non-Goals:** Der Formular-Designer (Phase 5). Für den geforderten
|
||||||
native Erweiterungssteuerelemente (Stufe 2); grafische Ausgabe in der
|
VBDOS-Kompatibilitätsnachweis notwendige Projektauflösung, native
|
||||||
PictureBox — sie ist eine Textzeichenfläche.
|
Erweiterungssteuerelemente und Grafikausgabe gehören zu diesem Change.
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|
||||||
@@ -66,10 +69,10 @@ PictureBox — sie ist eine Textzeichenfläche.
|
|||||||
|
|
||||||
- `crates/tb-ui`: Darstellung und Eingabebehandlung je Klasse, Menüs,
|
- `crates/tb-ui`: Darstellung und Eingabebehandlung je Klasse, Menüs,
|
||||||
Dialoge, Trefferprüfung, Fokusverwaltung.
|
Dialoge, Trefferprüfung, Fokusverwaltung.
|
||||||
- `crates/tb-runtime`: Fehler 260–480 des Forms-Bereichs als Auslöser;
|
- `crates/tb-runtime`: Fehler 260–480 des Forms-Bereichs als Auslöser,
|
||||||
`MSGBOX`/`INPUTBOX$` als Sprachelemente.
|
`MSGBOX`/`INPUTBOX$` und die benötigte Zellgrafik.
|
||||||
- `crates/tb-frontend`: Signaturen von `MSGBOX` (Anweisung und Funktion)
|
- `crates/tb-frontend`: Signaturen von `MSGBOX` (Anweisung und Funktion)
|
||||||
und `INPUTBOX$`; die `Unsupported`-Absenkung von `MSGBOX` entfällt.
|
und `INPUTBOX$`, Projektquellen, `RUN` und die verwendete Grafiksyntax.
|
||||||
- `tests/compat`: Formularprogramme mit Sollausgabe; die Programme aus
|
- `tests/compat`: Formularprogramme mit Sollausgabe; die Programme aus
|
||||||
`github.com/cout/vbdos` als Kompatibilitätsnachweis.
|
`github.com/cout/vbdos` als Kompatibilitätsnachweis.
|
||||||
- `docs/`: `forms-referenz.md` — die offenen Detailfragen (Optik je
|
- `docs/`: `forms-referenz.md` — die offenen Detailfragen (Optik je
|
||||||
@@ -19,11 +19,10 @@ Listen-Steuerelement, ein Timer und ein modaler Dialog.
|
|||||||
|
|
||||||
### Requirement: Kompatibilitätsnachweis an Fremdprogrammen
|
### Requirement: Kompatibilitätsnachweis an Fremdprogrammen
|
||||||
Die Formularprogramme aus dem öffentlichen Bestand des Vorbilds SHALL
|
Die Formularprogramme aus dem öffentlichen Bestand des Vorbilds SHALL
|
||||||
sich ohne Non-Features übersetzen lassen und bedienbar sein. Ein
|
sich ohne Diagnosen oder Non-Features übersetzen lassen und bedienbar
|
||||||
Programm, das an einem Non-Feature scheitert, MUST das Element
|
sein. Jeder entdeckte fehlende Sprach-, Projekt-, Formular- oder
|
||||||
namentlich nennen; ein Scheitern aus anderem Grund MUST als Befund
|
Laufzeitpfad MUST implementiert und als Befund festgehalten werden.
|
||||||
festgehalten werden.
|
|
||||||
|
|
||||||
#### Scenario: Fremdprogramm übersetzt
|
#### Scenario: Fremdprogramm übersetzt
|
||||||
- **WHEN** ein Formularprogramm des öffentlichen Bestands übersetzt wird
|
- **WHEN** ein Formularprogramm des öffentlichen Bestands übersetzt wird
|
||||||
- **THEN** entstehen keine Diagnosen außer namentlich genannten Non-Features
|
- **THEN** entstehen keine Diagnosen und das Programm ist bedienbar
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
## 1. Festlegungen vorab
|
## 1. Festlegungen vorab
|
||||||
|
|
||||||
- [ ] 1.1 Optik je Klasse und Zustand (normal, fokussiert, deaktiviert) als Zeichenbild in `docs/forms-referenz.md` festhalten, aus den Original-Screenshots abgeleitet (D3); verifiziert durch je ein Zeichenbild pro Klasse und Zustand
|
- [x] 1.1 Optik je Klasse und Zustand (normal, fokussiert, deaktiviert) als Zeichenbild in `docs/forms-referenz.md` festhalten, aus den Original-Screenshots abgeleitet (D3); verifiziert durch je ein Zeichenbild pro Klasse und Zustand
|
||||||
- [ ] 1.2 Z-Reihenfolge und Timer-Reihenfolge festlegen und als unsere Festlegung kennzeichnen (D3); verifiziert durch die beiden Abschnitte in der Forms-Referenz
|
- [x] 1.2 Z-Reihenfolge und Timer-Reihenfolge festlegen und als unsere Festlegung kennzeichnen (D3); verifiziert durch die beiden Abschnitte in der Forms-Referenz
|
||||||
|
|
||||||
## 2. Darstellung und Eingabekette
|
## 2. Darstellung und Eingabekette
|
||||||
|
|
||||||
- [ ] 2.1 Zeichnen je Klasse aus dem Eigenschaftszustand in den Zellenpuffer (D1); verifiziert durch Snapshot-Tests je Klasse, darunter die drei Höhenformen des CommandButton
|
- [x] 2.1 Zeichnen je Klasse aus dem Eigenschaftszustand in den Zellenpuffer (D1); verifiziert durch Snapshot-Tests je Klasse, darunter die drei Höhenformen des CommandButton
|
||||||
- [ ] 2.2 Eingabekette für Tastatur und Maus (D2); verifiziert durch Unit-Tests, die je Station den Vorrang prüfen
|
- [x] 2.2 Eingabekette für Tastatur und Maus (D2); verifiziert durch Unit-Tests, die je Station den Vorrang prüfen
|
||||||
- [ ] 2.3 Trefferprüfung über die Z-Reihenfolge; verifiziert durch einen Test mit überlappenden Steuerelementen und einem Klick ins Formular
|
- [x] 2.3 Trefferprüfung über die Z-Reihenfolge; verifiziert durch einen Test mit überlappenden Steuerelementen und einem Klick ins Formular
|
||||||
|
|
||||||
## 3. Fokus und Tastatur
|
## 3. Fokus und Tastatur
|
||||||
|
|
||||||
- [ ] 3.1 Tabreihenfolge über `TabIndex`/`TabStop` vorwärts und rückwärts, deaktivierte Elemente überspringen; verifiziert durch Unit-Tests je Fall
|
- [x] 3.1 Tabreihenfolge über `TabIndex`/`TabStop` vorwärts und rückwärts, deaktivierte Elemente überspringen; verifiziert durch Unit-Tests je Fall
|
||||||
- [ ] 3.2 Access-Keys aus `&`, `Default` für Enter, `Cancel` für Esc; verifiziert durch Unit-Tests je Auslöser
|
- [x] 3.2 Access-Keys aus `&`, `Default` für Enter, `Cancel` für Esc; verifiziert durch Unit-Tests je Auslöser
|
||||||
- [ ] 3.3 `SETFOCUS`, `GotFocus`/`LostFocus` in der festgelegten Reihenfolge; verifiziert durch einen Test, der die Reihenfolge der beiden Ereignisse prüft
|
- [x] 3.3 `SETFOCUS`, `GotFocus`/`LostFocus` in der festgelegten Reihenfolge; verifiziert durch einen Test, der die Reihenfolge der beiden Ereignisse prüft
|
||||||
|
|
||||||
## 4. Steuerelementklassen
|
## 4. Steuerelementklassen
|
||||||
|
|
||||||
- [ ] 4.1 CommandButton, Label, Frame, CheckBox, OptionButton (inkl. Gruppierung im Container); verifiziert durch Snapshot- und Verhaltenstests je Klasse
|
- [x] 4.1 CommandButton, Label, Frame, CheckBox, OptionButton (inkl. Gruppierung im Container); verifiziert durch Snapshot- und Verhaltenstests je Klasse
|
||||||
- [ ] 4.2 TextBox mit `Text`, `SelStart`/`SelLength`/`SelText`, `MultiLine`, `ScrollBars`, Ereignis `Change`; verifiziert durch Tests für Eingabe, Auswahl und Umbruch
|
- [x] 4.2 TextBox mit `Text`, `SelStart`/`SelLength`/`SelText`, `MultiLine`, `ScrollBars`, Ereignis `Change`; verifiziert durch Tests für Eingabe, Auswahl und Umbruch
|
||||||
- [ ] 4.3 ListBox und ComboBox mit `ADDITEM`/`REMOVEITEM`, `List`/`ListCount`/`ListIndex`, `Sorted`, den drei ComboBox-Stilarten; verifiziert durch Tests für Einfügen, Sortierung und „keine Auswahl"
|
- [x] 4.3 ListBox und ComboBox mit `ADDITEM`/`REMOVEITEM`, `List`/`ListCount`/`ListIndex`, `Sorted`, den drei ComboBox-Stilarten; verifiziert durch Tests für Einfügen, Sortierung und „keine Auswahl"
|
||||||
- [ ] 4.4 HScrollBar/VScrollBar mit `Min`/`Max`/`Value`/`SmallChange`/`LargeChange`/`Attached` und Ereignis `Change`; verifiziert durch Tests für Tastatur- und Mausbedienung
|
- [x] 4.4 HScrollBar/VScrollBar mit `Min`/`Max`/`Value`/`SmallChange`/`LargeChange`/`Attached` und Ereignis `Change`; verifiziert durch Tests für Tastatur- und Mausbedienung
|
||||||
- [ ] 4.5 PictureBox als Textzeichenfläche (`PRINT`, `CLS`, `CurrentX`/`CurrentY`, `TEXTWIDTH`/`TEXTHEIGHT`) und als Container; verifiziert durch Snapshot-Tests
|
- [x] 4.5 PictureBox als Textzeichenfläche (`PRINT`, `CLS`, `CurrentX`/`CurrentY`, `TEXTWIDTH`/`TEXTHEIGHT`) und als Container; verifiziert durch Snapshot-Tests
|
||||||
- [ ] 4.6 Timer auf der Zeitquelle der Ereignisschleife, Reihenfolge nach 1.2; verifiziert durch Tests mit virtueller Zeit für Abstand, `Interval = 0` und zwei gleichzeitig fällige Timer
|
- [x] 4.6 Timer auf der Zeitquelle der Ereignisschleife, Reihenfolge nach 1.2; verifiziert durch Tests mit virtueller Zeit für Abstand, `Interval = 0` und zwei gleichzeitig fällige Timer
|
||||||
- [ ] 4.7 DirListBox, DriveListBox, FileListBox plattformneutral (D6) mit `Path`/`Drive`/`FileName`/`Pattern` und ihren Ereignissen; verifiziert durch Tests auf einem angelegten Verzeichnisbaum
|
- [x] 4.7 DirListBox, DriveListBox, FileListBox plattformneutral (D6) mit `Path`/`Drive`/`FileName`/`Pattern` und ihren Ereignissen; verifiziert durch Tests auf einem angelegten Verzeichnisbaum
|
||||||
- [ ] 4.8 Ziehen und Ablegen: `DragMode`, `DRAG action%`, `DragOver` mit Zustand, `DragDrop` mit Quelle; verifiziert durch Tests für automatisches und manuelles Ziehen sowie Abbruch
|
- [x] 4.8 Ziehen und Ablegen: `DragMode`, `DRAG action%`, `DragOver` mit Zustand, `DragDrop` mit Quelle; verifiziert durch Tests für automatisches und manuelles Ziehen sowie Abbruch
|
||||||
|
|
||||||
## 5. Menüs und Dialoge
|
## 5. Menüs und Dialoge
|
||||||
|
|
||||||
- [ ] 5.1 Menüleiste mit bis zu sechs Ebenen, Access-Keys, Shortcuts, `Checked`/`Enabled`/`Visible`/`Separator`, Menü-Arrays; verifiziert durch Snapshot- und Verhaltenstests, darunter die Regelverstöße (Separator mit Shortcut)
|
- [x] 5.1 Menüleiste mit bis zu sechs Ebenen, Access-Keys, Shortcuts, `Checked`/`Enabled`/`Visible`/`Separator`, Menü-Arrays; verifiziert durch Snapshot- und Verhaltenstests, darunter die Regelverstöße (Separator mit Shortcut)
|
||||||
- [ ] 5.2 Ereigniszustellung ruht im geöffneten Menü über den vorhandenen `STOP`-Zustand (D5); verifiziert durch einen Test mit fälligem Zeit-Trap bei offenem Menü
|
- [x] 5.2 Ereigniszustellung ruht im geöffneten Menü über den vorhandenen `STOP`-Zustand (D5); verifiziert durch einen Test mit fälligem Zeit-Trap bei offenem Menü
|
||||||
- [ ] 5.3 `MSGBOX` als Anweisung und Funktion mit Schaltflächengruppen, Vorgabeschaltfläche und Rückgabewerten; verifiziert durch Tests je Gruppe und Rückgabewert
|
- [x] 5.3 `MSGBOX` als Anweisung und Funktion mit Schaltflächengruppen, Vorgabeschaltfläche und Rückgabewerten; verifiziert durch Tests je Gruppe und Rückgabewert
|
||||||
- [ ] 5.4 `INPUTBOX$` mit fester Größe 46×16, Positionierung in Zeichen, leerem String bei Abbruch; verifiziert durch Snapshot-Test und Abbruchtest
|
- [x] 5.4 `INPUTBOX$` mit fester Größe 46×16, Positionierung in Zeichen, leerem String bei Abbruch; verifiziert durch Snapshot-Test und Abbruchtest
|
||||||
- [ ] 5.5 Frontend-Signaturen für `MSGBOX` (Anweisung und Funktion) und `INPUTBOX$`, `Unsupported`-Absenkung entfernen; verifiziert durch `cargo test -p tb-frontend`
|
- [x] 5.5 Frontend-Signaturen für `MSGBOX` (Anweisung und Funktion) und `INPUTBOX$`, `Unsupported`-Absenkung entfernen; verifiziert durch `cargo test -p tb-frontend`
|
||||||
|
|
||||||
## 6. Korpus und Kompatibilität
|
## 6. Korpus und Kompatibilität
|
||||||
|
|
||||||
- [ ] 6.1 Ereignisfolge-Direktive im Programmkopf und ihre Einspeisung durch den Harness; verifiziert durch einen Harness-Test mit deklarierten Tasten-, Maus- und Zeitereignissen
|
- [x] 6.1 Ereignisfolge-Direktive im Programmkopf und ihre Einspeisung durch den Harness; verifiziert durch einen Harness-Test mit deklarierten Tasten-, Maus- und Zeitereignissen
|
||||||
- [ ] 6.2 Formular-Korpusprogramme für Fokus/Tab, Access-Key, Mausklick, Menüauswahl, Liste, Timer und modalen Dialog; verifiziert durch `cargo test -p tb-cli` gegen die Sollausgaben
|
- [x] 6.2 Formular-Korpusprogramme für Fokus/Tab, Access-Key, Mausklick, Menüauswahl, Liste, Timer und modalen Dialog; verifiziert durch `cargo test -p tb-cli` gegen die Sollausgaben
|
||||||
- [ ] 6.3 Meilenstein: die Formularprogramme aus `github.com/cout/vbdos` übersetzen und bedienen, jeden Befund festhalten; verifiziert durch den Lauf über den Bestand mit Befundliste
|
- [x] 6.3 Meilenstein: die Formularprogramme aus `github.com/cout/vbdos` übersetzen und bedienen, jeden Befund festhalten; verifiziert durch den Lauf über den Bestand mit Befundliste
|
||||||
- [ ] 6.4 Meilenstein: die Beispiel-Formularprogramme des Korpus laufen; verifiziert durch den grünen Korpuslauf
|
- [x] 6.4 Meilenstein: die Beispiel-Formularprogramme des Korpus laufen; verifiziert durch den grünen Korpuslauf
|
||||||
|
|
||||||
## 7. Inventar, Dokumentation, Abschluss
|
## 7. Inventar, Dokumentation, Abschluss
|
||||||
|
|
||||||
- [ ] 7.1 `docs/inventar.md`: Einträge dieses Changes auf `implementiert`; verifiziert durch `inventar_stimmt_mit_code_ueberein` mit Abdeckung 0 offen für Phase 4
|
- [x] 7.1 `docs/inventar.md`: Einträge dieses Changes auf `implementiert`; verifiziert durch `inventar_stimmt_mit_code_ueberein` mit Abdeckung 0 offen für Phase 4
|
||||||
- [ ] 7.2 Abweichungen (Dateisystem-Steuerelemente, festgelegte Optik, Z- und Timer-Reihenfolge) in `docs/sprachreferenz.md` eintragen; verifiziert durch den Abschnitt „Abweichungen"
|
- [x] 7.2 Abweichungen (Dateisystem-Steuerelemente, festgelegte Optik, Z- und Timer-Reihenfolge) in `docs/sprachreferenz.md` eintragen; verifiziert durch den Abschnitt „Abweichungen"
|
||||||
- [ ] 7.3 PLAN.md: die verbleibenden Phase-4-Punkte abhaken und die Befunde der Phase festhalten; verifiziert durch die abgeschlossene Phase-4-Liste
|
- [x] 7.3 PLAN.md: die verbleibenden Phase-4-Punkte abhaken und die Befunde der Phase festhalten; verifiziert durch die abgeschlossene Phase-4-Liste
|
||||||
- [ ] 7.4 `cargo test` grün und `openspec validate phase-4-steuerelemente --strict` ohne Befund; verifiziert durch beide Kommandos
|
- [x] 7.4 `cargo test` grün und `openspec validate phase-4-steuerelemente --strict` ohne Befund; verifiziert durch beide Kommandos
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Befunde am öffentlichen VBDOS-Bestand
|
||||||
|
|
||||||
|
Geprüft wurde `cout/vbdos` im Commit
|
||||||
|
`1cdd2b32b829fe1721d0b6aecc433abc47a96fb6`. Maßgeblich waren die
|
||||||
|
vorhandenen `.MAK`-Projekte; wo keines existiert, wurde die `.FRM` direkt
|
||||||
|
verwendet. Jeder Eintrag bestand `tbc check` ohne Diagnose. Die Programme
|
||||||
|
wurden anschließend in einem 100×30-Pseudoterminal gestartet und blieben nach
|
||||||
|
dem Ende des Modulrumpfs bedienbar, bis ein Steuerelement das Formular schloss
|
||||||
|
oder der Lauf bewusst abgebrochen wurde.
|
||||||
|
|
||||||
|
| Programm | Check | Bediennachweis |
|
||||||
|
|---|---:|---|
|
||||||
|
| `graphics/graphics.mak` | 0 Diagnosen | Alt+X löst `cmdExit_Click` aus und entlädt das Formular |
|
||||||
|
| `microsoft/check.mak` | 0 Diagnosen | Alt+F öffnet das Menü; X erreicht den Exit-/Speicherdialogpfad |
|
||||||
|
| `microsoft/qlbview.mak` | 0 Diagnosen | Esc löst die Cancel-Schaltfläche aus und entlädt das Formular |
|
||||||
|
| `microsoft/seek.mak` | 0 Diagnosen | Alt+X löst `cmdExit_Click` aus und entlädt das Formular |
|
||||||
|
| `microsoft/spindemo.mak` | 0 Diagnosen | Tab und Pfeiltaste werden dem Text-/Spin-Steuerpfad zugestellt |
|
||||||
|
| `microsoft/notepad.frm` | 0 Diagnosen | Alt+F öffnet das Menü; X löst `mnuFileExit_Click` aus |
|
||||||
|
| `misc/mentors/mentors.frm` | 0 Diagnosen | Alt+F öffnet das Menü; X erreicht den Exit-/Speicherdialogpfad |
|
||||||
|
|
||||||
|
## Geschlossene Befunde
|
||||||
|
|
||||||
|
- `.MAK`-Einträge, `$INCLUDE` und `RUN` werden relativ und
|
||||||
|
DOS-großschreibungsunabhängig aufgelöst; mehrere Module und Formulare
|
||||||
|
werden als gemeinsames Programm übersetzt.
|
||||||
|
- Binärformular-P-Code behält Block- und Anweisungsgrenzen, numerische
|
||||||
|
Suffixe, Record-/UDT-Ausdrücke und Control-Arrays korrekt bei.
|
||||||
|
- Globale Arrays werden vor `Form_Load` initialisiert; `LEN(record)` liefert
|
||||||
|
die feste Satzbreite. `INPUT$` liest auch Binärdateien.
|
||||||
|
- `SCREEN.CONTROLPANEL(index)`, `CLIPBOARD` und `PRINTER` haben
|
||||||
|
Laufzeitverhalten; die von Notepad verwendeten Common-Dialog-Deklarationen
|
||||||
|
arbeiten über die vorhandenen Textdialoge.
|
||||||
|
- Die nativen Controls `VSpin` und `HSpin` werden als bedienbares `Spin`
|
||||||
|
importiert, einschließlich Wertgrenzen, Wiederholung und `Custom`-Ereignis.
|
||||||
|
- `SCREEN` 0–13 sowie die im Grafikbeispiel verwendeten Anweisungen `LINE`,
|
||||||
|
`PAINT` und `VIEW` zeichnen auf den Zellenpuffer.
|
||||||
|
- Modellose Formulare halten nach dem Modulrumpf die VM-Ereignisschleife
|
||||||
|
offen; Formularereignisse laufen auf derselben VM weiter, bis alle Formulare
|
||||||
|
geschlossen sind. Derselbe Pfad funktioniert im Terminal und mit
|
||||||
|
vorab eingelesener Pipe-Eingabe.
|
||||||
|
|
||||||
|
Damit sind alle während dieses Kompatibilitätslaufs gefundenen
|
||||||
|
Implementierungslücken geschlossen; es verbleibt kein dokumentierter oder
|
||||||
|
umgangener Befund.
|
||||||
140
openspec/specs/forms-steuerelemente/spec.md
Normal file
140
openspec/specs/forms-steuerelemente/spec.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# forms-steuerelemente Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Die Steuerelemente machen ein Formular bedienbar: sie stellen sich im
|
||||||
|
Textbildschirm dar, nehmen Tastatur und Maus entgegen, führen Fokus und
|
||||||
|
Tabreihenfolge, tragen die Menüleiste und stellen die vordefinierten
|
||||||
|
Dialoge bereit.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: Darstellung im Zellenpuffer
|
||||||
|
Steuerelemente SHALL sich im vorhandenen Textbildschirm darstellen; es
|
||||||
|
MUST NOT eine zweite Zeichenschicht neben ihm entstehen. Jede Klasse
|
||||||
|
SHALL die in der Forms-Referenz festgelegte Optik für die Zustände
|
||||||
|
normal, fokussiert und deaktiviert zeigen. Der CommandButton SHALL seine
|
||||||
|
Darstellung nach der Höhe wählen: eine Zeile als `<Text>`, zwei Zeilen
|
||||||
|
mit einzeiligem Rahmen, ab drei Zeilen als Kasten. Überlappen
|
||||||
|
Steuerelemente, SHALL die festgelegte Z-Reihenfolge entscheiden, welches
|
||||||
|
sichtbar ist.
|
||||||
|
|
||||||
|
#### Scenario: Schaltfläche nach Höhe
|
||||||
|
- **WHEN** ein CommandButton mit `Height = 1` und `Caption = "OK"` gezeichnet wird
|
||||||
|
- **THEN** erscheint im Zellenpuffer `<OK>`
|
||||||
|
|
||||||
|
#### Scenario: Deaktivierter Zustand
|
||||||
|
- **WHEN** ein Steuerelement `Enabled = 0` trägt
|
||||||
|
- **THEN** unterscheidet sich seine Darstellung sichtbar vom aktiven Zustand und es nimmt keinen Fokus an
|
||||||
|
|
||||||
|
### Requirement: Fokus, Tabreihenfolge und Access-Keys
|
||||||
|
Der Fokus SHALL mit Tab in aufsteigender `TabIndex`-Folge und mit
|
||||||
|
Umschalt-Tab rückwärts wechseln; Elemente mit `TabStop = 0` oder
|
||||||
|
`Enabled = 0` MUST übersprungen werden. Ein `&` im Text SHALL den
|
||||||
|
folgenden Buchstaben zum Access-Key machen, der mit Alt das Element
|
||||||
|
auslöst oder ihm den Fokus gibt. Enter SHALL die `Default`-Schaltfläche
|
||||||
|
auslösen, Esc die `Cancel`-Schaltfläche. Fokuswechsel MUST `LostFocus`
|
||||||
|
am alten und `GotFocus` am neuen Element auslösen, in dieser Reihenfolge.
|
||||||
|
|
||||||
|
#### Scenario: Tab überspringt
|
||||||
|
- **WHEN** das mittlere von drei Elementen `TabStop = 0` trägt und Tab gedrückt wird
|
||||||
|
- **THEN** erhält das dritte Element den Fokus
|
||||||
|
|
||||||
|
#### Scenario: Access-Key
|
||||||
|
- **WHEN** eine Schaltfläche `Caption = "&OK"` trägt und Alt+O gedrückt wird
|
||||||
|
- **THEN** wird ihr `Click`-Ereignis ausgelöst
|
||||||
|
|
||||||
|
#### Scenario: Reihenfolge der Fokusereignisse
|
||||||
|
- **WHEN** der Fokus von `Text1` auf `Text2` wechselt
|
||||||
|
- **THEN** läuft erst `Text1_LostFocus`, danach `Text2_GotFocus`
|
||||||
|
|
||||||
|
### Requirement: Maussteuerung mit Trefferprüfung
|
||||||
|
Ein Mausereignis SHALL dem obersten Steuerelement an seiner Position
|
||||||
|
zugestellt werden; liegt dort keines, dem Formular. Klick, Doppelklick
|
||||||
|
sowie `MouseDown`, `MouseMove` und `MouseUp` SHALL mit Taste,
|
||||||
|
Umschaltzustand und Position in Zellen zugestellt werden. Bei
|
||||||
|
`DragMode = 1` SHALL das Ziehen automatisch beginnen; `DRAG action%`
|
||||||
|
SHALL es manuell beginnen, ablegen oder abbrechen, mit `DragOver` und
|
||||||
|
`DragDrop` am Ziel.
|
||||||
|
|
||||||
|
#### Scenario: Treffer nach Z-Reihenfolge
|
||||||
|
- **WHEN** zwei Steuerelemente überlappen und in den gemeinsamen Bereich geklickt wird
|
||||||
|
- **THEN** erhält das obere das Ereignis
|
||||||
|
|
||||||
|
#### Scenario: Klick ohne Steuerelement
|
||||||
|
- **WHEN** auf eine freie Stelle des Formulars geklickt wird
|
||||||
|
- **THEN** erhält das Formular das Ereignis
|
||||||
|
|
||||||
|
#### Scenario: Ziehen und Ablegen
|
||||||
|
- **WHEN** ein Element mit `DragMode = 1` auf ein anderes gezogen und dort losgelassen wird
|
||||||
|
- **THEN** läuft am Ziel `DragOver` mit dem Zustand Over und danach `DragDrop` mit der Quelle als Argument
|
||||||
|
|
||||||
|
### Requirement: Steuerelemente mit Listeninhalt
|
||||||
|
ListBox und ComboBox SHALL `ADDITEM` und `REMOVEITEM` unterstützen und
|
||||||
|
`List`, `ListCount`, `ListIndex` und `Text` konsistent führen; bei
|
||||||
|
`Sorted = -1` SHALL die Einfügereihenfolge der Sortierung folgen.
|
||||||
|
`ListIndex = -1` SHALL „keine Auswahl" bedeuten. Die ComboBox SHALL die
|
||||||
|
drei Stilarten (Dropdown, Simple, Dropdown List) darstellen.
|
||||||
|
|
||||||
|
#### Scenario: Element hinzufügen
|
||||||
|
- **WHEN** `List1.ADDITEM "b"` und `List1.ADDITEM "a"` bei `Sorted = -1` ausgeführt werden
|
||||||
|
- **THEN** liefert `List1.List(0)` den Wert `a` und `List1.ListCount` den Wert 2
|
||||||
|
|
||||||
|
#### Scenario: Keine Auswahl
|
||||||
|
- **WHEN** eine ListBox ohne Auswahl gelesen wird
|
||||||
|
- **THEN** liefert `ListIndex` den Wert −1
|
||||||
|
|
||||||
|
### Requirement: Timer-Steuerelement
|
||||||
|
Ein Timer SHALL bei `Enabled = -1` und `Interval > 0` sein
|
||||||
|
`Timer`-Ereignis im eingestellten Abstand auslösen, gestützt auf die
|
||||||
|
Zeitquelle der Ereignissteuerung. `Interval = 0` SHALL ihn abschalten.
|
||||||
|
Sind mehrere Timer gleichzeitig fällig, SHALL die Reihenfolge festgelegt
|
||||||
|
und dokumentiert sein.
|
||||||
|
|
||||||
|
#### Scenario: Timer feuert im Abstand
|
||||||
|
- **WHEN** ein Timer mit `Interval = 100` läuft und die Zeit um 250 ms vorrückt
|
||||||
|
- **THEN** ist sein Ereignis zweimal gelaufen
|
||||||
|
|
||||||
|
#### Scenario: Interval 0 schaltet ab
|
||||||
|
- **WHEN** `Timer1.Interval = 0` gesetzt wird
|
||||||
|
- **THEN** läuft kein weiteres Ereignis
|
||||||
|
|
||||||
|
### Requirement: Menüsystem
|
||||||
|
Ein Formular SHALL eine Menüleiste mit bis zu sechs Ebenen tragen.
|
||||||
|
Menüeinträge SHALL Access-Keys (`&`), Shortcuts, `Checked`, `Enabled`,
|
||||||
|
`Visible` und Separatoren (`-`) unterstützen; ein Separator MUST NOT
|
||||||
|
`Checked`, deaktiviert oder mit Shortcut versehen sein, ein Menütitel
|
||||||
|
MUST NOT einen Shortcut tragen. Solange ein Menü geöffnet ist, MUST die
|
||||||
|
Zustellung von Zeitereignissen und klassischen Traps ruhen und danach
|
||||||
|
fortgesetzt werden.
|
||||||
|
|
||||||
|
#### Scenario: Menüauswahl löst Click aus
|
||||||
|
- **WHEN** ein Menüeintrag über seinen Access-Key gewählt wird
|
||||||
|
- **THEN** läuft seine `Click`-Prozedur
|
||||||
|
|
||||||
|
#### Scenario: Traps ruhen im geöffneten Menü
|
||||||
|
- **WHEN** ein Menü geöffnet ist und ein Zeit-Trap fällig wird
|
||||||
|
- **THEN** läuft sein Handler erst, nachdem das Menü geschlossen wurde
|
||||||
|
|
||||||
|
### Requirement: Vordefinierte Dialoge
|
||||||
|
`MSGBOX text$ [, typ% [, titel$]]` SHALL als Anweisung und als Funktion
|
||||||
|
verfügbar sein; die Funktion SHALL die gedrückte Schaltfläche als
|
||||||
|
INTEGER liefern (1 OK, 2 Cancel/Esc, 3 Abort, 4 Retry, 5 Ignore, 6 Yes,
|
||||||
|
7 No). `typ%` SHALL die Schaltflächengruppe (0–5) und die
|
||||||
|
Vorgabeschaltfläche (0/256/512) tragen. `INPUTBOX$(text$ [, titel$
|
||||||
|
[, vorgabe$ [, x%, y%]]])` SHALL eine Zeichenkette liefern und bei
|
||||||
|
Abbruch den leeren String. Beide Dialoge SHALL modal sein; `INPUTBOX$`
|
||||||
|
SHALL 46×16 Zeichen messen und ohne Positionsangabe zentriert
|
||||||
|
erscheinen.
|
||||||
|
|
||||||
|
#### Scenario: MSGBOX als Funktion
|
||||||
|
- **WHEN** `a% = MSGBOX("Weiter?", 4, "Frage")` ausgeführt und `Yes` gewählt wird
|
||||||
|
- **THEN** liefert der Aufruf 6
|
||||||
|
|
||||||
|
#### Scenario: INPUTBOX$ abgebrochen
|
||||||
|
- **WHEN** ein `INPUTBOX$`-Dialog mit Esc geschlossen wird
|
||||||
|
- **THEN** liefert er den leeren String
|
||||||
|
|
||||||
|
#### Scenario: Dialog ist modal
|
||||||
|
- **WHEN** ein Dialog offen ist
|
||||||
|
- **THEN** wird die Anweisung nach dem Aufruf erst nach dem Schließen ausgeführt
|
||||||
@@ -128,3 +128,30 @@ Harness den Lauf abweisen statt auf die Systemuhr auszuweichen.
|
|||||||
#### Scenario: Fehlende Zeitdeklaration
|
#### Scenario: Fehlende Zeitdeklaration
|
||||||
- **WHEN** ein Korpusprogramm zeitgesteuerte Traps nutzt, aber keinen Zeitverlauf deklariert
|
- **WHEN** ein Korpusprogramm zeitgesteuerte Traps nutzt, aber keinen Zeitverlauf deklariert
|
||||||
- **THEN** schlägt der Harness mit einer Meldung fehl, die die fehlende Deklaration nennt
|
- **THEN** schlägt der Harness mit einer Meldung fehl, die die fehlende Deklaration nennt
|
||||||
|
|
||||||
|
### Requirement: Korpusabdeckung der Formularprogramme
|
||||||
|
Der Testkorpus SHALL Formularprogramme mit byte-genauer
|
||||||
|
Bildschirm-Sollausgabe führen. Ihre Ereignisfolge (Tasten, Maus, Zeit)
|
||||||
|
SHALL im Programmkopf deklariert und vom Harness eingespeist werden,
|
||||||
|
sodass ein Lauf ohne Terminal und ohne Wartezeit auskommt und zweimal
|
||||||
|
dasselbe Ergebnis liefert. Abgedeckt SHALL sein: Fokus- und
|
||||||
|
Tabreihenfolge, Access-Key, Klick über die Maus, Menüauswahl, ein
|
||||||
|
Listen-Steuerelement, ein Timer und ein modaler Dialog.
|
||||||
|
|
||||||
|
#### Scenario: Formularprogramm im Korpus
|
||||||
|
- **WHEN** ein Formular-Korpusprogramm mit deklarierter Ereignisfolge ausgeführt wird
|
||||||
|
- **THEN** entspricht der Bildschirminhalt byte-genau der Sollausgabe
|
||||||
|
|
||||||
|
#### Scenario: Wiederholbarkeit
|
||||||
|
- **WHEN** dasselbe Programm zweimal ausgeführt wird
|
||||||
|
- **THEN** ist die Ausgabe beide Male identisch
|
||||||
|
|
||||||
|
### Requirement: Kompatibilitätsnachweis an Fremdprogrammen
|
||||||
|
Die Formularprogramme aus dem öffentlichen Bestand des Vorbilds SHALL
|
||||||
|
sich ohne Diagnosen oder Non-Features übersetzen lassen und bedienbar
|
||||||
|
sein. Jeder entdeckte fehlende Sprach-, Projekt-, Formular- oder
|
||||||
|
Laufzeitpfad MUST implementiert und als Befund festgehalten werden.
|
||||||
|
|
||||||
|
#### Scenario: Fremdprogramm übersetzt
|
||||||
|
- **WHEN** ein Formularprogramm des öffentlichen Bestands übersetzt wird
|
||||||
|
- **THEN** entstehen keine Diagnosen und das Programm ist bedienbar
|
||||||
|
|||||||
97
tests/compat/formular.frm
Normal file
97
tests/compat/formular.frm
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
VERSION 1.00
|
||||||
|
Begin Form Formular
|
||||||
|
Caption = "Steuerelemente"
|
||||||
|
Height = 16
|
||||||
|
Left = 0
|
||||||
|
Top = 0
|
||||||
|
Width = 46
|
||||||
|
Begin Menu mnuDatei
|
||||||
|
Caption = "&Datei"
|
||||||
|
Begin Menu mnuOpen
|
||||||
|
Caption = "&Open"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
Begin TextBox Text1
|
||||||
|
Height = 3
|
||||||
|
Left = 2
|
||||||
|
TabIndex = 0
|
||||||
|
Text = "Eingabe"
|
||||||
|
Top = 2
|
||||||
|
Width = 18
|
||||||
|
End
|
||||||
|
Begin CommandButton Command1
|
||||||
|
Caption = "&OK"
|
||||||
|
Default = -1
|
||||||
|
Height = 1
|
||||||
|
Left = 23
|
||||||
|
TabIndex = 1
|
||||||
|
Top = 3
|
||||||
|
Width = 10
|
||||||
|
End
|
||||||
|
Begin ListBox List1
|
||||||
|
Height = 5
|
||||||
|
Left = 2
|
||||||
|
Sorted = -1
|
||||||
|
TabIndex = 2
|
||||||
|
Top = 6
|
||||||
|
Width = 18
|
||||||
|
End
|
||||||
|
Begin Label Status
|
||||||
|
Caption = "bereit"
|
||||||
|
Height = 1
|
||||||
|
Left = 23
|
||||||
|
Top = 7
|
||||||
|
Width = 18
|
||||||
|
End
|
||||||
|
Begin Label TimerStatus
|
||||||
|
Caption = "Timer 0"
|
||||||
|
Height = 1
|
||||||
|
Left = 23
|
||||||
|
Top = 9
|
||||||
|
Width = 18
|
||||||
|
End
|
||||||
|
Begin Timer Timer1
|
||||||
|
Enabled = -1
|
||||||
|
Interval = 100
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
' tb-screen: 80x25
|
||||||
|
' tb-events: key:<TAB> | mouse:down,1,0,4,25 | mouse:up,1,0,4,25 | time:0ms | key:<ENTER> | key:%d | key:o | time:250ms
|
||||||
|
DIM SHARED Klicks%, Ticks%
|
||||||
|
Formular.Show
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
DOEVENTS
|
||||||
|
END
|
||||||
|
|
||||||
|
SUB Form_Load()
|
||||||
|
List1.ADDITEM "zwei"
|
||||||
|
List1.ADDITEM "eins"
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
SUB Command1_Click()
|
||||||
|
SHARED Klicks%
|
||||||
|
Klicks% = Klicks% + 1
|
||||||
|
r% = MSGBOX("Weiter?", 0, "Klick")
|
||||||
|
Status.Caption = "Dialog " + STR$(r%)
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
SUB mnuOpen_Click()
|
||||||
|
Status.Caption = "Menü"
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
SUB Timer1_Timer()
|
||||||
|
SHARED Ticks%
|
||||||
|
Ticks% = Ticks% + 1
|
||||||
|
TimerStatus.Caption = "Timer " + STR$(Ticks%)
|
||||||
|
END SUB
|
||||||
33
tests/compat/formular.out
Normal file
33
tests/compat/formular.out
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
┌ Steuerelemente ────────────────────────────┐
|
||||||
|
│ Datei │
|
||||||
|
│ ┌────────────────┐ │
|
||||||
|
│ │Eingabe │ <OK> │
|
||||||
|
│ └────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────┐ │
|
||||||
|
│ │ eins │ Menü │
|
||||||
|
│ │ zwei │ │
|
||||||
|
│ │ │ Timer 2 │
|
||||||
|
│ └────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
└────────────────────────────────────────────┘
|
||||||
|
--- Attribute (Vordergrund/Hintergrund je Zelle, hex) ---
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70700707070707070707070707070707070707077070707070707070707070707070707070707070707070707070
|
||||||
|
70700707070707070707070707070707070707077070700707070707070707070770707070707070707070707070
|
||||||
|
70700707070707070707070707070707070707077070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70700707070707070707070707070707070707077070707070707070707070707070707070707070707070707070
|
||||||
|
70700707070707070707070707070707070707077070700707070707070707070707070707070707077070707070
|
||||||
|
70700707070707070707070707070707070707077070707070707070707070707070707070707070707070707070
|
||||||
|
70700707070707070707070707070707070707077070700707070707070707070707070707070707077070707070
|
||||||
|
70700707070707070707070707070707070707077070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
|
70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070
|
||||||
Reference in New Issue
Block a user