Projektmodule und vollständiges TBC-Kompilat umsetzen und Change archivieren
This commit is contained in:
@@ -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(¶m.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, ¶m.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![],
|
||||
|
||||
Reference in New Issue
Block a user