Phase 4: Formularmodell und Objektsprache

This commit is contained in:
2026-09-04 17:30:32 +02:00
parent 5cf5a6582c
commit 54488b5b67
35 changed files with 7195 additions and 627 deletions

2
Cargo.lock generated
View File

@@ -637,6 +637,7 @@ dependencies = [
"log", "log",
"ratatui", "ratatui",
"signal-hook", "signal-hook",
"tb-frontend",
"tb-runtime", "tb-runtime",
"thiserror", "thiserror",
] ]
@@ -648,6 +649,7 @@ dependencies = [
"log", "log",
"tb-frontend", "tb-frontend",
"tb-runtime", "tb-runtime",
"tb-ui",
"thiserror", "thiserror",
] ]

View File

@@ -399,7 +399,7 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
Ereignisprozedur) Ereignisprozedur)
Befund: Die VM stellt klassische Traps über einen eigenen Frame zu; Befund: Die VM stellt klassische Traps über einen eigenen Frame zu;
Forms-Ereignisprozeduren verwenden später denselben Rückrufweg. Forms-Ereignisprozeduren verwenden später denselben Rückrufweg.
- [ ] 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, - [ ] Steuerelemente: CommandButton, TextBox, ListBox, ComboBox, CheckBox,
OptionButton, Frame, Label, HScrollBar/VScrollBar, PictureBox (Text), OptionButton, Frame, Label, HScrollBar/VScrollBar, PictureBox (Text),
@@ -416,7 +416,7 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
- [ ] Meilenstein (erster Kompatibilitätstest): die Programme aus - [ ] 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)
- [ ] 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
`GOSUB label`, dazu die Steueranweisungen `TIMER`, `KEY(n)`, `GOSUB label`, dazu die Steueranweisungen `TIMER`, `KEY(n)`,

View File

@@ -14,6 +14,8 @@ pub enum TypeName {
Currency, Currency,
Str, Str,
FixedStr(i64), FixedStr(i64),
Form,
Control,
Udt(String), Udt(String),
} }
@@ -25,9 +27,24 @@ pub enum UnOp {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp { pub enum BinOp {
Pow, Mul, Div, IntDiv, Mod, Add, Sub, Pow,
Eq, Ne, Lt, Le, Gt, Ge, Mul,
And, Or, Xor, Eqv, Imp, Div,
IntDiv,
Mod,
Add,
Sub,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
Xor,
Eqv,
Imp,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -46,11 +63,26 @@ pub enum Expr {
args: Option<Vec<Expr>>, args: Option<Vec<Expr>>,
pos: SourcePos, pos: SourcePos,
}, },
Unary { op: UnOp, operand: Box<Expr>, pos: SourcePos }, Unary {
Binary { op: BinOp, lhs: Box<Expr>, rhs: Box<Expr>, pos: SourcePos }, op: UnOp,
operand: Box<Expr>,
pos: SourcePos,
},
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
pos: SourcePos,
},
/// Geklammerter Ausdruck. Semantisch transparent, aber als Argument /// Geklammerter Ausdruck. Semantisch transparent, aber als Argument
/// erzwingt die Klammer Wertübergabe (BYVAL) statt BYREF. /// erzwingt die Klammer Wertübergabe (BYVAL) statt BYREF.
Paren(Box<Expr>), Paren(Box<Expr>),
/// `TYPEOF ausdruck IS klasse`.
TypeOf {
value: Box<Expr>,
class: String,
pos: SourcePos,
},
/// Ausgelassenes Argument (`LOCATE , 5`). /// Ausgelassenes Argument (`LOCATE , 5`).
Missing, Missing,
} }
@@ -60,7 +92,8 @@ impl Expr {
match self { match self {
Expr::Name { pos, .. } Expr::Name { pos, .. }
| Expr::Unary { pos, .. } | Expr::Unary { pos, .. }
| Expr::Binary { pos, .. } => *pos, | Expr::Binary { pos, .. }
| Expr::TypeOf { pos, .. } => *pos,
Expr::Paren(e) => e.pos(), Expr::Paren(e) => e.pos(),
_ => SourcePos::default(), _ => SourcePos::default(),
} }
@@ -202,7 +235,11 @@ pub enum EventAction {
pub enum Stmt { pub enum Stmt {
Label(String), Label(String),
LineNumber(u32), LineNumber(u32),
Assign { target: Expr, value: Expr, pos: SourcePos }, Assign {
target: Expr,
value: Expr,
pos: SourcePos,
},
Print { Print {
/// LPRINT (Druckerausgabe) statt PRINT. /// LPRINT (Druckerausgabe) statt PRINT.
printer: bool, printer: bool,
@@ -226,7 +263,11 @@ pub enum Stmt {
else_body: Option<Vec<Stmt>>, else_body: Option<Vec<Stmt>>,
pos: SourcePos, pos: SourcePos,
}, },
Select { expr: Expr, arms: Vec<CaseArm>, pos: SourcePos }, Select {
expr: Expr,
arms: Vec<CaseArm>,
pos: SourcePos,
},
For { For {
var: Expr, var: Expr,
from: Expr, from: Expr,
@@ -236,25 +277,57 @@ pub enum Stmt {
pos: SourcePos, pos: SourcePos,
}, },
DoLoop { DoLoop {
pre: Option<(bool, Expr)>, // (ist UNTIL, Bedingung) pre: Option<(bool, Expr)>, // (ist UNTIL, Bedingung)
post: Option<(bool, Expr)>, post: Option<(bool, Expr)>,
body: Vec<Stmt>, body: Vec<Stmt>,
pos: SourcePos, pos: SourcePos,
}, },
While { cond: Expr, body: Vec<Stmt>, pos: SourcePos }, While {
Goto { target: LabelRef, pos: SourcePos }, cond: Expr,
Gosub { target: LabelRef, pos: SourcePos }, body: Vec<Stmt>,
OnGoto { expr: Expr, targets: Vec<LabelRef>, gosub: bool, pos: SourcePos }, pos: SourcePos,
Return { target: Option<LabelRef>, pos: SourcePos }, },
Goto {
target: LabelRef,
pos: SourcePos,
},
Gosub {
target: LabelRef,
pos: SourcePos,
},
OnGoto {
expr: Expr,
targets: Vec<LabelRef>,
gosub: bool,
pos: SourcePos,
},
Return {
target: Option<LabelRef>,
pos: SourcePos,
},
End(SourcePos), End(SourcePos),
StopStmt(SourcePos), StopStmt(SourcePos),
System(SourcePos), System(SourcePos),
Exit { kind: ExitKind, pos: SourcePos }, Exit {
Dim { shared: bool, redim: bool, decls: Vec<VarDecl>, pos: SourcePos }, kind: ExitKind,
pos: SourcePos,
},
Dim {
shared: bool,
redim: bool,
decls: Vec<VarDecl>,
pos: SourcePos,
},
/// `SHARED`-Anweisung in einer Prozedur (Zugriff auf Modulvariablen). /// `SHARED`-Anweisung in einer Prozedur (Zugriff auf Modulvariablen).
SharedDecl { decls: Vec<VarDecl>, pos: SourcePos }, SharedDecl {
decls: Vec<VarDecl>,
pos: SourcePos,
},
/// `STATIC`-Anweisung in einer Prozedur. /// `STATIC`-Anweisung in einer Prozedur.
StaticDecl { decls: Vec<VarDecl>, pos: SourcePos }, StaticDecl {
decls: Vec<VarDecl>,
pos: SourcePos,
},
/// `COMMON [SHARED] [/block/] liste`. /// `COMMON [SHARED] [/block/] liste`.
CommonDecl { CommonDecl {
shared: bool, shared: bool,
@@ -262,20 +335,64 @@ pub enum Stmt {
decls: Vec<VarDecl>, decls: Vec<VarDecl>,
pos: SourcePos, pos: SourcePos,
}, },
Erase { names: Vec<Expr>, pos: SourcePos }, Erase {
ConstDecl { items: Vec<(String, Option<Suffix>, Expr)>, pos: SourcePos }, names: Vec<Expr>,
DefType { ty: TypeName, ranges: Vec<(char, char)>, pos: SourcePos }, pos: SourcePos,
OptionStmt { kind: OptionKind, pos: SourcePos }, },
TypeDecl { name: String, fields: Vec<(String, TypeName)>, pos: SourcePos }, ConstDecl {
Declare { sig: ProcSig, pos: SourcePos }, items: Vec<(String, Option<Suffix>, Expr)>,
pos: SourcePos,
},
DefType {
ty: TypeName,
ranges: Vec<(char, char)>,
pos: SourcePos,
},
OptionStmt {
kind: OptionKind,
pos: SourcePos,
},
TypeDecl {
name: String,
fields: Vec<(String, TypeName)>,
pos: SourcePos,
},
Declare {
sig: ProcSig,
pos: SourcePos,
},
/// Expliziter oder impliziter Prozedur-/Builtin-Aufruf als Anweisung. /// Expliziter oder impliziter Prozedur-/Builtin-Aufruf als Anweisung.
Call { name: String, suffix: Option<Suffix>, args: Vec<Expr>, pos: SourcePos }, Call {
OnError { local: bool, action: OnErrorAction, pos: SourcePos }, name: String,
Resume { kind: ResumeKind, pos: SourcePos }, suffix: Option<Suffix>,
ErrorStmt { code: Expr, pos: SourcePos }, args: Vec<Expr>,
Data { items: Vec<String>, pos: SourcePos }, pos: SourcePos,
ReadStmt { vars: Vec<Expr>, pos: SourcePos }, },
Restore { target: Option<LabelRef>, pos: SourcePos }, OnError {
local: bool,
action: OnErrorAction,
pos: SourcePos,
},
Resume {
kind: ResumeKind,
pos: SourcePos,
},
ErrorStmt {
code: Expr,
pos: SourcePos,
},
Data {
items: Vec<String>,
pos: SourcePos,
},
ReadStmt {
vars: Vec<Expr>,
pos: SourcePos,
},
Restore {
target: Option<LabelRef>,
pos: SourcePos,
},
/// Einzeilige `DEF FNname(...) = ausdruck`-Definition. /// Einzeilige `DEF FNname(...) = ausdruck`-Definition.
DefFn { DefFn {
name: String, name: String,
@@ -312,8 +429,15 @@ pub enum Stmt {
len: Option<Expr>, len: Option<Expr>,
pos: SourcePos, pos: SourcePos,
}, },
CloseStmt { files: Vec<Expr>, pos: SourcePos }, CloseStmt {
FieldStmt { file: Expr, fields: Vec<(Expr, Expr)>, pos: SourcePos }, files: Vec<Expr>,
pos: SourcePos,
},
FieldStmt {
file: Expr,
fields: Vec<(Expr, Expr)>,
pos: SourcePos,
},
GetPut { GetPut {
put: bool, put: bool,
file: Expr, file: Expr,
@@ -321,9 +445,22 @@ pub enum Stmt {
var: Option<Expr>, var: Option<Expr>,
pos: SourcePos, pos: SourcePos,
}, },
LsetRset { rset: bool, target: Expr, value: Expr, pos: SourcePos }, LsetRset {
WriteStmt { file: Option<Expr>, items: Vec<Expr>, pos: SourcePos }, rset: bool,
SeekStmt { file: Expr, position: Expr, pos: SourcePos }, target: Expr,
value: Expr,
pos: SourcePos,
},
WriteStmt {
file: Option<Expr>,
items: Vec<Expr>,
pos: SourcePos,
},
SeekStmt {
file: Expr,
position: Expr,
pos: SourcePos,
},
LockStmt { LockStmt {
unlock: bool, unlock: bool,
file: Expr, file: Expr,
@@ -362,9 +499,18 @@ pub enum Stmt {
}, },
// ---- Metabefehle ---- // ---- Metabefehle ----
/// `'$INCLUDE: 'datei''` — Auflösung übernimmt der Compile-Treiber. /// `'$INCLUDE: 'datei''` — Auflösung übernimmt der Compile-Treiber.
Include { path: String, pos: SourcePos }, Include {
path: String,
pos: SourcePos,
},
/// `'$STATIC` / `'$DYNAMIC`. /// `'$STATIC` / `'$DYNAMIC`.
MetaArrays { static_arrays: bool, pos: SourcePos }, MetaArrays {
static_arrays: bool,
pos: SourcePos,
},
MetaForm {
pos: SourcePos,
},
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View File

@@ -0,0 +1,631 @@
//! Statische Forms-Typinformation. Namen werden beim Übersetzen in diese
//! Tabellenindizes aufgelöst; die Laufzeit muss keine Strings durchsuchen.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ObjectClass {
Form,
CheckBox,
ComboBox,
CommandButton,
DirListBox,
DriveListBox,
FileListBox,
Frame,
HScrollBar,
Label,
ListBox,
Menu,
OptionButton,
PictureBox,
TextBox,
Timer,
VScrollBar,
Screen,
}
impl ObjectClass {
pub const ALL: [Self; 18] = [
Self::Form,
Self::CheckBox,
Self::ComboBox,
Self::CommandButton,
Self::DirListBox,
Self::DriveListBox,
Self::FileListBox,
Self::Frame,
Self::HScrollBar,
Self::Label,
Self::ListBox,
Self::Menu,
Self::OptionButton,
Self::PictureBox,
Self::TextBox,
Self::Timer,
Self::VScrollBar,
Self::Screen,
];
pub fn name(self) -> &'static str {
match self {
Self::Form => "FORM",
Self::CheckBox => "CHECKBOX",
Self::ComboBox => "COMBOBOX",
Self::CommandButton => "COMMANDBUTTON",
Self::DirListBox => "DIRLISTBOX",
Self::DriveListBox => "DRIVELISTBOX",
Self::FileListBox => "FILELISTBOX",
Self::Frame => "FRAME",
Self::HScrollBar => "HSCROLLBAR",
Self::Label => "LABEL",
Self::ListBox => "LISTBOX",
Self::Menu => "MENU",
Self::OptionButton => "OPTIONBUTTON",
Self::PictureBox => "PICTUREBOX",
Self::TextBox => "TEXTBOX",
Self::Timer => "TIMER",
Self::VScrollBar => "VSCROLLBAR",
Self::Screen => "SCREEN",
}
}
pub fn parse(name: &str) -> Option<Self> {
Self::ALL
.into_iter()
.find(|c| c.name().eq_ignore_ascii_case(name))
}
pub fn id(self) -> u8 {
Self::ALL.iter().position(|c| *c == self).unwrap() as u8
}
pub fn from_id(id: u8) -> Option<Self> {
Self::ALL.get(id as usize).copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropertyType {
Integer,
Single,
String,
Boolean,
Object,
IntegerArray,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PropertyDefault {
Integer(i32),
Single(f32),
String(&'static str),
Boolean(bool),
Empty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PropertySpec {
pub name: &'static str,
pub ty: PropertyType,
pub default: PropertyDefault,
pub min: Option<i32>,
pub max: Option<i32>,
pub writable: bool,
}
const fn int(name: &'static str, default: i32) -> PropertySpec {
PropertySpec {
name,
ty: PropertyType::Integer,
default: PropertyDefault::Integer(default),
min: None,
max: None,
writable: true,
}
}
const fn range(name: &'static str, default: i32, min: i32, max: i32) -> PropertySpec {
PropertySpec {
name,
ty: PropertyType::Integer,
default: PropertyDefault::Integer(default),
min: Some(min),
max: Some(max),
writable: true,
}
}
const fn boolp(name: &'static str, default: bool) -> PropertySpec {
PropertySpec {
name,
ty: PropertyType::Boolean,
default: PropertyDefault::Boolean(default),
min: None,
max: None,
writable: true,
}
}
const fn string(name: &'static str, default: &'static str) -> PropertySpec {
PropertySpec {
name,
ty: PropertyType::String,
default: PropertyDefault::String(default),
min: None,
max: None,
writable: true,
}
}
const fn ro(name: &'static str, ty: PropertyType) -> PropertySpec {
PropertySpec {
name,
ty,
default: PropertyDefault::Empty,
min: None,
max: None,
writable: false,
}
}
/// Eigenschaftstabelle in stabiler Reihenfolge. Der Vektor ist klein und wird
/// nur beim Übersetzen bzw. Erzeugen eines Objekts aufgebaut.
pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
use ObjectClass::*;
let mut p = Vec::new();
if !matches!(class, Timer | Menu | Screen) {
p.extend([
range("BACKCOLOR", 7, 0, 15),
boolp("ENABLED", true),
range("HEIGHT", 1, 1, 254),
range("LEFT", 0, 0, 254),
range("MOUSEPOINTER", 0, 0, 12),
ro("PARENT", PropertyType::Object),
string("TAG", ""),
range("TOP", 0, 0, 254),
boolp("VISIBLE", true),
range("WIDTH", 1, 1, 254),
]);
}
if !matches!(
class,
Form | Frame | Label | PictureBox | Timer | Menu | Screen
) {
p.extend([int("INDEX", 0), int("TABINDEX", 0), boolp("TABSTOP", true)]);
}
if !matches!(class, Form | Timer | Menu | Screen) {
p.push(string("CTLNAME", ""));
}
if matches!(
class,
Form | TextBox
| ListBox
| ComboBox
| CheckBox
| OptionButton
| Label
| HScrollBar
| VScrollBar
| PictureBox
) {
p.push(range("FORECOLOR", 0, 0, 15));
}
if matches!(
class,
Form | CommandButton
| TextBox
| ListBox
| ComboBox
| CheckBox
| OptionButton
| Frame
| Label
| HScrollBar
| VScrollBar
| PictureBox
) {
p.push(range("DRAGMODE", 0, 0, 1));
}
match class {
Form => p.extend([
boolp("AUTOREDRAW", false),
range("BORDERSTYLE", 2, 0, 6),
string("CAPTION", ""),
boolp("CONTROLBOX", true),
int("CURRENTX", 0),
int("CURRENTY", 0),
string("FORMNAME", ""),
range("FORMTYPE", 0, 0, 1),
boolp("MAXBUTTON", true),
boolp("MINBUTTON", true),
ro("SCALEHEIGHT", PropertyType::Integer),
ro("SCALEWIDTH", PropertyType::Integer),
range("WINDOWSTATE", 0, 0, 2),
]),
CommandButton => p.extend([
boolp("CANCEL", false),
string("CAPTION", ""),
boolp("DEFAULT", false),
boolp("VALUE", false),
]),
TextBox => p.extend([
range("BORDERSTYLE", 1, 0, 1),
boolp("MULTILINE", false),
range("SCROLLBARS", 0, 0, 3),
int("SELLENGTH", 0),
int("SELSTART", 0),
string("SELTEXT", ""),
string("TEXT", ""),
]),
ListBox => p.extend([
ro("LIST", PropertyType::String),
ro("LISTCOUNT", PropertyType::Integer),
int("LISTINDEX", -1),
boolp("SORTED", false),
string("TEXT", ""),
]),
ComboBox => p.extend([
ro("LIST", PropertyType::String),
ro("LISTCOUNT", PropertyType::Integer),
int("LISTINDEX", -1),
boolp("SORTED", false),
int("SELLENGTH", 0),
int("SELSTART", 0),
string("SELTEXT", ""),
range("STYLE", 0, 0, 2),
string("TEXT", ""),
]),
CheckBox => p.extend([string("CAPTION", ""), range("VALUE", 0, 0, 2)]),
OptionButton => p.extend([string("CAPTION", ""), range("VALUE", 0, -1, 0)]),
Frame => p.push(string("CAPTION", "")),
Label => p.extend([
range("ALIGNMENT", 0, 0, 2),
boolp("AUTOSIZE", false),
range("BORDERSTYLE", 0, 0, 2),
string("CAPTION", ""),
]),
HScrollBar | VScrollBar => p.extend([
ro("ATTACHED", PropertyType::Boolean),
range("LARGECHANGE", 1, 1, 32767),
range("MIN", 0, -32768, 32767),
range("MAX", 32767, -32768, 32767),
range("SMALLCHANGE", 1, 1, 32767),
range("VALUE", 0, -32768, 32767),
]),
PictureBox => p.extend([
boolp("AUTOREDRAW", false),
range("BORDERSTYLE", 1, 0, 2),
int("CURRENTX", 0),
int("CURRENTY", 0),
ro("SCALEHEIGHT", PropertyType::Integer),
ro("SCALEWIDTH", PropertyType::Integer),
]),
Timer => p.extend([
string("CTLNAME", ""),
boolp("ENABLED", true),
int("INDEX", 0),
range("INTERVAL", 0, 0, 65535),
ro("PARENT", PropertyType::Object),
string("TAG", ""),
]),
Menu => p.extend([
string("CAPTION", ""),
boolp("CHECKED", false),
string("CTLNAME", ""),
boolp("ENABLED", true),
int("INDEX", 0),
ro("PARENT", PropertyType::Object),
boolp("SEPARATOR", false),
string("TAG", ""),
boolp("VISIBLE", true),
]),
DirListBox => p.extend([string("PATH", ""), string("TEXT", "")]),
DriveListBox => p.extend([string("DRIVE", ""), string("TEXT", "")]),
FileListBox => p.extend([
string("FILENAME", ""),
string("PATH", ""),
string("PATTERN", "*.*"),
boolp("ARCHIVE", true),
boolp("HIDDEN", false),
boolp("NORMAL", true),
boolp("READONLY", true),
boolp("SYSTEM", false),
string("TEXT", ""),
]),
Screen => p.extend([
ro("ACTIVECONTROL", PropertyType::Object),
ro("ACTIVEFORM", PropertyType::Object),
boolp("AUTOREDRAW", false),
PropertySpec {
name: "CONTROLPANEL",
ty: PropertyType::IntegerArray,
default: PropertyDefault::Integer(0),
min: Some(0),
max: Some(15),
writable: true,
},
ro("HEIGHT", PropertyType::Integer),
ro("WIDTH", PropertyType::Integer),
range("MOUSEPOINTER", 0, 0, 12),
]),
}
if class == Form {
if let Some(visible) = p.iter_mut().find(|p| p.name == "VISIBLE") {
visible.default = PropertyDefault::Boolean(false);
}
}
p
}
pub fn property(class: ObjectClass, name: &str) -> Option<(u16, PropertySpec)> {
properties(class)
.into_iter()
.enumerate()
.find(|(_, p)| p.name.eq_ignore_ascii_case(name))
.map(|(i, p)| (i as u16, p))
}
pub fn methods(class: ObjectClass) -> &'static [&'static str] {
use ObjectClass::*;
match class {
Form => &[
"CLS",
"DRAG",
"HIDE",
"LOAD",
"MOVE",
"PRINT",
"PRINTFORM",
"REFRESH",
"SHOW",
"TEXTHEIGHT",
"TEXTWIDTH",
"UNLOAD",
],
ListBox | ComboBox => &[
"ADDITEM",
"DRAG",
"MOVE",
"REFRESH",
"REMOVEITEM",
"SETFOCUS",
],
CommandButton | TextBox | CheckBox | OptionButton => {
&["DRAG", "MOVE", "REFRESH", "SETFOCUS"]
}
Frame | Label | HScrollBar | VScrollBar => &["DRAG", "MOVE", "REFRESH"],
PictureBox => &[
"CLS",
"DRAG",
"MOVE",
"PRINT",
"REFRESH",
"SETFOCUS",
"TEXTHEIGHT",
"TEXTWIDTH",
],
Screen => &["HIDE", "SHOW"],
_ => &[],
}
}
pub fn method_is_implemented(class: ObjectClass, name: &str) -> bool {
matches!(
(class, name),
(ObjectClass::Form, "HIDE" | "LOAD" | "SHOW" | "UNLOAD")
| (ObjectClass::Screen, "HIDE" | "SHOW")
)
}
pub fn method_arity(class: ObjectClass, name: &str) -> Option<(usize, usize)> {
match (class, name) {
(ObjectClass::Form, "SHOW") => Some((0, 1)),
(ObjectClass::Form, "HIDE" | "LOAD" | "UNLOAD")
| (ObjectClass::Screen, "HIDE" | "SHOW") => Some((0, 0)),
_ => None,
}
}
pub fn events(class: ObjectClass) -> &'static [&'static str] {
use ObjectClass::*;
match class {
Form => &[
"CLICK",
"DBLCLICK",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOAD",
"LOSTFOCUS",
"MOUSEDOWN",
"MOUSEMOVE",
"MOUSEUP",
"PAINT",
"RESIZE",
"UNLOAD",
],
CommandButton => &[
"CLICK",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
],
TextBox => &[
"CHANGE",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
],
ListBox => &[
"CLICK",
"DBLCLICK",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
"MOUSEDOWN",
"MOUSEMOVE",
"MOUSEUP",
],
ComboBox => &[
"CHANGE",
"CLICK",
"DBLCLICK",
"DRAGDROP",
"DRAGOVER",
"DROPDOWN",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
"MOUSEDOWN",
"MOUSEMOVE",
"MOUSEUP",
],
CheckBox => &[
"CLICK",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
],
OptionButton => &[
"CLICK",
"DBLCLICK",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
],
Frame => &["DRAGDROP", "DRAGOVER"],
Label => &[
"CHANGE",
"CLICK",
"DBLCLICK",
"DRAGDROP",
"DRAGOVER",
"MOUSEDOWN",
"MOUSEMOVE",
"MOUSEUP",
],
HScrollBar | VScrollBar => &[
"CHANGE",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
],
PictureBox => &[
"CLICK",
"DBLCLICK",
"DRAGDROP",
"DRAGOVER",
"GOTFOCUS",
"KEYDOWN",
"KEYPRESS",
"KEYUP",
"LOSTFOCUS",
"MOUSEDOWN",
"MOUSEMOVE",
"MOUSEUP",
"PAINT",
],
Timer => &["TIMER"],
Menu => &["CLICK"],
DirListBox => &["CHANGE", "CLICK", "DBLCLICK", "PATHCHANGE"],
DriveListBox => &["CHANGE"],
FileListBox => &["CLICK", "DBLCLICK", "PATTERNCHANGE", "PATHCHANGE"],
Screen => &[],
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventParamType {
Integer,
Single,
Control,
}
pub fn event_params(event: &str) -> Option<&'static [(&'static str, EventParamType)]> {
use EventParamType::*;
Some(match event.to_ascii_uppercase().as_str() {
"KEYDOWN" | "KEYUP" => &[("KEYCODE", Integer), ("SHIFT", Integer)],
"KEYPRESS" => &[("KEYASCII", Integer)],
"MOUSEDOWN" | "MOUSEMOVE" | "MOUSEUP" => &[
("BUTTON", Integer),
("SHIFT", Integer),
("X", Single),
("Y", Single),
],
"DRAGDROP" => &[("SOURCE", Control), ("X", Single), ("Y", Single)],
"DRAGOVER" => &[
("SOURCE", Control),
("X", Single),
("Y", Single),
("STATE", Integer),
],
"UNLOAD" => &[("CANCEL", Integer)],
"CLICK" | "DBLCLICK" | "CHANGE" | "DROPDOWN" | "GOTFOCUS" | "LOSTFOCUS" | "LOAD"
| "PAINT" | "RESIZE" | "TIMER" | "PATHCHANGE" | "PATTERNCHANGE" => &[],
_ => return None,
})
}
#[derive(Debug, Clone)]
pub struct FormObject {
pub name: String,
pub class: ObjectClass,
pub parent_form: Option<String>,
pub array: bool,
}
#[derive(Debug, Clone, Default)]
pub struct FormCatalog {
pub objects: Vec<FormObject>,
}
impl FormCatalog {
pub fn add(
&mut self,
name: impl Into<String>,
class: ObjectClass,
parent_form: Option<&str>,
array: bool,
) -> u16 {
let id = self.objects.len() as u16;
self.objects.push(FormObject {
name: name.into().to_uppercase(),
class,
parent_form: parent_form.map(str::to_uppercase),
array,
});
id
}
pub fn find(&self, name: &str) -> Option<(u16, &FormObject)> {
self.objects
.iter()
.enumerate()
.find(|(_, o)| o.name.eq_ignore_ascii_case(name))
.map(|(i, o)| (i as u16, o))
}
}

View File

@@ -41,6 +41,8 @@ pub enum HTy {
FixedStr(u32), FixedStr(u32),
/// Benutzerdefinierter Typ (Index in `HirModule::udts`). /// Benutzerdefinierter Typ (Index in `HirModule::udts`).
Udt(u16), Udt(u16),
Form,
Control,
} }
impl HTy { impl HTy {
@@ -131,6 +133,17 @@ pub struct HirModule {
pub procs: Vec<HProc>, pub procs: Vec<HProc>,
pub data: Vec<DataItem>, pub data: Vec<DataItem>,
pub option_base: u8, pub option_base: u8,
/// Zur Übersetzungszeit bekannte Forms-Objekte in Indexreihenfolge.
pub objects: Vec<crate::forms::FormObject>,
/// Ereignisprozeduren: Objektindex, Ereignisname, Prozedurindex.
pub event_procs: Vec<HEventProc>,
}
#[derive(Debug, Clone)]
pub struct HEventProc {
pub object: u16,
pub event: String,
pub proc: u16,
} }
// ---- Ausdrücke ------------------------------------------------------------- // ---- Ausdrücke -------------------------------------------------------------
@@ -367,6 +380,25 @@ pub enum HExpr {
Cur(i64), Cur(i64),
Str(String), Str(String),
Load(Box<HPlace>), Load(Box<HPlace>),
ObjectProperty {
object: u16,
index: Option<Box<HExpr>>,
property: u16,
ty: HTy,
},
DynamicObjectProperty {
object: Box<HExpr>,
property: String,
},
ObjectRef {
object: u16,
index: Option<Box<HExpr>>,
class: crate::forms::ObjectClass,
},
TypeOf {
value: Box<HExpr>,
class: crate::forms::ObjectClass,
},
/// Numerische Konvertierung nach Matrix (Rundung/Überlauf). /// Numerische Konvertierung nach Matrix (Rundung/Überlauf).
Conv { Conv {
from: NumTy, from: NumTy,
@@ -473,6 +505,27 @@ pub enum HStmtKind {
place: HPlace, place: HPlace,
value: HExpr, value: HExpr,
}, },
SetObjectProperty {
object: u16,
index: Option<HExpr>,
property: u16,
value: HExpr,
},
SetDynamicObjectProperty {
object: HExpr,
property: String,
value: HExpr,
},
ObjectMethod {
object: u16,
method: u16,
args: Vec<HExpr>,
},
ObjectLoad {
object: u16,
index: Option<HExpr>,
unload: bool,
},
Print { Print {
items: Vec<HPrintItem>, items: Vec<HPrintItem>,
/// Endet die Anweisung mit `;`/`,` (kein Zeilenumbruch)? /// Endet die Anweisung mit `;`/`,` (kein Zeilenumbruch)?

View File

@@ -62,43 +62,169 @@ pub enum NumValue {
/// und -anweisungen sind KEINE Keywords, sondern Builtins der Semantik). /// und -anweisungen sind KEINE Keywords, sondern Builtins der Semantik).
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kw { pub enum Kw {
And, As, Call, Case, Common, Const, Data, Declare, Def, DefCur, DefDbl, DefInt, And,
DefLng, DefSng, DefStr, Dim, Do, Double, Else, ElseIf, End, Eqv, Erase, As,
Error, Exit, For, Function, Gosub, Goto, If, Imp, Input, Integer, Is, Call,
Let, Line, Local, Long, Loop, Mod, Next, Not, On, Option, Or, Print, Case,
Read, ReDim, Rem, Restore, Resume, Return, Select, Shared, Single, Common,
Static, Step, Stop, String, Sub, System, Then, To, Type, Until, Using, Const,
Wend, While, Xor, Currency, Data,
Declare,
Def,
DefCur,
DefDbl,
DefInt,
DefLng,
DefSng,
DefStr,
Dim,
Do,
Double,
Else,
ElseIf,
End,
Eqv,
Erase,
Error,
Exit,
For,
Function,
Gosub,
Goto,
If,
Imp,
Input,
Integer,
Is,
Let,
Line,
Local,
Long,
Loop,
Mod,
Next,
Not,
On,
Option,
Or,
Print,
Read,
ReDim,
Rem,
Restore,
Resume,
Return,
Select,
Shared,
Single,
Static,
Step,
Stop,
String,
Sub,
System,
Then,
To,
Type,
Until,
Using,
Wend,
While,
Xor,
Currency,
// Datei-E/A-Keywords: werden geparst, aber erst in Phase 3 implementiert // Datei-E/A-Keywords: werden geparst, aber erst in Phase 3 implementiert
Open, Close, Write, Field, Get, Put, Seek, Lset, Rset, Open,
Close,
Write,
Field,
Get,
Put,
Seek,
Lset,
Rset,
} }
fn keyword(upper: &str) -> Option<Kw> { fn keyword(upper: &str) -> Option<Kw> {
use Kw::*; use Kw::*;
Some(match upper { Some(match upper {
"AND" => And, "AS" => As, "CALL" => Call, "CASE" => Case, "AND" => And,
"COMMON" => Common, "CONST" => Const, "CURRENCY" => Currency, "AS" => As,
"CALL" => Call,
"CASE" => Case,
"COMMON" => Common,
"CONST" => Const,
"CURRENCY" => Currency,
"DATA" => Data, "DATA" => Data,
"DECLARE" => Declare, "DEF" => Def, "DEFCUR" => DefCur, "DECLARE" => Declare,
"DEFDBL" => DefDbl, "DEFINT" => DefInt, "DEFLNG" => DefLng, "DEF" => Def,
"DEFSNG" => DefSng, "DEFSTR" => DefStr, "DIM" => Dim, "DO" => Do, "DEFCUR" => DefCur,
"DOUBLE" => Double, "ELSE" => Else, "ELSEIF" => ElseIf, "END" => End, "DEFDBL" => DefDbl,
"EQV" => Eqv, "ERASE" => Erase, "ERROR" => Error, "EXIT" => Exit, "DEFINT" => DefInt,
"FIELD" => Field, "FOR" => For, "FUNCTION" => Function, "DEFLNG" => DefLng,
"GET" => Get, "GOSUB" => Gosub, "GOTO" => Goto, "IF" => If, "DEFSNG" => DefSng,
"IMP" => Imp, "INPUT" => Input, "INTEGER" => Integer, "IS" => Is, "DEFSTR" => DefStr,
"LET" => Let, "LINE" => Line, "LOCAL" => Local, "LONG" => Long, "DIM" => Dim,
"LOOP" => Loop, "LSET" => Lset, "MOD" => Mod, "NEXT" => Next, "DO" => Do,
"NOT" => Not, "ON" => On, "OPEN" => Open, "OPTION" => Option, "DOUBLE" => Double,
"OR" => Or, "PRINT" => Print, "PUT" => Put, "READ" => Read, "ELSE" => Else,
"REDIM" => ReDim, "REM" => Rem, "RESTORE" => Restore, "ELSEIF" => ElseIf,
"RESUME" => Resume, "RETURN" => Return, "RSET" => Rset, "END" => End,
"SEEK" => Seek, "SELECT" => Select, "SHARED" => Shared, "EQV" => Eqv,
"SINGLE" => Single, "STATIC" => Static, "STEP" => Step, "ERASE" => Erase,
"STOP" => Stop, "STRING" => String, "SUB" => Sub, "ERROR" => Error,
"SYSTEM" => System, "THEN" => Then, "EXIT" => Exit,
"TO" => To, "TYPE" => Type, "UNTIL" => Until, "USING" => Using, "FIELD" => Field,
"WEND" => Wend, "WHILE" => While, "WRITE" => Write, "XOR" => Xor, "FOR" => For,
"FUNCTION" => Function,
"GET" => Get,
"GOSUB" => Gosub,
"GOTO" => Goto,
"IF" => If,
"IMP" => Imp,
"INPUT" => Input,
"INTEGER" => Integer,
"IS" => Is,
"LET" => Let,
"LINE" => Line,
"LOCAL" => Local,
"LONG" => Long,
"LOOP" => Loop,
"LSET" => Lset,
"MOD" => Mod,
"NEXT" => Next,
"NOT" => Not,
"ON" => On,
"OPEN" => Open,
"OPTION" => Option,
"OR" => Or,
"PRINT" => Print,
"PUT" => Put,
"READ" => Read,
"REDIM" => ReDim,
"REM" => Rem,
"RESTORE" => Restore,
"RESUME" => Resume,
"RETURN" => Return,
"RSET" => Rset,
"SEEK" => Seek,
"SELECT" => Select,
"SHARED" => Shared,
"SINGLE" => Single,
"STATIC" => Static,
"STEP" => Step,
"STOP" => Stop,
"STRING" => String,
"SUB" => Sub,
"SYSTEM" => System,
"THEN" => Then,
"TO" => To,
"TYPE" => Type,
"UNTIL" => Until,
"USING" => Using,
"WEND" => Wend,
"WHILE" => While,
"WRITE" => Write,
"XOR" => Xor,
"CLOSE" => Close, "CLOSE" => Close,
_ => return None, _ => return None,
}) })
@@ -107,18 +233,39 @@ fn keyword(upper: &str) -> Option<Kw> {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum TokenKind { pub enum TokenKind {
/// Bezeichner; `name` ist bereits in Großschreibung normalisiert. /// Bezeichner; `name` ist bereits in Großschreibung normalisiert.
Ident { name: String, suffix: Option<Suffix> }, Ident {
name: String,
suffix: Option<Suffix>,
},
Kw(Kw), Kw(Kw),
Num(NumValue), Num(NumValue),
Str(String), Str(String),
Plus, Minus, Star, Slash, Backslash, Caret, Plus,
Eq, Ne, Lt, Le, Gt, Ge, Minus,
LParen, RParen, Comma, Semicolon, Colon, Hash, Star,
Slash,
Backslash,
Caret,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
LParen,
RParen,
Comma,
Semicolon,
Colon,
Hash,
Dot,
/// Metabefehl `'$INCLUDE: 'datei''` (Pfad; leer = fehlerhafte Syntax). /// Metabefehl `'$INCLUDE: 'datei''` (Pfad; leer = fehlerhafte Syntax).
MetaInclude(String), MetaInclude(String),
/// Metabefehle `'$STATIC` / `'$DYNAMIC`. /// Metabefehle `'$STATIC` / `'$DYNAMIC`.
MetaStatic, MetaStatic,
MetaDynamic, MetaDynamic,
/// Metabefehl `'$FORM`: das Modul ist ein Formularmodul.
MetaForm,
/// Rohtext einer `DATA`-Anweisung bis zum Anweisungsende. Er wird /// Rohtext einer `DATA`-Anweisung bis zum Anweisungsende. Er wird
/// bewusst **nicht** zerlegt oder normalisiert: unquotierte Elemente /// bewusst **nicht** zerlegt oder normalisiert: unquotierte Elemente
/// behalten ihre Groß- und Kleinschreibung und ihren inneren Leerraum. /// behalten ihre Groß- und Kleinschreibung und ihren inneren Leerraum.
@@ -139,6 +286,9 @@ fn meta_token(rest: &[char]) -> Option<TokenKind> {
if up.starts_with("$DYNAMIC") { if up.starts_with("$DYNAMIC") {
return Some(TokenKind::MetaDynamic); return Some(TokenKind::MetaDynamic);
} }
if up.starts_with("$FORM") {
return Some(TokenKind::MetaForm);
}
if up.starts_with("$INCLUDE") { if up.starts_with("$INCLUDE") {
if let Some(colon) = t.find(':') { if let Some(colon) = t.find(':') {
let after = t[colon + 1..].trim(); let after = t[colon + 1..].trim();
@@ -191,14 +341,15 @@ pub fn lex(source: &str) -> LexOutput {
break; break;
} }
let start = i; let start = i;
let pos = SourcePos { line: line_no, column: (start + 1) as u32 }; let pos = SourcePos {
line: line_no,
column: (start + 1) as u32,
};
let c = chars[i]; let c = chars[i];
// Zeilenfortsetzung: `_` nach Leerraum, danach nur noch Leerraum // Zeilenfortsetzung: `_` nach Leerraum, danach nur noch Leerraum
if c == '_' if c == '_'
&& (start == 0 && (start == 0 || chars[start - 1] == ' ' || chars[start - 1] == '\t')
|| chars[start - 1] == ' '
|| chars[start - 1] == '\t')
&& chars[start + 1..].iter().all(|&ch| ch == ' ' || ch == '\t') && chars[start + 1..].iter().all(|&ch| ch == ' ' || ch == '\t')
{ {
line_continued = true; line_continued = true;
@@ -236,17 +387,16 @@ pub fn lex(source: &str) -> LexOutput {
// Das Vorbild toleriert fehlende schließende // Das Vorbild toleriert fehlende schließende
// Anführungszeichen am Zeilenende. // Anführungszeichen am Zeilenende.
} }
tokens.push(Token { kind: TokenKind::Str(s), pos }); tokens.push(Token {
kind: TokenKind::Str(s),
pos,
});
} }
'&' if i + 1 < chars.len() '&' if i + 1 < chars.len() && matches!(chars[i + 1], 'h' | 'H' | 'o' | 'O') => {
&& matches!(chars[i + 1], 'h' | 'H' | 'o' | 'O') =>
{
let hex = matches!(chars[i + 1], 'h' | 'H'); let hex = matches!(chars[i + 1], 'h' | 'H');
i += 2; i += 2;
let digit_start = i; let digit_start = i;
while i < chars.len() while i < chars.len() && chars[i].is_ascii_alphanumeric() {
&& chars[i].is_ascii_alphanumeric()
{
i += 1; i += 1;
} }
let digits: String = chars[digit_start..i].iter().collect(); let digits: String = chars[digit_start..i].iter().collect();
@@ -276,8 +426,8 @@ pub fn lex(source: &str) -> LexOutput {
}), }),
} }
} }
'0'..='9' | '.' if c != '.' '0'..='9' | '.'
|| (i + 1 < chars.len() && chars[i + 1].is_ascii_digit()) => if c != '.' || (i + 1 < chars.len() && chars[i + 1].is_ascii_digit()) =>
{ {
let int_start = i; let int_start = i;
while i < chars.len() && chars[i].is_ascii_digit() { while i < chars.len() && chars[i].is_ascii_digit() {
@@ -298,9 +448,7 @@ pub fn lex(source: &str) -> LexOutput {
// Exponent E/D // Exponent E/D
let mut exp_kind: Option<char> = None; let mut exp_kind: Option<char> = None;
let mut exp_str = String::new(); let mut exp_str = String::new();
if i < chars.len() if i < chars.len() && matches!(chars[i], 'e' | 'E' | 'd' | 'D') {
&& matches!(chars[i], 'e' | 'E' | 'd' | 'D')
{
let save = i; let save = i;
let k = chars[i].to_ascii_uppercase(); let k = chars[i].to_ascii_uppercase();
let mut j = i + 1; let mut j = i + 1;
@@ -403,22 +551,25 @@ pub fn lex(source: &str) -> LexOutput {
} }
} }
}; };
tokens.push(Token { kind: TokenKind::Num(value), pos }); tokens.push(Token {
kind: TokenKind::Num(value),
pos,
});
} }
c if c.is_alphabetic() => { c if c.is_alphabetic() => {
i += 1; i += 1;
while i < chars.len() while i < chars.len()
&& (chars[i].is_alphanumeric() && (chars[i].is_alphanumeric() || chars[i] == '.' || chars[i] == '_')
|| chars[i] == '.'
|| chars[i] == '_')
{ {
i += 1; i += 1;
} }
let mut name: String = chars[start..i] let mut name: String =
.iter() chars[start..i].iter().collect::<String>().to_uppercase();
.collect::<String>() let container_bang = i < chars.len()
.to_uppercase(); && chars[i] == '!'
let suffix = if i < chars.len() { && i + 1 < chars.len()
&& chars[i + 1].is_alphabetic();
let suffix = if i < chars.len() && !container_bang {
Suffix::from_char(chars[i]) Suffix::from_char(chars[i])
} else { } else {
None None
@@ -426,6 +577,16 @@ pub fn lex(source: &str) -> LexOutput {
if suffix.is_some() { if suffix.is_some() {
i += 1; i += 1;
} }
if container_bang {
name.push('!');
i += 1;
while i < chars.len()
&& (chars[i].is_alphanumeric() || chars[i] == '.' || chars[i] == '_')
{
name.push(chars[i].to_ascii_uppercase());
i += 1;
}
}
if suffix.is_none() { if suffix.is_none() {
if let Some(kw) = keyword(&name) { if let Some(kw) = keyword(&name) {
if kw == Kw::Rem { if kw == Kw::Rem {
@@ -435,7 +596,10 @@ pub fn lex(source: &str) -> LexOutput {
} }
break 'line; break 'line;
} }
tokens.push(Token { kind: TokenKind::Kw(kw), pos }); tokens.push(Token {
kind: TokenKind::Kw(kw),
pos,
});
if kw == Kw::Data { if kw == Kw::Data {
// Der Rest der Anweisung ist Rohtext: bis zum // Der Rest der Anweisung ist Rohtext: bis zum
// `:` außerhalb von Anführungszeichen oder bis // `:` außerhalb von Anführungszeichen oder bis
@@ -486,8 +650,9 @@ pub fn lex(source: &str) -> LexOutput {
';' => TokenKind::Semicolon, ';' => TokenKind::Semicolon,
':' => TokenKind::Colon, ':' => TokenKind::Colon,
'#' => TokenKind::Hash, '#' => TokenKind::Hash,
'.' => TokenKind::Dot,
'?' => TokenKind::Kw(Kw::Print), // Editor-Kurzform '?' => TokenKind::Kw(Kw::Print), // Editor-Kurzform
'&' => TokenKind::Kw(Kw::Long), // isoliertes & (selten) '&' => TokenKind::Kw(Kw::Long), // isoliertes & (selten)
'<' => { '<' => {
if i < chars.len() && chars[i] == '=' { if i < chars.len() && chars[i] == '=' {
i += 1; i += 1;
@@ -538,9 +703,15 @@ pub fn lex(source: &str) -> LexOutput {
tokens.push(Token { tokens.push(Token {
kind: TokenKind::Eof, kind: TokenKind::Eof,
pos: SourcePos { line: (source.lines().count() + 1) as u32, column: 1 }, pos: SourcePos {
line: (source.lines().count() + 1) as u32,
column: 1,
},
}); });
LexOutput { tokens, diagnostics } LexOutput {
tokens,
diagnostics,
}
} }
#[cfg(test)] #[cfg(test)]
@@ -565,7 +736,10 @@ mod tests {
let k = kinds("STRING$ STRING"); let k = kinds("STRING$ STRING");
assert_eq!( assert_eq!(
k[0], k[0],
TokenKind::Ident { name: "STRING".into(), suffix: Some(Suffix::Str) } TokenKind::Ident {
name: "STRING".into(),
suffix: Some(Suffix::Str)
}
); );
assert_eq!(k[1], TokenKind::Kw(Kw::String)); assert_eq!(k[1], TokenKind::Kw(Kw::String));
} }
@@ -620,4 +794,23 @@ mod tests {
} }
); );
} }
#[test]
fn bang_ist_suffix_oder_containeroperator_nach_folgetoken() {
let k = kinds("Wert! = 1.5\nForm1!Text1.Text = \"a\"");
assert_eq!(
k[0],
TokenKind::Ident {
name: "WERT".into(),
suffix: Some(Suffix::Single)
}
);
assert_eq!(
k[4],
TokenKind::Ident {
name: "FORM1!TEXT1.TEXT".into(),
suffix: None
}
);
}
} }

View File

@@ -6,6 +6,7 @@
//! übersetzt. //! übersetzt.
pub mod ast; pub mod ast;
pub mod forms;
pub mod hir; pub mod hir;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
@@ -42,12 +43,24 @@ pub struct Analysis {
/// Komplette Pipeline: Lexen → Parsen → semantische Prüfung + Lowering. /// Komplette Pipeline: Lexen → Parsen → semantische Prüfung + Lowering.
pub fn analyze_source(module_name: &str, source: &str) -> Analysis { pub fn analyze_source(module_name: &str, source: &str) -> Analysis {
analyze_source_with_forms(module_name, source, &forms::FormCatalog::default())
}
pub fn analyze_source_with_forms(
module_name: &str,
source: &str,
forms: &forms::FormCatalog,
) -> Analysis {
let lexed = lexer::lex(source); let lexed = lexer::lex(source);
let mut diagnostics = lexed.diagnostics; let mut diagnostics = lexed.diagnostics;
let parsed = parser::parse(module_name, &lexed.tokens); let parsed = parser::parse(module_name, &lexed.tokens);
diagnostics.extend(parsed.diagnostics); diagnostics.extend(parsed.diagnostics);
let (hir, sema_diags) = sema::lower(&parsed.module); let (hir, sema_diags) = sema::lower_with_forms(&parsed.module, forms);
diagnostics.extend(sema_diags); diagnostics.extend(sema_diags);
diagnostics.sort_by_key(|d| (d.pos.line, d.pos.column)); diagnostics.sort_by_key(|d| (d.pos.line, d.pos.column));
Analysis { module: parsed.module, diagnostics, hir } Analysis {
module: parsed.module,
diagnostics,
hir,
}
} }

View File

@@ -14,7 +14,12 @@ pub struct ParseOutput {
} }
pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput { pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput {
let mut p = P { toks: tokens, i: 0, diags: Vec::new(), at_line_start: true }; let mut p = P {
toks: tokens,
i: 0,
diags: Vec::new(),
at_line_start: true,
};
let mut body = Vec::new(); let mut body = Vec::new();
let mut procs = Vec::new(); let mut procs = Vec::new();
@@ -40,7 +45,11 @@ pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput {
} }
ParseOutput { ParseOutput {
module: Module { name: module_name.to_string(), body, procs }, module: Module {
name: module_name.to_string(),
body,
procs,
},
diagnostics: p.diags, diagnostics: p.diags,
} }
} }
@@ -85,7 +94,10 @@ impl<'a> P<'a> {
} }
fn err(&mut self, msg: impl Into<String>) { fn err(&mut self, msg: impl Into<String>) {
let pos = self.pos(); let pos = self.pos();
self.diags.push(Diagnostic { pos, message: msg.into() }); self.diags.push(Diagnostic {
pos,
message: msg.into(),
});
} }
fn expect_kw(&mut self, kw: Kw, what: &str) -> bool { fn expect_kw(&mut self, kw: Kw, what: &str) -> bool {
if self.eat_kw(kw) { if self.eat_kw(kw) {
@@ -205,11 +217,21 @@ impl<'a> P<'a> {
} }
TokenKind::MetaStatic => { TokenKind::MetaStatic => {
self.advance(); self.advance();
Some(Stmt::MetaArrays { static_arrays: true, pos }) Some(Stmt::MetaArrays {
static_arrays: true,
pos,
})
} }
TokenKind::MetaDynamic => { TokenKind::MetaDynamic => {
self.advance(); self.advance();
Some(Stmt::MetaArrays { static_arrays: false, pos }) Some(Stmt::MetaArrays {
static_arrays: false,
pos,
})
}
TokenKind::MetaForm => {
self.advance();
Some(Stmt::MetaForm { pos })
} }
TokenKind::Kw(Kw::Print) => self.parse_print(pos, false), TokenKind::Kw(Kw::Print) => self.parse_print(pos, false),
TokenKind::Kw(Kw::Input) => { TokenKind::Kw(Kw::Input) => {
@@ -368,8 +390,9 @@ impl<'a> P<'a> {
} }
Some(Stmt::ConstDecl { items, pos }) Some(Stmt::ConstDecl { items, pos })
} }
TokenKind::Kw(k @ (Kw::DefInt | Kw::DefLng | Kw::DefSng TokenKind::Kw(
| Kw::DefDbl | Kw::DefStr | Kw::DefCur)) => { k @ (Kw::DefInt | Kw::DefLng | Kw::DefSng | Kw::DefDbl | Kw::DefStr | Kw::DefCur),
) => {
self.advance(); self.advance();
let ty = match k { let ty = match k {
Kw::DefInt => TypeName::Integer, Kw::DefInt => TypeName::Integer,
@@ -382,9 +405,7 @@ impl<'a> P<'a> {
let mut ranges = Vec::new(); let mut ranges = Vec::new();
loop { loop {
let a = match self.k() { let a = match self.k() {
TokenKind::Ident { name, suffix: None } TokenKind::Ident { name, suffix: None } if name.len() == 1 => {
if name.len() == 1 =>
{
self.advance(); self.advance();
name.chars().next().unwrap() name.chars().next().unwrap()
} }
@@ -396,9 +417,7 @@ impl<'a> P<'a> {
}; };
let b = if self.eat(&TokenKind::Minus) { let b = if self.eat(&TokenKind::Minus) {
match self.k() { match self.k() {
TokenKind::Ident { name, suffix: None } TokenKind::Ident { name, suffix: None } if name.len() == 1 => {
if name.len() == 1 =>
{
self.advance(); self.advance();
name.chars().next().unwrap() name.chars().next().unwrap()
} }
@@ -420,11 +439,12 @@ impl<'a> P<'a> {
TokenKind::Kw(Kw::Option) => { TokenKind::Kw(Kw::Option) => {
self.advance(); self.advance();
match self.k() { match self.k() {
TokenKind::Ident { name, suffix: None } TokenKind::Ident { name, suffix: None } if name == "EXPLICIT" => {
if name == "EXPLICIT" =>
{
self.advance(); self.advance();
Some(Stmt::OptionStmt { kind: OptionKind::Explicit, pos }) Some(Stmt::OptionStmt {
kind: OptionKind::Explicit,
pos,
})
} }
TokenKind::Ident { name, suffix: None } if name == "BASE" => { TokenKind::Ident { name, suffix: None } if name == "BASE" => {
self.advance(); self.advance();
@@ -439,7 +459,10 @@ impl<'a> P<'a> {
0 0
} }
}; };
Some(Stmt::OptionStmt { kind: OptionKind::Base(base), pos }) Some(Stmt::OptionStmt {
kind: OptionKind::Base(base),
pos,
})
} }
_ => { _ => {
self.err("Expected: BASE or EXPLICIT"); self.err("Expected: BASE or EXPLICIT");
@@ -466,7 +489,12 @@ impl<'a> P<'a> {
} else { } else {
Vec::new() Vec::new()
}; };
Some(Stmt::Call { name, suffix, args, pos }) Some(Stmt::Call {
name,
suffix,
args,
pos,
})
} }
_ => { _ => {
self.err("Expected: identifier"); self.err("Expected: identifier");
@@ -510,7 +538,11 @@ impl<'a> P<'a> {
_ => cur.push(c), _ => cur.push(c),
} }
} }
items.push(if quotiert { cur } else { cur.trim().to_string() }); items.push(if quotiert {
cur
} else {
cur.trim().to_string()
});
Some(Stmt::Data { items, pos }) Some(Stmt::Data { items, pos })
} }
TokenKind::Kw(Kw::Read) => { TokenKind::Kw(Kw::Read) => {
@@ -594,7 +626,13 @@ impl<'a> P<'a> {
var = Some(self.parse_name_ref()?); var = Some(self.parse_name_ref()?);
} }
} }
Some(Stmt::GetPut { put: k == Kw::Put, file, recnum, var, pos }) Some(Stmt::GetPut {
put: k == Kw::Put,
file,
recnum,
var,
pos,
})
} }
TokenKind::Kw(k @ (Kw::Lset | Kw::Rset)) => { TokenKind::Kw(k @ (Kw::Lset | Kw::Rset)) => {
self.advance(); self.advance();
@@ -605,7 +643,12 @@ impl<'a> P<'a> {
return None; return None;
} }
let value = self.parse_expr()?; let value = self.parse_expr()?;
Some(Stmt::LsetRset { rset: k == Kw::Rset, target, value, pos }) Some(Stmt::LsetRset {
rset: k == Kw::Rset,
target,
value,
pos,
})
} }
TokenKind::Kw(Kw::Write) => { TokenKind::Kw(Kw::Write) => {
self.advance(); self.advance();
@@ -636,7 +679,11 @@ impl<'a> P<'a> {
self.err("Expected: ,"); self.err("Expected: ,");
} }
let position = self.parse_expr()?; let position = self.parse_expr()?;
Some(Stmt::SeekStmt { file, position, pos }) Some(Stmt::SeekStmt {
file,
position,
pos,
})
} }
TokenKind::Kw(Kw::Common) => { TokenKind::Kw(Kw::Common) => {
self.advance(); self.advance();
@@ -654,7 +701,12 @@ impl<'a> P<'a> {
} }
} }
let decls = self.parse_var_decls(); let decls = self.parse_var_decls();
Some(Stmt::CommonDecl { shared, block, decls, pos }) Some(Stmt::CommonDecl {
shared,
block,
decls,
pos,
})
} }
TokenKind::Kw(Kw::Shared) => { TokenKind::Kw(Kw::Shared) => {
self.advance(); self.advance();
@@ -681,19 +733,20 @@ impl<'a> P<'a> {
} }
Some(Stmt::ViewPrint { top, bottom, pos }) Some(Stmt::ViewPrint { top, bottom, pos })
} }
TokenKind::Ident { ref name, suffix: None } TokenKind::Ident {
if name == "NAME" && self.k_at(1) != TokenKind::Eq => ref name,
{ suffix: None,
} if name == "NAME" && self.k_at(1) != TokenKind::Eq => {
self.advance(); self.advance();
let old = self.parse_expr()?; let old = self.parse_expr()?;
self.expect_kw(Kw::As, "AS"); self.expect_kw(Kw::As, "AS");
let new = self.parse_expr()?; let new = self.parse_expr()?;
Some(Stmt::NameStmt { old, new, pos }) Some(Stmt::NameStmt { old, new, pos })
} }
TokenKind::Ident { ref name, suffix: None } TokenKind::Ident {
if (name == "LOCK" || name == "UNLOCK") ref name,
&& self.k_at(1) != TokenKind::Eq => suffix: None,
{ } if (name == "LOCK" || name == "UNLOCK") && self.k_at(1) != TokenKind::Eq => {
let unlock = name == "UNLOCK"; let unlock = name == "UNLOCK";
self.advance(); self.advance();
self.eat(&TokenKind::Hash); self.eat(&TokenKind::Hash);
@@ -707,7 +760,13 @@ impl<'a> P<'a> {
to = Some(self.parse_expr()?); to = Some(self.parse_expr()?);
} }
} }
Some(Stmt::LockStmt { unlock, file, from, to, pos }) Some(Stmt::LockStmt {
unlock,
file,
from,
to,
pos,
})
} }
// ISAM-Anweisungen: `NAME [#]n [, arg …]`. Sie unterscheiden sich // ISAM-Anweisungen: `NAME [#]n [, arg …]`. Sie unterscheiden sich
// von einem gewöhnlichen impliziten Aufruf nur durch das // von einem gewöhnlichen impliziten Aufruf nur durch das
@@ -716,10 +775,12 @@ impl<'a> P<'a> {
// `=` und `(` schließen die Anweisungsdeutung aus: dann ist der // `=` und `(` schließen die Anweisungsdeutung aus: dann ist der
// Name eine Variable bzw. ein Array (`DELETE = 1`, `DELETE(2) = 1`). // Name eine Variable bzw. ein Array (`DELETE = 1`, `DELETE(2) = 1`).
// Keine ISAM-Syntaxform beginnt mit einer Klammer. // Keine ISAM-Syntaxform beginnt mit einer Klammer.
TokenKind::Ident { ref name, suffix: None } TokenKind::Ident {
if ist_isam_anweisung(name) ref name,
&& self.k_at(1) != TokenKind::Eq suffix: None,
&& self.k_at(1) != TokenKind::LParen => } if ist_isam_anweisung(name)
&& self.k_at(1) != TokenKind::Eq
&& self.k_at(1) != TokenKind::LParen =>
{ {
self.parse_isam(name.clone(), pos) self.parse_isam(name.clone(), pos)
} }
@@ -740,11 +801,20 @@ impl<'a> P<'a> {
// Es senkt auf die Sentinel-Kennung ab, damit `ROLLBACK` eine // Es senkt auf die Sentinel-Kennung ab, damit `ROLLBACK` eine
// einzige Signatur behält. // einzige Signatur behält.
if name == "ROLLBACK" { if name == "ROLLBACK" {
if let TokenKind::Ident { name: ref w, suffix: None } = self.k() { if let TokenKind::Ident {
name: ref w,
suffix: None,
} = self.k()
{
if w == "ALL" { if w == "ALL" {
self.advance(); self.advance();
let args = vec![Expr::LongLit(ROLLBACK_ALL)]; let args = vec![Expr::LongLit(ROLLBACK_ALL)];
return Some(Stmt::Call { name, suffix: None, args, pos }); return Some(Stmt::Call {
name,
suffix: None,
args,
pos,
});
} }
} }
} }
@@ -754,7 +824,12 @@ impl<'a> P<'a> {
} else { } else {
self.parse_arg_list_to_stmt_end() self.parse_arg_list_to_stmt_end()
}; };
Some(Stmt::Call { name, suffix: None, args, pos }) Some(Stmt::Call {
name,
suffix: None,
args,
pos,
})
} }
fn parse_print(&mut self, pos: SourcePos, printer: bool) -> Option<Stmt> { fn parse_print(&mut self, pos: SourcePos, printer: bool) -> Option<Stmt> {
@@ -793,7 +868,13 @@ impl<'a> P<'a> {
}, },
} }
} }
Some(Stmt::Print { printer, file, using, items, pos }) Some(Stmt::Print {
printer,
file,
using,
items,
pos,
})
} }
/// `OPEN` in beiden Syntaxformen (FOR-Klausel und Kurzform). /// `OPEN` in beiden Syntaxformen (FOR-Klausel und Kurzform).
@@ -813,7 +894,13 @@ impl<'a> P<'a> {
} else { } else {
None None
}; };
return Some(Stmt::OpenLegacy { mode: first, number, file, len, pos }); return Some(Stmt::OpenLegacy {
mode: first,
number,
file,
len,
pos,
});
} }
let mut mode = None; let mut mode = None;
let mut isam = None; let mut isam = None;
@@ -823,7 +910,10 @@ impl<'a> P<'a> {
self.advance(); self.advance();
OpenMode::Input OpenMode::Input
} }
TokenKind::Ident { ref name, suffix: None } => { TokenKind::Ident {
ref name,
suffix: None,
} => {
let m = match name.as_str() { let m = match name.as_str() {
"OUTPUT" => OpenMode::Output, "OUTPUT" => OpenMode::Output,
"APPEND" => OpenMode::Append, "APPEND" => OpenMode::Append,
@@ -873,7 +963,11 @@ impl<'a> P<'a> {
} }
} }
let mut access = None; let mut access = None;
if let TokenKind::Ident { ref name, suffix: None } = self.k() { if let TokenKind::Ident {
ref name,
suffix: None,
} = self.k()
{
if name == "ACCESS" { if name == "ACCESS" {
self.advance(); self.advance();
let read = self.eat_kw(Kw::Read); let read = self.eat_kw(Kw::Read);
@@ -892,7 +986,11 @@ impl<'a> P<'a> {
let mut lock = None; let mut lock = None;
if self.eat_kw(Kw::Shared) { if self.eat_kw(Kw::Shared) {
lock = Some(LockClause::Shared); lock = Some(LockClause::Shared);
} else if let TokenKind::Ident { ref name, suffix: None } = self.k() { } else if let TokenKind::Ident {
ref name,
suffix: None,
} = self.k()
{
if name == "LOCK" { if name == "LOCK" {
self.advance(); self.advance();
let read = self.eat_kw(Kw::Read); let read = self.eat_kw(Kw::Read);
@@ -912,7 +1010,11 @@ impl<'a> P<'a> {
self.eat(&TokenKind::Hash); self.eat(&TokenKind::Hash);
let number = self.parse_expr()?; let number = self.parse_expr()?;
let mut len = None; let mut len = None;
if let TokenKind::Ident { ref name, suffix: None } = self.k() { if let TokenKind::Ident {
ref name,
suffix: None,
} = self.k()
{
if name == "LEN" { if name == "LEN" {
self.advance(); self.advance();
if !self.eat(&TokenKind::Eq) { if !self.eat(&TokenKind::Eq) {
@@ -921,7 +1023,16 @@ impl<'a> P<'a> {
len = Some(self.parse_expr()?); len = Some(self.parse_expr()?);
} }
} }
Some(Stmt::Open { file: first, mode, isam, access, lock, number, len, pos }) Some(Stmt::Open {
file: first,
mode,
isam,
access,
lock,
number,
len,
pos,
})
} }
fn parse_input(&mut self, line: bool, pos: SourcePos) -> Option<Stmt> { fn parse_input(&mut self, line: bool, pos: SourcePos) -> Option<Stmt> {
@@ -936,8 +1047,7 @@ impl<'a> P<'a> {
let mut prompt = None; let mut prompt = None;
if file.is_none() { if file.is_none() {
if let TokenKind::Str(s) = self.k() { if let TokenKind::Str(s) = self.k() {
if matches!(self.k_at(1), TokenKind::Semicolon | TokenKind::Comma) if matches!(self.k_at(1), TokenKind::Semicolon | TokenKind::Comma) {
{
self.advance(); self.advance();
let with_question = self.k() == TokenKind::Semicolon; let with_question = self.k() == TokenKind::Semicolon;
self.advance(); self.advance();
@@ -959,7 +1069,14 @@ impl<'a> P<'a> {
if vars.is_empty() { if vars.is_empty() {
self.err("Expected: variable"); self.err("Expected: variable");
} }
Some(Stmt::Input { line, file, keep_cursor, prompt, vars, pos }) Some(Stmt::Input {
line,
file,
keep_cursor,
prompt,
vars,
pos,
})
} }
fn parse_if(&mut self, pos: SourcePos) -> Option<Stmt> { fn parse_if(&mut self, pos: SourcePos) -> Option<Stmt> {
@@ -991,7 +1108,13 @@ impl<'a> P<'a> {
} else { } else {
self.err("Expected: END IF"); self.err("Expected: END IF");
} }
Some(Stmt::If { cond, then_body, elseifs, else_body, pos }) Some(Stmt::If {
cond,
then_body,
elseifs,
else_body,
pos,
})
} else { } else {
// Einzeilig // Einzeilig
let then_body = if let Some(n) = self.line_number_target() { let then_body = if let Some(n) = self.line_number_target() {
@@ -1008,7 +1131,13 @@ impl<'a> P<'a> {
} else { } else {
None None
}; };
Some(Stmt::If { cond, then_body, elseifs: Vec::new(), else_body, pos }) Some(Stmt::If {
cond,
then_body,
elseifs: Vec::new(),
else_body,
pos,
})
} }
} }
@@ -1112,7 +1241,14 @@ impl<'a> P<'a> {
} else { } else {
self.err("FOR without NEXT"); self.err("FOR without NEXT");
} }
Some(Stmt::For { var, from, to, step, body, pos }) Some(Stmt::For {
var,
from,
to,
step,
body,
pos,
})
} }
fn parse_do(&mut self, pos: SourcePos) -> Option<Stmt> { fn parse_do(&mut self, pos: SourcePos) -> Option<Stmt> {
@@ -1135,7 +1271,12 @@ impl<'a> P<'a> {
} else { } else {
self.err("DO without LOOP"); self.err("DO without LOOP");
} }
Some(Stmt::DoLoop { pre, post, body, pos }) Some(Stmt::DoLoop {
pre,
post,
body,
pos,
})
} }
fn parse_while(&mut self, pos: SourcePos) -> Option<Stmt> { fn parse_while(&mut self, pos: SourcePos) -> Option<Stmt> {
@@ -1252,13 +1393,23 @@ impl<'a> P<'a> {
break; break;
} }
} }
Some(Stmt::OnGoto { expr, targets, gosub, pos }) Some(Stmt::OnGoto {
expr,
targets,
gosub,
pos,
})
} }
fn parse_dim(&mut self, redim: bool, pos: SourcePos) -> Option<Stmt> { fn parse_dim(&mut self, redim: bool, pos: SourcePos) -> Option<Stmt> {
let shared = self.eat_kw(Kw::Shared); let shared = self.eat_kw(Kw::Shared);
let decls = self.parse_var_decls(); let decls = self.parse_var_decls();
Some(Stmt::Dim { shared, redim, decls, pos }) Some(Stmt::Dim {
shared,
redim,
decls,
pos,
})
} }
/// Deklarationsliste `name[suffix][(dims)] [AS typ], …` — gemeinsame /// Deklarationsliste `name[suffix][(dims)] [AS typ], …` — gemeinsame
@@ -1309,7 +1460,13 @@ impl<'a> P<'a> {
} else { } else {
None None
}; };
decls.push(VarDecl { name, suffix, dims, as_type, pos: dpos }); decls.push(VarDecl {
name,
suffix,
dims,
as_type,
pos: dpos,
});
if !self.eat(&TokenKind::Comma) { if !self.eat(&TokenKind::Comma) {
break; break;
} }
@@ -1362,7 +1519,11 @@ impl<'a> P<'a> {
} }
TokenKind::Ident { name, suffix: None } => { TokenKind::Ident { name, suffix: None } => {
self.advance(); self.advance();
Some(TypeName::Udt(name)) Some(match name.as_str() {
"FORM" => TypeName::Form,
"CONTROL" => TypeName::Control,
_ => TypeName::Udt(name),
})
} }
_ => { _ => {
self.err("Expected: type"); self.err("Expected: type");
@@ -1397,7 +1558,10 @@ impl<'a> P<'a> {
break; break;
} }
match self.k() { match self.k() {
TokenKind::Ident { name: fname, suffix: None } => { TokenKind::Ident {
name: fname,
suffix: None,
} => {
self.advance(); self.advance();
if self.expect_kw(Kw::As, "AS") { if self.expect_kw(Kw::As, "AS") {
if let Some(t) = self.parse_type_name() { if let Some(t) = self.parse_type_name() {
@@ -1442,7 +1606,10 @@ impl<'a> P<'a> {
if !self.eat(&TokenKind::RParen) { if !self.eat(&TokenKind::RParen) {
loop { loop {
match self.k() { match self.k() {
TokenKind::Ident { name: pname, suffix: psfx } => { TokenKind::Ident {
name: pname,
suffix: psfx,
} => {
self.advance(); self.advance();
let array = if self.eat(&TokenKind::LParen) { let array = if self.eat(&TokenKind::LParen) {
self.eat(&TokenKind::RParen); self.eat(&TokenKind::RParen);
@@ -1474,7 +1641,12 @@ impl<'a> P<'a> {
self.eat(&TokenKind::RParen); self.eat(&TokenKind::RParen);
} }
} }
Some(ProcSig { kind, name, suffix, params }) Some(ProcSig {
kind,
name,
suffix,
params,
})
} }
fn parse_proc(&mut self) -> Option<Proc> { fn parse_proc(&mut self) -> Option<Proc> {
@@ -1495,14 +1667,19 @@ impl<'a> P<'a> {
ProcKind::Function => "Expected: END FUNCTION", ProcKind::Function => "Expected: END FUNCTION",
}); });
} }
Some(Proc { sig, is_static, body, pos }) Some(Proc {
sig,
is_static,
body,
pos,
})
} }
fn parse_def_fn(&mut self, pos: SourcePos) -> Option<Stmt> { fn parse_def_fn(&mut self, pos: SourcePos) -> Option<Stmt> {
self.advance(); // DEF self.advance(); // DEF
// `DEF SEG` ist Segmentadressierung und damit deklariertes // `DEF SEG` ist Segmentadressierung und damit deklariertes
// Non-Feature — an der Syntax erkennbar, weil `DEF` sonst nur // Non-Feature — an der Syntax erkennbar, weil `DEF` sonst nur
// `DEF FNname` einleitet. // `DEF FNname` einleitet.
if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "SEG") { if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "SEG") {
self.err("Feature unavailable"); self.err("Feature unavailable");
self.sync(); self.sync();
@@ -1524,7 +1701,10 @@ impl<'a> P<'a> {
if !self.eat(&TokenKind::RParen) { if !self.eat(&TokenKind::RParen) {
loop { loop {
match self.k() { match self.k() {
TokenKind::Ident { name: pname, suffix: psfx } => { TokenKind::Ident {
name: pname,
suffix: psfx,
} => {
self.advance(); self.advance();
params.push(Param { params.push(Param {
name: pname, name: pname,
@@ -1547,7 +1727,13 @@ impl<'a> P<'a> {
} }
if self.eat(&TokenKind::Eq) { if self.eat(&TokenKind::Eq) {
let body = self.parse_expr()?; let body = self.parse_expr()?;
Some(Stmt::DefFn { name, suffix, params, body, pos }) Some(Stmt::DefFn {
name,
suffix,
params,
body,
pos,
})
} else { } else {
// Blockform: DEF FNname(...) … [EXIT DEF] … END DEF // Blockform: DEF FNname(...) … [EXIT DEF] … END DEF
let body = self.parse_stmt_list(|p: &P| p.at_end_pair(Kw::Def)); let body = self.parse_stmt_list(|p: &P| p.at_end_pair(Kw::Def));
@@ -1557,7 +1743,13 @@ impl<'a> P<'a> {
} else { } else {
self.err("Expected: END DEF"); self.err("Expected: END DEF");
} }
Some(Stmt::DefFnBlock { name, suffix, params, body, pos }) Some(Stmt::DefFnBlock {
name,
suffix,
params,
body,
pos,
})
} }
} }
@@ -1612,7 +1804,10 @@ impl<'a> P<'a> {
} }
} }
// Impliziter Aufruf: `name [arg [, arg …]]` // Impliziter Aufruf: `name [arg [, arg …]]`
if let Expr::Name { name, suffix, args, .. } = target { if let Expr::Name {
name, suffix, args, ..
} = target
{
// Ohne CALL-Keyword sind Klammern Wert-Klammern (BYVAL), keine // Ohne CALL-Keyword sind Klammern Wert-Klammern (BYVAL), keine
// Argumentlisten-Klammern: `Foo (n%)` übergibt `(n%)`. // Argumentlisten-Klammern: `Foo (n%)` übergibt `(n%)`.
let mut call_args: Vec<Expr> = args let mut call_args: Vec<Expr> = args
@@ -1626,7 +1821,12 @@ impl<'a> P<'a> {
if !self.at_stmt_end() && call_args.is_empty() { if !self.at_stmt_end() && call_args.is_empty() {
call_args = self.parse_arg_list_to_stmt_end(); call_args = self.parse_arg_list_to_stmt_end();
} }
Some(Stmt::Call { name, suffix, args: call_args, pos }) Some(Stmt::Call {
name,
suffix,
args: call_args,
pos,
})
} else { } else {
self.err("Syntax error"); self.err("Syntax error");
self.sync(); self.sync();
@@ -1640,7 +1840,7 @@ impl<'a> P<'a> {
fn parse_name_ref(&mut self) -> Option<Expr> { fn parse_name_ref(&mut self) -> Option<Expr> {
let pos = self.pos(); let pos = self.pos();
match self.k() { match self.k() {
TokenKind::Ident { name, suffix } => { TokenKind::Ident { mut name, suffix } => {
self.advance(); self.advance();
let args = if self.eat(&TokenKind::LParen) { let args = if self.eat(&TokenKind::LParen) {
let a = self.parse_arg_list(&TokenKind::RParen); let a = self.parse_arg_list(&TokenKind::RParen);
@@ -1649,7 +1849,25 @@ impl<'a> P<'a> {
} else { } else {
None None
}; };
Some(Expr::Name { name, suffix, args, pos }) if self.eat(&TokenKind::Dot) {
match self.k() {
TokenKind::Ident {
name: member,
suffix: None,
} => {
name.push('.');
name.push_str(&member);
self.advance();
}
_ => self.err("Expected: property"),
}
}
Some(Expr::Name {
name,
suffix,
args,
pos,
})
} }
_ => { _ => {
self.err("Expected: variable"); self.err("Expected: variable");
@@ -1665,7 +1883,12 @@ impl<'a> P<'a> {
match self.k() { match self.k() {
TokenKind::Ident { name, suffix } => { TokenKind::Ident { name, suffix } => {
self.advance(); self.advance();
Some(Expr::Name { name, suffix, args: None, pos }) Some(Expr::Name {
name,
suffix,
args: None,
pos,
})
} }
_ => { _ => {
self.err("Expected: variable"); self.err("Expected: variable");
@@ -1773,7 +1996,12 @@ impl<'a> P<'a> {
let pos = self.pos(); let pos = self.pos();
self.advance(); self.advance();
let rhs = self.parse_bin(prec + 1)?; let rhs = self.parse_bin(prec + 1)?;
lhs = Expr::Binary { op, lhs: Box::new(lhs), rhs: Box::new(rhs), pos }; lhs = Expr::Binary {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
pos,
};
} }
Some(lhs) Some(lhs)
} }
@@ -1809,7 +2037,11 @@ impl<'a> P<'a> {
TokenKind::Minus => { TokenKind::Minus => {
self.advance(); self.advance();
let operand = self.parse_bin(12)?; let operand = self.parse_bin(12)?;
Some(Expr::Unary { op: UnOp::Neg, operand: Box::new(operand), pos }) Some(Expr::Unary {
op: UnOp::Neg,
operand: Box::new(operand),
pos,
})
} }
TokenKind::Plus => { TokenKind::Plus => {
self.advance(); self.advance();
@@ -1818,7 +2050,11 @@ impl<'a> P<'a> {
TokenKind::Kw(Kw::Not) => { TokenKind::Kw(Kw::Not) => {
self.advance(); self.advance();
let operand = self.parse_bin(6)?; let operand = self.parse_bin(6)?;
Some(Expr::Unary { op: UnOp::Not, operand: Box::new(operand), pos }) Some(Expr::Unary {
op: UnOp::Not,
operand: Box::new(operand),
pos,
})
} }
_ => self.parse_primary(), _ => self.parse_primary(),
} }
@@ -1842,6 +2078,30 @@ impl<'a> P<'a> {
pos, pos,
}); });
} }
if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "TYPEOF") {
let pos = self.pos();
self.advance();
let value = self.parse_primary()?;
if !self.eat_kw(Kw::Is) {
self.err("Expected: IS");
return None;
}
let class = match self.k() {
TokenKind::Ident { name, suffix: None } => {
self.advance();
name
}
_ => {
self.err("Expected: control class");
return None;
}
};
return Some(Expr::TypeOf {
value: Box::new(value),
class,
pos,
});
}
match self.k() { match self.k() {
TokenKind::Num(n) => { TokenKind::Num(n) => {
self.advance(); self.advance();
@@ -1913,13 +2173,21 @@ mod tests {
"IF a > 1 THEN\nPRINT 1\nELSEIF a > 0 THEN\nPRINT 2\nELSE\nPRINT 3\nEND IF\nIF b THEN PRINT 4 ELSE PRINT 5", "IF a > 1 THEN\nPRINT 1\nELSEIF a > 0 THEN\nPRINT 2\nELSE\nPRINT 3\nEND IF\nIF b THEN PRINT 4 ELSE PRINT 5",
); );
match &m.body[0] { match &m.body[0] {
Stmt::If { elseifs, else_body, .. } => { Stmt::If {
elseifs, else_body, ..
} => {
assert_eq!(elseifs.len(), 1); assert_eq!(elseifs.len(), 1);
assert!(else_body.is_some()); assert!(else_body.is_some());
} }
other => panic!("erwartet IF, war {other:?}"), other => panic!("erwartet IF, war {other:?}"),
} }
assert!(matches!(&m.body[1], Stmt::If { else_body: Some(_), .. })); assert!(matches!(
&m.body[1],
Stmt::If {
else_body: Some(_),
..
}
));
} }
#[test] #[test]
@@ -1943,8 +2211,22 @@ mod tests {
"FOR i = 1 TO 10 STEP 2\nNEXT i\nDO WHILE x\nLOOP\nDO\nLOOP UNTIL x\nWHILE x\nWEND", "FOR i = 1 TO 10 STEP 2\nNEXT i\nDO WHILE x\nLOOP\nDO\nLOOP UNTIL x\nWHILE x\nWEND",
); );
assert!(matches!(&m.body[0], Stmt::For { step: Some(_), .. })); assert!(matches!(&m.body[0], Stmt::For { step: Some(_), .. }));
assert!(matches!(&m.body[1], Stmt::DoLoop { pre: Some((false, _)), post: None, .. })); assert!(matches!(
assert!(matches!(&m.body[2], Stmt::DoLoop { pre: None, post: Some((true, _)), .. })); &m.body[1],
Stmt::DoLoop {
pre: Some((false, _)),
post: None,
..
}
));
assert!(matches!(
&m.body[2],
Stmt::DoLoop {
pre: None,
post: Some((true, _)),
..
}
));
assert!(matches!(&m.body[3], Stmt::While { .. })); assert!(matches!(&m.body[3], Stmt::While { .. }));
} }
@@ -1963,9 +2245,29 @@ mod tests {
let m = parse_ok( let m = parse_ok(
"ON ERROR GOTO Handler\nON LOCAL ERROR RESUME NEXT\nON ERROR GOTO 0\nHandler:\nRESUME NEXT", "ON ERROR GOTO Handler\nON LOCAL ERROR RESUME NEXT\nON ERROR GOTO 0\nHandler:\nRESUME NEXT",
); );
assert!(matches!(&m.body[0], Stmt::OnError { local: false, action: OnErrorAction::Goto(_), .. })); assert!(matches!(
assert!(matches!(&m.body[1], Stmt::OnError { local: true, action: OnErrorAction::ResumeNext, .. })); &m.body[0],
assert!(matches!(&m.body[2], Stmt::OnError { action: OnErrorAction::Disable, .. })); Stmt::OnError {
local: false,
action: OnErrorAction::Goto(_),
..
}
));
assert!(matches!(
&m.body[1],
Stmt::OnError {
local: true,
action: OnErrorAction::ResumeNext,
..
}
));
assert!(matches!(
&m.body[2],
Stmt::OnError {
action: OnErrorAction::Disable,
..
}
));
} }
#[test] #[test]
@@ -1973,7 +2275,15 @@ mod tests {
let m = parse_ok("x = 1 + 2 * 3 ^ 2"); let m = parse_ok("x = 1 + 2 * 3 ^ 2");
// 1 + (2 * (3 ^ 2)) // 1 + (2 * (3 ^ 2))
match &m.body[0] { match &m.body[0] {
Stmt::Assign { value: Expr::Binary { op: BinOp::Add, rhs, .. }, .. } => { Stmt::Assign {
value:
Expr::Binary {
op: BinOp::Add,
rhs,
..
},
..
} => {
assert!(matches!(**rhs, Expr::Binary { op: BinOp::Mul, .. })); assert!(matches!(**rhs, Expr::Binary { op: BinOp::Mul, .. }));
} }
other => panic!("unerwartet: {other:?}"), other => panic!("unerwartet: {other:?}"),
@@ -1989,4 +2299,18 @@ mod tests {
assert!(matches!(&m.body[1], Stmt::Dim { shared: true, decls, .. } if decls.len() == 2)); assert!(matches!(&m.body[1], Stmt::Dim { shared: true, decls, .. } if decls.len() == 2));
assert!(matches!(&m.body[3], Stmt::DefType { ranges, .. } if ranges.len() == 2)); assert!(matches!(&m.body[3], Stmt::DefType { ranges, .. } if ranges.len() == 2));
} }
#[test]
fn forms_syntax() {
let m = parse_ok(
"'$FORM\nDIM f AS FORM, c AS CONTROL\nIF TYPEOF c IS CommandButton THEN PRINT 1",
);
assert!(matches!(m.body[0], Stmt::MetaForm { .. }));
assert!(matches!(&m.body[1], Stmt::Dim { decls, .. }
if decls[0].as_type == Some(TypeName::Form) && decls[1].as_type == Some(TypeName::Control)));
assert!(
matches!(&m.body[2], Stmt::If { cond: Expr::TypeOf { class, .. }, .. }
if class == "COMMANDBUTTON")
);
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ struct Eintrag {
art: String, art: String,
status: String, status: String,
fundstelle: String, fundstelle: String,
quelle: String,
zeile: usize, zeile: usize,
} }
@@ -61,6 +62,7 @@ fn inventar() -> Vec<Eintrag> {
art: spalten[1].to_string(), art: spalten[1].to_string(),
status: spalten[3].to_string(), status: spalten[3].to_string(),
fundstelle: spalten[4].to_string(), fundstelle: spalten[4].to_string(),
quelle: spalten[5].to_string(),
zeile: i + 1, zeile: i + 1,
}); });
} }
@@ -155,7 +157,10 @@ fn stand() -> Stand {
signaturen, signaturen,
verboten: literale(ban), verboten: literale(ban),
nicht_verfuegbar, nicht_verfuegbar,
syntaktisch: literale(syn).into_iter().map(|s| s.to_uppercase()).collect(), syntaktisch: literale(syn)
.into_iter()
.map(|s| s.to_uppercase())
.collect(),
} }
} }
@@ -167,7 +172,15 @@ fn statusvokabular_ist_beschraenkt() {
assert!( assert!(
matches!( matches!(
e.art.as_str(), e.art.as_str(),
"Anweisung" | "Funktion" | "Metabefehl" | "Routine" "Anweisung"
| "Funktion"
| "Metabefehl"
| "Routine"
| "Eigenschaft"
| "Methode"
| "Ereignis"
| "Datentyp"
| "Operator"
), ),
"Zeile {}: `{}` trägt unzulässige Art `{}`", "Zeile {}: `{}` trägt unzulässige Art `{}`",
e.zeile, e.zeile,
@@ -185,6 +198,84 @@ fn statusvokabular_ist_beschraenkt() {
} }
} }
fn pruefe_forms(inv: &[Eintrag]) -> Vec<String> {
use tb_frontend::forms::{events, method_is_implemented, methods, properties, ObjectClass};
let mut erwartet = BTreeMap::new();
for class in ObjectClass::ALL {
for p in properties(class) {
erwartet.insert(
format!("{}.{}", class.name(), p.name),
("Eigenschaft", IMPLEMENTIERT, "tb-ui::forms"),
);
}
for m in methods(class) {
erwartet.insert(
format!("{}.{m}", class.name()),
if method_is_implemented(class, m) {
("Methode", IMPLEMENTIERT, "tb-ui::forms")
} else {
("Methode", OFFEN, "-")
},
);
}
for e in events(class) {
erwartet.insert(
format!("{}_{e}", class.name()),
("Ereignis", IMPLEMENTIERT, "tb-vm::interp"),
);
}
}
let gefunden: BTreeMap<_, _> = inv
.iter()
.filter(|e| matches!(e.art.as_str(), "Eigenschaft" | "Methode" | "Ereignis"))
.map(|e| (e.name.to_uppercase(), e))
.collect();
let mut fehler = Vec::new();
for (name, (art, status, fundstelle)) in &erwartet {
match gefunden.get(name) {
None => fehler.push(format!("`{name}` fehlt")),
Some(e)
if e.art != *art
|| e.status != *status
|| e.fundstelle != *fundstelle
|| e.quelle != "forms-referenz" =>
{
fehler.push(format!("`{name}`: Art/Status/Fundstelle/Quelle falsch"))
}
Some(_) => {}
}
}
for name in gefunden.keys() {
if !erwartet.contains_key(name) {
fehler.push(format!("`{name}` ist nicht in der Klassentabelle"));
}
}
fehler
}
#[test]
fn forms_inventar_stimmt_mit_der_klassentabelle_ueberein() {
let fehler = pruefe_forms(&inventar());
assert!(
fehler.is_empty(),
"Forms-Inventar weicht ab:\n {}",
fehler.join("\n ")
);
}
#[test]
fn falsch_gesetzte_eigenschaft_wird_erkannt() {
let mut inv = inventar();
let e = inv
.iter_mut()
.find(|e| e.name == "TEXTBOX.SELSTART")
.unwrap();
e.status = OFFEN.into();
assert!(pruefe_forms(&inv)
.iter()
.any(|f| f.contains("TEXTBOX.SELSTART")));
}
#[test] #[test]
fn non_features_nennen_eine_fundstelle_in_der_sprachreferenz() { fn non_features_nennen_eine_fundstelle_in_der_sprachreferenz() {
for e in inventar() { for e in inventar() {
@@ -343,11 +434,7 @@ fn inventar_stimmt_mit_code_ueberein() {
fn abdeckungsstand_wird_ausgewiesen() { fn abdeckungsstand_wird_ausgewiesen() {
let inv = inventar(); let inv = inventar();
let zaehle = |s: &str| inv.iter().filter(|e| e.status == s).count(); let zaehle = |s: &str| inv.iter().filter(|e| e.status == s).count();
let (i, o, n) = ( let (i, o, n) = (zaehle(IMPLEMENTIERT), zaehle(OFFEN), zaehle(NON_FEATURE));
zaehle(IMPLEMENTIERT),
zaehle(OFFEN),
zaehle(NON_FEATURE),
);
println!( println!(
"Abdeckung: implementiert {i} · offen {o} · Non-Feature {n} · gesamt {}", "Abdeckung: implementiert {i} · offen {o} · Non-Feature {n} · gesamt {}",
inv.len() inv.len()
@@ -384,10 +471,22 @@ fn dokumentierte_elemente_werden_namentlich_abgewiesen() {
("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"), ("LINE (1, 1)-(2, 2)", "LINE", "Feature unavailable"),
("GET (1, 1)-(2, 2), A", "GET (Grafik)", "Feature unavailable"), (
(r#"OPEN "COM1:9600,N,8,1" AS #1"#, "OPEN COM", "Feature unavailable"), "GET (1, 1)-(2, 2), A",
"GET (Grafik)",
"Feature unavailable",
),
(
r#"OPEN "COM1:9600,N,8,1" AS #1"#,
"OPEN COM",
"Feature unavailable",
),
// Ereignisgerät als Funktionsform // Ereignisgerät als Funktionsform
("ON PEN GOSUB Ziel\nZiel:\nRETURN", "ON PEN", "Feature unavailable"), (
"ON PEN GOSUB Ziel\nZiel:\nRETURN",
"ON PEN",
"Feature unavailable",
),
]; ];
for (quelle, element, erwartet) in faelle { for (quelle, element, erwartet) in faelle {
@@ -411,7 +510,12 @@ fn dokumentierte_elemente_werden_namentlich_abgewiesen() {
/// generischen Syntaxfehler. /// generischen Syntaxfehler.
#[test] #[test]
fn offene_elemente_erzeugen_keinen_syntaxfehler() { fn offene_elemente_erzeugen_keinen_syntaxfehler() {
for quelle in ["CLS", "LOCATE 5, 10", "COLOR 14, 1", r#"OPEN "d.txt" FOR OUTPUT AS #1"#] { for quelle in [
"CLS",
"LOCATE 5, 10",
"COLOR 14, 1",
r#"OPEN "d.txt" FOR OUTPUT AS #1"#,
] {
let a = tb_frontend::analyze_source("probe.bas", quelle); let a = tb_frontend::analyze_source("probe.bas", quelle);
assert!( assert!(
a.diagnostics.is_empty(), a.diagnostics.is_empty(),

File diff suppressed because it is too large Load Diff

View File

@@ -70,6 +70,11 @@ impl RuntimeError {
pub const INVALID_SCREEN_MODE: Self = Self(271); pub const INVALID_SCREEN_MODE: Self = Self(271);
pub const INVALID_WHEN_FORMS_SHOWING: Self = Self(272); pub const INVALID_WHEN_FORMS_SHOWING: Self = Self(272);
pub const INVALID_PROPERTY_VALUE: Self = Self(380); pub const INVALID_PROPERTY_VALUE: Self = Self(380);
pub const DESIGN_TIME_CONTROL: Self = Self(362);
pub const FORM_ALREADY_DISPLAYED: Self = Self(400);
pub const NON_MODAL_WHILE_MODAL: Self = Self(401);
pub const MODAL_NOT_TOPMOST: Self = Self(402);
pub const MDI_FORM_MODAL: Self = Self(403);
pub const NO_ACTIVE_CONTROL: Self = Self(430); pub const NO_ACTIVE_CONTROL: Self = Self(430);
pub const NO_ACTIVE_FORM: Self = Self(431); pub const NO_ACTIVE_FORM: Self = Self(431);
@@ -219,7 +224,10 @@ mod tests {
"Invalid when forms are showing" "Invalid when forms are showing"
); );
assert_eq!(RuntimeError::NO_ACTIVE_FORM.code(), 431); assert_eq!(RuntimeError::NO_ACTIVE_FORM.code(), 431);
assert_eq!(RuntimeError(403).message(), "MDI form cannot be shown modally"); assert_eq!(
RuntimeError(403).message(),
"MDI form cannot be shown modally"
);
} }
#[test] #[test]

View File

@@ -18,6 +18,8 @@ pub enum Value {
Dbl(f64), Dbl(f64),
Cur(i64), Cur(i64),
Str(Rc<str>), Str(Rc<str>),
/// Formular-/Control-Objekt mit optionalem Index eines Control-Arrays.
Obj(u16, Option<i32>),
Arr(Rc<RefCell<ArrayObj>>), Arr(Rc<RefCell<ArrayObj>>),
Rec(Rc<RefCell<RecordObj>>), Rec(Rc<RefCell<RecordObj>>),
/// Referenz (BYREF-Parameter-Slot). /// Referenz (BYREF-Parameter-Slot).
@@ -91,7 +93,11 @@ pub struct ArrayObj {
} }
impl ArrayObj { impl ArrayObj {
pub fn new(elem: TypeInit, dims: Vec<(i32, i32)>, udts: &[UdtLayout]) -> Result<Self, RuntimeError> { pub fn new(
elem: TypeInit,
dims: Vec<(i32, i32)>,
udts: &[UdtLayout],
) -> Result<Self, RuntimeError> {
let mut len: usize = 1; let mut len: usize = 1;
for (lo, hi) in &dims { for (lo, hi) in &dims {
if hi < lo { if hi < lo {

View File

@@ -8,6 +8,7 @@ authors.workspace = true
[dependencies] [dependencies]
tb-runtime.workspace = true tb-runtime.workspace = true
tb-frontend.workspace = true
ratatui.workspace = true ratatui.workspace = true
crossterm.workspace = true crossterm.workspace = true
signal-hook.workspace = true signal-hook.workspace = true

View File

@@ -3,4 +3,592 @@
//! Ziel ist volle Kompatibilität zum Forms-Modell des Vorbilds inklusive //! Ziel ist volle Kompatibilität zum Forms-Modell des Vorbilds inklusive
//! des textbasierten Formular-Dateiformats (`.FRM`). //! des textbasierten Formular-Dateiformats (`.FRM`).
// Platzhalter — wird in Phase 4 ausgearbeitet (siehe PLAN.md) use std::collections::{BTreeMap, VecDeque};
use tb_frontend::forms::{self, FormObject, ObjectClass, PropertyDefault, PropertyType};
use tb_runtime::errors::RuntimeError;
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyValue {
Integer(i32),
Single(f32),
String(String),
Boolean(bool),
Object(Option<(u16, Option<i32>)>),
IntegerArray(Vec<i32>),
}
impl PropertyValue {
fn from_default(default: PropertyDefault, ty: PropertyType) -> Self {
match default {
PropertyDefault::Integer(v) if ty == PropertyType::IntegerArray => {
Self::IntegerArray(vec![v; 18])
}
PropertyDefault::Integer(v) => Self::Integer(v),
PropertyDefault::Single(v) => Self::Single(v),
PropertyDefault::String(v) => Self::String(v.into()),
PropertyDefault::Boolean(v) => Self::Boolean(v),
PropertyDefault::Empty if ty == PropertyType::Object => Self::Object(None),
PropertyDefault::Empty if ty == PropertyType::String => Self::String(String::new()),
PropertyDefault::Empty => Self::Integer(0),
}
}
}
#[derive(Debug, Clone)]
pub struct ObjectInstance {
pub description: FormObject,
properties: Vec<PropertyValue>,
pub loaded: bool,
pub visible: bool,
pub design_time: bool,
pub array_index: Option<i32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FormEvent {
pub object: u16,
pub array_index: Option<i32>,
pub name: String,
pub args: Vec<PropertyValue>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShowResult {
Modeless,
ModalWait,
}
/// Reines Zustandsmodell. Zeichnen und Bedienung kommen im Folge-Change;
/// diese Schicht hält Objekte, Lebenszyklus, Modalität und Ereignisqueue.
pub struct FormsModel {
pub objects: Vec<ObjectInstance>,
dynamic: BTreeMap<(u16, i32), ObjectInstance>,
pub events: VecDeque<FormEvent>,
modal: Vec<u16>,
visible_forms: Vec<u16>,
active_form: Option<u16>,
active_control: Option<(u16, Option<i32>)>,
width: usize,
height: usize,
screen_visible: bool,
}
impl FormsModel {
fn set_visible_property(obj: &mut ObjectInstance, visible: bool) {
if let Some((property, _)) = forms::property(obj.description.class, "VISIBLE") {
obj.properties[property as usize] = PropertyValue::Boolean(visible);
}
}
fn clear_active_control_for_form(&mut self, form: u16) {
let Some((control, _)) = self.active_control else {
return;
};
let Some(form_name) = self
.objects
.get(form as usize)
.map(|o| o.description.name.as_str())
else {
return;
};
if self
.objects
.get(control as usize)
.and_then(|o| o.description.parent_form.as_deref())
.is_some_and(|parent| parent.eq_ignore_ascii_case(form_name))
{
self.active_control = None;
}
}
pub fn new(objects: Vec<FormObject>, width: usize, height: usize) -> Self {
let objects = objects
.into_iter()
.map(|description| {
let properties = forms::properties(description.class)
.into_iter()
.map(|p| PropertyValue::from_default(p.default, p.ty))
.collect();
let loaded = description.class == ObjectClass::Screen;
ObjectInstance {
description,
properties,
loaded,
visible: false,
design_time: true,
array_index: None,
}
})
.collect();
Self {
objects,
dynamic: BTreeMap::new(),
events: VecDeque::new(),
modal: Vec::new(),
visible_forms: Vec::new(),
active_form: None,
active_control: None,
width,
height,
screen_visible: true,
}
}
pub fn resize(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
}
pub fn get(&mut self, object: u16, property: u16) -> Result<PropertyValue, RuntimeError> {
self.get_at(object, None, property)
}
pub fn get_at(
&mut self,
object: u16,
index: Option<i32>,
property: u16,
) -> Result<PropertyValue, RuntimeError> {
self.ensure_loaded_at(object, index)?;
let obj = if index.is_some_and(|i| i != 0) {
self.dynamic
.get(&(object, index.unwrap()))
.ok_or(RuntimeError(340))?
} else {
self.objects.get(object as usize).ok_or(RuntimeError(420))?
};
let spec = forms::properties(obj.description.class)
.get(property as usize)
.copied()
.ok_or(RuntimeError(422))?;
if obj.description.class == ObjectClass::Screen {
return Ok(match spec.name {
"WIDTH" => PropertyValue::Integer(self.width as i32),
"HEIGHT" => PropertyValue::Integer(self.height as i32),
"ACTIVEFORM" => PropertyValue::Object(self.active_form.map(|id| (id, None))),
"ACTIVECONTROL" => PropertyValue::Object(self.active_control),
_ => obj.properties[property as usize].clone(),
});
}
Ok(obj.properties[property as usize].clone())
}
pub fn set(
&mut self,
object: u16,
property: u16,
value: PropertyValue,
) -> Result<(), RuntimeError> {
self.set_at(object, None, property, value)
}
pub fn set_at(
&mut self,
object: u16,
index: Option<i32>,
property: u16,
value: PropertyValue,
) -> Result<(), RuntimeError> {
self.ensure_loaded_at(object, index)?;
let obj = if index.is_some_and(|i| i != 0) {
self.dynamic
.get_mut(&(object, index.unwrap()))
.ok_or(RuntimeError(340))?
} else {
self.objects
.get_mut(object as usize)
.ok_or(RuntimeError(420))?
};
let spec = forms::properties(obj.description.class)
.get(property as usize)
.copied()
.ok_or(RuntimeError(422))?;
if !spec.writable {
return Err(RuntimeError(383));
}
let type_ok = matches!(
(&value, spec.ty),
(PropertyValue::Integer(_), PropertyType::Integer)
| (PropertyValue::Single(_), PropertyType::Single)
| (PropertyValue::String(_), PropertyType::String)
| (PropertyValue::Boolean(_), PropertyType::Boolean)
| (PropertyValue::Object(_), PropertyType::Object)
| (PropertyValue::IntegerArray(_), PropertyType::IntegerArray)
);
if !type_ok {
return Err(RuntimeError::TYPE_MISMATCH);
}
if let PropertyValue::Integer(v) = value {
if spec.min.is_some_and(|min| v < min) || spec.max.is_some_and(|max| v > max) {
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
}
obj.properties[property as usize] = PropertyValue::Integer(v);
} else {
obj.properties[property as usize] = value;
}
if spec.name == "VISIBLE" {
obj.visible = matches!(
obj.properties[property as usize],
PropertyValue::Boolean(true)
);
}
Ok(())
}
pub fn ensure_loaded(&mut self, object: u16) -> Result<(), RuntimeError> {
let obj = self
.objects
.get_mut(object as usize)
.ok_or(RuntimeError(420))?;
if obj.loaded {
return Ok(());
}
obj.loaded = true;
if obj.description.class == ObjectClass::Form {
self.events.push_back(FormEvent {
object,
array_index: None,
name: "LOAD".into(),
args: Vec::new(),
});
}
Ok(())
}
pub fn ensure_loaded_at(
&mut self,
object: u16,
index: Option<i32>,
) -> Result<(), RuntimeError> {
if index.is_some_and(|i| i != 0) {
self.dynamic
.get(&(object, index.unwrap()))
.map(|_| ())
.ok_or(RuntimeError(340))
} else {
self.ensure_loaded(object)
}
}
pub fn show(&mut self, object: u16, modal: bool) -> Result<ShowResult, RuntimeError> {
self.ensure_loaded(object)?;
let obj = self
.objects
.get_mut(object as usize)
.ok_or(RuntimeError(420))?;
if obj.description.class != ObjectClass::Form {
return Err(RuntimeError(421));
}
if modal {
if obj.visible {
return Err(RuntimeError(400));
}
let form_type = forms::property(ObjectClass::Form, "FORMTYPE").unwrap().0 as usize;
if obj.properties.get(form_type) == Some(&PropertyValue::Integer(1)) {
return Err(RuntimeError(403));
}
self.modal.push(object);
} else if !self.modal.is_empty() {
return Err(RuntimeError(401));
}
obj.visible = true;
Self::set_visible_property(obj, true);
self.visible_forms.retain(|id| *id != object);
self.visible_forms.push(object);
self.active_form = Some(object);
Ok(if modal {
ShowResult::ModalWait
} else {
ShowResult::Modeless
})
}
pub fn hide(&mut self, object: u16) -> Result<(), RuntimeError> {
if self.modal.contains(&object) && self.modal.last().copied() != Some(object) {
return Err(RuntimeError(402));
}
let obj = self
.objects
.get_mut(object as usize)
.ok_or(RuntimeError(420))?;
obj.visible = false;
Self::set_visible_property(obj, false);
self.visible_forms.retain(|id| *id != object);
if self.modal.last().copied() == Some(object) {
self.modal.pop();
}
if self.active_form == Some(object) {
self.active_form = self.visible_forms.last().copied();
}
self.clear_active_control_for_form(object);
Ok(())
}
pub fn unload_with(
&mut self,
object: u16,
mut handler: impl FnMut(&mut i16),
) -> Result<bool, RuntimeError> {
if self.modal.contains(&object) && self.modal.last().copied() != Some(object) {
return Err(RuntimeError(402));
}
let class = self
.objects
.get(object as usize)
.ok_or(RuntimeError(420))?
.description
.class;
if class != ObjectClass::Form {
return Err(RuntimeError(361));
}
let mut cancel = 0i16;
handler(&mut cancel);
if cancel != 0 {
return Ok(false);
}
let obj = &mut self.objects[object as usize];
obj.visible = false;
Self::set_visible_property(obj, false);
obj.loaded = false;
self.visible_forms.retain(|id| *id != object);
if self.modal.last().copied() == Some(object) {
self.modal.pop();
}
if self.active_form == Some(object) {
self.active_form = self.visible_forms.last().copied();
}
self.clear_active_control_for_form(object);
Ok(true)
}
pub fn load_array(&mut self, base: u16, index: i32) -> Result<(), RuntimeError> {
let template = self.objects.get(base as usize).ok_or(RuntimeError(420))?;
if !template.description.array {
return Err(RuntimeError(343));
}
if index == 0 {
return Err(RuntimeError(360));
}
let key = (base, index);
if self.dynamic.contains_key(&key) {
return Err(RuntimeError(360));
}
let mut obj = template.clone();
obj.loaded = true;
obj.visible = false;
obj.design_time = false;
obj.array_index = Some(index);
self.dynamic.insert(key, obj);
Ok(())
}
pub fn unload_array(&mut self, base: u16, index: i32) -> Result<(), RuntimeError> {
let template = self.objects.get(base as usize).ok_or(RuntimeError(420))?;
if !template.description.array {
return Err(RuntimeError(343));
}
if template.array_index == Some(index) || (template.array_index.is_none() && index == 0) {
return Err(RuntimeError(362));
}
self.dynamic
.remove(&(base, index))
.map(|_| ())
.ok_or(RuntimeError(340))
}
pub fn queue(&mut self, event: FormEvent) {
self.events.push_back(event);
}
pub fn next_event(&mut self) -> Option<FormEvent> {
self.events.pop_front()
}
pub fn is_visible(&self, object: u16) -> bool {
self.objects.get(object as usize).is_some_and(|o| o.visible)
}
pub fn is_loaded(&self, object: u16) -> bool {
self.objects.get(object as usize).is_some_and(|o| o.loaded)
}
pub fn is_loaded_at(&self, object: u16, index: Option<i32>) -> bool {
if index.is_some_and(|i| i != 0) {
self.dynamic.contains_key(&(object, index.unwrap()))
} else {
self.is_loaded(object)
}
}
pub fn active_form(&self) -> Option<u16> {
self.active_form
}
pub fn set_active_control(
&mut self,
object: u16,
index: Option<i32>,
) -> Result<(), RuntimeError> {
self.ensure_loaded_at(object, index)?;
let description = if index.is_some_and(|i| i != 0) {
&self
.dynamic
.get(&(object, index.unwrap()))
.unwrap()
.description
} else {
&self
.objects
.get(object as usize)
.ok_or(RuntimeError(420))?
.description
};
if matches!(description.class, ObjectClass::Form | ObjectClass::Screen) {
return Err(RuntimeError(421));
}
let parent = description
.parent_form
.as_deref()
.ok_or(RuntimeError(421))?;
self.active_form = self
.objects
.iter()
.position(|o| {
o.description.class == ObjectClass::Form
&& o.description.name.eq_ignore_ascii_case(parent)
})
.map(|id| id as u16);
self.active_control = Some((object, index));
Ok(())
}
pub fn modal_top(&self) -> Option<u16> {
self.modal.last().copied()
}
pub fn screen_show(&mut self, visible: bool) {
self.screen_visible = visible;
}
pub fn screen_visible(&self) -> bool {
self.screen_visible
}
}
#[cfg(test)]
mod tests {
use super::*;
fn model() -> FormsModel {
let mut c = forms::FormCatalog::default();
c.add("Form1", ObjectClass::Form, None, false);
c.add("Check1", ObjectClass::CheckBox, Some("Form1"), false);
c.add("Command1", ObjectClass::CommandButton, Some("Form1"), true);
c.add("SCREEN", ObjectClass::Screen, None, false);
FormsModel::new(c.objects, 80, 25)
}
#[test]
fn defaults_bereich_und_implizites_laden() {
let mut m = model();
let value = forms::property(ObjectClass::CheckBox, "VALUE").unwrap().0;
assert_eq!(m.get(1, value).unwrap(), PropertyValue::Integer(0));
assert!(m.objects[1].loaded);
assert_eq!(
m.set(1, value, PropertyValue::Integer(3)),
Err(RuntimeError(5))
);
}
#[test]
fn hide_entlaedt_nicht_und_unload_ist_abbrechbar() {
let mut m = model();
m.show(0, false).unwrap();
m.hide(0).unwrap();
assert!(m.objects[0].loaded);
assert!(!m.unload_with(0, |cancel| *cancel = 1).unwrap());
assert!(m.objects[0].loaded);
assert!(m.unload_with(0, |_| {}).unwrap());
}
#[test]
fn arrays_und_designzeitfehler() {
let mut m = model();
let caption = forms::property(ObjectClass::CommandButton, "CAPTION")
.unwrap()
.0;
m.set(2, caption, PropertyValue::String("Vorlage".into()))
.unwrap();
assert_eq!(m.load_array(2, 0), Err(RuntimeError(360)));
m.load_array(2, 3).unwrap();
assert_eq!(
m.get_at(2, Some(3), caption).unwrap(),
PropertyValue::String("Vorlage".into())
);
m.set_at(2, Some(3), caption, PropertyValue::String("Klon".into()))
.unwrap();
assert_eq!(
m.get(2, caption).unwrap(),
PropertyValue::String("Vorlage".into())
);
assert_eq!(m.unload_array(2, 0), Err(RuntimeError(362)));
m.unload_array(2, 3).unwrap();
}
#[test]
fn screen_meldet_oberstes_formular_und_aktives_control() {
let mut c = forms::FormCatalog::default();
c.add("A", ObjectClass::Form, None, false);
c.add("B", ObjectClass::Form, None, false);
c.add("Text1", ObjectClass::TextBox, Some("B"), false);
c.add("SCREEN", ObjectClass::Screen, None, false);
let mut m = FormsModel::new(c.objects, 80, 25);
m.show(0, false).unwrap();
m.show(1, false).unwrap();
m.set_active_control(2, None).unwrap();
let active_form = forms::property(ObjectClass::Screen, "ACTIVEFORM")
.unwrap()
.0;
let active_control = forms::property(ObjectClass::Screen, "ACTIVECONTROL")
.unwrap()
.0;
assert_eq!(
m.get(3, active_form).unwrap(),
PropertyValue::Object(Some((1, None)))
);
assert_eq!(
m.get(3, active_control).unwrap(),
PropertyValue::Object(Some((2, None)))
);
m.hide(1).unwrap();
assert_eq!(
m.get(3, active_form).unwrap(),
PropertyValue::Object(Some((0, None)))
);
assert_eq!(
m.get(3, active_control).unwrap(),
PropertyValue::Object(None)
);
}
#[test]
fn screen_folgt_resize() {
let mut m = model();
m.resize(120, 40);
let width = forms::property(ObjectClass::Screen, "WIDTH").unwrap().0;
let height = forms::property(ObjectClass::Screen, "HEIGHT").unwrap().0;
assert_eq!(m.get(3, width).unwrap(), PropertyValue::Integer(120));
assert_eq!(m.get(3, height).unwrap(), PropertyValue::Integer(40));
}
#[test]
fn modalfehler_400_bis_403() {
let mut m = model();
m.show(0, false).unwrap();
assert_eq!(m.show(0, true), Err(RuntimeError(400)));
m.hide(0).unwrap();
m.show(0, true).unwrap();
assert_eq!(m.show(0, false), Err(RuntimeError(401)));
let mut c = forms::FormCatalog::default();
c.add("A", ObjectClass::Form, None, false);
c.add("B", ObjectClass::Form, None, false);
let mut stacked = FormsModel::new(c.objects, 80, 25);
stacked.show(0, true).unwrap();
stacked.show(1, true).unwrap();
assert_eq!(stacked.hide(0), Err(RuntimeError(402)));
let ft = forms::property(ObjectClass::Form, "FORMTYPE").unwrap().0;
let mut mdi = model();
mdi.set(0, ft, PropertyValue::Integer(1)).unwrap();
assert_eq!(mdi.show(0, true), Err(RuntimeError(403)));
}
}

View File

@@ -11,6 +11,7 @@ tb-frontend.workspace = true
thiserror.workspace = true thiserror.workspace = true
log.workspace = true log.workspace = true
tb-runtime.workspace = true tb-runtime.workspace = true
tb-ui.workspace = true
[[bench]] [[bench]]
name = "compile" name = "compile"

View File

@@ -8,10 +8,12 @@
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use tb_frontend::forms::{FormObject, ObjectClass};
use tb_frontend::hir::HEventProc;
use tb_runtime::value::{TypeInit, UdtLayout}; use tb_runtime::value::{TypeInit, UdtLayout};
pub const TBC_MAGIC: &[u8; 4] = b"TBC\0"; pub const TBC_MAGIC: &[u8; 4] = b"TBC\0";
pub const TBC_VERSION: u16 = 1; pub const TBC_VERSION: u16 = 3;
/// Vergleichsoperator (Operand der `Cmp*`-Instruktionen). /// Vergleichsoperator (Operand der `Cmp*`-Instruktionen).
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -52,7 +54,10 @@ impl fmt::Display for LoadError {
match self { match self {
LoadError::BadMagic => write!(f, "Keine .tbc-Datei (Magic fehlt)"), LoadError::BadMagic => write!(f, "Keine .tbc-Datei (Magic fehlt)"),
LoadError::Version(v) => { LoadError::Version(v) => {
write!(f, "Unbekannte .tbc-Formatversion {v} (unterstützt: {TBC_VERSION})") write!(
f,
"Unbekannte .tbc-Formatversion {v} (unterstützt: {TBC_VERSION})"
)
} }
LoadError::Corrupt(what) => write!(f, "Beschädigte .tbc-Datei ({what})"), LoadError::Corrupt(what) => write!(f, "Beschädigte .tbc-Datei ({what})"),
} }
@@ -303,6 +308,14 @@ instrs! {
0xB1 RetProc; 0xB1 RetProc;
0xB2 RetFn; 0xB2 RetFn;
0xB3 CallBuiltin(a: u16, b: u8); 0xB3 CallBuiltin(a: u16, b: u8);
0xB4 LoadObjectProperty(a: u16, b: u16, c: bool);
0xB5 StoreObjectProperty(a: u16, b: u16, c: bool);
0xB6 PushObject(a: u16, b: bool);
0xB7 TypeOf(a: u8);
0xB8 ObjectMethod(a: u16, b: u16, c: u8);
0xB9 ObjectLoad(a: u16, b: bool, c: bool); // unload?, Index liegt auf Stack?
0xBA LoadDynamicObjectProperty(a: u16);
0xBB StoreDynamicObjectProperty(a: 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
@@ -375,6 +388,8 @@ pub struct CompiledModule {
pub data: Vec<DataItem>, pub data: Vec<DataItem>,
/// Sprungtabellen für `ON n GOTO/GOSUB`. /// Sprungtabellen für `ON n GOTO/GOSUB`.
pub jump_tables: Vec<Vec<u32>>, pub jump_tables: Vec<Vec<u32>>,
pub objects: Vec<FormObject>,
pub event_procs: Vec<HEventProc>,
} }
fn w_string(out: &mut Vec<u8>, s: &str) { fn w_string(out: &mut Vec<u8>, s: &str) {
@@ -458,6 +473,22 @@ impl CompiledModule {
} }
sections.push((*b"JMPT", jmpt)); sections.push((*b"JMPT", jmpt));
let mut objs = Vec::new();
objs.extend_from_slice(&(self.objects.len() as u32).to_le_bytes());
for o in &self.objects {
w_string(&mut objs, &o.name);
objs.push(o.class.id());
w_string(&mut objs, o.parent_form.as_deref().unwrap_or(""));
objs.push(o.array as u8);
}
objs.extend_from_slice(&(self.event_procs.len() as u32).to_le_bytes());
for e in &self.event_procs {
objs.extend_from_slice(&e.object.to_le_bytes());
w_string(&mut objs, &e.event);
objs.extend_from_slice(&e.proc.to_le_bytes());
}
sections.push((*b"OBJS", objs));
// Header + Abschnittstabelle // Header + Abschnittstabelle
let mut out = Vec::new(); let mut out = Vec::new();
out.extend_from_slice(TBC_MAGIC); out.extend_from_slice(TBC_MAGIC);
@@ -567,7 +598,13 @@ impl CompiledModule {
for _ in 0..n_instr { for _ in 0..n_instr {
code.push(Instr::decode(&mut cr)?); code.push(Instr::decode(&mut cr)?);
} }
procs.push(ProcCode { name, n_params, locals_init, local_names, code }); procs.push(ProcCode {
name,
n_params,
locals_init,
local_names,
code,
});
} }
let mut r = section(b"DATA")?; let mut r = section(b"DATA")?;
@@ -591,6 +628,31 @@ impl CompiledModule {
jump_tables.push(t); jump_tables.push(t);
} }
let mut r = section(b"OBJS")?;
let n = r.u32()? as usize;
let mut objects = Vec::with_capacity(n);
for _ in 0..n {
let name = r.string()?;
let class = ObjectClass::from_id(r.u8()?).ok_or(LoadError::Corrupt("Objektklasse"))?;
let parent = r.string()?;
let array = r.u8()? != 0;
objects.push(FormObject {
name,
class,
parent_form: (!parent.is_empty()).then_some(parent),
array,
});
}
let n = r.u32()? as usize;
let mut event_procs = Vec::with_capacity(n);
for _ in 0..n {
event_procs.push(HEventProc {
object: r.u16()?,
event: r.string()?,
proc: r.u16()?,
});
}
Ok(CompiledModule { Ok(CompiledModule {
name, name,
option_base, option_base,
@@ -601,6 +663,8 @@ impl CompiledModule {
procs, procs,
data, data,
jump_tables, jump_tables,
objects,
event_procs,
}) })
} }
} }
@@ -660,8 +724,13 @@ mod tests {
local_names: vec![], local_names: vec![],
code: vec![Instr::Stmt(1), Instr::PushStr(0), Instr::End], code: vec![Instr::Stmt(1), Instr::PushStr(0), Instr::End],
}], }],
data: vec![DataItem { text: "1.5".into(), line: 3 }], data: vec![DataItem {
text: "1.5".into(),
line: 3,
}],
jump_tables: vec![vec![4, 9]], jump_tables: vec![vec![4, 9]],
objects: vec![],
event_procs: vec![],
}; };
let bytes = m.to_tbc(); let bytes = m.to_tbc();
let back = CompiledModule::from_tbc(&bytes).unwrap(); let back = CompiledModule::from_tbc(&bytes).unwrap();
@@ -687,6 +756,8 @@ mod tests {
procs: vec![], procs: vec![],
data: vec![], data: vec![],
jump_tables: vec![], jump_tables: vec![],
objects: vec![],
event_procs: vec![],
}; };
let mut bytes = m.to_tbc(); let mut bytes = m.to_tbc();
bytes[4] = 0xFF; // Version hochsetzen bytes[4] = 0xFF; // Version hochsetzen

View File

@@ -43,9 +43,14 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
data: hir data: hir
.data .data
.iter() .iter()
.map(|d| DataItem { text: d.text.clone(), line: d.line }) .map(|d| DataItem {
text: d.text.clone(),
line: d.line,
})
.collect(), .collect(),
jump_tables: cg.jump_tables, jump_tables: cg.jump_tables,
objects: hir.objects.clone(),
event_procs: hir.event_procs.clone(),
} }
} }
@@ -68,6 +73,7 @@ fn type_init(t: &HTy) -> TypeInit {
HTy::Str => TypeInit::Str, HTy::Str => TypeInit::Str,
HTy::FixedStr(n) => TypeInit::FixedStr(*n), HTy::FixedStr(n) => TypeInit::FixedStr(*n),
HTy::Udt(id) => TypeInit::Udt(*id), HTy::Udt(id) => TypeInit::Udt(*id),
HTy::Form | HTy::Control => TypeInit::Empty,
} }
} }
@@ -169,8 +175,8 @@ impl Codegen {
for (idx, label) in std::mem::take(&mut ctx.fixups) { for (idx, label) in std::mem::take(&mut ctx.fixups) {
// Modulweites `ON ERROR GOTO` in einer Prozedur: das Label lebt // Modulweites `ON ERROR GOTO` in einer Prozedur: das Label lebt
// im Modulrumpf, nicht im eigenen. // im Modulrumpf, nicht im eigenen.
let modulweit = proc.kind != hir::HProcKind::Main let modulweit =
&& matches!(ctx.code[idx], Instr::OnErrorGoto(_)); proc.kind != hir::HProcKind::Main && matches!(ctx.code[idx], Instr::OnErrorGoto(_));
let pc = if modulweit { let pc = if modulweit {
self.modul_label_pc self.modul_label_pc
.get(label as usize) .get(label as usize)
@@ -241,6 +247,52 @@ impl Codegen {
HStmtKind::Assign { place, value } => { HStmtKind::Assign { place, value } => {
self.store_place(ctx, place, |cg, ctx| cg.expr(ctx, value)); self.store_place(ctx, place, |cg, ctx| cg.expr(ctx, value));
} }
HStmtKind::SetObjectProperty {
object,
index,
property,
value,
} => {
if let Some(index) = index {
self.expr(ctx, index);
}
self.expr(ctx, value);
ctx.emit(Instr::StoreObjectProperty(
*object,
*property,
index.is_some(),
));
}
HStmtKind::SetDynamicObjectProperty {
object,
property,
value,
} => {
self.expr(ctx, object);
self.expr(ctx, value);
let property = self.pool(property);
ctx.emit(Instr::StoreDynamicObjectProperty(property));
}
HStmtKind::ObjectMethod {
object,
method,
args,
} => {
for arg in args {
self.expr(ctx, arg);
}
ctx.emit(Instr::ObjectMethod(*object, *method, args.len() as u8));
}
HStmtKind::ObjectLoad {
object,
index,
unload,
} => {
if let Some(index) = index {
self.expr(ctx, index);
}
ctx.emit(Instr::ObjectLoad(*object, *unload, index.is_some()));
}
HStmtKind::Print { items, trailing } => { HStmtKind::Print { items, trailing } => {
for item in items { for item in items {
match item { match item {
@@ -277,12 +329,21 @@ impl Codegen {
} }
ctx.emit(Instr::Field(fields.len() as u8)); ctx.emit(Instr::Field(fields.len() as u8));
} }
HStmtKind::LsetRset { rset, target, value } => { HStmtKind::LsetRset {
rset,
target,
value,
} => {
self.make_ref(ctx, target); self.make_ref(ctx, target);
self.expr(ctx, value); self.expr(ctx, value);
ctx.emit(Instr::LsetRset(*rset)); ctx.emit(Instr::LsetRset(*rset));
} }
HStmtKind::GetPut { put, file, recnum, var } => { HStmtKind::GetPut {
put,
file,
recnum,
var,
} => {
self.expr(ctx, file); self.expr(ctx, file);
if let Some(r) = recnum { if let Some(r) = recnum {
self.expr(ctx, r); self.expr(ctx, r);
@@ -301,12 +362,19 @@ impl Codegen {
HTy::Udt(i) => (7, *i), HTy::Udt(i) => (7, *i),
// Variable Strings: Länge erst zur Laufzeit. // Variable Strings: Länge erst zur Laufzeit.
HTy::Str => (8, 0), HTy::Str => (8, 0),
HTy::Form | HTy::Control => (8, 0),
} }
} }
}; };
ctx.emit(Instr::GetPut(*put, recnum.is_some(), art, zusatz)); ctx.emit(Instr::GetPut(*put, recnum.is_some(), art, zusatz));
} }
HStmtKind::Input { file, line_mode, prompt, question, targets } => { HStmtKind::Input {
file,
line_mode,
prompt,
question,
targets,
} => {
if let Some(f) = file { if let Some(f) = file {
// Dateinummer zuerst, dann die Referenzen darüber. // Dateinummer zuerst, dann die Referenzen darüber.
self.expr(ctx, f); self.expr(ctx, f);
@@ -506,7 +574,12 @@ impl Codegen {
} }
} }
HStmtKind::Restore(idx) => ctx.emit(Instr::Restore(*idx)), HStmtKind::Restore(idx) => ctx.emit(Instr::Restore(*idx)),
HStmtKind::Dim { slot, elem, dims, redim } => { HStmtKind::Dim {
slot,
elem,
dims,
redim,
} => {
for (lo, hi) in dims { for (lo, hi) in dims {
self.expr(ctx, lo); self.expr(ctx, lo);
self.expr(ctx, hi); self.expr(ctx, hi);
@@ -637,6 +710,36 @@ impl Codegen {
ctx.emit(Instr::PushStr(idx)); ctx.emit(Instr::PushStr(idx));
} }
HExpr::Load(p) => self.load_place(ctx, p), HExpr::Load(p) => self.load_place(ctx, p),
HExpr::ObjectProperty {
object,
index,
property,
..
} => {
if let Some(index) = index {
self.expr(ctx, index);
}
ctx.emit(Instr::LoadObjectProperty(
*object,
*property,
index.is_some(),
));
}
HExpr::DynamicObjectProperty { object, property } => {
self.expr(ctx, object);
let property = self.pool(property);
ctx.emit(Instr::LoadDynamicObjectProperty(property));
}
HExpr::ObjectRef { object, index, .. } => {
if let Some(index) = index {
self.expr(ctx, index);
}
ctx.emit(Instr::PushObject(*object, index.is_some()));
}
HExpr::TypeOf { value, class } => {
self.expr(ctx, value);
ctx.emit(Instr::TypeOf(class.id()));
}
HExpr::Conv { from, to, arg } => { HExpr::Conv { from, to, arg } => {
self.expr(ctx, arg); self.expr(ctx, arg);
emit_conv(ctx, *from, *to); emit_conv(ctx, *from, *to);
@@ -1276,7 +1379,12 @@ mod tests {
let m = compile_src("SUB Inc (x%)\nx% = x% + 1\nEND SUB\nn% = 1\nInc n%\nInc (n%)"); let m = compile_src("SUB Inc (x%)\nx% = x% + 1\nEND SUB\nn% = 1\nInc n%\nInc (n%)");
let code = main_code(&m); let code = main_code(&m);
assert!(code.contains(&Instr::MakeRefGlobal(0))); assert!(code.contains(&Instr::MakeRefGlobal(0)));
assert!(code.iter().filter(|i| matches!(i, Instr::Call(1, 1))).count() == 2); assert!(
code.iter()
.filter(|i| matches!(i, Instr::Call(1, 1)))
.count()
== 2
);
// Prozedurrumpf liest/schreibt über Referenz // Prozedurrumpf liest/schreibt über Referenz
let sub = &m.procs[1].code; let sub = &m.procs[1].code;
assert!(sub.contains(&Instr::LoadRef(0))); assert!(sub.contains(&Instr::LoadRef(0)));
@@ -1309,7 +1417,12 @@ mod tests {
let m = compile_src("DATA 1, 2\nREAD a%, b%\nRESTORE\nREAD c%"); let m = compile_src("DATA 1, 2\nREAD a%, b%\nRESTORE\nREAD c%");
let code = main_code(&m); let code = main_code(&m);
assert_eq!(m.data.len(), 2); assert_eq!(m.data.len(), 2);
assert!(code.iter().filter(|i| matches!(i, Instr::ReadData(1))).count() == 3); assert!(
code.iter()
.filter(|i| matches!(i, Instr::ReadData(1)))
.count()
== 3
);
assert!(code.contains(&Instr::Restore(0))); assert!(code.contains(&Instr::Restore(0)));
assert!(code.contains(&Instr::ConvR8I2)); assert!(code.contains(&Instr::ConvR8I2));
} }
@@ -1318,7 +1431,9 @@ mod tests {
fn fehlerbehandlung_emit() { fn fehlerbehandlung_emit() {
let m = compile_src("ON ERROR GOTO H\nERROR 5\nEND\nH:\nRESUME NEXT"); let m = compile_src("ON ERROR GOTO H\nERROR 5\nEND\nH:\nRESUME NEXT");
let code = main_code(&m); let code = main_code(&m);
assert!(code.iter().any(|i| matches!(i, Instr::OnErrorGoto(t) if *t > 0))); assert!(code
.iter()
.any(|i| matches!(i, Instr::OnErrorGoto(t) if *t > 0)));
assert!(code.contains(&Instr::RaiseError)); assert!(code.contains(&Instr::RaiseError));
assert!(code.contains(&Instr::ResumeNext)); assert!(code.contains(&Instr::ResumeNext));
} }

View File

@@ -17,6 +17,7 @@ use tb_runtime::errors::RuntimeError;
use tb_runtime::host::Host; use tb_runtime::host::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};
/// Warum die VM die Kontrolle abgibt. /// Warum die VM die Kontrolle abgibt.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -24,12 +25,24 @@ pub enum RunEvent {
/// `END`, `SYSTEM` oder Programmende. /// `END`, `SYSTEM` oder Programmende.
Ended, Ended,
/// `STOP` — VM-Zustand bleibt fortsetzbar (IDE: CONT). /// `STOP` — VM-Zustand bleibt fortsetzbar (IDE: CONT).
Stopped { line: u32 }, Stopped {
Breakpoint { line: u32 }, line: u32,
Stepped { line: u32 }, },
Interrupted { line: u32 }, Breakpoint {
line: u32,
},
Stepped {
line: u32,
},
Interrupted {
line: u32,
},
/// Unbehandelter Laufzeitfehler. /// Unbehandelter Laufzeitfehler.
Error { code: u16, line: u32, message: String }, Error {
code: u16,
line: u32,
message: String,
},
} }
const F_STEP: u32 = 1; const F_STEP: u32 = 1;
@@ -58,6 +71,15 @@ struct Frame {
/// Satz würde dem Handler leere Modulvariablen zeigen. Sein `RETURN` /// Satz würde dem Handler leere Modulvariablen zeigen. Sein `RETURN`
/// beendet den Handler (design.md, D2). /// beendet den Handler (design.md, D2).
trap: Option<Quelle>, trap: Option<Quelle>,
waiting_form: Option<u16>,
pending_show: Option<u16>,
form_event: Option<FormEventReturn>,
}
#[derive(Clone, Copy)]
enum FormEventReturn {
Normal,
Unload(u16),
} }
pub struct Vm { pub struct Vm {
@@ -84,6 +106,7 @@ pub struct Vm {
tick_zaehler: u32, tick_zaehler: u32,
breakpoints: HashSet<u32>, breakpoints: HashSet<u32>,
data_ptr: usize, data_ptr: usize,
pub forms: FormsModel,
} }
/// Quellenarten, wie der Codegenerator sie kodiert. /// Quellenarten, wie der Codegenerator sie kodiert.
@@ -126,6 +149,7 @@ impl Vm {
.iter() .iter()
.map(|t| default_value(t, &module.udts)) .map(|t| default_value(t, &module.udts))
.collect(); .collect();
let forms = FormsModel::new(module.objects.clone(), 80, 25);
let mut vm = Vm { let mut vm = Vm {
globals, globals,
locals: Vec::new(), locals: Vec::new(),
@@ -142,6 +166,7 @@ impl Vm {
tick_zaehler: 0, tick_zaehler: 0,
breakpoints: HashSet::new(), breakpoints: HashSet::new(),
data_ptr: 0, data_ptr: 0,
forms,
module, module,
}; };
// ISAM leitet das Satzlayout aus dem Typ der `OPEN`-Anweisung ab und // ISAM leitet das Satzlayout aus dem Typ der `OPEN`-Anweisung ab und
@@ -173,9 +198,156 @@ impl Vm {
last_stmt_pc: 0, last_stmt_pc: 0,
line: 0, line: 0,
trap: None, trap: None,
waiting_form: None,
pending_show: None,
form_event: None,
}); });
} }
pub fn queue_form_event(&mut self, event: FormEvent) {
self.forms.queue(event);
}
fn form_value(v: PropertyValue) -> Value {
match v {
PropertyValue::Integer(v) => Value::Int(v as i16),
PropertyValue::Single(v) => Value::Sng(v),
PropertyValue::String(v) => Value::Str(Rc::from(v)),
PropertyValue::Boolean(v) => Value::Int(if v { -1 } else { 0 }),
PropertyValue::Object(v) => v
.map(|(object, index)| Value::Obj(object, index))
.unwrap_or(Value::Empty),
PropertyValue::IntegerArray(_) => Value::Empty,
}
}
fn property_value(
&self,
object: u16,
property: u16,
v: Value,
) -> Result<PropertyValue, RuntimeError> {
let class = self
.module
.objects
.get(object as usize)
.ok_or(RuntimeError(420))?
.class;
let spec = tb_frontend::forms::properties(class)
.get(property as usize)
.copied()
.ok_or(RuntimeError(422))?;
Ok(match (spec.ty, v) {
(tb_frontend::forms::PropertyType::Integer, Value::Int(v)) => {
PropertyValue::Integer(v as i32)
}
(tb_frontend::forms::PropertyType::Integer, Value::Lng(v)) => PropertyValue::Integer(v),
(tb_frontend::forms::PropertyType::Boolean, Value::Int(v)) => {
PropertyValue::Boolean(v != 0)
}
(tb_frontend::forms::PropertyType::Boolean, Value::Lng(v)) => {
PropertyValue::Boolean(v != 0)
}
(tb_frontend::forms::PropertyType::Single, Value::Sng(v)) => PropertyValue::Single(v),
(tb_frontend::forms::PropertyType::String, Value::Str(v)) => {
PropertyValue::String(v.to_string())
}
(tb_frontend::forms::PropertyType::Object, Value::Obj(object, index)) => {
PropertyValue::Object(Some((object, index)))
}
_ => return Err(RuntimeError::TYPE_MISMATCH),
})
}
fn dispatch_form_event(&mut self, event: FormEvent, on_return: FormEventReturn) -> bool {
if self
.module
.objects
.get(event.object as usize)
.is_some_and(|o| {
!matches!(
o.class,
tb_frontend::forms::ObjectClass::Form | tb_frontend::forms::ObjectClass::Screen
)
})
{
let _ = self
.forms
.set_active_control(event.object, event.array_index);
}
let Some(binding) = self
.module
.event_procs
.iter()
.find(|e| e.object == event.object && e.event.eq_ignore_ascii_case(&event.name))
.cloned()
else {
return false;
};
if let Some(index) = event.array_index {
self.push(Value::Int(index as i16));
}
for arg in event.args {
self.push(Self::form_value(arg));
}
self.push_frame(
binding.proc as usize,
self.module.procs[binding.proc as usize].n_params as usize,
);
if let Some(frame) = self.frames.last_mut() {
frame.form_event = Some(on_return);
}
true
}
fn dispatch_next_form_event(&mut self) -> bool {
while let Some(event) = self.forms.next_event() {
if self.dispatch_form_event(event, FormEventReturn::Normal) {
return true;
}
}
false
}
fn request_unload(&mut self, object: u16) -> Result<(), RuntimeError> {
let event = FormEvent {
object,
array_index: None,
name: "UNLOAD".into(),
args: vec![PropertyValue::Integer(0)],
};
if !self.dispatch_form_event(event, FormEventReturn::Unload(object)) {
self.forms.unload_with(object, |_| {})?;
}
Ok(())
}
fn forms_zustellen(&mut self) -> bool {
self.forms
.resize(self.rt.screen.cols(), self.rt.screen.rows());
while let Some(m) = self.rt.maus.pop_front() {
if let Some(object) = self.forms.active_form() {
let name = match m.art {
tb_runtime::host::MausArt::Druck => "MOUSEDOWN",
tb_runtime::host::MausArt::Loslassen => "MOUSEUP",
tb_runtime::host::MausArt::Bewegung => "MOUSEMOVE",
};
self.forms.queue(FormEvent {
object,
array_index: None,
name: name.into(),
args: vec![
PropertyValue::Integer(m.taste as i32),
PropertyValue::Integer(m.shift as i32),
PropertyValue::Single(m.spalte as f32),
PropertyValue::Single(m.zeile as f32),
],
});
}
}
self.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 {
@@ -243,6 +415,9 @@ impl Vm {
last_stmt_pc: 0, last_stmt_pc: 0,
line: 0, line: 0,
trap: Some(q), trap: Some(q),
waiting_form: None,
pending_show: None,
form_event: None,
}); });
} }
@@ -358,6 +533,19 @@ impl Vm {
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 !self.forms.is_visible(form) {
self.frames.last_mut().unwrap().waiting_form = None;
} else {
self.rt.tick(host);
self.forms_zustellen();
if self.rt.ende {
return RunEvent::Ended;
}
std::thread::yield_now();
continue;
}
}
let frame = self.frames.last_mut().expect("kein Frame"); let frame = self.frames.last_mut().expect("kein Frame");
let proc = frame.proc; let proc = frame.proc;
let pc = frame.pc; let pc = frame.pc;
@@ -642,6 +830,9 @@ impl Vm {
self.rt.tick(host); self.rt.tick(host);
self.rt.screen.veraenderung_quittieren(); self.rt.screen.veraenderung_quittieren();
} }
if self.forms_zustellen() {
return Ok(Flow::Normal);
}
// Ereigniszustellung (design.md, D1): nur wenn überhaupt // Ereigniszustellung (design.md, D1): nur wenn überhaupt
// ein Trap definiert ist — sonst kostet die Grenze nichts. // ein Trap definiert ist — sonst kostet die Grenze nichts.
if self.rt.traps.aktiv() { if self.rt.traps.aktiv() {
@@ -711,6 +902,7 @@ impl Vm {
// 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.rt.tick(host);
self.forms_zustellen();
self.zustellen(host); self.zustellen(host);
Ok(Flow::Normal) Ok(Flow::Normal)
} }
@@ -757,6 +949,202 @@ impl Vm {
self.push(Value::Str(self.module.strings[i as usize].clone())); self.push(Value::Str(self.module.strings[i as usize].clone()));
Ok(Flow::Normal) Ok(Flow::Normal)
} }
I::LoadObjectProperty(object, property, has_index) => {
let index = if has_index {
Some(self.pop_i32()?)
} else {
None
};
if !self.forms.is_loaded_at(object, index) {
self.forms.ensure_loaded_at(object, index)?;
if index.is_none() && self.dispatch_next_form_event() {
let caller = self.frames.len() - 2;
self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
return Ok(Flow::Normal);
}
}
let value = self.forms.get_at(object, index, property)?;
self.push(Self::form_value(value));
Ok(Flow::Normal)
}
I::StoreObjectProperty(object, property, has_index) => {
let value = self.pop()?;
let index = if has_index {
Some(self.pop_i32()?)
} else {
None
};
if !self.forms.is_loaded_at(object, index) {
self.forms.ensure_loaded_at(object, index)?;
if index.is_none() {
self.push(value.clone());
}
if index.is_none() && self.dispatch_next_form_event() {
let caller = self.frames.len() - 2;
self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
return Ok(Flow::Normal);
}
if index.is_none() {
let value = self.pop()?;
let value = self.property_value(object, property, value)?;
self.forms.set_at(object, index, property, value)?;
return Ok(Flow::Normal);
}
}
let value = self.property_value(object, property, value)?;
self.forms.set_at(object, index, property, value)?;
Ok(Flow::Normal)
}
I::PushObject(object, has_index) => {
let index = if has_index {
Some(self.pop_i32()?)
} else {
None
};
self.forms.ensure_loaded_at(object, index)?;
self.push(Value::Obj(object, index));
self.dispatch_next_form_event();
Ok(Flow::Normal)
}
I::LoadDynamicObjectProperty(property) => {
let Value::Obj(object, index) = self.pop()? else {
return Err(RuntimeError::TYPE_MISMATCH);
};
let class = self
.module
.objects
.get(object as usize)
.ok_or(RuntimeError(420))?
.class;
let name = self
.module
.strings
.get(property as usize)
.ok_or(RuntimeError(422))?;
let property = tb_frontend::forms::property(class, name)
.map(|(property, _)| property)
.ok_or(RuntimeError(422))?;
let value = self.forms.get_at(object, index, property)?;
self.push(Self::form_value(value));
Ok(Flow::Normal)
}
I::StoreDynamicObjectProperty(property) => {
let value = self.pop()?;
let Value::Obj(object, index) = self.pop()? else {
return Err(RuntimeError::TYPE_MISMATCH);
};
let class = self
.module
.objects
.get(object as usize)
.ok_or(RuntimeError(420))?
.class;
let name = self
.module
.strings
.get(property as usize)
.ok_or(RuntimeError(422))?;
let property = tb_frontend::forms::property(class, name)
.map(|(property, _)| property)
.ok_or(RuntimeError(422))?;
let value = self.property_value(object, property, value)?;
self.forms.set_at(object, index, property, value)?;
Ok(Flow::Normal)
}
I::TypeOf(class) => {
let matches = match self.pop()? {
Value::Obj(object, _) => self
.module
.objects
.get(object as usize)
.is_some_and(|o| o.class.id() == class),
_ => false,
};
self.push(Value::Int(if matches { -1 } else { 0 }));
Ok(Flow::Normal)
}
I::ObjectMethod(object, method, argc) => {
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 resumed_show = self.frames.last().unwrap().pending_show == Some(object);
if resumed_show {
self.frames.last_mut().unwrap().pending_show = None;
} else if class == tb_frontend::forms::ObjectClass::Form
&& name == "SHOW"
&& !self.forms.is_loaded(object)
{
self.forms.ensure_loaded(object)?;
if self.dispatch_next_form_event() {
let caller = self.frames.len() - 2;
self.frames[caller].pending_show = Some(object);
self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
return Ok(Flow::Normal);
}
}
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(self.pop()?);
}
args.reverse();
if resumed_show && !self.forms.is_loaded(object) {
return Ok(Flow::Normal);
}
match (class, name) {
(tb_frontend::forms::ObjectClass::Form, "SHOW") => {
let style = match args.first() {
None => 0,
Some(Value::Int(v)) => *v as i32,
Some(Value::Lng(v)) => *v,
_ => return Err(RuntimeError::TYPE_MISMATCH),
};
if !matches!(style, 0 | 1) {
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
}
if self.forms.show(object, style == 1)? == ShowResult::ModalWait {
self.frames.last_mut().unwrap().waiting_form = Some(object);
}
}
(tb_frontend::forms::ObjectClass::Form, "HIDE") => self.forms.hide(object)?,
(tb_frontend::forms::ObjectClass::Form, "LOAD") => {
self.forms.ensure_loaded(object)?
}
(tb_frontend::forms::ObjectClass::Form, "UNLOAD") => {
self.request_unload(object)?
}
(tb_frontend::forms::ObjectClass::Screen, "SHOW") => {
self.forms.screen_show(true)
}
(tb_frontend::forms::ObjectClass::Screen, "HIDE") => {
self.forms.screen_show(false)
}
_ => return Err(RuntimeError::FEATURE_UNAVAILABLE),
}
self.dispatch_next_form_event();
Ok(Flow::Normal)
}
I::ObjectLoad(object, unload, has_index) => {
if has_index {
let index = self.pop_i32()?;
if unload {
self.forms.unload_array(object, index)?;
} else {
self.forms.load_array(object, index)?;
}
} else if unload {
self.request_unload(object)?;
} else {
self.forms.ensure_loaded(object)?;
self.dispatch_next_form_event();
}
Ok(Flow::Normal)
}
I::Dup => { I::Dup => {
let v = self.stack.last().cloned().ok_or(RuntimeError(51))?; let v = self.stack.last().cloned().ok_or(RuntimeError(51))?;
self.push(v); self.push(v);
@@ -906,8 +1294,7 @@ impl Vm {
let dst = self.pop_rec()?; let dst = self.pop_rec()?;
let src = self.pop_rec()?; let src = self.pop_rec()?;
if !Rc::ptr_eq(&dst, &src) { if !Rc::ptr_eq(&dst, &src) {
let copied: Vec<Value> = let copied: Vec<Value> = src.borrow().fields.iter().map(deep_copy).collect();
src.borrow().fields.iter().map(deep_copy).collect();
dst.borrow_mut().fields = copied; dst.borrow_mut().fields = copied;
} }
Ok(Flow::Normal) Ok(Flow::Normal)
@@ -1334,7 +1721,25 @@ impl Vm {
Ok(Flow::Normal) Ok(Flow::Normal)
} }
I::RetProc => { I::RetProc => {
let event = self.frames.last().and_then(|f| f.form_event);
let cancel = self
.frames
.last()
.and_then(|f| {
let base = f.locals_base;
match self.locals.get(base) {
Some(Value::Int(v)) => Some(*v),
_ => None,
}
})
.unwrap_or(0);
self.pop_frame(); self.pop_frame();
if let Some(FormEventReturn::Unload(object)) = event {
self.forms.unload_with(object, |c| *c = cancel)?;
}
if event.is_some() {
self.dispatch_next_form_event();
}
Ok(Flow::Normal) Ok(Flow::Normal)
} }
I::RetFn => { I::RetFn => {
@@ -1616,13 +2021,7 @@ impl Vm {
match treffer { match treffer {
Some((nummer, start, laenge)) => { Some((nummer, start, laenge)) => {
let datei = self.rt.dateien.get(nummer)?; let datei = self.rt.dateien.get(nummer)?;
tb_runtime::fileio::feld_setzen( tb_runtime::fileio::feld_setzen(&mut datei.puffer, start, laenge, text, rset);
&mut datei.puffer,
start,
laenge,
text,
rset,
);
let neu = tb_runtime::fileio::feld_lesen(&datei.puffer, start, laenge); let neu = tb_runtime::fileio::feld_lesen(&datei.puffer, start, laenge);
self.write_ref(&ziel, Value::Str(Rc::from(neu.as_str())))?; self.write_ref(&ziel, Value::Str(Rc::from(neu.as_str())))?;
} }
@@ -1918,7 +2317,10 @@ fn input_value(target: &Value, text: &str) -> Option<Value> {
let t = text.trim(); let t = text.trim();
match target { match target {
Value::Str(_) => { Value::Str(_) => {
let s = t.strip_prefix('"').and_then(|s| s.strip_suffix('"')).unwrap_or(t); let s = t
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or(t);
Some(Value::Str(Rc::from(s))) Some(Value::Str(Rc::from(s)))
} }
Value::Int(_) => { Value::Int(_) => {
@@ -1946,4 +2348,3 @@ fn strict_number(t: &str) -> Option<f64> {
let cleaned = t.replace(['d', 'D'], "E").replace('e', "E"); let cleaned = t.replace(['d', 'D'], "E").replace('e', "E");
cleaned.parse::<f64>().ok() cleaned.parse::<f64>().ok()
} }

View File

@@ -27,3 +27,17 @@ pub fn compile_source(
let hir = analysis.hir.expect("diagnose-frei, aber kein HIR"); let hir = analysis.hir.expect("diagnose-frei, aber kein HIR");
Ok(codegen::compile(&hir)) Ok(codegen::compile(&hir))
} }
pub fn compile_source_with_forms(
module_name: &str,
source: &str,
forms: &tb_frontend::forms::FormCatalog,
) -> Result<bytecode::CompiledModule, Vec<Diagnostic>> {
let analysis = tb_frontend::analyze_source_with_forms(module_name, source, forms);
if !analysis.diagnostics.is_empty() {
return Err(analysis.diagnostics);
}
Ok(codegen::compile(
&analysis.hir.expect("diagnose-frei, aber kein HIR"),
))
}

View File

@@ -99,7 +99,10 @@ fn gemischte_arithmetik() {
#[test] #[test]
fn logik_bitweise() { fn logik_bitweise() {
assert_eq!(out("PRINT 6 AND 3; 6 OR 3; 6 XOR 3; NOT 0"), " 2 7 5 -1 \n"); assert_eq!(
out("PRINT 6 AND 3; 6 OR 3; 6 XOR 3; NOT 0"),
" 2 7 5 -1 \n"
);
// Operanden werden gerundet: 1.5 AND 1 → 2 AND 1 = 0 // Operanden werden gerundet: 1.5 AND 1 → 2 AND 1 = 0
assert_eq!(out("PRINT 1.5 AND 1"), " 0 \n"); assert_eq!(out("PRINT 1.5 AND 1"), " 0 \n");
} }
@@ -122,12 +125,18 @@ fn stringvergleich_und_verkettung() {
#[test] #[test]
fn for_ohne_durchlauf() { fn for_ohne_durchlauf() {
// Spec-Szenario: FOR i% = 3 TO 1 → Körper wird nicht betreten // Spec-Szenario: FOR i% = 3 TO 1 → Körper wird nicht betreten
assert_eq!(out("FOR i% = 3 TO 1\nPRINT i%\nNEXT\nPRINT \"ende\""), "ende\n"); assert_eq!(
out("FOR i% = 3 TO 1\nPRINT i%\nNEXT\nPRINT \"ende\""),
"ende\n"
);
} }
#[test] #[test]
fn for_mit_negativem_step() { fn for_mit_negativem_step() {
assert_eq!(out("FOR i% = 3 TO 1 STEP -1\nPRINT i%;\nNEXT\nPRINT"), " 3 2 1 \n"); assert_eq!(
out("FOR i% = 3 TO 1 STEP -1\nPRINT i%;\nNEXT\nPRINT"),
" 3 2 1 \n"
);
} }
#[test] #[test]
@@ -172,11 +181,13 @@ fn exit_for_und_do() {
fn kontrollfluss_korpusdatei() { fn kontrollfluss_korpusdatei() {
// 5.2-Verifikation: kontrollfluss.bas byte-genau korrekt. // 5.2-Verifikation: kontrollfluss.bas byte-genau korrekt.
let src = std::fs::read_to_string( let src = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/compat/kontrollfluss.bas"), std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/compat/kontrollfluss.bas"),
) )
.unwrap(); .unwrap();
let want = std::fs::read_to_string( let want = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/compat/kontrollfluss.out"), std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/compat/kontrollfluss.out"),
) )
.unwrap(); .unwrap();
assert_eq!(out(&src), want); assert_eq!(out(&src), want);
@@ -268,7 +279,8 @@ fn data_read_restore() {
// Anmerkung: unquotierte DATA-Texte verlieren derzeit die // Anmerkung: unquotierte DATA-Texte verlieren derzeit die
// Groß-/Kleinschreibung (Lexer normalisiert Bezeichner); // Groß-/Kleinschreibung (Lexer normalisiert Bezeichner);
// Rohtext-Erhalt ist als Aufgabe in PLAN.md Phase 3 eingeplant. // Rohtext-Erhalt ist als Aufgabe in PLAN.md Phase 3 eingeplant.
let src = "DATA 1, 2.5, \"hallo\"\nREAD a%, b!, c$\nPRINT a%; b!; c$\nRESTORE\nREAD x%\nPRINT x%"; let src =
"DATA 1, 2.5, \"hallo\"\nREAD a%, b!, c$\nPRINT a%; b!; c$\nRESTORE\nREAD x%\nPRINT x%";
assert_eq!(out(src), " 1 2.5 hallo\n 1 \n"); assert_eq!(out(src), " 1 2.5 hallo\n 1 \n");
} }
@@ -444,10 +456,7 @@ fn end_und_system() {
#[test] #[test]
fn input_mit_redo() { fn input_mit_redo() {
let (ev, output) = run_with_input( let (ev, output) = run_with_input("INPUT \"Zahl\"; n%\nPRINT n% * 2", &["abc", "21"]);
"INPUT \"Zahl\"; n%\nPRINT n% * 2",
&["abc", "21"],
);
assert_eq!(ev, RunEvent::Ended); assert_eq!(ev, RunEvent::Ended);
assert!(output.contains("Redo from start")); assert!(output.contains("Redo from start"));
assert!(output.ends_with(" 42 \n"), "{output}"); assert!(output.ends_with(" 42 \n"), "{output}");
@@ -464,7 +473,10 @@ fn line_input_liest_ganze_zeile() {
#[test] #[test]
fn mid_anweisung_mutiert() { fn mid_anweisung_mutiert() {
assert_eq!(out("s$ = \"hallo\"\nMID$(s$, 2, 2) = \"EY\"\nPRINT s$"), "hEYlo\n"); assert_eq!(
out("s$ = \"hallo\"\nMID$(s$, 2, 2) = \"EY\"\nPRINT s$"),
"hEYlo\n"
);
} }
#[test] #[test]
@@ -495,12 +507,10 @@ fn unsupported_feature_fehler_73() {
#[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( let (ev, ausgabe) = run("TYPE T\n f AS INTEGER\nEND TYPE\n\
"TYPE T\n f AS INTEGER\nEND TYPE\n\
OPEN \"db.isam\" FOR ISAM T \"Tab\" AS #1\n\ OPEN \"db.isam\" FOR ISAM T \"Tab\" AS #1\n\
PRINT \"offen\"\n\ PRINT \"offen\"\n\
CLOSE #1", CLOSE #1");
);
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}");
} }
@@ -552,7 +562,10 @@ fn groessenaenderung_wirkt_zur_laufzeit() {
let module = tb_vm::compile_source("TEST", "PRINT \"a\"\nFOR i% = 1 TO 50\nNEXT i%\n").unwrap(); let module = tb_vm::compile_source("TEST", "PRINT \"a\"\nFOR i% = 1 TO 50\nNEXT i%\n").unwrap();
let mut vm = Vm::new(module); let mut vm = Vm::new(module);
let mut host = CaptureHost::default(); let mut host = CaptureHost::default();
host.ereignis(Ereignis::Groesse { cols: 120, rows: 40 }); host.ereignis(Ereignis::Groesse {
cols: 120,
rows: 40,
});
assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!((vm.rt.screen.cols(), vm.rt.screen.rows()), (120, 40)); assert_eq!((vm.rt.screen.cols(), vm.rt.screen.rows()), (120, 40));
// Inhalt bleibt oben links erhalten. // Inhalt bleibt oben links erhalten.
@@ -639,10 +652,7 @@ fn cls_setzt_cursor_zurueck() {
#[test] #[test]
fn width_hebt_die_groesse_an() { fn width_hebt_die_groesse_an() {
assert_eq!( assert_eq!(out("WIDTH 120, 40\nPRINT SCREEN(40, 120)"), " 32 \n");
out("WIDTH 120, 40\nPRINT SCREEN(40, 120)"),
" 32 \n"
);
} }
// ---- ON ERROR-Scoping (Spec sprach-frontend) ------------------------------- // ---- ON ERROR-Scoping (Spec sprach-frontend) -------------------------------
@@ -736,8 +746,14 @@ fn time_und_now_stimmen_ueberein() {
#[test] #[test]
fn fester_versatz_verschiebt_die_uhr() { fn fester_versatz_verschiebt_die_uhr() {
let src = "PRINT INT(TIMER)"; let src = "PRINT INT(TIMER)";
let ohne: f64 = out_mit_zone(src, Zeitzone::Unbekannt).trim().parse().unwrap(); let ohne: f64 = out_mit_zone(src, Zeitzone::Unbekannt)
let mit: f64 = out_mit_zone(src, Zeitzone::Fest(3600)).trim().parse().unwrap(); .trim()
.parse()
.unwrap();
let mit: f64 = out_mit_zone(src, Zeitzone::Fest(3600))
.trim()
.parse()
.unwrap();
// Modulo Tageslänge, damit ein Mitternachtsübergang nichts kaputt macht. // Modulo Tageslänge, damit ein Mitternachtsübergang nichts kaputt macht.
let diff = (mit - ohne).rem_euclid(86_400.0); let diff = (mit - ohne).rem_euclid(86_400.0);
assert!((diff - 3600.0).abs() <= 2.0, "Differenz {diff}"); assert!((diff - 3600.0).abs() <= 2.0, "Differenz {diff}");
@@ -746,7 +762,10 @@ fn fester_versatz_verschiebt_die_uhr() {
/// `TIMER` zählt ab der lokalen Mitternacht, nicht ab der UTC-Mitternacht. /// `TIMER` zählt ab der lokalen Mitternacht, nicht ab der UTC-Mitternacht.
#[test] #[test]
fn timer_zaehlt_ab_lokaler_mitternacht() { fn timer_zaehlt_ab_lokaler_mitternacht() {
let t = out_mit_zone("PRINT INT(TIMER / 3600); VAL(LEFT$(TIME$, 2))", Zeitzone::Fest(-18000)); let t = out_mit_zone(
"PRINT INT(TIMER / 3600); VAL(LEFT$(TIME$, 2))",
Zeitzone::Fest(-18000),
);
let zahlen: Vec<f64> = t let zahlen: Vec<f64> = t
.split_whitespace() .split_whitespace()
.filter_map(|w| w.parse().ok()) .filter_map(|w| w.parse().ok())
@@ -1093,3 +1112,169 @@ fn erl_nennt_die_fehlerzeile_nicht_die_handlerzeile() {
110 RESUME NEXT\n"); 110 RESUME NEXT\n");
assert_eq!(out.trim(), "20"); assert_eq!(out.trim(), "20");
} }
fn forms_catalog() -> tb_frontend::forms::FormCatalog {
use tb_frontend::forms::ObjectClass;
let mut c = tb_frontend::forms::FormCatalog::default();
c.add("Form1", ObjectClass::Form, None, false);
c.add("Text1", ObjectClass::TextBox, Some("Form1"), false);
c.add("Command1", ObjectClass::CommandButton, Some("Form1"), true);
c
}
fn form_vm(src: &str) -> Vm {
let module = tb_vm::compile_source_with_forms("FORM1", src, &forms_catalog())
.unwrap_or_else(|d| panic!("Compile-Fehler: {d:?}"));
Vm::new(module)
}
#[test]
fn objektzugriff_laeuft_ueber_objekt_und_eigenschaftsindex() {
let mut vm = form_vm("Form1!Text1.Text = \"hallo\"\nPRINT Form1!Text1.Text");
let ev = vm.run(&mut CaptureHost::default());
assert_eq!(ev, RunEvent::Ended);
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "hallo");
}
#[test]
fn steuerarray_eigenschaften_und_objektparameter_funktionieren() {
let mut vm = form_vm(
"LOAD Command1(3)\nCommand1(3).Caption = \"drei\"\nPRINT Command1(3).Caption\n\
CALL Aus(Text1)\nPRINT Text1.Visible\nCALL FormAus(Form1)\nPRINT Form1.Caption\nEND\n\
SUB Aus(c AS CONTROL)\nPRINT TYPEOF c IS TextBox\nc.Visible = 0\nEND SUB\n\
SUB FormAus(f AS FORM)\nPRINT TYPEOF f IS Form\nf.Caption = \"Formular\"\nEND SUB",
);
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
assert_eq!(
tb_runtime::snapshot::text(&vm.rt.screen).trim(),
"drei\n-1 \n 0 \n-1 \nFormular"
);
}
#[test]
fn modales_show_setzt_nach_unload_fort() {
let mut vm = form_vm(
"DIM SHARED gesehen%\nForm1.Show 1\nPRINT 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",
);
let mut host = CaptureHost::default();
host.ereignis_nach(
2,
tb_runtime::host::Ereignis::Maus(tb_runtime::host::MausEreignis {
art: tb_runtime::host::MausArt::Druck,
taste: 1,
shift: 0,
zeile: 1,
spalte: 1,
}),
);
let ev = vm.run(&mut host);
assert_eq!(ev, RunEvent::Ended);
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "1");
assert!(host.presents >= 2);
}
#[test]
fn fehlende_ereignisprozedur_verfaellt_und_typeof_prueft_klasse() {
let mut vm = form_vm("Form1.Show\nIF TYPEOF Text1 IS TextBox THEN PRINT \"ja\"");
vm.queue_form_event(tb_ui::forms::FormEvent {
object: 0,
array_index: None,
name: "CLICK".into(),
args: vec![],
});
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "ja");
}
#[test]
fn mausargumente_kommen_in_zellen_an() {
use tb_runtime::host::{Ereignis, MausArt, MausEreignis};
let mut vm = form_vm(
"DIM SHARED b%, s%, gx!, gy!\nForm1.Show\nSTOP\nDOEVENTS\nEND\n\
SUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\n\
SHARED b%, s%, gx!, gy!\nb% = Button: s% = Shift: gx! = X: gy! = Y\nEND SUB",
);
let mut host = CaptureHost::default();
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
host.ereignis(Ereignis::Maus(MausEreignis {
art: MausArt::Druck,
taste: 1,
shift: 2,
zeile: 5,
spalte: 7,
}));
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert!(matches!(vm.inspect("b"), Some(Value::Int(1))));
assert!(matches!(vm.inspect("s"), Some(Value::Int(2))));
assert!(matches!(vm.inspect("gx"), Some(Value::Sng(7.0))));
assert!(matches!(vm.inspect("gy"), Some(Value::Sng(5.0))));
}
#[test]
fn form_load_laeuft_vor_dem_sichtbarsetzen() {
let mut vm = form_vm(
"Form1.Show\nEND\n\
SUB Form_Load()\nPRINT Form1.Visible\nEND SUB",
);
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "0");
}
#[test]
fn steuerarray_ereignis_stellt_index_voran() {
let mut vm = form_vm(
"DIM SHARED seen%\nSTOP\nDOEVENTS\nEND\n\
SUB Command1_Click(Index AS INTEGER)\nSHARED seen%\nseen% = Index\nEND SUB",
);
let mut host = CaptureHost::default();
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
vm.forms.load_array(2, 3).unwrap();
vm.queue_form_event(tb_ui::forms::FormEvent {
object: 2,
array_index: Some(3),
name: "CLICK".into(),
args: vec![],
});
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert!(matches!(vm.inspect("seen"), Some(Value::Int(3))));
let active =
tb_frontend::forms::property(tb_frontend::forms::ObjectClass::Screen, "ACTIVECONTROL")
.unwrap()
.0;
assert_eq!(
vm.forms.get(3, active).unwrap(),
tb_ui::forms::PropertyValue::Object(Some((2, Some(3))))
);
}
#[test]
fn form_load_laeuft_einmal_und_unload_cancel_verhindert_entladen() {
let mut vm = form_vm(
"DIM SHARED loads%\nx$ = Form1.Caption\nForm1.Hide\nx$ = Form1.Caption\n\
Form1.Show\nUNLOAD Form1\nEND\n\
SUB Form_Load()\nSHARED loads%\nloads% = loads% + 1\nEND SUB\n\
SUB Form_Unload(Cancel AS INTEGER)\nCancel = 1\nEND SUB",
);
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
assert!(matches!(vm.inspect("loads"), Some(Value::Int(1))));
assert!(vm.forms.objects[0].loaded);
assert!(vm.forms.objects[0].visible);
}
#[test]
fn objektcode_erzeugt_keine_zusaetzlichen_zustellopcodes() {
let module = tb_vm::compile_source_with_forms(
"FORM1",
"Form1.Caption = \"x\"\nDOEVENTS\nx$ = Form1.Caption",
&forms_catalog(),
)
.unwrap();
let n = module.procs[0]
.code
.iter()
.filter(|i| matches!(i, tb_vm::bytecode::Instr::Doevents))
.count();
assert_eq!(n, 1, "nur das explizite DOEVENTS ist ein Zustellopcode");
}

View File

@@ -4,6 +4,14 @@ Rekonstruierte Referenz der Forms-Engine des Vorbilds (VBDOS 1.0) —
Grundlage für `tb-ui::forms` und den Formular-Designer. Primärquellen: Grundlage für `tb-ui::forms` und den Formular-Designer. Primärquellen:
Original-Hilfedatei (dekodiert), `CONSTANT.BI`, Stringtabellen der Original-Hilfedatei (dekodiert), `CONSTANT.BI`, Stringtabellen der
Original-Programme, Beispielprojekte. Aussagen ohne TODO sind daraus belegt. Original-Programme, Beispielprojekte. Aussagen ohne TODO sind daraus belegt.
Die zeilenweise Matrix mit Typ, Bereich, Vorgabe, Laufzeit-Schreibbarkeit
und Klassenquelle liegt im Change-Dokument
`openspec/changes/phase-4-objektmodell/klassen-und-eigenschaften.md`.
Als Beispiel-Gegenprobe dient `cout/vbdos` Commit
`1cdd2b32b829fe1721d0b6aecc433abc47a96fb6`; dessen binäre Formulare
`microsoft/check.frm`, `notepad.frm`, `qlbview.frm`, `seek.frm`,
`spindemo.frm`, `graphics/graphics.frm` und `misc/mentors/mentors.frm`
enthalten die dort ausgewiesenen Eigenschaften und Ereignisprozeduren.
## Koordinatenmodell ## Koordinatenmodell
@@ -135,6 +143,9 @@ DragOver-State 0 Enter · 1 Leave · 2 Over. Bei Steuerelement-Arrays steht
`Index AS INTEGER` immer vorn. KeyAscii ist bei uns ein Unicode-Codepoint `Index AS INTEGER` immer vorn. KeyAscii ist bei uns ein Unicode-Codepoint
(UTF-8-Abweichung); klassische KeyCode-Konstanten bleiben gültig (UTF-8-Abweichung); klassische KeyCode-Konstanten bleiben gültig
(Vorbild-Eigenheit: Entf = 127). (Vorbild-Eigenheit: Entf = 127).
Ereignisse ohne eine der oben genannten Argumentfamilien haben keine
Parameter. Bei Arrays wird ausschließlich `Index AS INTEGER` vorangestellt;
die restliche Signatur bleibt unverändert.
## SCREEN-Objekt ## SCREEN-Objekt
@@ -154,7 +165,8 @@ Modal-Stacking-Regeln → Fehler 400403 (u. a. „MDI form cannot be shown
modally"). `HIDE` setzt nur `Visible = False`; `LOAD`/`UNLOAD` mit modally"). `HIDE` setzt nur `Visible = False`; `LOAD`/`UNLOAD` mit
Load-/Unload-Ereignis (Cancel-Parameter). `LOAD/UNLOAD ctl(index%)` für Load-/Unload-Ereignis (Cancel-Parameter). `LOAD/UNLOAD ctl(index%)` für
Steuerelement-Arrays (Entwurfszeit-Elemente sind nicht entladbar, Steuerelement-Arrays (Entwurfszeit-Elemente sind nicht entladbar,
Fehler 362). Fehler 362); deren Eigenschaften sind über `ctl(index%).Eigenschaft`
adressierbar und werden beim `LOAD` vom Entwurfszeit-Element 0 geklont.
## Menüsystem ## Menüsystem

View File

@@ -9,8 +9,9 @@ nicht falsch werden, ohne dass die Testsuite rot wird.
**Quellen.** `bas7advr` = Themenliste von `bas7advr.hlp` (BASIC 7 **Quellen.** `bas7advr` = Themenliste von `bas7advr.hlp` (BASIC 7
Advisor). `qb45advr` = Gegenprobe über `qb45advr.hlp`, nötig weil die Advisor). `qb45advr` = Gegenprobe über `qb45advr.hlp`, nötig weil die
erste Liste nachweislich unvollständig ist. `sprachreferenz` = erste Liste nachweislich unvollständig ist. `sprachreferenz` =
docs/sprachreferenz.md für Elemente des Forms-Teils, die keine der beiden docs/sprachreferenz.md für Sprachelemente des Forms-Teils;
Themenlisten führt. Erhebung siehe `forms-referenz` = docs/forms-referenz.md für das Forms-Objektmodell.
Erhebung siehe
`openspec/changes/phase-3-runtime-bildschirm/rohliste-original-hilfe.md`. `openspec/changes/phase-3-runtime-bildschirm/rohliste-original-hilfe.md`.
**Status.** `implementiert` = Frontend senkt ab und die Laufzeit führt **Status.** `implementiert` = Frontend senkt ab und die Laufzeit führt
@@ -21,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 227 · offen 5 · Non-Feature 53 · gesamt 285 **Abdeckung.** implementiert 682 · offen 61 · Non-Feature 53 · gesamt 796
| Name | Art | Gruppe | Status | Fundstelle | Quelle | | Name | Art | Gruppe | Status | Fundstelle | Quelle |
|---|---|---|---|---|---| |---|---|---|---|---|---|
@@ -310,3 +311,514 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
| `INKEY$` | Funktion | Tastatur | implementiert | tb-runtime::builtins | bas7advr | | `INKEY$` | Funktion | Tastatur | implementiert | tb-runtime::builtins | bas7advr |
| `INPUT$` | Funktion | Tastatur | implementiert | tb-runtime::builtins | bas7advr | | `INPUT$` | Funktion | Tastatur | implementiert | tb-runtime::builtins | bas7advr |
| `KEY` | Anweisung | Tastatur | implementiert | tb-runtime::builtins | bas7advr | | `KEY` | Anweisung | Tastatur | implementiert | tb-runtime::builtins | bas7advr |
| `$FORM` | Metabefehl | Forms/Objektsprache | implementiert | tb-frontend::lexer | forms-referenz |
| `FORM` | Datentyp | Forms/Objektsprache | implementiert | tb-frontend::sema | forms-referenz |
| `CONTROL` | Datentyp | Forms/Objektsprache | implementiert | tb-frontend::sema | forms-referenz |
| `TYPEOF … IS` | Operator | Forms/Objektsprache | implementiert | tb-vm::interp | forms-referenz |
| `!` | Operator | Forms/Objektsprache | implementiert | tb-frontend::lexer | forms-referenz |
| `FORM.BACKCOLOR` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.ENABLED` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.HEIGHT` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.LEFT` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.MOUSEPOINTER` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.PARENT` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.TAG` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.TOP` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.VISIBLE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.WIDTH` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.FORECOLOR` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.DRAGMODE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.AUTOREDRAW` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.BORDERSTYLE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.CAPTION` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.CONTROLBOX` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.CURRENTX` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.CURRENTY` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.FORMNAME` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.FORMTYPE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.MAXBUTTON` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.MINBUTTON` | 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.WINDOWSTATE` | Eigenschaft | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.CLS` | Methode | Forms/FORM | offen | - | forms-referenz |
| `FORM.DRAG` | Methode | Forms/FORM | offen | - | 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.MOVE` | Methode | Forms/FORM | offen | - | forms-referenz |
| `FORM.PRINT` | Methode | Forms/FORM | offen | - | forms-referenz |
| `FORM.PRINTFORM` | Methode | Forms/FORM | offen | - | forms-referenz |
| `FORM.REFRESH` | Methode | Forms/FORM | offen | - | forms-referenz |
| `FORM.SHOW` | Methode | Forms/FORM | implementiert | tb-ui::forms | forms-referenz |
| `FORM.TEXTHEIGHT` | Methode | Forms/FORM | offen | - | forms-referenz |
| `FORM.TEXTWIDTH` | Methode | Forms/FORM | offen | - | 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_DBLCLICK` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_DRAGDROP` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_DRAGOVER` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_GOTFOCUS` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_KEYDOWN` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_KEYPRESS` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_KEYUP` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_LOAD` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_LOSTFOCUS` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_MOUSEDOWN` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_MOUSEMOVE` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_MOUSEUP` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_PAINT` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_RESIZE` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `FORM_UNLOAD` | Ereignis | Forms/FORM | implementiert | tb-vm::interp | forms-referenz |
| `CHECKBOX.BACKCOLOR` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.ENABLED` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.HEIGHT` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.LEFT` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.MOUSEPOINTER` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.PARENT` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.TAG` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.TOP` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.VISIBLE` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.WIDTH` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.INDEX` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.TABINDEX` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.TABSTOP` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.CTLNAME` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.FORECOLOR` | 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.VALUE` | Eigenschaft | Forms/CHECKBOX | implementiert | tb-ui::forms | forms-referenz |
| `CHECKBOX.DRAG` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
| `CHECKBOX.MOVE` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
| `CHECKBOX.REFRESH` | Methode | Forms/CHECKBOX | offen | - | forms-referenz |
| `CHECKBOX.SETFOCUS` | Methode | Forms/CHECKBOX | offen | - | 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_DRAGOVER` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
| `CHECKBOX_GOTFOCUS` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
| `CHECKBOX_KEYDOWN` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
| `CHECKBOX_KEYPRESS` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
| `CHECKBOX_KEYUP` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
| `CHECKBOX_LOSTFOCUS` | Ereignis | Forms/CHECKBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX.BACKCOLOR` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.ENABLED` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.HEIGHT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.LEFT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.MOUSEPOINTER` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.PARENT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.TAG` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.TOP` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.VISIBLE` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.WIDTH` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.INDEX` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.TABINDEX` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.TABSTOP` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.CTLNAME` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.FORECOLOR` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.DRAGMODE` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.LIST` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.LISTCOUNT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.LISTINDEX` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.SORTED` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.SELLENGTH` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.SELSTART` | 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.TEXT` | Eigenschaft | Forms/COMBOBOX | implementiert | tb-ui::forms | forms-referenz |
| `COMBOBOX.ADDITEM` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
| `COMBOBOX.DRAG` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
| `COMBOBOX.MOVE` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
| `COMBOBOX.REFRESH` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
| `COMBOBOX.REMOVEITEM` | Methode | Forms/COMBOBOX | offen | - | forms-referenz |
| `COMBOBOX.SETFOCUS` | Methode | Forms/COMBOBOX | offen | - | 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_DBLCLICK` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_DRAGDROP` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_DRAGOVER` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_DROPDOWN` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_GOTFOCUS` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_KEYDOWN` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_KEYPRESS` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_KEYUP` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_LOSTFOCUS` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_MOUSEDOWN` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_MOUSEMOVE` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMBOBOX_MOUSEUP` | Ereignis | Forms/COMBOBOX | implementiert | tb-vm::interp | forms-referenz |
| `COMMANDBUTTON.BACKCOLOR` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.ENABLED` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.HEIGHT` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.LEFT` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.MOUSEPOINTER` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.PARENT` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.TAG` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.TOP` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.VISIBLE` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.WIDTH` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.INDEX` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.TABINDEX` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.TABSTOP` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.CTLNAME` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.DRAGMODE` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.CANCEL` | 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.VALUE` | Eigenschaft | Forms/COMMANDBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `COMMANDBUTTON.DRAG` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
| `COMMANDBUTTON.MOVE` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
| `COMMANDBUTTON.REFRESH` | Methode | Forms/COMMANDBUTTON | offen | - | forms-referenz |
| `COMMANDBUTTON.SETFOCUS` | Methode | Forms/COMMANDBUTTON | offen | - | 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_DRAGOVER` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `COMMANDBUTTON_GOTFOCUS` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `COMMANDBUTTON_KEYDOWN` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `COMMANDBUTTON_KEYPRESS` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `COMMANDBUTTON_KEYUP` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `COMMANDBUTTON_LOSTFOCUS` | Ereignis | Forms/COMMANDBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `DIRLISTBOX.BACKCOLOR` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.ENABLED` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.HEIGHT` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.LEFT` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.MOUSEPOINTER` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.PARENT` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.TAG` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.TOP` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.VISIBLE` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.WIDTH` | Eigenschaft | Forms/DIRLISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DIRLISTBOX.INDEX` | 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.CTLNAME` | 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_CHANGE` | Ereignis | Forms/DIRLISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `DIRLISTBOX_CLICK` | Ereignis | Forms/DIRLISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `DIRLISTBOX_DBLCLICK` | Ereignis | Forms/DIRLISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `DIRLISTBOX_PATHCHANGE` | Ereignis | Forms/DIRLISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `DRIVELISTBOX.BACKCOLOR` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.ENABLED` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.HEIGHT` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.LEFT` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.MOUSEPOINTER` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.PARENT` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.TAG` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.TOP` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.VISIBLE` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.WIDTH` | Eigenschaft | Forms/DRIVELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `DRIVELISTBOX.INDEX` | 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.CTLNAME` | 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_CHANGE` | Ereignis | Forms/DRIVELISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `FILELISTBOX.BACKCOLOR` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.ENABLED` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.HEIGHT` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.LEFT` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.MOUSEPOINTER` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.PARENT` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.TAG` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.TOP` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.VISIBLE` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.WIDTH` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.INDEX` | 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.CTLNAME` | 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.PATTERN` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.ARCHIVE` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.HIDDEN` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.NORMAL` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.READONLY` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.SYSTEM` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX.TEXT` | Eigenschaft | Forms/FILELISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `FILELISTBOX_CLICK` | Ereignis | Forms/FILELISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `FILELISTBOX_DBLCLICK` | Ereignis | Forms/FILELISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `FILELISTBOX_PATTERNCHANGE` | Ereignis | Forms/FILELISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `FILELISTBOX_PATHCHANGE` | Ereignis | Forms/FILELISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `FRAME.BACKCOLOR` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.ENABLED` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.HEIGHT` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.LEFT` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.MOUSEPOINTER` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.PARENT` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.TAG` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.TOP` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.VISIBLE` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.WIDTH` | 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.CAPTION` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.DRAG` | Methode | Forms/FRAME | offen | - | forms-referenz |
| `FRAME.MOVE` | Methode | Forms/FRAME | offen | - | forms-referenz |
| `FRAME.REFRESH` | Methode | Forms/FRAME | offen | - | forms-referenz |
| `FRAME_DRAGDROP` | 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.ENABLED` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.HEIGHT` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.LEFT` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.MOUSEPOINTER` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.PARENT` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.TAG` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.TOP` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.VISIBLE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.WIDTH` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.INDEX` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.TABINDEX` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.TABSTOP` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.CTLNAME` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.FORECOLOR` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.DRAGMODE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.ATTACHED` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.LARGECHANGE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.MIN` | 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.VALUE` | Eigenschaft | Forms/HSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `HSCROLLBAR.DRAG` | Methode | Forms/HSCROLLBAR | offen | - | forms-referenz |
| `HSCROLLBAR.MOVE` | Methode | Forms/HSCROLLBAR | offen | - | forms-referenz |
| `HSCROLLBAR.REFRESH` | Methode | Forms/HSCROLLBAR | offen | - | 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_DRAGOVER` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `HSCROLLBAR_GOTFOCUS` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `HSCROLLBAR_KEYDOWN` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `HSCROLLBAR_KEYPRESS` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `HSCROLLBAR_KEYUP` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `HSCROLLBAR_LOSTFOCUS` | Ereignis | Forms/HSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `LABEL.BACKCOLOR` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.ENABLED` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.HEIGHT` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.LEFT` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.MOUSEPOINTER` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.PARENT` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.TAG` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.TOP` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.VISIBLE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.WIDTH` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.CTLNAME` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.FORECOLOR` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.DRAGMODE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.ALIGNMENT` | 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.CAPTION` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.DRAG` | Methode | Forms/LABEL | offen | - | forms-referenz |
| `LABEL.MOVE` | Methode | Forms/LABEL | offen | - | forms-referenz |
| `LABEL.REFRESH` | Methode | Forms/LABEL | offen | - | 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_DBLCLICK` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
| `LABEL_DRAGDROP` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
| `LABEL_DRAGOVER` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
| `LABEL_MOUSEDOWN` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
| `LABEL_MOUSEMOVE` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
| `LABEL_MOUSEUP` | Ereignis | Forms/LABEL | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX.BACKCOLOR` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.ENABLED` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.HEIGHT` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.LEFT` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.MOUSEPOINTER` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.PARENT` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.TAG` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.TOP` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.VISIBLE` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.WIDTH` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.INDEX` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.TABINDEX` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.TABSTOP` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.CTLNAME` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.FORECOLOR` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.DRAGMODE` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.LIST` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.LISTCOUNT` | 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.TEXT` | Eigenschaft | Forms/LISTBOX | implementiert | tb-ui::forms | forms-referenz |
| `LISTBOX.ADDITEM` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
| `LISTBOX.DRAG` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
| `LISTBOX.MOVE` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
| `LISTBOX.REFRESH` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
| `LISTBOX.REMOVEITEM` | Methode | Forms/LISTBOX | offen | - | forms-referenz |
| `LISTBOX.SETFOCUS` | Methode | Forms/LISTBOX | offen | - | 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_DRAGDROP` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_DRAGOVER` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_GOTFOCUS` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_KEYDOWN` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_KEYPRESS` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_KEYUP` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_LOSTFOCUS` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_MOUSEDOWN` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_MOUSEMOVE` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `LISTBOX_MOUSEUP` | Ereignis | Forms/LISTBOX | implementiert | tb-vm::interp | forms-referenz |
| `MENU.CAPTION` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
| `MENU.CHECKED` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
| `MENU.CTLNAME` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
| `MENU.ENABLED` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
| `MENU.INDEX` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | forms-referenz |
| `MENU.PARENT` | 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.VISIBLE` | Eigenschaft | Forms/MENU | implementiert | tb-ui::forms | 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.ENABLED` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.HEIGHT` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.LEFT` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.MOUSEPOINTER` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.PARENT` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.TAG` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.TOP` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.VISIBLE` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.WIDTH` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.INDEX` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.TABINDEX` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.TABSTOP` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.CTLNAME` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.FORECOLOR` | 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.VALUE` | Eigenschaft | Forms/OPTIONBUTTON | implementiert | tb-ui::forms | forms-referenz |
| `OPTIONBUTTON.DRAG` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
| `OPTIONBUTTON.MOVE` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
| `OPTIONBUTTON.REFRESH` | Methode | Forms/OPTIONBUTTON | offen | - | forms-referenz |
| `OPTIONBUTTON.SETFOCUS` | Methode | Forms/OPTIONBUTTON | offen | - | 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_DRAGDROP` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `OPTIONBUTTON_DRAGOVER` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `OPTIONBUTTON_GOTFOCUS` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `OPTIONBUTTON_KEYDOWN` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `OPTIONBUTTON_KEYPRESS` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `OPTIONBUTTON_KEYUP` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `OPTIONBUTTON_LOSTFOCUS` | Ereignis | Forms/OPTIONBUTTON | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX.BACKCOLOR` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.ENABLED` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.HEIGHT` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.LEFT` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.MOUSEPOINTER` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.PARENT` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.TAG` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.TOP` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.VISIBLE` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.WIDTH` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.CTLNAME` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.FORECOLOR` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.DRAGMODE` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.AUTOREDRAW` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.BORDERSTYLE` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.CURRENTX` | 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.SCALEWIDTH` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.CLS` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.DRAG` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.MOVE` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.PRINT` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.REFRESH` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.SETFOCUS` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.TEXTHEIGHT` | Methode | Forms/PICTUREBOX | offen | - | forms-referenz |
| `PICTUREBOX.TEXTWIDTH` | Methode | Forms/PICTUREBOX | offen | - | 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_DRAGDROP` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_DRAGOVER` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_GOTFOCUS` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_KEYDOWN` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_KEYPRESS` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_KEYUP` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_LOSTFOCUS` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_MOUSEDOWN` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_MOUSEMOVE` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_MOUSEUP` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `PICTUREBOX_PAINT` | Ereignis | Forms/PICTUREBOX | implementiert | tb-vm::interp | forms-referenz |
| `TEXTBOX.BACKCOLOR` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.ENABLED` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.HEIGHT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.LEFT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.MOUSEPOINTER` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.PARENT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.TAG` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.TOP` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.VISIBLE` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.WIDTH` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.INDEX` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.TABINDEX` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.TABSTOP` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.CTLNAME` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.FORECOLOR` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.DRAGMODE` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.BORDERSTYLE` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.MULTILINE` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.SCROLLBARS` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.SELLENGTH` | 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.TEXT` | Eigenschaft | Forms/TEXTBOX | implementiert | tb-ui::forms | forms-referenz |
| `TEXTBOX.DRAG` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
| `TEXTBOX.MOVE` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
| `TEXTBOX.REFRESH` | Methode | Forms/TEXTBOX | offen | - | forms-referenz |
| `TEXTBOX.SETFOCUS` | Methode | Forms/TEXTBOX | offen | - | 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_DRAGOVER` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
| `TEXTBOX_GOTFOCUS` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
| `TEXTBOX_KEYDOWN` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
| `TEXTBOX_KEYPRESS` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
| `TEXTBOX_KEYUP` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
| `TEXTBOX_LOSTFOCUS` | Ereignis | Forms/TEXTBOX | implementiert | tb-vm::interp | forms-referenz |
| `TIMER.CTLNAME` | Eigenschaft | Forms/TIMER | implementiert | tb-ui::forms | forms-referenz |
| `TIMER.ENABLED` | Eigenschaft | Forms/TIMER | implementiert | tb-ui::forms | forms-referenz |
| `TIMER.INDEX` | Eigenschaft | Forms/TIMER | implementiert | tb-ui::forms | forms-referenz |
| `TIMER.INTERVAL` | Eigenschaft | Forms/TIMER | implementiert | tb-ui::forms | forms-referenz |
| `TIMER.PARENT` | Eigenschaft | Forms/TIMER | implementiert | tb-ui::forms | forms-referenz |
| `TIMER.TAG` | Eigenschaft | Forms/TIMER | implementiert | tb-ui::forms | forms-referenz |
| `TIMER_TIMER` | Ereignis | Forms/TIMER | implementiert | tb-vm::interp | forms-referenz |
| `VSCROLLBAR.BACKCOLOR` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.ENABLED` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.HEIGHT` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.LEFT` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.MOUSEPOINTER` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.PARENT` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.TAG` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.TOP` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.VISIBLE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.WIDTH` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.INDEX` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.TABINDEX` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.TABSTOP` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.CTLNAME` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.FORECOLOR` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.DRAGMODE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.ATTACHED` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.LARGECHANGE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.MIN` | 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.VALUE` | Eigenschaft | Forms/VSCROLLBAR | implementiert | tb-ui::forms | forms-referenz |
| `VSCROLLBAR.DRAG` | Methode | Forms/VSCROLLBAR | offen | - | forms-referenz |
| `VSCROLLBAR.MOVE` | Methode | Forms/VSCROLLBAR | offen | - | forms-referenz |
| `VSCROLLBAR.REFRESH` | Methode | Forms/VSCROLLBAR | offen | - | 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_DRAGOVER` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `VSCROLLBAR_GOTFOCUS` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | forms-referenz |
| `VSCROLLBAR_KEYDOWN` | 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_LOSTFOCUS` | Ereignis | Forms/VSCROLLBAR | implementiert | tb-vm::interp | 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.AUTOREDRAW` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
| `SCREEN.CONTROLPANEL` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
| `SCREEN.HEIGHT` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
| `SCREEN.WIDTH` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
| `SCREEN.MOUSEPOINTER` | Eigenschaft | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
| `SCREEN.HIDE` | Methode | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |
| `SCREEN.SHOW` | Methode | Forms/SCREEN | implementiert | tb-ui::forms | forms-referenz |

View File

@@ -430,11 +430,40 @@ uns Pseudowerte), `VARPTR`/`VARSEG`/`SADD`/`PEEK`/`POKE` → Nicht-Ziel
## 12. Forms-Anbindung (Details in forms-referenz.md) ## 12. Forms-Anbindung (Details in forms-referenz.md)
`form.eigenschaft = wert`, `form!steuerelement.eigenschaft`, `'$FORM` kennzeichnet ein Formularmodul. Formulare und die aus seiner
Ereignisprozeduren `SUB name_Ereignis (…)`, `LOAD`/`UNLOAD form`, Beschreibung bekannten Controls sind benannte, statisch typisierte Objekte.
`form.SHOW [style%]` (0 = modeless, Standard; 1 = modal), `form.HIDE`, Der Punkt liest oder schreibt eine explizite Eigenschaft
`DOEVENTS()`, `SCREEN`-Objekt, Datentypen `FORM`/`CONTROL`, `IF TYPEOF`, (`Text1.Text = "x"`); es gibt keine Standardeigenschaft. Der
`$FORM`-Metabefehl, Include-Dateien (Konstanten-`.BI`). Container-Operator `Form1!Text1.Text` wählt dasselbe Control innerhalb
seines Formulars. Ein `!` vor einem Bezeichner ist dieser Operator, sonst
bleibt es das SINGLE-Suffix. Unbekannte Objekte, Eigenschaften und Methoden
werden bei der Übersetzung mit Namen abgewiesen; UDT-Feldpfade behalten ihre
bisherige Bedeutung.
`FORM` und `CONTROL` sind als Parametertypen zulässig; ihre Eigenschaften
werden anhand der zur Laufzeit übergebenen Objektklasse gelesen und geschrieben. Mit
`IF TYPEOF ausdruck IS Klasse THEN` wird die konkrete, bekannte Klasse
geprüft. `SCREEN` ist das globale Objekt mit `ActiveForm`, `ActiveControl`,
`Height`, `Width`, `MousePointer`, `ControlPanel` sowie `SHOW`/`HIDE`;
`Height` und `Width` folgen der aktuellen Terminalgröße und sind read-only.
`LOAD form` lädt unsichtbar und ruft zuerst `Form_Load` auf. `UNLOAD form`
ruft `Form_Unload(Cancel AS INTEGER)` auf; `Cancel <> 0` verhindert das
Entladen. `form.HIDE` ändert nur die Sichtbarkeit. Ein Eigenschaftszugriff
lädt ein noch nicht geladenes Formular implizit. `form.SHOW [style%]` zeigt
es mit 0 (Standard) modeless oder mit 1 modal; bei modal wird der aufrufende
VM-Frame bis `HIDE`/`UNLOAD` markiert, während die Ereignisschleife weiter
zustellt. Stapelverstöße liefern Fehler 400403.
Steuerelement-Arrays verwenden `LOAD name(index)` und
`UNLOAD name(index)`; Eigenschaften werden als `name(index).Eigenschaft`
adressiert. Entwurfszeit-Elemente liefern beim Entladen Fehler 362.
Ihre Ereignisprozedur erhält `Index AS INTEGER` als erstes Argument.
Ansonsten heißt ein Handler `SUB Name_Ereignis (…)` und muss exakt die
Signatur der Forms-Referenz tragen. Fehlt er, verfällt das Ereignis. Der
Dispatch nutzt ausschließlich die vorhandenen Zustellpunkte der
Ereignissteuerung. Mausargumente sind Taste und Shift als INTEGER sowie X/Y
als SINGLE in 1-basierten Textzellen.
Dialoge: Dialoge:
- `MSGBOX msg$ [,type% [,title$]]` (Anweisung) bzw. Funktion → INTEGER. - `MSGBOX msg$ [,type% [,title$]]` (Anweisung) bzw. Funktion → INTEGER.

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-04

View File

@@ -0,0 +1,140 @@
# Design — Formularmodell und Objektsprache
## Context
Siehe proposal.md — Why. Drei Bestandsbefunde schneiden den Entwurf zu:
1. **Der Punkt gehört heute zum Bezeichner.** Der Lexer nimmt `.` in den
Namen auf (`a.b` ist ein Token), die Semantik zerlegt ihn nur, wenn
die Basis ein UDT ist. Ein Objektzugriff fällt deshalb durch bis zur
impliziten Variablen.
2. **`!` ist heute immer Typsuffix.** `Form1!Text1` liest sich als
`Form1` (SINGLE) gefolgt von `Text1`.
3. **Der Rückrufweg existiert bereits** aus `phase-4-ereignisschleife`:
die VM kann ein Ziel anspringen und zurückkehren. Ereignisprozeduren
brauchen davon die Variante „Prozedur mit Argumenten" statt
`GOSUB`-Ziel".
## Goals / Non-Goals
**Goals:**
- Objektzugriffe, die zur Übersetzungszeit gegen die Klasse geprüft
werden — kein Nachschlagen von Eigenschaftsnamen zur Laufzeit im
Normalfall.
- Ein Formularmodell, das die Steuerelemente des Folge-Changes nur noch
füllen, nicht umbauen müssen.
- Modales `SHOW` ohne zweiten Ausführungsstrang.
**Non-Goals:**
- Darstellung und Eingabebehandlung der Steuerelemente.
- Ein offenes Objektsystem für Anwenderklassen (Stufe 2).
## Decisions
### D1 — Objektnamen werden zur Übersetzungszeit aufgelöst
Formulare und Steuerelemente sind zur Übersetzungszeit bekannt: sie
stammen aus der `.FRM`-Beschreibung bzw. dem Formular-Designer. Der
Compiler löst `Text1.Text` deshalb in „Objekt 7, Eigenschaft 12" auf und
prüft Typ und Schreibbarkeit sofort.
| Alternative | Warum nicht |
|---|---|
| Eigenschaftsnamen zur Laufzeit nachschlagen | Verlagert jeden Tippfehler in eine Laufzeitmeldung — genau die stille Lücke, die der Guiding Principle verbietet; zudem eine Hashsuche pro Zugriff |
Der Zugriff über `CONTROL`-Parameter (`SUB Setze (c AS CONTROL)`) bleibt
dynamisch; dort prüft die Laufzeit und `TYPEOF` fragt die Klasse ab.
### D2 — Der Punkt wird im Lexer nicht mehr gefaltet, sondern im Parser zerlegt
Der Lexer liefert weiterhin ein Token, das Punkte enthalten kann (die
Rückwärtskompatibilität für Variablennamen mit Punkt bleibt: klassische
Programme nutzen `Kunde.Name` als gewöhnlichen Variablennamen). Die
Auflösung entscheidet die Semantik in dieser Reihenfolge:
```
Basis ist UDT-Variable -> Feldzugriff wie bisher
Basis ist Formular/Control -> Eigenschaftszugriff
Basis ist SCREEN -> Bildschirmobjekt
sonst -> Diagnose mit Basis- und Gliedname
(heute: implizite Variable)
```
Damit ändert sich für bestehende Programme nichts, solange kein Objekt
den Namen trägt; Objekte gewinnen gegen implizite Variablen, weil
implizite Variablen mit Punkt im Namen ohnehin ein Bug waren.
### D3 — `!` entscheidet sich am folgenden Token
Folgt auf `!` unmittelbar ein Bezeichner, ist es der Container-Operator;
sonst das Typsuffix. `Wert! = 1.5` und `Form1!Text1.Text = "a"` sind
damit unterscheidbar, ohne dass der Lexer wissen muss, was `Form1` ist.
Der Fall `Wert!Text` — SINGLE-Variable direkt gefolgt von einem
Bezeichner — ist in keiner gültigen Syntax des Dialekts erreichbar.
### D4 — Modales SHOW ist ein Frame-Zustand, kein zweiter Strang
`SHOW 1` markiert den aufrufenden Frame als „wartet auf Formular X" und
gibt an die Ereignisschleife zurück. Die Schleife stellt weiter zu; wird
X entladen oder verborgen, läuft der markierte Frame an der Anweisung
nach `SHOW` weiter.
```
Form1.SHOW 1
|
+-- Frame markieren (wartet auf Form1) --> Ereignisschleife
|
Ereignisse zustellen <--+
|
UNLOAD Form1 ------------+
|
PRINT "danach" <-- Marke faellt, Frame laeuft weiter
```
Eine geschachtelte Schleife innerhalb des `SHOW`-Aufrufs wäre die
naheliegende Variante, macht aber die Stapeltiefe von der Zahl
verschachtelter modaler Formulare abhängig und verträgt sich nicht mit
dem Einzelschritt des Debuggers in Phase 5.
### D5 — Ereignisprozeduren sind gewöhnliche Prozeduren mit fester Signatur
Der Dispatch ruft `Command1_Click` wie jeden anderen Prozeduraufruf, nur
angestoßen von der Schleife statt von einer Anweisung. Damit gelten
Frames, `ON ERROR`, `EXIT SUB` und der Debugger unverändert. Die
Zuordnung Name → Objekt/Ereignis entsteht zur Übersetzungszeit; zur
Laufzeit ist es ein Index.
### D6 — Das Inventar bekommt eine zweite Quelle
Die Themenliste der Original-Hilfe führt das Objektmodell nicht. Statt
es unerfasst zu lassen — und damit die Abnahme der Phase 6 blind zu
machen —, führt das Inventar Eigenschaften, Methoden und Ereignisse je
Klasse mit der Forms-Referenz als Quelle. Der bestehende Abgleichstest
prüft sie mit denselben Regeln.
Größenordnung: 17 Klassen, in Summe einige hundert Einträge. Das ist der
Preis dafür, dass „Vollständigkeit ist das Soll" für Forms überhaupt
messbar wird.
## Risks / Trade-offs
- **Namenskollision Objekt gegen Variable** → Objekte gewinnen; ein
Programm mit einer Variablen `Text1` und einem Steuerelement `Text1`
bekommt eine Diagnose statt einer stillen Umdeutung.
- **Der Inventarzuwachs erschlägt die Tabelle** → eigene Gruppe je
Klasse, damit die bestehenden Gruppen lesbar bleiben; der Abgleichstest
nennt weiterhin einzelne Einträge.
- **Modale Marke und Debugger** → der Fortsetzungspunkt ist derselbe
Mechanismus wie `STOP`/`CONT`; er wird zusammen mit Phase 5 geprüft.
- **`.FRM` fehlt noch** → bis dahin entstehen Formulare im Test aus einer
im Code aufgebauten Beschreibung; das Objektmodell hängt nicht am
Dateiformat.
## Open Questions
- Z-Reihenfolge bei überlappenden Steuerelementen (offene Detailfrage der
Forms-Referenz) — betrifft die Darstellung, nicht das Objektmodell.
- Ob `ControlPanel` alle 18 Systemfarbwerte des Vorbilds abbildet oder
eine Teilmenge, entscheidet sich mit der Darstellung im Folge-Change.

View File

@@ -0,0 +1,644 @@
# Klassen und Eigenschaften — Belegstand
Diese Matrix ist die maschinennahe Auswertung der bestehenden
[Forms-Referenz](../../../docs/forms-referenz.md). Deren Primärquellen sind die
dekodierte Originalhilfe, `CONSTANT.BI`, die Stringtabellen der
Originalprogramme und zeitgenössische Beispielprojekte. Die Quelle steht in
jeder Tabellenzeile; „Forms-Referenz: Klasse-Art“ meint den gleichnamigen,
quellengeprüften Klassenabschnitt, nicht eine neue Behauptung dieses Changes.
Als unabhängige Gegenprobe wurde `cout/vbdos` in Commit
`1cdd2b32b829fe1721d0b6aecc433abc47a96fb6` ausgewertet. Die binären
Formulare belegen unter anderem `Form_Load`, `Form_Unload`,
`Form_Resize`, `*_Click`, `Caption`, `Text`, `Visible`,
`Enabled`, `SelText` und `MousePointer` in
`microsoft/check.frm`, `notepad.frm`, `qlbview.frm`, `seek.frm`,
`spindemo.frm`, `graphics/graphics.frm` und
`misc/mentors/mentors.frm`. Die SHA-256-Werte sind der Reihe nach
`ed552601…52c9`, `e11b76f6…3c97`, `358e7bab…b97`,
`2c7bf1df…b68`, `259e67a3…c738`, `58901348…ecb0f` und
`e3ac6a4e…87fb`. Die Beispiele erweitern die Originalhilfe nicht; sie
bestätigen nur tatsächlich verwendete Namen und Signaturen.
„INTEGER (Boolean)“ wird in BASIC als 0/1 gesehen. Ein fehlender
Wertebereich bedeutet, dass der Typ selbst begrenzt. „leer/Objektzustand“
ist kein frei schreibbarer Zahlenwert. Die Tabelle enthält bewusst auch
Elemente des Folge-Changes: hier werden ihre Namen und Verträge festgelegt,
nicht Darstellung oder Eingabeverhalten.
## Ereignissignaturen und Steuerelement-Arrays
| Ereignisfamilie | Forms-Referenz | Compiler-Signatur |
|---|---|---|
| `KeyDown`, `KeyUp` | `(KeyCode AS INTEGER, Shift AS INTEGER)` | identisch |
| `KeyPress` | `(KeyAscii AS INTEGER)` | identisch |
| `MouseDown`, `MouseMove`, `MouseUp` | `(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)` | identisch |
| `DragDrop` | `(Source AS CONTROL, X AS SINGLE, Y AS SINGLE)` | identisch |
| `DragOver` | `(Source AS CONTROL, X AS SINGLE, Y AS SINGLE, State AS INTEGER)` | identisch |
| `Unload` | `(Cancel AS INTEGER)` | identisch; ungleich 0 verhindert Entladen |
| alle übrigen geführten Ereignisse | keine Parameter | identisch |
Bei einem Steuerelement-Array steht gemäß Forms-Referenz
`Index AS INTEGER` immer als erster Parameter vor der jeweiligen
Signatur. `LOAD name(index)` klont Element 0 als Vorlage;
`UNLOAD name(index)` darf ein Entwurfszeit-Element nicht entfernen
(Fehler 362). `KeyAscii` ist die dokumentierte UTF-8-Abweichung als
Unicode-Codepoint; Shift-Bits (1/2/4), Maustasten (1/2/4) und
DragOver-State (0/1/2) bleiben kompatibel.
## FORM
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: FORM-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `AUTOREDRAW` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `BORDERSTYLE` | Eigenschaft / INTEGER | 0…6 | 2 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `CONTROLBOX` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `CURRENTX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `CURRENTY` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `FORMNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `FORMTYPE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `MAXBUTTON` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `MINBUTTON` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `SCALEHEIGHT` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: FORM-Eigenschaften |
| `SCALEWIDTH` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: FORM-Eigenschaften |
| `WINDOWSTATE` | Eigenschaft / INTEGER | 0…2 | 0 | lesen/schreiben | Forms-Referenz: FORM-Eigenschaften |
| `CLS` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `HIDE` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `LOAD` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `PRINT` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `PRINTFORM` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `SHOW` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `TEXTHEIGHT` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `TEXTWIDTH` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `UNLOAD` | Methode | — | — | Aufruf | Forms-Referenz: FORM-Methoden |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `LOAD` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `MOUSEDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `MOUSEMOVE` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `MOUSEUP` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `PAINT` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `RESIZE` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
| `UNLOAD` | Ereignis | — | — | Dispatch | Forms-Referenz: FORM-Ereignisse |
## CHECKBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: CHECKBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `VALUE` | Eigenschaft / INTEGER | 0…2 | 0 | lesen/schreiben | Forms-Referenz: CHECKBOX-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: CHECKBOX-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: CHECKBOX-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: CHECKBOX-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: CHECKBOX-Methoden |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: CHECKBOX-Ereignisse |
## COMBOBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: COMBOBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `LIST` | Eigenschaft / STRING | — | leer/Objektzustand | read-only | Forms-Referenz: COMBOBOX-Eigenschaften |
| `LISTCOUNT` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: COMBOBOX-Eigenschaften |
| `LISTINDEX` | Eigenschaft / INTEGER | — | -1 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `SORTED` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `SELLENGTH` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `SELSTART` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `SELTEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `STYLE` | Eigenschaft / INTEGER | 0…2 | 0 | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `TEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMBOBOX-Eigenschaften |
| `ADDITEM` | Methode | — | — | Aufruf | Forms-Referenz: COMBOBOX-Methoden |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: COMBOBOX-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: COMBOBOX-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: COMBOBOX-Methoden |
| `REMOVEITEM` | Methode | — | — | Aufruf | Forms-Referenz: COMBOBOX-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: COMBOBOX-Methoden |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `DROPDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `MOUSEDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `MOUSEMOVE` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
| `MOUSEUP` | Ereignis | — | — | Dispatch | Forms-Referenz: COMBOBOX-Ereignisse |
## COMMANDBUTTON
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `CANCEL` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `DEFAULT` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `VALUE` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: COMMANDBUTTON-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: COMMANDBUTTON-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: COMMANDBUTTON-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: COMMANDBUTTON-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: COMMANDBUTTON-Methoden |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: COMMANDBUTTON-Ereignisse |
## DIRLISTBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `PATH` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `TEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DIRLISTBOX-Eigenschaften |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: DIRLISTBOX-Ereignisse |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: DIRLISTBOX-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: DIRLISTBOX-Ereignisse |
| `PATHCHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: DIRLISTBOX-Ereignisse |
## DRIVELISTBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `DRIVE` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `TEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: DRIVELISTBOX-Eigenschaften |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: DRIVELISTBOX-Ereignisse |
## FILELISTBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `FILENAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `PATH` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `PATTERN` | Eigenschaft / STRING | — | `*.*` | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `ARCHIVE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `HIDDEN` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `NORMAL` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `READONLY` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `SYSTEM` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `TEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FILELISTBOX-Eigenschaften |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: FILELISTBOX-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: FILELISTBOX-Ereignisse |
| `PATTERNCHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: FILELISTBOX-Ereignisse |
| `PATHCHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: FILELISTBOX-Ereignisse |
## FRAME
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: FRAME-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: FRAME-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: FRAME-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: FRAME-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: FRAME-Methoden |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: FRAME-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: FRAME-Ereignisse |
## HSCROLLBAR
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `ATTACHED` | Eigenschaft / INTEGER (Boolean) | — | leer/Objektzustand | read-only | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `LARGECHANGE` | Eigenschaft / INTEGER | 1…32767 | 1 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `MIN` | Eigenschaft / INTEGER | -32768…32767 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `MAX` | Eigenschaft / INTEGER | -32768…32767 | 32767 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `SMALLCHANGE` | Eigenschaft / INTEGER | 1…32767 | 1 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `VALUE` | Eigenschaft / INTEGER | -32768…32767 | 0 | lesen/schreiben | Forms-Referenz: HSCROLLBAR-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: HSCROLLBAR-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: HSCROLLBAR-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: HSCROLLBAR-Methoden |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: HSCROLLBAR-Ereignisse |
## LABEL
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: LABEL-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `ALIGNMENT` | Eigenschaft / INTEGER | 0…2 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `AUTOSIZE` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `BORDERSTYLE` | Eigenschaft / INTEGER | 0…2 | 0 | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: LABEL-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: LABEL-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: LABEL-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: LABEL-Methoden |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `MOUSEDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `MOUSEMOVE` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
| `MOUSEUP` | Ereignis | — | — | Dispatch | Forms-Referenz: LABEL-Ereignisse |
## LISTBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: LISTBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `LIST` | Eigenschaft / STRING | — | leer/Objektzustand | read-only | Forms-Referenz: LISTBOX-Eigenschaften |
| `LISTCOUNT` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: LISTBOX-Eigenschaften |
| `LISTINDEX` | Eigenschaft / INTEGER | — | -1 | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `SORTED` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `TEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: LISTBOX-Eigenschaften |
| `ADDITEM` | Methode | — | — | Aufruf | Forms-Referenz: LISTBOX-Methoden |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: LISTBOX-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: LISTBOX-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: LISTBOX-Methoden |
| `REMOVEITEM` | Methode | — | — | Aufruf | Forms-Referenz: LISTBOX-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: LISTBOX-Methoden |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `MOUSEDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `MOUSEMOVE` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
| `MOUSEUP` | Ereignis | — | — | Dispatch | Forms-Referenz: LISTBOX-Ereignisse |
## MENU
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `CHECKED` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: MENU-Eigenschaften |
| `SEPARATOR` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: MENU-Eigenschaften |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: MENU-Ereignisse |
## OPTIONBUTTON
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `CAPTION` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `VALUE` | Eigenschaft / INTEGER | -1…0 | 0 | lesen/schreiben | Forms-Referenz: OPTIONBUTTON-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: OPTIONBUTTON-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: OPTIONBUTTON-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: OPTIONBUTTON-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: OPTIONBUTTON-Methoden |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: OPTIONBUTTON-Ereignisse |
## PICTUREBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `AUTOREDRAW` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `BORDERSTYLE` | Eigenschaft / INTEGER | 0…2 | 1 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `CURRENTX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `CURRENTY` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `SCALEHEIGHT` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `SCALEWIDTH` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: PICTUREBOX-Eigenschaften |
| `CLS` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `PRINT` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `TEXTHEIGHT` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `TEXTWIDTH` | Methode | — | — | Aufruf | Forms-Referenz: PICTUREBOX-Methoden |
| `CLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `DBLCLICK` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `MOUSEDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `MOUSEMOVE` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `MOUSEUP` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
| `PAINT` | Ereignis | — | — | Dispatch | Forms-Referenz: PICTUREBOX-Ereignisse |
## TEXTBOX
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: TEXTBOX-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `BORDERSTYLE` | Eigenschaft / INTEGER | 0…1 | 1 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `MULTILINE` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `SCROLLBARS` | Eigenschaft / INTEGER | 0…3 | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `SELLENGTH` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `SELSTART` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `SELTEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `TEXT` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: TEXTBOX-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: TEXTBOX-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: TEXTBOX-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: TEXTBOX-Methoden |
| `SETFOCUS` | Methode | — | — | Aufruf | Forms-Referenz: TEXTBOX-Methoden |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: TEXTBOX-Ereignisse |
## TIMER
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: TIMER-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: TIMER-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: TIMER-Eigenschaften |
| `INTERVAL` | Eigenschaft / INTEGER | 0…65535 | 0 | lesen/schreiben | Forms-Referenz: TIMER-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: TIMER-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: TIMER-Eigenschaften |
| `TIMER` | Ereignis | — | — | Dispatch | Forms-Referenz: TIMER-Ereignisse |
## VSCROLLBAR
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `BACKCOLOR` | Eigenschaft / INTEGER | 0…15 | 7 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `ENABLED` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `LEFT` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `PARENT` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `TAG` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `TOP` | Eigenschaft / INTEGER | 0…254 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `VISIBLE` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | 1…254 | 1 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `INDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `TABINDEX` | Eigenschaft / INTEGER | — | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `TABSTOP` | Eigenschaft / INTEGER (Boolean) | — | True | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `CTLNAME` | Eigenschaft / STRING | — | `` | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `FORECOLOR` | Eigenschaft / INTEGER | 0…15 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `DRAGMODE` | Eigenschaft / INTEGER | 0…1 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `ATTACHED` | Eigenschaft / INTEGER (Boolean) | — | leer/Objektzustand | read-only | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `LARGECHANGE` | Eigenschaft / INTEGER | 1…32767 | 1 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `MIN` | Eigenschaft / INTEGER | -32768…32767 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `MAX` | Eigenschaft / INTEGER | -32768…32767 | 32767 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `SMALLCHANGE` | Eigenschaft / INTEGER | 1…32767 | 1 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `VALUE` | Eigenschaft / INTEGER | -32768…32767 | 0 | lesen/schreiben | Forms-Referenz: VSCROLLBAR-Eigenschaften |
| `DRAG` | Methode | — | — | Aufruf | Forms-Referenz: VSCROLLBAR-Methoden |
| `MOVE` | Methode | — | — | Aufruf | Forms-Referenz: VSCROLLBAR-Methoden |
| `REFRESH` | Methode | — | — | Aufruf | Forms-Referenz: VSCROLLBAR-Methoden |
| `CHANGE` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `DRAGDROP` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `DRAGOVER` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `GOTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `KEYDOWN` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `KEYPRESS` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `KEYUP` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
| `LOSTFOCUS` | Ereignis | — | — | Dispatch | Forms-Referenz: VSCROLLBAR-Ereignisse |
## SCREEN
| Element | Art/Typ | Bereich | Vorgabe | Laufzeit | Quelle |
|---|---|---|---|---|---|
| `ACTIVECONTROL` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: SCREEN-Eigenschaften |
| `ACTIVEFORM` | Eigenschaft / CONTROL/FORM | — | leer/Objektzustand | read-only | Forms-Referenz: SCREEN-Eigenschaften |
| `AUTOREDRAW` | Eigenschaft / INTEGER (Boolean) | — | False | lesen/schreiben | Forms-Referenz: SCREEN-Eigenschaften |
| `CONTROLPANEL` | Eigenschaft / INTEGER-Array | 0…15 | 0 | lesen/schreiben | Forms-Referenz: SCREEN-Eigenschaften |
| `HEIGHT` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: SCREEN-Eigenschaften |
| `WIDTH` | Eigenschaft / INTEGER | — | leer/Objektzustand | read-only | Forms-Referenz: SCREEN-Eigenschaften |
| `MOUSEPOINTER` | Eigenschaft / INTEGER | 0…12 | 0 | lesen/schreiben | Forms-Referenz: SCREEN-Eigenschaften |
| `HIDE` | Methode | — | — | Aufruf | Forms-Referenz: SCREEN-Methoden |
| `SHOW` | Methode | — | — | Aufruf | Forms-Referenz: SCREEN-Methoden |

View File

@@ -0,0 +1,106 @@
# Phase 4 — Formularmodell und Objektsprache
## Why
Nach der Ereignisschleife (`phase-4-ereignisschleife`) fehlt der Sprache
alles, worauf ein Formularprogramm zugreift: Formulare als Objekte,
Steuerelemente als benannte Eigenschaftsträger, Ereignisprozeduren
`Name_Ereignis` und die Sprachmittel aus Sprachreferenz §12
(`FORM`/`CONTROL`, `IF TYPEOF`, `$FORM`, `form!steuerelement`,
`SCREEN`-Objekt).
Heute kompiliert ein Formularprogramm nicht etwa mit einer Ablehnung,
sondern **falsch**. Belegt an einem Dreizeiler:
```
Text1.Text = "hallo" -> Type mismatch (implizite SINGLE-Variable "Text1.Text")
Form1.Show 1 -> Subprogram not defined
UNLOAD Form1 -> Subprogram not defined
```
Der Lexer faltet den Punkt in den Bezeichner, die Semantik löst Punktpfade
nur auf, wenn die Basis ein benutzerdefinierter Typ ist — sonst entsteht
stillschweigend eine Variable namens `Text1.Text`. Das ist genau der Fall,
den der Guiding Principle als Bug definiert: ein dokumentiertes Element
wird ohne Namensnennung abgewiesen. Zusätzlich kollidiert der
Container-Operator `form!steuerelement` mit dem Typsuffix `!` für SINGLE.
Dazu kommt eine Lücke im Messinstrument: das Inventar führt zum
Forms-Objektmodell **null** Einträge — weder `SHOW` noch `TYPEOF`, keine
Eigenschaft, keine Steuerelementklasse. Es ist Abnahmekriterium für
Phase 6 und läuft für den größten Teil der Phase 4 ins Leere.
## What Changes
- **Objektsprache im Frontend**: Punktpfade auf Formulare und
Steuerelemente (`Form1.Caption`, `Text1.Text`), der Container-Operator
`form!steuerelement.eigenschaft` mit einer Auflösung der Kollision zum
SINGLE-Suffix, die Datentypen `FORM` und `CONTROL` als Parametertypen,
`IF TYPEOF ctl IS klasse`, der Metabefehl `$FORM` und die
Ereignisprozeduren `SUB Name_Ereignis (…)` mit den Signaturen der
Forms-Referenz.
- **Namentliche Abweisung statt Falschübersetzung** (**BREAKING** für
bestehende Diagnosen): Ein Punktpfad auf ein unbekanntes Objekt oder
eine unbekannte Eigenschaft MUST das Objekt und die Eigenschaft
namentlich nennen; eine implizite Variable mit Punkt im Namen MUST NOT
mehr entstehen.
- **Formularmodell zur Laufzeit**: Formulare mit Eigenschaften,
Steuerelementbaum, Steuerelement-Arrays (`ctl(index%)`),
Lade-/Entladezyklus (`LOAD`/`UNLOAD` mit `Form_Load`/`Form_Unload` und
wirksamem `Cancel`), `SHOW [style%]` modeless und modal, `HIDE`,
Sichtbarkeit und Z-Reihenfolge.
- **Modalität**: `form.SHOW 1` gibt die Kontrolle erst zurück, wenn das
Formular entladen oder verborgen wird; die Ereignisschleife läuft
währenddessen weiter. Die Stapelregeln des Vorbilds lösen die Fehler
400403 aus.
- **Ereignisdispatch**: Ein Ereignis der Warteschlange findet die
Prozedur `Name_Ereignis` des betroffenen Objekts und ruft sie mit den
Argumenten ihrer Signatur auf; fehlt die Prozedur, verfällt das
Ereignis wirkungslos. Die Zustellpunkte sind die der
Ereignissteuerung, nicht neue.
- **`SCREEN`-Objekt**: `ActiveControl`, `ActiveForm`, `Height`, `Width`
(aktuelle Terminalgröße, Abweichung vom Vorbild), `MousePointer`,
`ControlPanel`, `SHOW`/`HIDE`.
- **Inventar deckt das Objektmodell** (**BREAKING** für das
Inventarformat): Eigenschaften, Methoden und Ereignisse je Klasse
werden mit eigener Quellenangabe (Forms-Referenz statt Themenliste der
Hilfe) als Einträge geführt, damit die Leitplanke „Vollständigkeit ist
das Soll" auch für Forms ein Maß hat.
**Non-Goals:** Darstellung, Fokus, Tastatur- und Maussteuerung der
einzelnen Steuerelemente sowie Menüs und Dialoge (Change
`phase-4-steuerelemente`); das `.FRM`-Dateiformat (Change
`phase-4-frm`); native Erweiterungssteuerelemente (Stufe 2).
## Capabilities
### New Capabilities
- `forms-objektmodell`: Formulare und Steuerelemente als benannte
Objekte mit Eigenschaften, Lade-/Entladezyklus, Sichtbarkeit und
Modalität, Steuerelement-Arrays, Ereignisprozeduren und ihr Dispatch,
`SCREEN`-Objekt.
### Modified Capabilities
- `sprach-frontend`: Grammatik und Semantik SHALL Punktpfade auf Objekte,
den Container-Operator, `FORM`/`CONTROL`, `TYPEOF`, `$FORM` und
Ereignisprozeduren umfassen; ein Punktpfad MUST NOT mehr zu einer
impliziten Variablen mit Punkt im Namen werden.
- `sprachinventar`: Das Inventar SHALL neben den Elementen der
Original-Hilfe auch das Forms-Objektmodell führen.
## Impact
- `crates/tb-frontend`: Lexer (Punkt und `!` im Objektkontext), Parser
(`TYPEOF`, `$FORM`, Ereignisprozedur-Signaturen), Semantik
(Objektauflösung, Eigenschaftstypen, Ereignisprozedur-Erkennung).
- `crates/tb-ui`: Formularmodell, Steuerelementbaum, Lade-/Entladezyklus,
Dispatch — der Platzhalter `forms.rs` wird ausgefüllt.
- `crates/tb-vm`: Aufruf von Ereignisprozeduren über den Rückrufweg aus
`phase-4-ereignisschleife`; Fortsetzungspunkt für modales `SHOW`.
- `crates/tb-runtime`: Fehler 362 und 400403 als Auslöser.
- `docs/`: `inventar.md` um das Objektmodell erweitert;
`forms-referenz.md` um die belegten Detailfragen ergänzt;
`sprachreferenz.md` §12 ausformuliert.
- PLAN.md: Punkte „Formular-Modell" und „Ereignisdispatch" der Phase 4.

View File

@@ -0,0 +1,136 @@
## Purpose
Das Forms-Objektmodell macht Formulare und Steuerelemente zu benannten
Objekten des laufenden Programms: Eigenschaften lesen und schreiben,
Formulare laden, zeigen, verbergen und entladen — modal wie nicht-modal —,
Steuerelement-Arrays führen und Ereignisse an die Prozeduren
`Name_Ereignis` zustellen. Es ist die Grundlage der Steuerelemente und der
vordefinierten Dialoge.
## ADDED Requirements
### Requirement: Formulare und Steuerelemente als benannte Objekte
Ein Formular SHALL unter seinem Namen ansprechbar sein, ein
Steuerelement unter seinem Namen innerhalb seines Formulars. Eigenschaften
SHALL über `objekt.eigenschaft` gelesen und geschrieben werden, mit dem
in der Forms-Referenz festgelegten Typ und Wertebereich. Es SHALL keine
Standardeigenschaft geben: `Text1 = "x"` MUST abgewiesen werden. Das
Schreiben eines Wertes außerhalb des Wertebereichs MUST Laufzeitfehler 5
auslösen; eine zur Laufzeit schreibgeschützte Eigenschaft MUST bei
Zuweisung einen Fehler auslösen.
#### Scenario: Eigenschaft lesen und schreiben
- **WHEN** `Text1.Text = "hallo"` ausgeführt und danach `Text1.Text` gelesen wird
- **THEN** liefert der Zugriff `hallo`
#### Scenario: Keine Standardeigenschaft
- **WHEN** ein Programm `Text1 = "x"` enthält
- **THEN** wird es abgewiesen und die Meldung nennt `Text1`
#### Scenario: Wert außerhalb des Bereichs
- **WHEN** `Check1.Value = 3` ausgeführt wird
- **THEN** tritt Laufzeitfehler 5 auf
### Requirement: Container-Zugriff über den Bang-Operator
`form!steuerelement.eigenschaft` SHALL ein Steuerelement eines benannten
Formulars ansprechen. Die Schreibweise MUST NOT mit dem Typsuffix `!`
für SINGLE verwechselt werden; eine Variable `Form1!` und ein Zugriff
`Form1!Text1` MUST unterscheidbar bleiben.
#### Scenario: Zugriff über den Container
- **WHEN** `Form1!Text1.Text = "a"` ausgeführt wird
- **THEN** trägt die Eigenschaft `Text` des Steuerelements `Text1` in `Form1` den Wert `a`
#### Scenario: Typsuffix bleibt Typsuffix
- **WHEN** ein Programm die Variable `Wert!` verwendet
- **THEN** bleibt sie eine SINGLE-Variable und wird nicht als Containerzugriff gelesen
### Requirement: Lade- und Entladezyklus
`LOAD form` SHALL das Formular anlegen und `Form_Load` ausführen, bevor
es sichtbar wird. `UNLOAD form` SHALL `Form_Unload (Cancel)` ausführen;
ein von der Prozedur gesetzter Wert `Cancel <> 0` MUST das Entladen
verhindern. `HIDE` SHALL nur die Sichtbarkeit zurücknehmen und das
Formular geladen lassen. Der Zugriff auf ein nicht geladenes Formular
SHALL es implizit laden.
#### Scenario: Load läuft vor der Anzeige
- **WHEN** ein Formular gezeigt wird, dessen `Form_Load` eine Eigenschaft setzt
- **THEN** ist der gesetzte Wert bereits in der ersten Anzeige sichtbar
#### Scenario: Entladen abbrechen
- **WHEN** `Form_Unload` den Parameter `Cancel` auf 1 setzt und `UNLOAD Form1` ausgeführt wird
- **THEN** bleibt das Formular geladen und sichtbar
#### Scenario: HIDE entlädt nicht
- **WHEN** `Form1.HIDE` ausgeführt und danach eine Eigenschaft von `Form1` gelesen wird
- **THEN** ist der zuvor gesetzte Wert erhalten und `Form_Load` läuft nicht erneut
### Requirement: Modales und nicht-modales Anzeigen
`form.SHOW [style%]` SHALL das Formular anzeigen; `style%` 0 (Vorgabe)
zeigt es nicht-modal, 1 modal. Bei modaler Anzeige MUST die Anweisung
nach `SHOW` erst ausgeführt werden, wenn das Formular entladen oder
verborgen wurde; die Ereignisschleife MUST währenddessen weiterlaufen.
Verstöße gegen die Stapelregeln des Vorbilds MUST die Fehler 400403
auslösen.
#### Scenario: Modal hält den Ablauf an
- **WHEN** ein Programm `Form1.SHOW 1 : PRINT "danach"` ausführt und ein Ereignis `Form1` entlädt
- **THEN** erscheint `danach` erst nach dem Entladen
#### Scenario: Ereignisse laufen während der Modalität
- **WHEN** ein modales Formular angezeigt wird und ein Ereignis eintrifft
- **THEN** läuft die zugehörige Ereignisprozedur
### Requirement: Steuerelement-Arrays
Steuerelemente mit gleichem Namen und `Index` SHALL als Array
ansprechbar sein. `LOAD ctl(index%)` SHALL ein weiteres Element zur
Laufzeit anlegen, `UNLOAD ctl(index%)` es entfernen. Der Versuch, ein zur
Entwurfszeit angelegtes Element zu entladen, MUST Fehler 362 auslösen.
Bei einem Array MUST `Index AS INTEGER` als erster Parameter der
Ereignisprozedur übergeben werden.
#### Scenario: Element zur Laufzeit anlegen
- **WHEN** `LOAD Command1(3)` ausgeführt wird
- **THEN** existiert `Command1(3)` mit den Eigenschaften des Elements 0 als Vorlage
#### Scenario: Entwurfszeit-Element ist nicht entladbar
- **WHEN** `UNLOAD Command1(0)` für ein zur Entwurfszeit angelegtes Element ausgeführt wird
- **THEN** tritt Fehler 362 auf
#### Scenario: Index als erster Parameter
- **WHEN** ein Klick auf `Command1(3)` zugestellt wird
- **THEN** wird `Command1_Click` mit `Index = 3` aufgerufen
### Requirement: Ereignisdispatch an Name_Ereignis
Ein Ereignis SHALL die Prozedur `Name_Ereignis` des betroffenen Objekts
aufrufen und ihr die Argumente der in der Forms-Referenz festgelegten
Signatur übergeben. Fehlt die Prozedur, MUST das Ereignis wirkungslos
verfallen, ohne Fehler. Die Zustellung SHALL an den Zustellpunkten der
Ereignissteuerung erfolgen; es MUST NOT neue Zustellpunkte geben.
#### Scenario: Ereignisprozedur wird gerufen
- **WHEN** ein Klick auf `Command1` zugestellt wird und `SUB Command1_Click` existiert
- **THEN** läuft die Prozedur genau einmal
#### Scenario: Fehlende Ereignisprozedur
- **WHEN** ein Klick auf ein Steuerelement ohne passende Prozedur zugestellt wird
- **THEN** läuft das Programm unverändert weiter und es entsteht kein Fehler
#### Scenario: Signatur mit Mausargumenten
- **WHEN** `Form_MouseDown` definiert ist und ein Mausereignis in Zeile 5, Spalte 7 zugestellt wird
- **THEN** erhält die Prozedur Taste, Umschaltzustand und die Position in Zellen
### Requirement: SCREEN-Objekt
`SCREEN` SHALL als Objekt ansprechbar sein mit `ActiveControl`,
`ActiveForm`, `Height`, `Width`, `MousePointer` und `ControlPanel` sowie
den Methoden `SHOW` und `HIDE`. `Height` und `Width` SHALL die
**aktuelle** Terminalgröße in Zeichen liefern und schreibgeschützt sein;
die feste Größe des Vorbilds MUST als Abweichung dokumentiert sein.
#### Scenario: Größe folgt dem Terminal
- **WHEN** die Darstellungsfläche 120×40 misst und `SCREEN.Width` gelesen wird
- **THEN** liefert es 120
#### Scenario: Aktives Formular
- **WHEN** zwei Formulare geladen sind und eines den Fokus hat
- **THEN** liefert `SCREEN.ActiveForm` dieses Formular

View File

@@ -0,0 +1,53 @@
## ADDED Requirements
### Requirement: Objektzugriffe in Grammatik und Semantik
Der Parser SHALL Punktpfade auf Formulare und Steuerelemente
(`objekt.eigenschaft`), den Container-Operator
`form!steuerelement[.eigenschaft]`, `IF TYPEOF ausdruck IS klasse`, den
Metabefehl `$FORM` und die Datentypen `FORM` und `CONTROL` als
Parametertypen erkennen. Die Semantik SHALL Eigenschaften gegen die
Klasse des Objekts prüfen und ihren Typ bestimmen. Ein Punktpfad, dessen
Basis weder ein benutzerdefinierter Typ noch ein bekanntes Objekt ist,
MUST eine Diagnose erzeugen, die Basis und Eigenschaft namentlich nennt;
er MUST NOT zu einer impliziten Variablen mit Punkt im Namen werden.
#### Scenario: Unbekanntes Objekt wird benannt
- **WHEN** ein Modul `Text9.Text = "a"` ohne Steuerelement `Text9` enthält
- **THEN** nennt die Diagnose `Text9` als unbekanntes Objekt statt „Type mismatch" zu melden
#### Scenario: Unbekannte Eigenschaft wird benannt
- **WHEN** ein Modul `Text1.Farbe = 3` enthält
- **THEN** nennt die Diagnose `Farbe` als unbekannte Eigenschaft von `TextBox`
#### Scenario: Eigenschaftstyp wird geprüft
- **WHEN** ein Modul `Text1.Text = 5` enthält
- **THEN** meldet die Semantik einen Typkonflikt mit Nennung der Eigenschaft
#### Scenario: TYPEOF
- **WHEN** ein Modul `IF TYPEOF Ziel IS CommandButton THEN` enthält
- **THEN** wird es übersetzt und die Klasse gegen die bekannten Steuerelementklassen geprüft
### Requirement: Bang-Operator neben dem SINGLE-Suffix
Der Lexer SHALL `!` als Container-Operator erkennen, wenn ihm ein
Bezeichner folgt, und andernfalls als Typsuffix für SINGLE. Beide Formen
MUST im selben Modul nebeneinander verwendbar sein.
#### Scenario: Beide Bedeutungen im selben Modul
- **WHEN** ein Modul `Wert! = 1.5` und `Form1!Text1.Text = "a"` enthält
- **THEN** wird die erste Zeile als SINGLE-Variable und die zweite als Containerzugriff übersetzt
### Requirement: Ereignisprozeduren mit festen Signaturen
Die Semantik SHALL eine `SUB`, deren Name der Form `Objekt_Ereignis`
entspricht und deren Objekt bekannt ist, als Ereignisprozedur erkennen
und ihre Parameterliste gegen die Signatur der Forms-Referenz prüfen.
Eine abweichende Parameterliste MUST namentlich gemeldet werden. Eine
`SUB` mit Unterstrich im Namen ohne bekanntes Objekt MUST weiterhin eine
gewöhnliche Prozedur sein.
#### Scenario: Falsche Signatur
- **WHEN** ein Modul `SUB Form_MouseDown (X AS SINGLE)` enthält
- **THEN** nennt die Diagnose die erwartete Parameterliste der Ereignisprozedur
#### Scenario: Gewöhnliche SUB mit Unterstrich
- **WHEN** ein Modul `SUB Zins_Berechnen (n AS INTEGER)` ohne Objekt `Zins` enthält
- **THEN** wird sie als gewöhnliche Prozedur übersetzt

View File

@@ -0,0 +1,29 @@
## MODIFIED Requirements
### Requirement: Vollständiges Inventar der Original-Hilfe
`docs/inventar.md` SHALL jede in der Original-Hilfe des Vorbilds
dokumentierte Anweisung, Funktion, Metabefehl, Operator-Schlüsselwort und
Klausel als eigenen Eintrag führen. Darüber hinaus SHALL es das
**Forms-Objektmodell** führen: je Klasse ihre Eigenschaften, Methoden und
Ereignisse als eigene Einträge. Ein dokumentiertes Element MUST NOT
fehlen; Vollständigkeit gegenüber der Quelle geht der Kürze vor. Jeder
Eintrag SHALL Name, Art (Anweisung/Funktion/Metabefehl/Eigenschaft/
Methode/Ereignis), Themengruppe, Status, Fundstelle und Quelle tragen.
Für Einträge des Objektmodells SHALL die Quelle die Forms-Referenz sein,
weil die Themenliste der Original-Hilfe sie nicht führt.
#### Scenario: Dokumentiertes Element ist erfasst
- **WHEN** die Original-Hilfe die Anweisung `VIEW PRINT` dokumentiert
- **THEN** enthält das Inventar einen Eintrag `VIEW PRINT` mit Art, Gruppe, Status und Fundstelle
#### Scenario: Non-Feature ist erfasst, nicht weggelassen
- **WHEN** `PEEK` als Non-Feature deklariert ist
- **THEN** steht `PEEK` mit Status `Non-Feature` im Inventar und die Fundstelle verweist auf den Abschnitt „Abweichungen" der Sprachreferenz
#### Scenario: Eigenschaft einer Steuerelementklasse ist erfasst
- **WHEN** die Forms-Referenz für `TextBox` die Eigenschaft `SelStart` führt
- **THEN** enthält das Inventar einen Eintrag für `TextBox.SelStart` mit Art `Eigenschaft`, Status und Quelle Forms-Referenz
#### Scenario: Ereignis einer Klasse ist erfasst
- **WHEN** die Forms-Referenz für `Form` das Ereignis `Resize` führt
- **THEN** enthält das Inventar einen Eintrag für `Form_Resize` mit Art `Ereignis`

View File

@@ -0,0 +1,42 @@
## 1. Belege
- [x] 1.1 Eigenschaften, Methoden und Ereignisse je Klasse aus Original-Hilfe, `CONSTANT.BI` und den Beispielprojekten in `klassen-und-eigenschaften.md` dieses Changes zusammenstellen (Typ, Wertebereich, Vorgabewert, Schreibbarkeit zur Laufzeit); verifiziert durch die Datei mit Quellenangabe je Zeile
- [x] 1.2 Signaturen der Ereignisprozeduren und die Regeln für Steuerelement-Arrays belegen; verifiziert durch einen Abschnitt in derselben Datei, der jede Signatur der Forms-Referenz gegenüberstellt
## 2. Frontend: Objektsprache
- [x] 2.1 `!` am folgenden Token entscheiden (D3); verifiziert durch Lexer-Tests für beide Bedeutungen im selben Modul
- [x] 2.2 Punktpfade in der Semantik nach D2 auflösen, mit Diagnose statt impliziter Variablen; verifiziert durch Sema-Tests für unbekanntes Objekt, unbekannte Eigenschaft und unveränderten UDT-Feldzugriff
- [x] 2.3 Eigenschaftstabelle je Klasse aus 1.1 im Frontend führen und Typ/Schreibbarkeit prüfen; verifiziert durch Sema-Tests für Typkonflikt und schreibgeschützte Eigenschaft
- [x] 2.4 `IF TYPEOF … IS klasse`, Datentypen `FORM`/`CONTROL` als Parametertypen, Metabefehl `$FORM`; verifiziert durch Parser- und Sema-Tests je Konstrukt
- [x] 2.5 Ereignisprozeduren erkennen und ihre Parameterlisten prüfen; verifiziert durch Sema-Tests für richtige Signatur, falsche Signatur und gewöhnliche `SUB` mit Unterstrich
- [x] 2.6 Absenkung ins HIR: Objektzugriff als Objekt-/Eigenschaftsindex, Ereignisprozeduren mit ihrer Zuordnung; verifiziert durch `cargo test -p tb-frontend`
## 3. Laufzeit: Formularmodell
- [x] 3.1 Formular- und Steuerelementobjekte mit Eigenschaftsspeicher und Vorgabewerten aus 1.1; verifiziert durch Unit-Tests für Vorgabewerte und Wertebereichsprüfung (Fehler 5)
- [x] 3.2 Lade-/Entladezyklus mit `Form_Load` und `Form_Unload (Cancel)`; verifiziert durch Unit-Tests, darunter das verhinderte Entladen
- [x] 3.3 `SHOW`/`HIDE`, Sichtbarkeit, implizites Laden beim Zugriff; verifiziert durch Unit-Tests für „HIDE entlädt nicht" und implizites Laden
- [x] 3.4 Steuerelement-Arrays: `LOAD ctl(i)`, `UNLOAD ctl(i)`, Fehler 362 für Entwurfszeit-Elemente; verifiziert durch Unit-Tests je Fall
- [x] 3.5 `SCREEN`-Objekt mit `ActiveForm`, `ActiveControl`, `Height`/`Width` aus der aktuellen Terminalgröße; verifiziert durch Unit-Tests, darunter einer nach Größenänderung
## 4. Modalität und Dispatch
- [x] 4.1 Modales `SHOW` als Frame-Marke (D4) mit Fortsetzung nach dem Entladen; verifiziert durch einen VM-Test, der die Anweisung nach `SHOW 1` erst nach dem Entladen ausführt
- [x] 4.2 Stapelregeln der Modalität mit den Fehlern 400403; verifiziert durch Unit-Tests je Fehlercode
- [x] 4.3 Ereignisdispatch: Ereignis der Warteschlange auf `Name_Ereignis` abbilden und mit Argumenten aufrufen (D5); verifiziert durch VM-Tests für vorhandene und fehlende Prozedur
- [x] 4.4 Mausereignisse aus `phase-4-ereignisschleife` an die Formularereignisse übergeben (Taste, Umschaltzustand, Position in Zellen); verifiziert durch einen Test, der `Form_MouseDown` mit den erwarteten Argumenten sieht
- [x] 4.5 Nachweis, dass keine neuen Zustellpunkte entstehen; verifiziert durch einen Test, der die Zustellpunkte der Ereignissteuerung gegenprüft
## 5. Inventar und Dokumentation
- [x] 5.1 `docs/inventar.md` um Art `Eigenschaft`/`Methode`/`Ereignis` und die Spalte Quelle erweitern, Einträge aus 1.1 eintragen (Status zunächst `offen`); verifiziert durch `inventar_stimmt_mit_code_ueberein`
- [x] 5.2 Abgleichstest auf die neuen Arten ausdehnen; verifiziert durch einen Testfall, der eine falsch gesetzte Eigenschaft im Inventar erkennt
- [x] 5.3 Einträge dieses Changes auf `implementiert` setzen; verifiziert durch denselben Test
- [x] 5.4 `docs/sprachreferenz.md` §12 ausformulieren und `docs/forms-referenz.md` um die Belege aus 1.1 ergänzen; verifiziert durch die Abschnitte
- [x] 5.5 PLAN.md: Punkte „Formular-Modell" und „Ereignisdispatch" abhaken; verifiziert durch die aktualisierte Liste
## 6. Abnahme
- [x] 6.1 Der Dreizeiler aus proposal.md wird namentlich abgewiesen statt falsch übersetzt; verifiziert durch einen Test, der die drei Diagnosen prüft
- [x] 6.2 `cargo test` grün und `openspec validate phase-4-objektmodell --strict` ohne Befund; verifiziert durch beide Kommandos

View File

@@ -0,0 +1,138 @@
# forms-objektmodell Specification
## Purpose
Das Forms-Objektmodell macht Formulare und Steuerelemente zu benannten
Objekten des laufenden Programms: Eigenschaften lesen und schreiben,
Formulare laden, zeigen, verbergen und entladen — modal wie nicht-modal —,
Steuerelement-Arrays führen und Ereignisse an die Prozeduren
`Name_Ereignis` zustellen. Es ist die Grundlage der Steuerelemente und der
vordefinierten Dialoge.
## Requirements
### Requirement: Formulare und Steuerelemente als benannte Objekte
Ein Formular SHALL unter seinem Namen ansprechbar sein, ein
Steuerelement unter seinem Namen innerhalb seines Formulars. Eigenschaften
SHALL über `objekt.eigenschaft` gelesen und geschrieben werden, mit dem
in der Forms-Referenz festgelegten Typ und Wertebereich. Es SHALL keine
Standardeigenschaft geben: `Text1 = "x"` MUST abgewiesen werden. Das
Schreiben eines Wertes außerhalb des Wertebereichs MUST Laufzeitfehler 5
auslösen; eine zur Laufzeit schreibgeschützte Eigenschaft MUST bei
Zuweisung einen Fehler auslösen.
#### Scenario: Eigenschaft lesen und schreiben
- **WHEN** `Text1.Text = "hallo"` ausgeführt und danach `Text1.Text` gelesen wird
- **THEN** liefert der Zugriff `hallo`
#### Scenario: Keine Standardeigenschaft
- **WHEN** ein Programm `Text1 = "x"` enthält
- **THEN** wird es abgewiesen und die Meldung nennt `Text1`
#### Scenario: Wert außerhalb des Bereichs
- **WHEN** `Check1.Value = 3` ausgeführt wird
- **THEN** tritt Laufzeitfehler 5 auf
### Requirement: Container-Zugriff über den Bang-Operator
`form!steuerelement.eigenschaft` SHALL ein Steuerelement eines benannten
Formulars ansprechen. Die Schreibweise MUST NOT mit dem Typsuffix `!`
für SINGLE verwechselt werden; eine Variable `Form1!` und ein Zugriff
`Form1!Text1` MUST unterscheidbar bleiben.
#### Scenario: Zugriff über den Container
- **WHEN** `Form1!Text1.Text = "a"` ausgeführt wird
- **THEN** trägt die Eigenschaft `Text` des Steuerelements `Text1` in `Form1` den Wert `a`
#### Scenario: Typsuffix bleibt Typsuffix
- **WHEN** ein Programm die Variable `Wert!` verwendet
- **THEN** bleibt sie eine SINGLE-Variable und wird nicht als Containerzugriff gelesen
### Requirement: Lade- und Entladezyklus
`LOAD form` SHALL das Formular anlegen und `Form_Load` ausführen, bevor
es sichtbar wird. `UNLOAD form` SHALL `Form_Unload (Cancel)` ausführen;
ein von der Prozedur gesetzter Wert `Cancel <> 0` MUST das Entladen
verhindern. `HIDE` SHALL nur die Sichtbarkeit zurücknehmen und das
Formular geladen lassen. Der Zugriff auf ein nicht geladenes Formular
SHALL es implizit laden.
#### Scenario: Load läuft vor der Anzeige
- **WHEN** ein Formular gezeigt wird, dessen `Form_Load` eine Eigenschaft setzt
- **THEN** ist der gesetzte Wert bereits in der ersten Anzeige sichtbar
#### Scenario: Entladen abbrechen
- **WHEN** `Form_Unload` den Parameter `Cancel` auf 1 setzt und `UNLOAD Form1` ausgeführt wird
- **THEN** bleibt das Formular geladen und sichtbar
#### Scenario: HIDE entlädt nicht
- **WHEN** `Form1.HIDE` ausgeführt und danach eine Eigenschaft von `Form1` gelesen wird
- **THEN** ist der zuvor gesetzte Wert erhalten und `Form_Load` läuft nicht erneut
### Requirement: Modales und nicht-modales Anzeigen
`form.SHOW [style%]` SHALL das Formular anzeigen; `style%` 0 (Vorgabe)
zeigt es nicht-modal, 1 modal. Bei modaler Anzeige MUST die Anweisung
nach `SHOW` erst ausgeführt werden, wenn das Formular entladen oder
verborgen wurde; die Ereignisschleife MUST währenddessen weiterlaufen.
Verstöße gegen die Stapelregeln des Vorbilds MUST die Fehler 400403
auslösen.
#### Scenario: Modal hält den Ablauf an
- **WHEN** ein Programm `Form1.SHOW 1 : PRINT "danach"` ausführt und ein Ereignis `Form1` entlädt
- **THEN** erscheint `danach` erst nach dem Entladen
#### Scenario: Ereignisse laufen während der Modalität
- **WHEN** ein modales Formular angezeigt wird und ein Ereignis eintrifft
- **THEN** läuft die zugehörige Ereignisprozedur
### Requirement: Steuerelement-Arrays
Steuerelemente mit gleichem Namen und `Index` SHALL als Array
ansprechbar sein. `LOAD ctl(index%)` SHALL ein weiteres Element zur
Laufzeit anlegen, `UNLOAD ctl(index%)` es entfernen. Der Versuch, ein zur
Entwurfszeit angelegtes Element zu entladen, MUST Fehler 362 auslösen.
Bei einem Array MUST `Index AS INTEGER` als erster Parameter der
Ereignisprozedur übergeben werden.
#### Scenario: Element zur Laufzeit anlegen
- **WHEN** `LOAD Command1(3)` ausgeführt wird
- **THEN** existiert `Command1(3)` mit den Eigenschaften des Elements 0 als Vorlage
#### Scenario: Entwurfszeit-Element ist nicht entladbar
- **WHEN** `UNLOAD Command1(0)` für ein zur Entwurfszeit angelegtes Element ausgeführt wird
- **THEN** tritt Fehler 362 auf
#### Scenario: Index als erster Parameter
- **WHEN** ein Klick auf `Command1(3)` zugestellt wird
- **THEN** wird `Command1_Click` mit `Index = 3` aufgerufen
### Requirement: Ereignisdispatch an Name_Ereignis
Ein Ereignis SHALL die Prozedur `Name_Ereignis` des betroffenen Objekts
aufrufen und ihr die Argumente der in der Forms-Referenz festgelegten
Signatur übergeben. Fehlt die Prozedur, MUST das Ereignis wirkungslos
verfallen, ohne Fehler. Die Zustellung SHALL an den Zustellpunkten der
Ereignissteuerung erfolgen; es MUST NOT neue Zustellpunkte geben.
#### Scenario: Ereignisprozedur wird gerufen
- **WHEN** ein Klick auf `Command1` zugestellt wird und `SUB Command1_Click` existiert
- **THEN** läuft die Prozedur genau einmal
#### Scenario: Fehlende Ereignisprozedur
- **WHEN** ein Klick auf ein Steuerelement ohne passende Prozedur zugestellt wird
- **THEN** läuft das Programm unverändert weiter und es entsteht kein Fehler
#### Scenario: Signatur mit Mausargumenten
- **WHEN** `Form_MouseDown` definiert ist und ein Mausereignis in Zeile 5, Spalte 7 zugestellt wird
- **THEN** erhält die Prozedur Taste, Umschaltzustand und die Position in Zellen
### Requirement: SCREEN-Objekt
`SCREEN` SHALL als Objekt ansprechbar sein mit `ActiveControl`,
`ActiveForm`, `Height`, `Width`, `MousePointer` und `ControlPanel` sowie
den Methoden `SHOW` und `HIDE`. `Height` und `Width` SHALL die
**aktuelle** Terminalgröße in Zeichen liefern und schreibgeschützt sein;
die feste Größe des Vorbilds MUST als Abweichung dokumentiert sein.
#### Scenario: Größe folgt dem Terminal
- **WHEN** die Darstellungsfläche 120×40 misst und `SCREEN.Width` gelesen wird
- **THEN** liefert es 120
#### Scenario: Aktives Formular
- **WHEN** zwei Formulare geladen sind und eines den Fokus hat
- **THEN** liefert `SCREEN.ActiveForm` dieses Formular

View File

@@ -247,3 +247,55 @@ und nennt dabei das Label.
#### Scenario: Unbekanntes Trap-Ziel #### Scenario: Unbekanntes Trap-Ziel
- **WHEN** ein Modul `ON TIMER(1) GOSUB Fehlt` ohne Label `Fehlt` enthält - **WHEN** ein Modul `ON TIMER(1) GOSUB Fehlt` ohne Label `Fehlt` enthält
- **THEN** meldet die Übersetzung ein undefiniertes Sprungziel und nennt `Fehlt` - **THEN** meldet die Übersetzung ein undefiniertes Sprungziel und nennt `Fehlt`
### Requirement: Objektzugriffe in Grammatik und Semantik
Der Parser SHALL Punktpfade auf Formulare und Steuerelemente
(`objekt.eigenschaft`), den Container-Operator
`form!steuerelement[.eigenschaft]`, `IF TYPEOF ausdruck IS klasse`, den
Metabefehl `$FORM` und die Datentypen `FORM` und `CONTROL` als
Parametertypen erkennen. Die Semantik SHALL Eigenschaften gegen die
Klasse des Objekts prüfen und ihren Typ bestimmen. Ein Punktpfad, dessen
Basis weder ein benutzerdefinierter Typ noch ein bekanntes Objekt ist,
MUST eine Diagnose erzeugen, die Basis und Eigenschaft namentlich nennt;
er MUST NOT zu einer impliziten Variablen mit Punkt im Namen werden.
#### Scenario: Unbekanntes Objekt wird benannt
- **WHEN** ein Modul `Text9.Text = "a"` ohne Steuerelement `Text9` enthält
- **THEN** nennt die Diagnose `Text9` als unbekanntes Objekt statt „Type mismatch" zu melden
#### Scenario: Unbekannte Eigenschaft wird benannt
- **WHEN** ein Modul `Text1.Farbe = 3` enthält
- **THEN** nennt die Diagnose `Farbe` als unbekannte Eigenschaft von `TextBox`
#### Scenario: Eigenschaftstyp wird geprüft
- **WHEN** ein Modul `Text1.Text = 5` enthält
- **THEN** meldet die Semantik einen Typkonflikt mit Nennung der Eigenschaft
#### Scenario: TYPEOF
- **WHEN** ein Modul `IF TYPEOF Ziel IS CommandButton THEN` enthält
- **THEN** wird es übersetzt und die Klasse gegen die bekannten Steuerelementklassen geprüft
### Requirement: Bang-Operator neben dem SINGLE-Suffix
Der Lexer SHALL `!` als Container-Operator erkennen, wenn ihm ein
Bezeichner folgt, und andernfalls als Typsuffix für SINGLE. Beide Formen
MUST im selben Modul nebeneinander verwendbar sein.
#### Scenario: Beide Bedeutungen im selben Modul
- **WHEN** ein Modul `Wert! = 1.5` und `Form1!Text1.Text = "a"` enthält
- **THEN** wird die erste Zeile als SINGLE-Variable und die zweite als Containerzugriff übersetzt
### Requirement: Ereignisprozeduren mit festen Signaturen
Die Semantik SHALL eine `SUB`, deren Name der Form `Objekt_Ereignis`
entspricht und deren Objekt bekannt ist, als Ereignisprozedur erkennen
und ihre Parameterliste gegen die Signatur der Forms-Referenz prüfen.
Eine abweichende Parameterliste MUST namentlich gemeldet werden. Eine
`SUB` mit Unterstrich im Namen ohne bekanntes Objekt MUST weiterhin eine
gewöhnliche Prozedur sein.
#### Scenario: Falsche Signatur
- **WHEN** ein Modul `SUB Form_MouseDown (X AS SINGLE)` enthält
- **THEN** nennt die Diagnose die erwartete Parameterliste der Ereignisprozedur
#### Scenario: Gewöhnliche SUB mit Unterstrich
- **WHEN** ein Modul `SUB Zins_Berechnen (n AS INTEGER)` ohne Objekt `Zins` enthält
- **THEN** wird sie als gewöhnliche Prozedur übersetzt

View File

@@ -12,10 +12,14 @@ jederzeit prüfen lässt.
### Requirement: Vollständiges Inventar der Original-Hilfe ### Requirement: Vollständiges Inventar der Original-Hilfe
`docs/inventar.md` SHALL jede in der Original-Hilfe des Vorbilds `docs/inventar.md` SHALL jede in der Original-Hilfe des Vorbilds
dokumentierte Anweisung, Funktion, Metabefehl, Operator-Schlüsselwort und dokumentierte Anweisung, Funktion, Metabefehl, Operator-Schlüsselwort und
Klausel als eigenen Eintrag führen. Ein dokumentiertes Element MUST NOT Klausel als eigenen Eintrag führen. Darüber hinaus SHALL es das
**Forms-Objektmodell** führen: je Klasse ihre Eigenschaften, Methoden und
Ereignisse als eigene Einträge. Ein dokumentiertes Element MUST NOT
fehlen; Vollständigkeit gegenüber der Quelle geht der Kürze vor. Jeder fehlen; Vollständigkeit gegenüber der Quelle geht der Kürze vor. Jeder
Eintrag SHALL Name, Art (Anweisung/Funktion/Metabefehl), Themengruppe, Eintrag SHALL Name, Art (Anweisung/Funktion/Metabefehl/Eigenschaft/
Status und Fundstelle tragen. Methode/Ereignis), Themengruppe, Status, Fundstelle und Quelle tragen.
Für Einträge des Objektmodells SHALL die Quelle die Forms-Referenz sein,
weil die Themenliste der Original-Hilfe sie nicht führt.
#### Scenario: Dokumentiertes Element ist erfasst #### Scenario: Dokumentiertes Element ist erfasst
- **WHEN** die Original-Hilfe die Anweisung `VIEW PRINT` dokumentiert - **WHEN** die Original-Hilfe die Anweisung `VIEW PRINT` dokumentiert
@@ -25,6 +29,14 @@ Status und Fundstelle tragen.
- **WHEN** `PEEK` als Non-Feature deklariert ist - **WHEN** `PEEK` als Non-Feature deklariert ist
- **THEN** steht `PEEK` mit Status `Non-Feature` im Inventar und die Fundstelle verweist auf den Abschnitt „Abweichungen" der Sprachreferenz - **THEN** steht `PEEK` mit Status `Non-Feature` im Inventar und die Fundstelle verweist auf den Abschnitt „Abweichungen" der Sprachreferenz
#### Scenario: Eigenschaft einer Steuerelementklasse ist erfasst
- **WHEN** die Forms-Referenz für `TextBox` die Eigenschaft `SelStart` führt
- **THEN** enthält das Inventar einen Eintrag für `TextBox.SelStart` mit Art `Eigenschaft`, Status und Quelle Forms-Referenz
#### Scenario: Ereignis einer Klasse ist erfasst
- **WHEN** die Forms-Referenz für `Form` das Ereignis `Resize` führt
- **THEN** enthält das Inventar einen Eintrag für `Form_Resize` mit Art `Ereignis`
### Requirement: Festes Statusvokabular ### Requirement: Festes Statusvokabular
Der Status eines Eintrags SHALL genau einen von drei Werten tragen: Der Status eines Eintrags SHALL genau einen von drei Werten tragen:
`implementiert` (Frontend senkt ab und die Laufzeit führt aus), `implementiert` (Frontend senkt ab und die Laufzeit führt aus),