Projektmodule und vollständiges TBC-Kompilat umsetzen und Change archivieren

This commit is contained in:
2026-09-05 23:06:56 +02:00
parent 58b1f620ea
commit 815825dde7
40 changed files with 4177 additions and 555 deletions

View File

@@ -9,11 +9,14 @@
use std::fmt;
use std::rc::Rc;
use tb_frontend::forms::{FormObject, ObjectClass};
use tb_frontend::hir::HEventProc;
use tb_frontend::hir::{HEventProc, HParam, HProcKind, HTy, NumTy};
use tb_frontend::source::SourceFile;
use tb_runtime::value::{TypeInit, UdtLayout};
use tb_ui::forms::PropertyValue;
use tb_ui::frm::FormInitial;
pub const TBC_MAGIC: &[u8; 4] = b"TBC\0";
pub const TBC_VERSION: u16 = 3;
pub const TBC_VERSION: u16 = 4;
/// Vergleichsoperator (Operand der `Cmp*`-Instruktionen).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -75,8 +78,15 @@ impl<'a> Reader<'a> {
pub fn new(buf: &'a [u8]) -> Self {
Reader { buf, pos: 0 }
}
fn finish(&self) -> Result<(), LoadError> {
if self.pos != self.buf.len() {
Err(LoadError::Corrupt("überzählige Abschnittsdaten"))
} else {
Ok(())
}
}
fn take(&mut self, n: usize) -> Result<&'a [u8], LoadError> {
if self.pos + n > self.buf.len() {
if n > self.buf.len().saturating_sub(self.pos) {
return Err(LoadError::Corrupt("unerwartetes Dateiende"));
}
let s = &self.buf[self.pos..self.pos + n];
@@ -138,7 +148,11 @@ impl Enc for bool {
out.push(*self as u8);
}
fn dec(r: &mut Reader) -> Result<Self, LoadError> {
Ok(r.u8()? != 0)
match r.u8()? {
0 => Ok(false),
1 => Ok(true),
_ => Err(LoadError::Corrupt("bool")),
}
}
}
@@ -170,6 +184,9 @@ impl Enc for TypeInit {
fn dec(r: &mut Reader) -> Result<Self, LoadError> {
let tag = r.u8()?;
let extra = r.u32()?;
if (tag != 6 && tag != 7 && extra != 0) || (tag == 7 && extra > u16::MAX as u32) {
return Err(LoadError::Corrupt("TypeInit-Zusatz"));
}
Ok(match tag {
0 => TypeInit::Int,
1 => TypeInit::Lng,
@@ -224,6 +241,8 @@ instrs! {
0x03 StopInstr;
0x04 SystemInstr;
0x05 Unsupported(a: u16); // Name im Stringpool → Fehler 73
0x06 Source(a: u32, b: u32); // Quelldatei-ID, physische Spalte der folgenden Stmt-Grenze
0x07 InitStmt(a: u32); // globale Initialisierung: Quellort/Debugger, noch keine Ereignisse
// 0x10 — Konstanten und Stack
0x10 PushInt(a: i16);
@@ -234,6 +253,7 @@ instrs! {
0x15 PushStr(a: u16);
0x16 Dup;
0x17 Pop;
0x18 PushUdtId(a: u16); // TYPE-Index als LONG auf dem Stack (ISAM)
// 0x20 — Variablen und Referenzen
0x20 LoadGlobal(a: u16);
@@ -260,6 +280,8 @@ instrs! {
0x39 ArrBound(a: bool); // true = LBOUND
0x3A FixStr(a: u32); // auf feste Länge kürzen/padden
0x3B CommonArr(a: bool, b: u16, c: u8, d: TypeInit); // gemeinsame Initialisierung/Layoutprüfung
// 0x40 — Arithmetik (monomorph)
0x40 AddI2; 0x41 AddI4; 0x42 AddR4; 0x43 AddR8; 0x44 AddCy;
0x45 SubI2; 0x46 SubI4; 0x47 SubR4; 0x48 SubR8; 0x49 SubCy;
@@ -355,10 +377,122 @@ instrs! {
0xD6 LsetRset(a: bool); // rset?; Stack: Referenz, Wert // argc, line_mode — Dateinummer liegt unter den Referenzen
}
impl Enc for HTy {
fn enc(&self, out: &mut Vec<u8>) {
let tag = match self {
HTy::Num(NumTy::Int) => 0,
HTy::Num(NumTy::Lng) => 1,
HTy::Num(NumTy::Sng) => 2,
HTy::Num(NumTy::Dbl) => 3,
HTy::Num(NumTy::Cur) => 4,
HTy::Str => 5,
HTy::FixedStr(_) => 6,
HTy::Udt(_) => 7,
HTy::Form => 8,
HTy::Control => 9,
};
out.push(tag);
match self {
HTy::FixedStr(n) => n.enc(out),
HTy::Udt(n) => n.enc(out),
_ => {}
}
}
fn dec(r: &mut Reader) -> Result<Self, LoadError> {
Ok(match r.u8()? {
0 => HTy::Num(NumTy::Int),
1 => HTy::Num(NumTy::Lng),
2 => HTy::Num(NumTy::Sng),
3 => HTy::Num(NumTy::Dbl),
4 => HTy::Num(NumTy::Cur),
5 => HTy::Str,
6 => HTy::FixedStr(r.u32()?),
7 => HTy::Udt(r.u16()?),
8 => HTy::Form,
9 => HTy::Control,
_ => return Err(LoadError::Corrupt("Signaturtyp")),
})
}
}
impl Enc for PropertyValue {
fn enc(&self, out: &mut Vec<u8>) {
match self {
Self::Integer(v) => {
out.push(0);
v.enc(out);
}
Self::Single(v) => {
out.push(1);
v.enc(out);
}
Self::String(v) => {
out.push(2);
w_string(out, v);
}
Self::Boolean(v) => {
out.push(3);
v.enc(out);
}
Self::Object(v) => {
out.push(4);
v.is_some().enc(out);
if let Some((object, index)) = v {
object.enc(out);
index.is_some().enc(out);
if let Some(index) = index {
index.enc(out);
}
}
}
Self::IntegerArray(v) => {
out.push(5);
(v.len() as u32).enc(out);
for v in v {
v.enc(out);
}
}
}
}
fn dec(r: &mut Reader) -> Result<Self, LoadError> {
Ok(match r.u8()? {
0 => Self::Integer(i32::dec(r)?),
1 => Self::Single(f32::dec(r)?),
2 => Self::String(r.string()?),
3 => Self::Boolean(bool::dec(r)?),
4 => Self::Object(if bool::dec(r)? {
Some((
r.u16()?,
if bool::dec(r)? {
Some(i32::dec(r)?)
} else {
None
},
))
} else {
None
}),
5 => {
let n = r.u32()?;
let mut values = Vec::new();
for _ in 0..n {
values.push(i32::dec(r)?);
}
Self::IntegerArray(values)
}
_ => return Err(LoadError::Corrupt("Anfangswerttyp")),
})
}
}
// ---- Modulstruktur ------------------------------------------------------------
#[derive(Debug, Clone)]
pub struct ProcCode {
pub module: u16,
pub kind: HProcKind,
pub params: Vec<HParam>,
pub ret_ty: Option<HTy>,
pub name: String,
pub n_params: u16,
/// Initialisierung aller Frame-Slots (Parameter zuerst; deren Init
@@ -378,6 +512,10 @@ pub struct DataItem {
/// Übersetztes Modul — Inhalt des `.tbc`-Containers.
#[derive(Debug)]
pub struct CompiledModule {
pub modules: Vec<(String, u8)>,
pub sources: Vec<SourceFile>,
pub form_initial: Vec<FormInitial>,
pub startup_form: Option<u16>,
pub name: String,
/// `OPTION BASE` (Untergrenze impliziter Arrays).
pub option_base: u8,
@@ -402,9 +540,204 @@ fn w_string(out: &mut Vec<u8>, s: &str) {
}
impl CompiledModule {
pub fn validate(&self) -> Result<(), LoadError> {
let bad = || LoadError::Corrupt("ungültige Tabellenreferenz oder Anfangsdaten");
if self.procs.is_empty()
|| self.sources.is_empty()
|| self.modules.is_empty()
|| self.modules.len() > u16::MAX as usize
|| self.procs.len() > u16::MAX as usize
|| self.objects.len() >= u16::MAX as usize
|| self.strings.len() >= u16::MAX as usize
|| self.globals_init.len() > u16::MAX as usize
|| self.global_names.len() != self.globals_init.len()
|| self.modules.iter().any(|(_, base)| *base > 1)
|| self.option_base != self.modules[0].1
|| self
.sources
.iter()
.any(|s| s.module as usize >= self.modules.len())
{
return Err(bad());
}
let ty_ok =
|ty: &TypeInit| !matches!(ty, TypeInit::Udt(id) if *id as usize >= self.udts.len());
let sig_ok = |ty: &HTy| !matches!(ty, HTy::Udt(id) if *id as usize >= self.udts.len());
if self.globals_init.iter().any(|t| !ty_ok(t)) {
return Err(bad());
}
for (id, udt) in self.udts.iter().enumerate() {
if udt
.fields
.iter()
.any(|t| matches!(t, TypeInit::Udt(n) if *n as usize >= id))
{
return Err(bad());
}
}
for (id, o) in self.objects.iter().enumerate() {
if o.parent.is_some_and(|parent| parent as usize >= id) {
return Err(bad());
}
if o.parent_form.as_deref()
!= o.parent
.map(|parent| self.objects[parent as usize].name.as_str())
{
return Err(bad());
}
}
if self.startup_form.is_some_and(|id| {
self.objects
.get(id as usize)
.is_none_or(|o| o.class != ObjectClass::Form)
}) {
return Err(bad());
}
for e in &self.event_procs {
if e.object as usize >= self.objects.len() || e.proc as usize >= self.procs.len() {
return Err(bad());
}
}
let mut initials = std::collections::HashSet::new();
for initial in &self.form_initial {
let object = self.objects.get(initial.object as usize).ok_or_else(bad)?;
if !initials.insert((initial.object, initial.index))
|| (initial.index != 0 && !object.array)
{
return Err(bad());
}
for (id, value) in &initial.properties {
use tb_frontend::forms::PropertyType as T;
let spec = tb_frontend::forms::properties(object.class)
.get(*id as usize)
.copied()
.ok_or_else(bad)?;
let valid = match (value, spec.ty) {
(PropertyValue::Integer(v), T::Integer) => {
!spec.min.is_some_and(|min| *v < min)
&& !spec.max.is_some_and(|max| *v > max)
&& (spec.name != "INDEX" || *v == initial.index)
}
(PropertyValue::Single(v), T::Single) => v.is_finite(),
(PropertyValue::String(_), T::String)
| (PropertyValue::Boolean(_), T::Boolean)
| (PropertyValue::IntegerArray(_), T::IntegerArray) => true,
(PropertyValue::Object(v), T::Object) => {
v.is_none_or(|(id, _)| (id as usize) < self.objects.len())
}
_ => false,
};
if !valid {
return Err(bad());
}
}
}
for p in &self.procs {
if p.module as usize >= self.modules.len()
|| p.n_params as usize != p.params.len()
|| p.params.len() > p.locals_init.len()
|| p.locals_init.len() != p.local_names.len()
|| p.locals_init.iter().any(|t| !ty_ok(t))
|| p.params.iter().any(|p| !sig_ok(&p.ty))
|| p.ret_ty.as_ref().is_some_and(|t| !sig_ok(t))
|| matches!(p.kind, HProcKind::Function | HProcKind::DefFn) != p.ret_ty.is_some()
|| p.params.iter().zip(&p.locals_init).any(|(param, init)| {
*init
!= if param.array {
TypeInit::Empty
} else {
crate::codegen::type_init(&param.ty)
}
})
{
return Err(bad());
}
for instruction in &p.code {
use Instr::*;
let valid = match instruction {
Source(id, _) => (*id as usize) < self.sources.len(),
PushUdtId(id) => (*id as usize) < self.udts.len(),
PushStr(id) | Unsupported(id) => (*id as usize) < self.strings.len(),
LoadGlobal(id) | StoreGlobal(id) | MakeRefGlobal(id) => {
(*id as usize) < self.globals_init.len()
}
LoadLocal(id) | StoreLocal(id) | MakeRefLocal(id) | LoadRef(id)
| StoreRef(id) => (*id as usize) < p.locals_init.len(),
LoadArr(global, id, _, ty)
| DimArr(global, id, _, ty)
| CommonArr(global, id, _, ty)
| RedimArr(global, id, _, ty) => {
ty_ok(ty)
&& (*id as usize)
< if *global {
self.globals_init.len()
} else {
p.locals_init.len()
}
}
EraseSlot(global, id) => {
(*id as usize)
< if *global {
self.globals_init.len()
} else {
p.locals_init.len()
}
}
Call(id, argc) => self
.procs
.get(*id as usize)
.is_some_and(|p| p.n_params == *argc as u16),
Jump(pc) | JumpIfFalse(pc) | JumpIfTrue(pc) | Gosub(pc) | RetGosubTo(pc)
| OnErrorLocal(pc) | ResumeLabel(pc) => (*pc as usize) < p.code.len(),
OnErrorGoto(pc) => self
.procs
.first()
.is_some_and(|p| (*pc as usize) < p.code.len()),
OnJump(id, _) => self
.jump_tables
.get(*id as usize)
.is_some_and(|t| t.iter().all(|pc| (*pc as usize) < p.code.len())),
Restore(id) => (*id as usize) <= self.data.len(),
Input(_, _, id, _) => *id == u16::MAX || (*id as usize) < self.strings.len(),
LoadObjectProperty(id, prop, _)
| StoreObjectProperty(id, prop, _)
| LoadObjectIndexedProperty(id, prop)
| StoreObjectIndexedProperty(id, prop) => {
self.objects.get(*id as usize).is_some_and(|o| {
((*prop & 0x7fff) as usize)
< tb_frontend::forms::properties(o.class).len()
})
}
PushObject(id, _) | ObjectLoad(id, _, _) => (*id as usize) < self.objects.len(),
ObjectMethod(id, method, _) | ObjectMethodFn(id, method, _) => {
self.objects.get(*id as usize).is_some_and(|o| {
(*method as usize) < tb_frontend::forms::methods(o.class).len()
})
}
LoadDynamicObjectProperty(name) | StoreDynamicObjectProperty(name) => {
(*name as usize) < self.strings.len()
}
GetPut(_, _, 7, id) => (*id as usize) < self.udts.len(),
GetPut(_, _, kind, _) => *kind <= 8,
TypeOf(class) => ObjectClass::from_id(*class).is_some(),
TrapDefine(kind, pc) => *kind <= 3 && (*pc as usize) < p.code.len(),
TrapDisable(kind) => *kind <= 3,
TrapSet(kind, state) => *kind <= 3 && *state <= 2,
Run(kind) => *kind <= 2,
ReadData(kind) => *kind <= 1,
_ => true,
};
if !valid {
return Err(bad());
}
}
}
Ok(())
}
/// `.tbc`-Container schreiben: Magic, Version, Flags, Abschnittstabelle
/// (Kennung/Offset/Länge), Abschnitte MODN, CONS, TYPS, GLOB, PROC
/// (mit eingebettetem Code und Zeileninfo), DATA, JMPT.
/// (Kennung/Offset/Länge), Abschnitte MODN, SRCS, CONS, TYPS, GLOB, PROC
/// (mit eingebettetem Code und Quellorten), DATA, JMPT, OBJS.
pub fn to_tbc(&self) -> Vec<u8> {
let mut sections: Vec<([u8; 4], Vec<u8>)> = Vec::new();
@@ -413,6 +746,19 @@ impl CompiledModule {
modn.push(self.option_base);
sections.push((*b"MODN", modn));
let mut srcs = Vec::new();
(self.modules.len() as u32).enc(&mut srcs);
for (name, base) in &self.modules {
w_string(&mut srcs, name);
base.enc(&mut srcs);
}
(self.sources.len() as u32).enc(&mut srcs);
for source in &self.sources {
source.module.enc(&mut srcs);
w_string(&mut srcs, &source.path);
}
sections.push((*b"SRCS", srcs));
let mut cons = Vec::new();
cons.extend_from_slice(&(self.strings.len() as u32).to_le_bytes());
for s in &self.strings {
@@ -443,6 +789,19 @@ impl CompiledModule {
proc.extend_from_slice(&(self.procs.len() as u32).to_le_bytes());
for p in &self.procs {
w_string(&mut proc, &p.name);
p.module.enc(&mut proc);
(p.kind as u8).enc(&mut proc);
(p.params.len() as u32).enc(&mut proc);
for param in &p.params {
w_string(&mut proc, &param.name);
param.ty.enc(&mut proc);
param.array.enc(&mut proc);
param.by_ref.enc(&mut proc);
}
p.ret_ty.is_some().enc(&mut proc);
if let Some(ty) = &p.ret_ty {
ty.enc(&mut proc);
}
proc.extend_from_slice(&p.n_params.to_le_bytes());
proc.extend_from_slice(&(p.locals_init.len() as u32).to_le_bytes());
for (init, name) in p.locals_init.iter().zip(&p.local_names) {
@@ -484,6 +843,7 @@ impl CompiledModule {
objs.push(o.class.id());
w_string(&mut objs, o.parent_form.as_deref().unwrap_or(""));
objs.push(o.array as u8);
o.parent.unwrap_or(u16::MAX).enc(&mut objs);
}
objs.extend_from_slice(&(self.event_procs.len() as u32).to_le_bytes());
for e in &self.event_procs {
@@ -491,6 +851,17 @@ impl CompiledModule {
w_string(&mut objs, &e.event);
objs.extend_from_slice(&e.proc.to_le_bytes());
}
self.startup_form.unwrap_or(u16::MAX).enc(&mut objs);
(self.form_initial.len() as u32).enc(&mut objs);
for initial in &self.form_initial {
initial.object.enc(&mut objs);
initial.index.enc(&mut objs);
(initial.properties.len() as u32).enc(&mut objs);
for (id, value) in &initial.properties {
id.enc(&mut objs);
value.enc(&mut objs);
}
}
sections.push((*b"OBJS", objs));
// Header + Abschnittstabelle
@@ -527,8 +898,13 @@ impl CompiledModule {
if version != TBC_VERSION {
return Err(LoadError::Version(version));
}
let _flags = r.u16()?;
if r.u16()? != 0 {
return Err(LoadError::Corrupt("Header-Flags"));
}
let n_sections = r.u32()? as usize;
if n_sections != 9 {
return Err(LoadError::Corrupt("Abschnittsanzahl"));
}
let mut table = Vec::new();
for _ in 0..n_sections {
let id: [u8; 4] = r.take(4)?.try_into().unwrap();
@@ -536,6 +912,27 @@ impl CompiledModule {
let len = r.u32()? as usize;
table.push((id, off, len));
}
let expected = [
*b"MODN", *b"SRCS", *b"CONS", *b"TYPS", *b"GLOB", *b"PROC", *b"DATA", *b"JMPT",
*b"OBJS",
];
let mut seen = std::collections::HashSet::new();
let mut ranges = Vec::new();
for (id, off, len) in &table {
if !expected.contains(id)
|| !seen.insert(*id)
|| *off < r.pos
|| *off > buf.len()
|| *len > buf.len() - *off
{
return Err(LoadError::Corrupt("Abschnittstabelle"));
}
ranges.push((*off, off + len));
}
ranges.sort_unstable();
if ranges.windows(2).any(|pair| pair[0].1 > pair[1].0) {
return Err(LoadError::Corrupt("überlappende Abschnitte"));
}
let section = |id: &[u8; 4]| -> Result<Reader, LoadError> {
for (sid, off, len) in &table {
if sid == id {
@@ -552,44 +949,86 @@ impl CompiledModule {
let name = r.string()?;
let option_base = r.u8()?;
r.finish()?;
let mut r = section(b"SRCS")?;
let n = r.u32()?;
let mut modules = Vec::new();
for _ in 0..n {
modules.push((r.string()?, r.u8()?));
}
let n = r.u32()?;
let mut sources = Vec::new();
for _ in 0..n {
sources.push(SourceFile {
module: r.u16()?,
path: r.string()?,
});
}
r.finish()?;
let mut r = section(b"CONS")?;
let n = r.u32()? as usize;
let mut strings = Vec::with_capacity(n);
let mut strings = Vec::new();
for _ in 0..n {
strings.push(Rc::from(r.string()?.as_str()));
}
r.finish()?;
let mut r = section(b"TYPS")?;
let n = r.u32()? as usize;
let mut udts = Vec::with_capacity(n);
let mut udts = Vec::new();
for _ in 0..n {
let name = r.string()?;
let nf = r.u32()? as usize;
let mut fields = Vec::with_capacity(nf);
let mut fields = Vec::new();
for _ in 0..nf {
fields.push(TypeInit::dec(&mut r)?);
}
udts.push(UdtLayout { name, fields });
}
r.finish()?;
let mut r = section(b"GLOB")?;
let n = r.u32()? as usize;
let mut globals_init = Vec::with_capacity(n);
let mut global_names = Vec::with_capacity(n);
let mut globals_init = Vec::new();
let mut global_names = Vec::new();
for _ in 0..n {
globals_init.push(TypeInit::dec(&mut r)?);
global_names.push(r.string()?);
}
r.finish()?;
let mut r = section(b"PROC")?;
let n = r.u32()? as usize;
let mut procs = Vec::with_capacity(n);
let mut procs = Vec::new();
for _ in 0..n {
let name = r.string()?;
let module = r.u16()?;
let kind = match r.u8()? {
0 => HProcKind::Main,
1 => HProcKind::Sub,
2 => HProcKind::Function,
3 => HProcKind::DefFn,
_ => return Err(LoadError::Corrupt("Prozedurart")),
};
let np = r.u32()?;
let mut params = Vec::new();
for _ in 0..np {
params.push(HParam {
name: r.string()?,
ty: HTy::dec(&mut r)?,
array: bool::dec(&mut r)?,
by_ref: bool::dec(&mut r)?,
});
}
let ret_ty = if bool::dec(&mut r)? {
Some(HTy::dec(&mut r)?)
} else {
None
};
let n_params = r.u16()?;
let nl = r.u32()? as usize;
let mut locals_init = Vec::with_capacity(nl);
let mut local_names = Vec::with_capacity(nl);
let mut locals_init = Vec::new();
let mut local_names = Vec::new();
for _ in 0..nl {
locals_init.push(TypeInit::dec(&mut r)?);
local_names.push(r.string()?);
@@ -598,11 +1037,16 @@ impl CompiledModule {
let code_len = r.u32()? as usize;
let code_bytes = r.take(code_len)?;
let mut cr = Reader::new(code_bytes);
let mut code = Vec::with_capacity(n_instr);
let mut code = Vec::new();
for _ in 0..n_instr {
code.push(Instr::decode(&mut cr)?);
}
cr.finish()?;
procs.push(ProcCode {
module,
kind,
params,
ret_ty,
name,
n_params,
locals_init,
@@ -611,44 +1055,49 @@ impl CompiledModule {
});
}
r.finish()?;
let mut r = section(b"DATA")?;
let n = r.u32()? as usize;
let mut data = Vec::with_capacity(n);
let mut data = Vec::new();
for _ in 0..n {
let text = r.string()?;
let line = r.u32()?;
data.push(DataItem { text, line });
}
r.finish()?;
let mut r = section(b"JMPT")?;
let n = r.u32()? as usize;
let mut jump_tables = Vec::with_capacity(n);
let mut jump_tables = Vec::new();
for _ in 0..n {
let m = r.u32()? as usize;
let mut t = Vec::with_capacity(m);
let mut t = Vec::new();
for _ in 0..m {
t.push(r.u32()?);
}
jump_tables.push(t);
}
r.finish()?;
let mut r = section(b"OBJS")?;
let n = r.u32()? as usize;
let mut objects = Vec::with_capacity(n);
let mut objects = Vec::new();
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;
let array = bool::dec(&mut r)?;
let parent_id = r.u16()?;
objects.push(FormObject {
name,
class,
parent_form: (!parent.is_empty()).then_some(parent),
parent: (parent_id != u16::MAX).then_some(parent_id),
array,
});
}
let n = r.u32()? as usize;
let mut event_procs = Vec::with_capacity(n);
let mut event_procs = Vec::new();
for _ in 0..n {
event_procs.push(HEventProc {
object: r.u16()?,
@@ -657,7 +1106,35 @@ impl CompiledModule {
});
}
Ok(CompiledModule {
let startup = r.u16()?;
let startup_form = (startup != u16::MAX).then_some(startup);
let n = r.u32()?;
let mut form_initial = Vec::new();
for _ in 0..n {
let object = r.u16()?;
let index = i32::dec(&mut r)?;
let n = r.u32()?;
let mut properties = std::collections::BTreeMap::new();
for _ in 0..n {
if properties
.insert(r.u16()?, PropertyValue::dec(&mut r)?)
.is_some()
{
return Err(LoadError::Corrupt("doppelte Anfangseigenschaft"));
}
}
form_initial.push(FormInitial {
object,
index,
properties,
});
}
r.finish()?;
let module = CompiledModule {
modules,
sources,
form_initial,
startup_form,
name,
option_base,
strings,
@@ -669,7 +1146,9 @@ impl CompiledModule {
jump_tables,
objects,
event_procs,
})
};
module.validate()?;
Ok(module)
}
}
@@ -712,6 +1191,13 @@ mod tests {
#[test]
fn tbc_roundtrip() {
let m = CompiledModule {
modules: vec![("TEST".into(), 1)],
sources: vec![SourceFile {
module: 0,
path: "test.bas".into(),
}],
form_initial: vec![],
startup_form: None,
name: "TEST".into(),
option_base: 1,
strings: vec![Rc::from("Hallo"), Rc::from("Welt")],
@@ -722,6 +1208,10 @@ mod tests {
fields: vec![TypeInit::FixedStr(30), TypeInit::Dbl],
}],
procs: vec![ProcCode {
module: 0,
kind: HProcKind::Main,
params: vec![],
ret_ty: None,
name: "TEST".into(),
n_params: 0,
locals_init: vec![],
@@ -751,6 +1241,13 @@ mod tests {
#[test]
fn unbekannte_version_wird_abgelehnt() {
let m = CompiledModule {
modules: vec![("TEST".into(), 0)],
sources: vec![SourceFile {
module: 0,
path: "test.bas".into(),
}],
form_initial: vec![],
startup_form: None,
name: "T".into(),
option_base: 0,
strings: vec![],