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

View File

@@ -8,10 +8,12 @@
use std::fmt;
use std::rc::Rc;
use tb_frontend::forms::{FormObject, ObjectClass};
use tb_frontend::hir::HEventProc;
use tb_runtime::value::{TypeInit, UdtLayout};
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).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -52,7 +54,10 @@ impl fmt::Display for LoadError {
match self {
LoadError::BadMagic => write!(f, "Keine .tbc-Datei (Magic fehlt)"),
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})"),
}
@@ -303,6 +308,14 @@ instrs! {
0xB1 RetProc;
0xB2 RetFn;
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 TrapDefine(a: u8, b: u32); // Quellenart, Sprungziel
@@ -375,6 +388,8 @@ pub struct CompiledModule {
pub data: Vec<DataItem>,
/// Sprungtabellen für `ON n GOTO/GOSUB`.
pub jump_tables: Vec<Vec<u32>>,
pub objects: Vec<FormObject>,
pub event_procs: Vec<HEventProc>,
}
fn w_string(out: &mut Vec<u8>, s: &str) {
@@ -458,6 +473,22 @@ impl CompiledModule {
}
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
let mut out = Vec::new();
out.extend_from_slice(TBC_MAGIC);
@@ -567,7 +598,13 @@ impl CompiledModule {
for _ in 0..n_instr {
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")?;
@@ -591,6 +628,31 @@ impl CompiledModule {
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 {
name,
option_base,
@@ -601,6 +663,8 @@ impl CompiledModule {
procs,
data,
jump_tables,
objects,
event_procs,
})
}
}
@@ -660,8 +724,13 @@ mod tests {
local_names: vec![],
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]],
objects: vec![],
event_procs: vec![],
};
let bytes = m.to_tbc();
let back = CompiledModule::from_tbc(&bytes).unwrap();
@@ -687,6 +756,8 @@ mod tests {
procs: vec![],
data: vec![],
jump_tables: vec![],
objects: vec![],
event_procs: vec![],
};
let mut bytes = m.to_tbc();
bytes[4] = 0xFF; // Version hochsetzen