Files
TerminalBasic/crates/tb-ui/src/frm_pcode.rs

2725 lines
81 KiB
Rust

//! Leser fuer den tokenisierten BASIC-Codeteil binaerer VBDOS-Formulare.
use crate::frm::{decode_cp437, error, read_u16, FormError};
fn decode_pcode_bytes(bytes: &[u8]) -> String {
bytes
.iter()
.map(|byte| {
if *byte < 0x80 {
char::from(*byte)
} else {
decode_cp437(&[*byte]).chars().next().unwrap()
}
})
.collect()
}
fn letter_ranges(mask: u32) -> String {
let mut ranges = Vec::new();
let mut first = None;
for index in 0..=26 {
let set = index < 26 && mask & (1 << (31 - index)) != 0;
if set && first.is_none() {
first = Some(index);
} else if !set && first.is_some() {
let start = first.take().unwrap();
let mut range = char::from(b'A' + start as u8).to_string();
if index - start > 1 {
range.push('-');
range.push(char::from(b'A' + index as u8 - 1));
}
ranges.push(range);
}
}
ranges.join(", ")
}
#[derive(Clone, Copy)]
struct TokenDef {
len: i16,
rules: &'static [&'static str],
}
fn token_def(code: u16) -> Option<TokenDef> {
match code {
0x000 => Some(TokenDef {
len: 0,
rules: &["newline::=.{#newline}{#tabh}"],
}),
0x001 => Some(TokenDef {
len: 2,
rules: &["newline::=.{#newline}{#tabi}"],
}),
0x002 => Some(TokenDef {
len: 2,
rules: &["newline::=.{#newline-include}"],
}),
0x003 => Some(TokenDef {
len: 4,
rules: &["newline::=.{#newline-include}{#indent:2} "],
}),
0x004 => Some(TokenDef {
len: 4,
rules: &[".{#newline}{#thaddr:0}{#label:2}"],
}),
0x005 => Some(TokenDef {
len: 6,
rules: &[".{#newline}{#thaddr:0}{#label:2} {#indent:4}"],
}),
0x006 => Some(TokenDef {
len: 0,
rules: &[": "],
}),
0x007 => Some(TokenDef {
len: 2,
rules: &[":{#tabi}"],
}),
0x008 => Some(TokenDef {
len: 0,
rules: &["."],
}),
0x009 => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x00a => Some(TokenDef {
len: -1,
rules: &["{#raw:2}"],
}),
0x00b => Some(TokenDef {
len: 2,
rules: &["expr::={#id+}"],
}),
0x00c => Some(TokenDef {
len: 2,
rules: &[
"consts::={const:1} {#id+} = {0}",
"consts::={consts:1}, {#id+} = {0}",
"{#id+} = {0}",
],
}),
0x00d => Some(TokenDef {
len: 2,
rules: &[
"decls::={decls:1}, {#id+:0} {astype:0}",
"decls::={decls:0}, {#id+:0}",
"decls::={decl:1} {#id+:0} {astype:0}",
"decls::={decl:0} {#id+:0}",
"{#id+:0} {astype:0}",
"{#id+:0}",
],
}),
0x00e => Some(TokenDef {
len: 4,
rules: &["expr::={##id(expr)}"],
}),
0x00f => Some(TokenDef {
len: 4,
rules: &["{##id(expr)} = {$+0}"],
}),
0x010 => Some(TokenDef {
len: 4,
rules: &["decls::={##id(decl)}"],
}),
0x011 => Some(TokenDef {
len: 2,
rules: &["expr::={0}.{#id}"],
}),
0x012 => Some(TokenDef {
len: 2,
rules: &["{0}.{#id} = {1}"],
}),
0x013 => Some(TokenDef {
len: 4,
rules: &["expr::={0}.{#id:2}({1})"],
}),
0x014 => Some(TokenDef {
len: 4,
rules: &["{0}.{#id:2}({1}) = {2}"],
}),
0x015 => Some(TokenDef {
len: 4,
rules: &["astype::={#tabi:2}AS {#type:0}"],
}),
0x016 => Some(TokenDef {
len: 4,
rules: &["astype::={#tabi:2}AS {#type:0}"],
}),
0x017 => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x018 => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x019 => Some(TokenDef {
len: 2,
rules: &["{#id}"],
}),
0x01a => Some(TokenDef {
len: 0,
rules: &["declmod::=SHARED"],
}),
0x01b => Some(TokenDef {
len: 6,
rules: &["deftype::={#thaddr:0}{#DEFxxx}"],
}),
0x01c => Some(TokenDef {
len: 0,
rules: &["{self:1}, {0}", "REDIM {declmod:1} {0}", "REDIM {0}"],
}),
0x01d => Some(TokenDef {
len: 2,
rules: &["END TYPE"],
}),
0x01e => Some(TokenDef {
len: 2,
rules: &["decl::=SHARED"],
}),
0x01f => Some(TokenDef {
len: 2,
rules: &["decl::=STATIC"],
}),
0x020 => Some(TokenDef {
len: 4,
rules: &["TYPE {#id:2}"],
}),
0x021 => Some(TokenDef {
len: -1,
rules: &["$STATIC{#raw}"],
}),
0x022 => Some(TokenDef {
len: -1,
rules: &["$DYNAMIC{#raw}"],
}),
0x023 => Some(TokenDef {
len: 0,
rules: &["const::=CONST"],
}),
0x024 => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x025 => Some(TokenDef {
len: 0,
rules: &["BYVAL {0}"],
}),
0x026 => Some(TokenDef {
len: -1,
rules: &["{deffn:1} = {0}"],
}),
0x027 => Some(TokenDef {
len: 0,
rules: &["COM({0})"],
}),
0x028 => Some(TokenDef {
len: 2,
rules: &["ON {0} GOSUB {#id}"],
}),
0x029 => Some(TokenDef {
len: 0,
rules: &["KEY({0})"],
}),
0x02a => Some(TokenDef {
len: 0,
rules: &["{0} OFF"],
}),
0x02b => Some(TokenDef {
len: 0,
rules: &["{0} ON"],
}),
0x02c => Some(TokenDef {
len: 0,
rules: &["{0} STOP"],
}),
0x02d => Some(TokenDef {
len: 0,
rules: &["PEN"],
}),
0x02e => Some(TokenDef {
len: 0,
rules: &["PLAY"],
}),
0x02f => Some(TokenDef {
len: 0,
rules: &["PLAY({0})"],
}),
0x030 => Some(TokenDef {
len: 0,
rules: &["SIGNAL({0})"],
}),
0x031 => Some(TokenDef {
len: 0,
rules: &["STRIG({0})"],
}),
0x032 => Some(TokenDef {
len: 0,
rules: &["TIMER"],
}),
0x033 => Some(TokenDef {
len: 0,
rules: &["TIMER({0})"],
}),
0x034 => Some(TokenDef {
len: 4,
rules: &["newline::={#thaddr:0}{#label:2} "],
}),
0x035 => Some(TokenDef {
len: 6,
rules: &["newline::={#thaddr:0}{#label:2} {#indent:4}"],
}),
0x037 => Some(TokenDef {
len: 4,
rules: &["CALL {#id:2}{##call()}"],
}),
0x038 => Some(TokenDef {
len: 4,
rules: &["{#id:2}{##call}"],
}),
0x039 => Some(TokenDef {
len: 4,
rules: &["CALLS {#id:2}{##call()}"],
}),
0x03a => Some(TokenDef {
len: 0,
rules: &["CASE ELSE"],
}),
0x03b => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, {0}", "case::=CASE {0}"],
}),
0x03c => Some(TokenDef {
len: 0,
rules: &["case::={case:2}, {1} TO {0}", "case::=CASE {1} TO {0}"],
}),
0x03d => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, IS = {0}", "case::=CASE IS = {0}"],
}),
0x03e => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, IS < {0}", "case::=CASE IS < {0}"],
}),
0x03f => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, IS > {0}", "case::=CASE IS > {0}"],
}),
0x040 => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, IS <= {0}", "case::=CASE IS <= {0}"],
}),
0x041 => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, IS >= {0}", "case::=CASE IS >= {0}"],
}),
0x042 => Some(TokenDef {
len: 0,
rules: &["case::={case:1}, IS <> {0}", "case::=CASE IS <> {0}"],
}),
0x043 => Some(TokenDef {
len: 0,
rules: &["ON"],
}),
0x044 => Some(TokenDef {
len: -1,
rules: &["DECLARE {#procdecl()}"],
}),
0x045 => Some(TokenDef {
len: -1,
rules: &["deffn::={#procdecl:2}"],
}),
0x046 => Some(TokenDef {
len: 0,
rules: &["DO"],
}),
0x047 => Some(TokenDef {
len: 0,
rules: &["DO UNTIL {0}"],
}),
0x048 => Some(TokenDef {
len: 2,
rules: &["DO WHILE {0}"],
}),
0x049 => Some(TokenDef {
len: 2,
rules: &["{newline:0}ELSE ", " ELSE "],
}),
0x04a => Some(TokenDef {
len: 2,
rules: &["{#id}"],
}),
0x04c => Some(TokenDef {
len: 0,
rules: &[" ELSE "],
}),
0x04d => Some(TokenDef {
len: 2,
rules: &["ELSEIF {0} THEN"],
}),
0x04e => Some(TokenDef {
len: 0,
rules: &["END"],
}),
0x04f => Some(TokenDef {
len: -1,
rules: &["END DEF"],
}),
0x050 => Some(TokenDef {
len: 0,
rules: &["END IF"],
}),
0x051 => Some(TokenDef {
len: 0,
rules: &["END {#proctype}"],
}),
0x052 => Some(TokenDef {
len: 0,
rules: &["END SELECT"],
}),
0x053 => Some(TokenDef {
len: 2,
rules: &["EXIT DO"],
}),
0x054 => Some(TokenDef {
len: 2,
rules: &["EXIT FOR"],
}),
0x055 => Some(TokenDef {
len: 2,
rules: &["EXIT {#proctype}"],
}),
0x056 => Some(TokenDef {
len: 4,
rules: &["FOR {2} = {1} TO {0}"],
}),
0x057 => Some(TokenDef {
len: 4,
rules: &["FOR {3} = {2} TO {1} STEP {0}"],
}),
0x058 => Some(TokenDef {
len: -1,
rules: &["funcdef::={#procdecl}"],
}),
0x059 => Some(TokenDef {
len: 2,
rules: &["GOSUB {#id}"],
}),
0x05b => Some(TokenDef {
len: 2,
rules: &["GOTO {#id}"],
}),
0x05d => Some(TokenDef {
len: 2,
rules: &["IF {0} THEN "],
}),
0x05e => Some(TokenDef {
len: 2,
rules: &["IF {0} THEN {#id}"],
}),
0x060 => Some(TokenDef {
len: 2,
rules: &["IF {0} GOTO {#id}"],
}),
0x061 => Some(TokenDef {
len: 2,
rules: &["IF {0} THEN"],
}),
0x062 => Some(TokenDef {
len: 2,
rules: &["LOOP"],
}),
0x063 => Some(TokenDef {
len: 2,
rules: &["LOOP UNTIL {0}"],
}),
0x064 => Some(TokenDef {
len: 2,
rules: &["LOOP WHILE {0}"],
}),
0x065 => Some(TokenDef {
len: 4,
rules: &["NEXT"],
}),
0x066 => Some(TokenDef {
len: 4,
rules: &["{self:1}, {0}", "NEXT {0}"],
}),
0x067 => Some(TokenDef {
len: 2,
rules: &["ON ERROR GOTO {#id}"],
}),
0x068 => Some(TokenDef {
len: -1,
rules: &["ON {0} GOSUB {#id-list}"],
}),
0x069 => Some(TokenDef {
len: -1,
rules: &["ON {0} GOTO {#id-list}"],
}),
0x06a => Some(TokenDef {
len: 0,
rules: &["RESTORE"],
}),
0x06b => Some(TokenDef {
len: 2,
rules: &["RESTORE {#id}"],
}),
0x06c => Some(TokenDef {
len: 0,
rules: &["RESUME"],
}),
0x06d => Some(TokenDef {
len: 2,
rules: &["RESUME {#id}"],
}),
0x06e => Some(TokenDef {
len: 0,
rules: &["RESUME NEXT"],
}),
0x06f => Some(TokenDef {
len: 0,
rules: &["RETURN"],
}),
0x070 => Some(TokenDef {
len: 2,
rules: &["RETURN {#id}"],
}),
0x071 => Some(TokenDef {
len: 0,
rules: &["RUN {0}"],
}),
0x072 => Some(TokenDef {
len: 2,
rules: &["RUN {#id}"],
}),
0x073 => Some(TokenDef {
len: 0,
rules: &["RUN"],
}),
0x074 => Some(TokenDef {
len: 2,
rules: &["SELECT CASE {0}"],
}),
0x075 => Some(TokenDef {
len: 2,
rules: &["STOP"],
}),
0x076 => Some(TokenDef {
len: -1,
rules: &["subdef::={#procdecl}"],
}),
0x077 => Some(TokenDef {
len: 0,
rules: &["WAIT {1}, {0}"],
}),
0x078 => Some(TokenDef {
len: 0,
rules: &["WAIT {2}, {1}, {0}"],
}),
0x079 => Some(TokenDef {
len: 2,
rules: &["WEND"],
}),
0x07a => Some(TokenDef {
len: 2,
rules: &["WHILE {0}"],
}),
0x07b => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x07c => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x07d => Some(TokenDef {
len: 0,
rules: &["prnmod::={prnmod:1} {0},", "prnmod::=PRINT {0},"],
}),
0x07e => Some(TokenDef {
len: 0,
rules: &["{0}"],
}),
0x07f => Some(TokenDef {
len: 0,
rules: &["{0}"],
}),
0x080 => Some(TokenDef {
len: 0,
rules: &["{0}"],
}),
0x081 => Some(TokenDef {
len: 0,
rules: &["1st-coord::=({1}, {0})"],
}),
0x082 => Some(TokenDef {
len: 0,
rules: &["1st-coord::=STEP({1}, {0})"],
}),
0x083 => Some(TokenDef {
len: 0,
rules: &["{1st-coord:2}-({1}, {0})", "({1}, {0})"],
}),
0x084 => Some(TokenDef {
len: 0,
rules: &["{1st-coord:2}-STEP({1}, {0})", "-STEP({1}, {0})"],
}),
0x085 => Some(TokenDef {
len: 0,
rules: &["FIELD {0}"],
}),
0x086 => Some(TokenDef {
len: 0,
rules: &[", {1} AS {0}"],
}),
0x087 => Some(TokenDef {
len: 0,
rules: &["finput::=INPUT {0},"],
}),
0x088 => Some(TokenDef {
len: 0,
rules: &["{input|finput:1} {inputs:0}"],
}),
0x089 => Some(TokenDef {
len: -1,
rules: &["input::=INPUT {##input-args}"],
}),
0x08a => Some(TokenDef {
len: 0,
rules: &["#{0}"],
}),
0x08c => Some(TokenDef {
len: 2,
rules: &[""],
}),
0x08f => Some(TokenDef {
len: 0,
rules: &[
"prnsmc::={self|prncma|prnsrl:1} SPC({0});",
"prnsmc::=SPC({0});",
],
}),
0x090 => Some(TokenDef {
len: 0,
rules: &[
"prnsmc::={self|prncma|prnsrl:1} TAB({0});",
"prnsmc::=TAB({0});",
],
}),
0x091 => Some(TokenDef {
len: 0,
rules: &["prncma::={self|prnsmc|prnsrl:0} ,", "prncma::=,"],
}),
0x092 => Some(TokenDef {
len: 0,
rules: &[
"prnsmc::={self:0}",
"prnsmc::={prncma|prnsrl:0} ;",
"prnsmc::=;",
],
}),
0x093 => Some(TokenDef {
len: 0,
rules: &[
"{prnmod:2} {prnuse:1} {prnsrl|prnsmc|prncma:0}",
"{prnmod:1} {prnsrl|prnsmc|prncma:0}",
"{prnmod:1} {prnuse:0}",
"{prnmod:1}",
"PRINT {prnuse:1} {prnsrl|prnsmc|prncma:0}",
"PRINT {prnsrl|prnsmc|prncma:0}",
"PRINT {prnuse:0}",
"PRINT",
],
}),
0x094 => Some(TokenDef {
len: 0,
rules: &[
"prnsrl::={prncma|prnsmc|self:1} {expr:0},",
"prnsrl::={expr:0},",
],
}),
0x095 => Some(TokenDef {
len: 0,
rules: &[
"prnsrl::={prncma|prnsmc|self:1} {expr:0};",
"prnsrl::={expr:0};",
],
}),
0x096 => Some(TokenDef {
len: 0,
rules: &[
"{prnmod:3} {prnuse:2} {prnsmc|prncma|prnsrl:1} {expr:0}",
"{prnmod:2} {prnsmc|prncma|prnsrl:1} {expr:0}",
"{prnmod:1} {prnsmc|prncma|prnsrl|expr:0}",
"PRINT {prnuse:2} {prnsmc|prncma|prnsrl:1} {expr:0}",
"PRINT {prnsmc|prncma|prnsrl:1} {expr:0}",
"PRINT {prnsmc|prncma|prnsrl|expr:0}",
],
}),
0x097 => Some(TokenDef {
len: -1,
rules: &["{#tabi:0}'{#raw:2}"],
}),
0x099 => Some(TokenDef {
len: -1,
rules: &["$INCLUDE: '{#raw:0}"],
}),
0x09a => Some(TokenDef {
len: 0,
rules: &["BEEP"],
}),
0x09b => Some(TokenDef {
len: 0,
rules: &["BLOAD {0}"],
}),
0x09c => Some(TokenDef {
len: 0,
rules: &["BLOAD {1}, {0}"],
}),
0x09d => Some(TokenDef {
len: 0,
rules: &["BSAVE {2}, {1}, {0}"],
}),
0x09e => Some(TokenDef {
len: 0,
rules: &["CHDIR {0}"],
}),
0x09f => Some(TokenDef {
len: 0,
rules: &["CIRCLE {##circle-args}"],
}),
0x0a0 => Some(TokenDef {
len: 0,
rules: &["CIRCLE {##circle-args}"],
}),
0x0a1 => Some(TokenDef {
len: 2,
rules: &["CLEAR{##varargs}"],
}),
0x0a2 => Some(TokenDef {
len: 2,
rules: &["CLOSE{##varargs}"],
}),
0x0a3 => Some(TokenDef {
len: 0,
rules: &["CLS {expr:0}", "CLS "],
}),
0x0a4 => Some(TokenDef {
len: 2,
rules: &["COLOR{##varargs}"],
}),
0x0a5 => Some(TokenDef {
len: 4,
rules: &[
"decl::=COMMON {declmod:0}{#blockname:2}",
"decl::=COMMON{#blockname:2}",
],
}),
0x0a6 => Some(TokenDef {
len: -1,
rules: &["DATA{#cstr:2}"],
}),
0x0a7 => Some(TokenDef {
len: 0,
rules: &["DATE$ = {0}"],
}),
0x0a8 => Some(TokenDef {
len: 0,
rules: &["DEF SEG"],
}),
0x0a9 => Some(TokenDef {
len: 0,
rules: &["DEF SEG = {0}"],
}),
0x0aa => Some(TokenDef {
len: 0,
rules: &["DRAW {0}"],
}),
0x0ab => Some(TokenDef {
len: 0,
rules: &["ENVIRON {0}"],
}),
0x0ac => Some(TokenDef {
len: 2,
rules: &["ERASE{##varargs}"],
}),
0x0ad => Some(TokenDef {
len: 0,
rules: &["ERROR {0}"],
}),
0x0ae => Some(TokenDef {
len: 0,
rules: &["FILES"],
}),
0x0af => Some(TokenDef {
len: 0,
rules: &["FILES {0}"],
}),
0x0b0 => Some(TokenDef {
len: 0,
rules: &["GET {0}"],
}),
0x0b1 => Some(TokenDef {
len: 0,
rules: &["GET {1}, {0}"],
}),
0x0b2 => Some(TokenDef {
len: 2,
rules: &["GET {1}, , {0}"],
}),
0x0b3 => Some(TokenDef {
len: 2,
rules: &["GET {2}, {1}, {0}"],
}),
0x0b4 => Some(TokenDef {
len: 0,
rules: &["GET {1}, {0}"],
}),
0x0b5 => Some(TokenDef {
len: 2,
rules: &["PUT {1}, {0}, {#action-verb}"],
}),
0x0b6 => Some(TokenDef {
len: 0,
rules: &["inputs::={inputs:1}, {0}", "inputs::={0}"],
}),
0x0b7 => Some(TokenDef {
len: 0,
rules: &["IOCTL {1}, {0}"],
}),
0x0b8 => Some(TokenDef {
len: 2,
rules: &["KEY {#keymode}"],
}),
0x0b9 => Some(TokenDef {
len: 0,
rules: &["KEY {1}, {0}"],
}),
0x0ba => Some(TokenDef {
len: 0,
rules: &["KILL {0}"],
}),
0x0bb => Some(TokenDef {
len: 2,
rules: &["LINE {##line-args}"],
}),
0x0bc => Some(TokenDef {
len: 2,
rules: &["LINE {##line-args}"],
}),
0x0bd => Some(TokenDef {
len: 2,
rules: &["LINE {##line-args}"],
}),
0x0be => Some(TokenDef {
len: 2,
rules: &["LINE {##line-args}"],
}),
0x0bf => Some(TokenDef {
len: 0,
rules: &["LET "],
}),
0x0c0 => Some(TokenDef {
len: 2,
rules: &[
"input::=LINE {finput:1} {0}",
"input::=LINE INPUT {##input-args} {0}",
],
}),
0x0c1 => Some(TokenDef {
len: 2,
rules: &["LOCATE{##varargs}"],
}),
0x0c2 => Some(TokenDef {
len: 2,
rules: &["LOCK {##lock-args}"],
}),
0x0c3 => Some(TokenDef {
len: 0,
rules: &["prnmod::=LPRINT"],
}),
0x0c4 => Some(TokenDef {
len: 0,
rules: &["LSET {0} = {1}"],
}),
0x0c5 => Some(TokenDef {
len: 0,
rules: &["MID$({0}, {2}) = {1}"],
}),
0x0c6 => Some(TokenDef {
len: 0,
rules: &["MID$({0}, {3}, {2}) = {1}"],
}),
0x0c7 => Some(TokenDef {
len: 0,
rules: &["MKDIR {0}"],
}),
0x0c8 => Some(TokenDef {
len: 0,
rules: &["NAME {1} AS {0}"],
}),
0x0c9 => Some(TokenDef {
len: 2,
rules: &["OPEN {1} {#open-args} AS {0}"],
}),
0x0ca => Some(TokenDef {
len: 2,
rules: &["OPEN {2} {#open-args} AS {1} LEN = {0}"],
}),
0x0cb => Some(TokenDef {
len: 0,
rules: &["OPEN {2}, {1}, {0}"],
}),
0x0cc => Some(TokenDef {
len: 0,
rules: &["OPEN {3}, {2}, {1}, {0}"],
}),
0x0cd => Some(TokenDef {
len: 0,
rules: &["OPTION BASE 0"],
}),
0x0ce => Some(TokenDef {
len: 0,
rules: &["OPTION BASE 1"],
}),
0x0cf => Some(TokenDef {
len: 0,
rules: &["OUT {1}, {0}"],
}),
0x0d0 => Some(TokenDef {
len: 0,
rules: &[
"PAINT {2}{nularg:1}{nularg:0}",
"PAINT {2}, {nularg:1}, {0}",
"PAINT {2}, {1}{nularg:0}",
"PAINT {2}, {1}, {0}",
],
}),
0x0d1 => Some(TokenDef {
len: 0,
rules: &["PAINT {3}, {2}, {1}, {0}"],
}),
0x0d2 => Some(TokenDef {
len: 0,
rules: &["PALETTE"],
}),
0x0d3 => Some(TokenDef {
len: 0,
rules: &["PALETTE {1}, {0}"],
}),
0x0d4 => Some(TokenDef {
len: 0,
rules: &["PALETTE {0}"],
}),
0x0d5 => Some(TokenDef {
len: 0,
rules: &["PCOPY {1}, {0}"],
}),
0x0d6 => Some(TokenDef {
len: 0,
rules: &["PLAY {0}"],
}),
0x0d7 => Some(TokenDef {
len: 0,
rules: &["POKE {1}, {0}"],
}),
0x0d8 => Some(TokenDef {
len: 0,
rules: &["PRESET {0}"],
}),
0x0d9 => Some(TokenDef {
len: 0,
rules: &["PRESET {0}, {1}"],
}),
0x0da => Some(TokenDef {
len: 0,
rules: &["PSET {0}"],
}),
0x0db => Some(TokenDef {
len: 0,
rules: &["PSET {1}, {0}"],
}),
0x0dd => Some(TokenDef {
len: 0,
rules: &["PUT {1}, {0}"],
}),
0x0de => Some(TokenDef {
len: 2,
rules: &["PUT {1}, , {0}"],
}),
0x0df => Some(TokenDef {
len: 2,
rules: &["PUT {2}, {1}, {0}"],
}),
0x0e0 => Some(TokenDef {
len: 0,
rules: &["RANDOMIZE"],
}),
0x0e1 => Some(TokenDef {
len: 0,
rules: &["RANDOMIZE {0}"],
}),
0x0e2 => Some(TokenDef {
len: 0,
rules: &["{self:1}, {0}", "READ {0}"],
}),
0x0e3 => Some(TokenDef {
len: -1,
rules: &["REM{#raw}"],
}),
0x0e4 => Some(TokenDef {
len: 0,
rules: &["RESET"],
}),
0x0e5 => Some(TokenDef {
len: 0,
rules: &["RMDIR {0}"],
}),
0x0e6 => Some(TokenDef {
len: 0,
rules: &["RSET {0} = {1}"],
}),
0x0e7 => Some(TokenDef {
len: 2,
rules: &["SCREEN{##varargs}"],
}),
0x0e8 => Some(TokenDef {
len: 0,
rules: &["SEEK {1}, {0}"],
}),
0x0e9 => Some(TokenDef {
len: 0,
rules: &["SHELL"],
}),
0x0ea => Some(TokenDef {
len: 0,
rules: &["SHELL {0}"],
}),
0x0eb => Some(TokenDef {
len: 0,
rules: &["SLEEP"],
}),
0x0ec => Some(TokenDef {
len: 0,
rules: &["SOUND {1}, {0}"],
}),
0x0ed => Some(TokenDef {
len: 2,
rules: &["SWAP {1}, {0}"],
}),
0x0ee => Some(TokenDef {
len: 0,
rules: &["SYSTEM"],
}),
0x0ef => Some(TokenDef {
len: 0,
rules: &["TIME$ = {0}"],
}),
0x0f0 => Some(TokenDef {
len: 0,
rules: &["TROFF"],
}),
0x0f1 => Some(TokenDef {
len: 0,
rules: &["TRON"],
}),
0x0f2 => Some(TokenDef {
len: 2,
rules: &["UNLOCK {##lock-args}"],
}),
0x0f3 => Some(TokenDef {
len: 0,
rules: &[
"VIEW ({5}, {4})-({3}, {2}){nularg:1}{nularg:0}",
"VIEW ({5}, {4})-({3}, {2}), {nularg:1}, {0}",
"VIEW ({5}, {4})-({3}, {2}), {1}{nularg:0}",
"VIEW ({5}, {4})-({3}, {2})",
],
}),
0x0f4 => Some(TokenDef {
len: 0,
rules: &["VIEW"],
}),
0x0f5 => Some(TokenDef {
len: 0,
rules: &["VIEW PRINT"],
}),
0x0f6 => Some(TokenDef {
len: 0,
rules: &["VIEW PRINT {1} TO {0}"],
}),
0x0f7 => Some(TokenDef {
len: 0,
rules: &[
"VIEW SCREEN ({5}, {4})-({3}, {2}){nularg:1}{nularg:0}",
"VIEW SCREEN ({5}, {4})-({3}, {2}), {nularg:1}, {0}",
"VIEW SCREEN ({5}, {4})-({3}, {2}), {1}{nularg:0}",
"VIEW SCREEN ({5}, {4})-({3}, {2})",
],
}),
0x0f8 => Some(TokenDef {
len: 0,
rules: &["WIDTH {1}{nularg:0}", "WIDTH {1}, {0}"],
}),
0x0f9 => Some(TokenDef {
len: 0,
rules: &["WIDTH LPRINT {0}"],
}),
0x0fa => Some(TokenDef {
len: 0,
rules: &["WIDTH {1}, {0}"],
}),
0x0fb => Some(TokenDef {
len: 0,
rules: &["WINDOW ({3}, {2})-({1}, {0})"],
}),
0x0fc => Some(TokenDef {
len: 0,
rules: &["WINDOW"],
}),
0x0fd => Some(TokenDef {
len: 0,
rules: &["WINDOW SCREEN ({3}, {2})-({1}, {0})"],
}),
0x0fe => Some(TokenDef {
len: 0,
rules: &["prnmod::=WRITE"],
}),
0x0ff => Some(TokenDef {
len: 0,
rules: &["prnuse::=USING {0};"],
}),
0x100 => Some(TokenDef {
len: 0,
rules: &["expr::={1} + {0}"],
}),
0x101 => Some(TokenDef {
len: 0,
rules: &["expr::={1} AND {0}"],
}),
0x102 => Some(TokenDef {
len: 0,
rules: &["expr::={1} / {0}"],
}),
0x103 => Some(TokenDef {
len: 0,
rules: &["expr::={1} = {0}"],
}),
0x104 => Some(TokenDef {
len: 0,
rules: &["expr::={1} EQV {0}"],
}),
0x105 => Some(TokenDef {
len: 0,
rules: &["expr::=ABS({0})"],
}),
0x106 => Some(TokenDef {
len: 0,
rules: &["expr::=ASC({0})"],
}),
0x107 => Some(TokenDef {
len: 0,
rules: &["expr::=ATN({0})"],
}),
0x108 => Some(TokenDef {
len: 0,
rules: &["expr::=C{#type-abbr}({0})"],
}),
0x109 => Some(TokenDef {
len: 0,
rules: &["expr::=CHR$({0})"],
}),
0x10a => Some(TokenDef {
len: 0,
rules: &["expr::=COMMAND$"],
}),
0x10b => Some(TokenDef {
len: 0,
rules: &["expr::=COS({0})"],
}),
0x10c => Some(TokenDef {
len: 0,
rules: &["expr::=CSRLIN"],
}),
0x10d => Some(TokenDef {
len: 0,
rules: &["expr::=CVD({0})"],
}),
0x10e => Some(TokenDef {
len: 0,
rules: &["expr::=CVDMBD({0})"],
}),
0x10f => Some(TokenDef {
len: 0,
rules: &["expr::=CVI({0})"],
}),
0x110 => Some(TokenDef {
len: 0,
rules: &["expr::=CVL({0})"],
}),
0x111 => Some(TokenDef {
len: 0,
rules: &["expr::=CVS({0})"],
}),
0x112 => Some(TokenDef {
len: 0,
rules: &["expr::=CVSMBF({0})"],
}),
0x113 => Some(TokenDef {
len: 0,
rules: &["expr::=DATE$"],
}),
0x114 => Some(TokenDef {
len: 0,
rules: &["expr::=ENVIRON$({0})"],
}),
0x115 => Some(TokenDef {
len: 0,
rules: &["expr::=EOF({0})"],
}),
0x116 => Some(TokenDef {
len: 0,
rules: &["expr::=ERDEV"],
}),
0x117 => Some(TokenDef {
len: 0,
rules: &["expr::=ERDEV$"],
}),
0x118 => Some(TokenDef {
len: 0,
rules: &["expr::=ERL"],
}),
0x119 => Some(TokenDef {
len: 0,
rules: &["expr::=ERR"],
}),
0x11a => Some(TokenDef {
len: 0,
rules: &["expr::=EXP({0})"],
}),
0x11b => Some(TokenDef {
len: 0,
rules: &["expr::=FILEATTR({1}, {0})"],
}),
0x11c => Some(TokenDef {
len: 0,
rules: &["expr::=FIX({0})"],
}),
0x11d => Some(TokenDef {
len: 0,
rules: &["expr::=FRE({0})"],
}),
0x11e => Some(TokenDef {
len: 0,
rules: &["expr::=FREEFILE"],
}),
0x11f => Some(TokenDef {
len: 0,
rules: &["expr::=HEX$({0})"],
}),
0x120 => Some(TokenDef {
len: 0,
rules: &["expr::=INKEY$"],
}),
0x121 => Some(TokenDef {
len: 0,
rules: &["expr::=INP({0})"],
}),
0x122 => Some(TokenDef {
len: 0,
rules: &["expr::=INPUT$({0})"],
}),
0x123 => Some(TokenDef {
len: 0,
rules: &["expr::=INPUT$({1}, {0})"],
}),
0x124 => Some(TokenDef {
len: 0,
rules: &["expr::=INSTR({1}, {0})"],
}),
0x125 => Some(TokenDef {
len: 0,
rules: &["expr::=INSTR({2}, {1}, {0})"],
}),
0x126 => Some(TokenDef {
len: 0,
rules: &["expr::=INT({0})"],
}),
0x127 => Some(TokenDef {
len: 0,
rules: &["expr::=IOCTL$({0})"],
}),
0x128 => Some(TokenDef {
len: 0,
rules: &["expr::=LBOUND({0})"],
}),
0x129 => Some(TokenDef {
len: 0,
rules: &["expr::=LBOUND({1}, {0})"],
}),
0x12a => Some(TokenDef {
len: 0,
rules: &["expr::=LCASE$({0})"],
}),
0x12b => Some(TokenDef {
len: 0,
rules: &["expr::=LTRIM$({0})"],
}),
0x12c => Some(TokenDef {
len: 0,
rules: &["expr::=LEFT$({1}, {0})"],
}),
0x12d => Some(TokenDef {
len: 2,
rules: &["expr::=LEN({0})"],
}),
0x12e => Some(TokenDef {
len: 0,
rules: &["expr::=LOC({0})"],
}),
0x12f => Some(TokenDef {
len: 0,
rules: &["expr::=LOF({0})"],
}),
0x130 => Some(TokenDef {
len: 0,
rules: &["expr::=LOG({0})"],
}),
0x131 => Some(TokenDef {
len: 0,
rules: &["expr::=LPOS({0})"],
}),
0x132 => Some(TokenDef {
len: 0,
rules: &["expr::=MID$({1}, {0})"],
}),
0x133 => Some(TokenDef {
len: 0,
rules: &["expr::=MID$({2}, {1}, {0})"],
}),
0x134 => Some(TokenDef {
len: 0,
rules: &["expr::=MKD$({0})"],
}),
0x135 => Some(TokenDef {
len: 0,
rules: &["expr::=MKDMBF$({0})"],
}),
0x136 => Some(TokenDef {
len: 0,
rules: &["expr::=MKI$({0})"],
}),
0x137 => Some(TokenDef {
len: 0,
rules: &["expr::=MKL$({0})"],
}),
0x138 => Some(TokenDef {
len: 0,
rules: &["expr::=MKS$({expr:0})", "{0}"],
}),
0x139 => Some(TokenDef {
len: 0,
rules: &["expr::=MKSMBF({0})"],
}),
0x13a => Some(TokenDef {
len: 0,
rules: &["expr::=OCT$({0})"],
}),
0x13b => Some(TokenDef {
len: 0,
rules: &["expr::=PEEK({0})"],
}),
0x13c => Some(TokenDef {
len: 0,
rules: &["expr::=PEN"],
}),
0x13d => Some(TokenDef {
len: 0,
rules: &["expr::=PLAY"],
}),
0x13e => Some(TokenDef {
len: 0,
rules: &["expr::=PMAP({1}, {0})"],
}),
0x13f => Some(TokenDef {
len: 0,
rules: &["expr::=POINT({0})"],
}),
0x140 => Some(TokenDef {
len: 0,
rules: &["expr::=POINT({1}, {0})"],
}),
0x141 => Some(TokenDef {
len: 0,
rules: &["expr::=POS({0})"],
}),
0x142 => Some(TokenDef {
len: 0,
rules: &["expr::=RIGHT$({1}, {0})"],
}),
0x143 => Some(TokenDef {
len: 0,
rules: &["expr::=RND"],
}),
0x144 => Some(TokenDef {
len: 0,
rules: &["expr::=RND({0})"],
}),
0x145 => Some(TokenDef {
len: 0,
rules: &["expr::=RTRIM$({0})"],
}),
0x146 => Some(TokenDef {
len: 0,
rules: &["expr::=SADD({0})"],
}),
0x147 => Some(TokenDef {
len: 0,
rules: &["expr::=SCREEN({1}, {0})"],
}),
0x148 => Some(TokenDef {
len: 0,
rules: &["expr::=SCREEN({2}, {1}, {0})"],
}),
0x149 => Some(TokenDef {
len: 0,
rules: &["expr::=SEEK({0})"],
}),
0x14a => Some(TokenDef {
len: 0,
rules: &["expr::=SETMEM({0})"],
}),
0x14b => Some(TokenDef {
len: 0,
rules: &["expr::=SGN({0})"],
}),
0x14c => Some(TokenDef {
len: 0,
rules: &["expr::=SHELL({0})"],
}),
0x14d => Some(TokenDef {
len: 0,
rules: &["expr::=SIN({0})"],
}),
0x14e => Some(TokenDef {
len: 0,
rules: &["expr::=SPACE$({0})"],
}),
0x14f => Some(TokenDef {
len: 0,
rules: &["expr::=SQR({0})"],
}),
0x150 => Some(TokenDef {
len: 0,
rules: &["expr::=STICK({0})"],
}),
0x151 => Some(TokenDef {
len: 0,
rules: &["expr::=STR$({0})"],
}),
0x152 => Some(TokenDef {
len: 0,
rules: &["expr::=STRIG({0})"],
}),
0x153 => Some(TokenDef {
len: 0,
rules: &["expr::=STRING$({1}, {0})"],
}),
0x154 => Some(TokenDef {
len: 0,
rules: &["expr::=TAN({0})"],
}),
0x155 => Some(TokenDef {
len: 0,
rules: &["expr::=TIME$"],
}),
0x156 => Some(TokenDef {
len: 0,
rules: &["expr::=TIMER"],
}),
0x157 => Some(TokenDef {
len: 0,
rules: &["expr::=UBOUND({0})"],
}),
0x158 => Some(TokenDef {
len: 0,
rules: &["expr::=UBOUND({1}, {0})"],
}),
0x159 => Some(TokenDef {
len: 0,
rules: &["expr::=UCASE$({0})"],
}),
0x15a => Some(TokenDef {
len: 0,
rules: &["expr::=VAL({0})"],
}),
0x15b => Some(TokenDef {
len: 0,
rules: &["expr::=VARPTR({0})"],
}),
0x15c => Some(TokenDef {
len: 2,
rules: &["expr::=VARPTR$({0})"],
}),
0x15d => Some(TokenDef {
len: 0,
rules: &["expr::=VARSEG({0})"],
}),
0x15e => Some(TokenDef {
len: 0,
rules: &["expr::={1} >= {0}"],
}),
0x15f => Some(TokenDef {
len: 0,
rules: &["expr::={1} > {0}"],
}),
0x160 => Some(TokenDef {
len: 0,
rules: &["expr::={1} \\ {0}"],
}),
0x161 => Some(TokenDef {
len: 0,
rules: &["expr::={1} IMP {0}"],
}),
0x162 => Some(TokenDef {
len: 0,
rules: &["expr::={1} <= {0}"],
}),
0x163 => Some(TokenDef {
len: 0,
rules: &["expr::={1} < {0}"],
}),
0x164 => Some(TokenDef {
len: 0,
rules: &["expr::={#hprm}"],
}),
0x165 => Some(TokenDef {
len: 2,
rules: &["expr::={#int}"],
}),
0x166 => Some(TokenDef {
len: 4,
rules: &["expr::={#lng}"],
}),
0x167 => Some(TokenDef {
len: 2,
rules: &["expr::={#int&h}"],
}),
0x168 => Some(TokenDef {
len: 4,
rules: &["expr::={#lng&h}"],
}),
0x169 => Some(TokenDef {
len: 2,
rules: &["expr::={#int&o}"],
}),
0x16a => Some(TokenDef {
len: 4,
rules: &["expr::={#lng&o}"],
}),
0x16b => Some(TokenDef {
len: 4,
rules: &["expr::={#sng}"],
}),
0x16c => Some(TokenDef {
len: 8,
rules: &["expr::={#dbl}"],
}),
0x16d => Some(TokenDef {
len: -1,
rules: &["expr::={#qstr}"],
}),
0x16e => Some(TokenDef {
len: 0,
rules: &["expr::=({0})"],
}),
0x16f => Some(TokenDef {
len: 0,
rules: &["expr::={1} MOD {0}"],
}),
0x170 => Some(TokenDef {
len: 0,
rules: &["expr::={1} * {0}"],
}),
0x171 => Some(TokenDef {
len: 0,
rules: &["expr::={1} <> {0}"],
}),
0x172 => Some(TokenDef {
len: 0,
rules: &["expr::={#nul}"],
}),
0x173 => Some(TokenDef {
len: 0,
rules: &["nularg::={#nul}"],
}),
0x174 => Some(TokenDef {
len: 0,
rules: &["expr::=NOT {0}"],
}),
0x175 => Some(TokenDef {
len: 0,
rules: &["expr::={1} OR {0}"],
}),
0x176 => Some(TokenDef {
len: 0,
rules: &["expr::={1} ^ {0}"],
}),
0x177 => Some(TokenDef {
len: 0,
rules: &["expr::={1} - {0}"],
}),
0x178 => Some(TokenDef {
len: 0,
rules: &["expr::=-{0}"],
}),
0x179 => Some(TokenDef {
len: 0,
rules: &["expr::={1} XOR {0}"],
}),
0x17a => Some(TokenDef {
len: 0,
rules: &["UEVENT"],
}),
0x17b => Some(TokenDef {
len: 0,
rules: &["SLEEP {0}"],
}),
0x17c => Some(TokenDef {
len: 6,
rules: &["astype::={#tabi:4}AS STRING * {#int:2}"],
}),
0x17d => Some(TokenDef {
len: 2,
rules: &["decl::=DIM {declmod:0}", "decl::=DIM"],
}),
0x181 => Some(TokenDef {
len: 0,
rules: &["expr::=CURDIR$"],
}),
0x183 => Some(TokenDef {
len: 0,
rules: &["expr::=ERROR$"],
}),
0x184 => Some(TokenDef {
len: 0,
rules: &["expr::=DIR$({0})"],
}),
0x186 => Some(TokenDef {
len: 0,
rules: &["expr::=BOF({0})"],
}),
0x188 => Some(TokenDef {
len: 0,
rules: &["expr::=GETINDEX$({0})"],
}),
0x194 => Some(TokenDef {
len: 0,
rules: &["expr::={0}"],
}),
0x192 => Some(TokenDef {
len: 0,
rules: &["DELETE {0}"],
}),
0x197 => Some(TokenDef {
len: 0,
rules: &["INSERT {1}, {0}"],
}),
0x198 => Some(TokenDef {
len: 2,
rules: &[
"{#method:0}MOVEFIRST {0}",
"{#method:4}MOVELAST {0}",
"{#method:8}MOVENEXT {0}",
"{#method:12}MOVEPREVIOUS {0}",
],
}),
0x199 => Some(TokenDef {
len: 2,
rules: &["ON LOCAL ERROR GOTO {#id}"],
}),
0x19b => Some(TokenDef {
len: 2,
rules: &["RETRIEVE {1}, {0}"],
}),
0x19f => Some(TokenDef {
len: 0,
rules: &["SEEKGT {1}, {0}"],
}),
0x1a1 => Some(TokenDef {
len: 0,
rules: &["SETINDEX {1}, {0}"],
}),
0x1a8 => Some(TokenDef {
len: 0,
rules: &["REDIM {0}"],
}),
0x1a9 => Some(TokenDef {
len: 0,
rules: &["implicit-control::="],
}),
0x1aa => Some(TokenDef {
len: 2,
rules: &["expr::=TYPEOF {0} IS {#id}"],
}),
0x1ab => Some(TokenDef {
len: 0,
rules: &["LOAD {0}"],
}),
0x1ac => Some(TokenDef {
len: 0,
rules: &["UNLOAD {0}"],
}),
0x1ad => Some(TokenDef {
len: 0,
rules: &["expr::=DOEVENTS"],
}),
0x1b0 => Some(TokenDef {
len: 0,
rules: &["expr::=ERROR$"],
}),
0x1b2 => Some(TokenDef {
len: 0,
rules: &["expr::=STR$({expr:0})"],
}),
0x1b3 => Some(TokenDef {
len: 0,
rules: &["expr::=FORMAT$({1}, {0})"],
}),
0x1ba => Some(TokenDef {
len: 0,
rules: &["expr::=DATE$"],
}),
0x1bc => Some(TokenDef {
len: 0,
rules: &["expr::=TIMEVALUE({0})"],
}),
0x1c0 => Some(TokenDef {
len: 2,
rules: &[""],
}),
0x1c1 => Some(TokenDef {
len: 0,
rules: &["expr::={0}"],
}),
0x1c2 => Some(TokenDef {
len: -1,
rules: &[""],
}),
0x1c3 => Some(TokenDef {
len: 0,
rules: &["expr::=INPUTBOX$({2}, {1}, {0})"],
}),
0x1c5 => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x1c6 => Some(TokenDef {
len: 0,
rules: &[""],
}),
0x1c7 | 0x1c8 => Some(TokenDef {
len: 0,
rules: &["nularg::="],
}),
0x1c9 => Some(TokenDef {
len: 2,
rules: &[
"controlstmt::={#method:1}{0}.CLS",
"controlstmt::={#method:2}{implicit-control:0}HIDE",
"controlstmt::={#method:2}{0}.HIDE",
"prnmod::={#method:4}{0}.PRINT",
"controlstmt::={#method:6}{0}.REFRESH",
"controlstmt::={#method:8}{0}.SETFOCUS",
"controlstmt::={#method:9}{implicit-control:0}SHOW",
"controlstmt::={#method:9}{0}.SHOW",
"controlstmt::={#method:14}{0}.ENDDOC",
"controlstmt::={#method:16}{0}.NEWPAGE",
],
}),
0x1ca => Some(TokenDef {
len: 2,
rules: &["{1}.ADDITEM {0}"],
}),
0x1cb => Some(TokenDef {
len: 2,
rules: &["{1}.REMOVEITEM {0}"],
}),
0x1cc => Some(TokenDef {
len: 2,
rules: &["{2}.MOVE {1}, {0}"],
}),
0x1ce => Some(TokenDef {
len: 4,
rules: &["{4}.MOVE {3}, {2}, {1}, {0}"],
}),
0x1d0 => Some(TokenDef {
len: 2,
rules: &["expr::={0}.GETTEXT"],
}),
0x3ff => Some(TokenDef {
len: 0,
rules: &[""],
}),
_ => None,
}
}
#[derive(Clone)]
struct Item {
kind: String,
text: String,
}
struct Decoder<'a> {
file: &'a str,
sym: &'a [u8],
code: &'a [u8],
base: usize,
at: usize,
pcode: u16,
hparam: u16,
stack: Vec<Item>,
pending: String,
lines: Vec<String>,
procedure_type: &'static str,
modified_defaults: u32,
return_name: Option<String>,
return_suffix: &'static str,
}
impl<'a> Decoder<'a> {
fn new(file: &'a str, sym: &'a [u8], code: &'a [u8], base: usize) -> Self {
Self {
file,
sym,
code,
base,
at: 0,
pcode: 0,
hparam: 0,
stack: Vec::new(),
pending: String::new(),
lines: Vec::new(),
procedure_type: "SUB",
modified_defaults: 0,
return_name: None,
return_suffix: "",
}
}
fn fail(&self, at: usize, name: impl Into<String>, message: impl Into<String>) -> FormError {
error(self.file, self.base + at, name, message)
}
fn ident(&self, offset: u16) -> Result<String, FormError> {
if matches!(offset, u16::MAX | 0xfffe) {
return Ok("0".into());
}
let at = offset as usize;
let header = self.sym.get(at..at + 4).ok_or_else(|| {
self.fail(
self.at,
format!("Symbol 0x{offset:04x}"),
"ungueltiger Symbolverweis",
)
})?;
if header[2] & 2 != 0 {
return read_u16(self.sym, at + 4)
.map(|value| value.to_string())
.ok_or_else(|| self.fail(self.at, "Symboltabelle", "abgeschnittene Zeilennummer"));
}
let len = header[3] as usize;
self.sym
.get(at + 4..at + 4 + len)
.map(decode_cp437)
.ok_or_else(|| self.fail(self.at, "Symboltabelle", "abgeschnittener Name"))
}
fn suffix(kind: u16) -> &'static str {
match kind {
1 => "%",
2 => "&",
3 => "!",
4 => "#",
5 => "@",
7 => "$",
_ => "",
}
}
fn type_name(&self, kind: u16) -> Result<String, FormError> {
Ok(match kind {
0 => "ANY".into(),
1 => "INTEGER".into(),
2 => "LONG".into(),
3 => "SINGLE".into(),
4 => "DOUBLE".into(),
5 => "CURRENCY".into(),
7 => "STRING".into(),
9 => "CONTROL".into(),
// VBDOS verwendet fuer eingebaute Typen zusaetzlich die
// internen Kennungen des Editors.
0x013e => "STRING".into(),
other => self.ident(other)?,
})
}
fn peek(&self, offset: usize) -> Option<&Item> {
self.stack.get(self.stack.len().checked_sub(offset + 1)?)
}
fn split_tag<'b>(&self, tag: &'b str) -> (&'b str, isize, bool) {
if let Some(value) = tag.strip_prefix('$') {
return ("", value.parse().unwrap_or(0), true);
}
if let Some((kind, value)) = tag.rsplit_once(':') {
let relative = value.starts_with('$');
let value = value.strip_prefix('$').unwrap_or(value);
return (kind, value.parse().unwrap_or(0), relative);
}
if let Ok(offset) = tag.parse() {
("", offset, false)
} else {
(tag, 0, false)
}
}
fn valid(&self, kind: &str, offset: isize, own: &str) -> bool {
let Some(item) = usize::try_from(offset)
.ok()
.and_then(|offset| self.peek(offset))
else {
return kind.is_empty();
};
kind.is_empty()
|| kind.split('|').any(|expected| {
expected == "*" || item.kind == if expected == "self" { own } else { expected }
})
}
fn word(data: &[u8], at: usize) -> Option<u16> {
read_u16(data, at)
}
fn call(&self, data: &[u8], parentheses: bool) -> Result<String, FormError> {
let count = Self::word(data, 0)
.ok_or_else(|| self.fail(self.at, "CALL", "Argumentzahl fehlt"))?
as usize;
let args = (0..count)
.rev()
.map(|index| format!("{{{index}}}"))
.collect::<Vec<_>>()
.join(", ");
Ok(if args.is_empty() {
String::new()
} else if parentheses {
format!("({args})")
} else {
format!(" {args}")
})
}
fn proc_decl(
&mut self,
data: &[u8],
offset: usize,
parentheses: bool,
) -> Result<String, FormError> {
let id =
Self::word(data, offset).ok_or_else(|| self.fail(self.at, "Prozedur", "Name fehlt"))?;
let flags = Self::word(data, offset + 2)
.ok_or_else(|| self.fail(self.at, "Prozedur", "Flags fehlen"))?;
let count = Self::word(data, offset + 4)
.ok_or_else(|| self.fail(self.at, "Prozedur", "Argumentzahl fehlt"))?
as usize;
let mut name = self.ident(id)?;
let kind = match (flags & 0x300) >> 8 {
1 => "SUB",
2 => "FUNCTION",
3 => "DEF",
_ => "",
};
if !kind.is_empty() {
self.procedure_type = kind;
name = format!("{kind} {name}");
}
if flags & 0x80 != 0 {
let suffix = Self::suffix(flags & 7);
if self.pcode == 0x058 {
self.return_name = Some(name.strip_prefix("FUNCTION ").unwrap_or(&name).into());
self.return_suffix = suffix;
}
name.push_str(suffix);
}
let mut args = Vec::with_capacity(count);
for index in 1..=count {
let at = offset + 6 + (index - 1) * 8;
let mut arg = self.ident(
Self::word(data, at)
.ok_or_else(|| self.fail(self.at, "Prozedurargument", "Name fehlt"))?,
)?;
let arg_flags = Self::word(data, at + 2)
.ok_or_else(|| self.fail(self.at, "Prozedurargument", "Flags fehlen"))?;
let arg_type = Self::word(data, at + 4)
.ok_or_else(|| self.fail(self.at, "Prozedurargument", "Typ fehlt"))?;
if arg_flags & 0x200 != 0 {
arg.push_str(Self::suffix(arg_type));
}
if arg_flags & 0x400 != 0 {
arg.push_str("()");
}
if arg_flags & 0x800 != 0 {
arg = format!("SEG {arg}");
}
if arg_flags & 0x1000 != 0 {
arg = format!("BYVAL {arg}");
}
if arg_flags & 0x2000 != 0 {
let ty = self.type_name(arg_type)?;
arg.push_str(&format!(" AS {ty}"));
}
args.push(arg);
}
if flags & 0x8000 != 0 {
name.push_str(" CDECL");
}
let alias_len = ((flags >> 10) & 0x1f) as usize;
let tail = offset + 6 + count * 8;
if alias_len != 0 {
name.push_str(" ALIAS ");
name.push_str(&decode_cp437(
data.get(tail..tail + alias_len)
.ok_or_else(|| self.fail(self.at, "ALIAS", "Name abgeschnitten"))?,
));
}
if parentheses || !args.is_empty() {
name.push_str(" (");
name.push_str(&args.join(", "));
name.push(')');
}
Ok(name)
}
fn array(&self, data: &[u8], declaration: bool) -> Result<String, FormError> {
let declaration =
declaration || read_u16(self.code, self.at).is_some_and(|word| word & 0x3ff == 0x1a8);
let count = Self::word(data, 0)
.ok_or_else(|| self.fail(self.at, "Feld", "Dimension fehlt"))?
as usize;
let mut name = self
.ident(Self::word(data, 2).ok_or_else(|| self.fail(self.at, "Feld", "Name fehlt"))?)?;
name.push_str(Self::suffix(self.hparam));
let mut stack_offset =
usize::from(declaration && self.peek(0).is_some_and(|item| item.kind == "astype"));
if count != 0 {
let mut dimensions = String::new();
if declaration {
let mut remaining = count;
while remaining != 0 {
remaining -= 1;
dimensions.insert_str(0, &format!("{{{stack_offset}}}"));
stack_offset += 1;
if remaining & 1 != 0 {
if self.peek(stack_offset).is_none_or(|item| item.kind != "24") {
dimensions.insert_str(0, " TO ");
}
} else if remaining != 0 {
dimensions.insert_str(0, ", ");
}
}
} else {
dimensions = (0..count)
.rev()
.map(|offset| format!("{{{offset}}}"))
.collect::<Vec<_>>()
.join(", ");
stack_offset = count;
}
name.push('(');
name.push_str(&dimensions);
name.push(')');
} else if !declaration {
name.push_str("()");
}
if declaration && self.peek(0).is_some_and(|item| item.kind == "astype") {
name.push_str("{0}");
}
if declaration {
if self
.peek(stack_offset)
.is_some_and(|item| item.kind == "decls")
{
return Ok(format!("{{{stack_offset}}}, {name}"));
}
if self
.peek(stack_offset)
.is_some_and(|item| item.kind == "decl")
{
return Ok(format!("{{{stack_offset}}} {name}"));
}
}
Ok(name)
}
fn varargs(&self, data: &[u8]) -> Result<String, FormError> {
let count = Self::word(data, 0)
.ok_or_else(|| self.fail(self.at, "Argumentliste", "Anzahl fehlt"))?
as usize;
let args = (0..count)
.rev()
.map(|index| format!("{{{index}}}"))
.collect::<Vec<_>>()
.join(", ");
Ok(if args.is_empty() {
args
} else {
format!(" {args}")
})
}
fn data_tag(&mut self, tag: &str, offset: usize, data: &[u8]) -> Result<String, FormError> {
let word = || {
Self::word(data, offset)
.ok_or_else(|| self.fail(self.at, tag, "Tokendaten abgeschnitten"))
};
Ok(match tag.to_ascii_lowercase().as_str() {
"" | "nul" | "thaddr" | "optargs" => String::new(),
"newline" => "\r\u{1}".into(),
"newline-include" => "\r\u{2}".into(),
"tabh" => format!(
"\r\0{}{}",
char::from(self.hparam as u8),
char::from((self.hparam >> 8) as u8)
),
"tabi" => {
let value = word()?;
format!(
"\r\0{}{}",
char::from(value as u8),
char::from((value >> 8) as u8)
)
}
"indent" => " ".repeat(word()? as usize),
"raw" => decode_pcode_bytes(data.get(offset..).unwrap_or_default()),
"cstr" => decode_pcode_bytes(
data.get(offset..)
.unwrap_or_default()
.split(|byte| *byte == 0)
.next()
.unwrap_or_default(),
),
"qstr" => format!(
"\"{}\"",
decode_pcode_bytes(data.get(offset..).unwrap_or_default())
),
"id" => self.ident(word()?)?,
"id+" => {
let name = self.ident(word()?)?;
let suffix = Self::suffix(self.hparam);
format!(
"{name}{}",
if suffix.is_empty() && self.return_name.as_deref() == Some(&name) {
self.return_suffix
} else {
suffix
}
)
}
"id-list" => data
.get(offset..)
.unwrap_or_default()
.chunks_exact(2)
.map(|bytes| self.ident(u16::from_le_bytes([bytes[0], bytes[1]])))
.collect::<Result<Vec<_>, _>>()?
.join(", "),
"type" => self.type_name(word()?)?,
"type-abbr" => ["", "INT", "LNG", "SNG", "DBL", "STR"]
.get(self.hparam as usize)
.copied()
.unwrap_or("")
.into(),
"hprm" => self.hparam.to_string(),
"int" => (word()? as i16).to_string(),
"int&h" => format!("&H{:X}", word()?),
"int&o" => format!("&O{:o}", word()?),
"lng" => i32::from_le_bytes(
data.get(offset..offset + 4)
.ok_or_else(|| self.fail(self.at, tag, "LONG abgeschnitten"))?
.try_into()
.unwrap(),
)
.to_string(),
"lng&h" => format!(
"&H{:X}",
u32::from_le_bytes(
data.get(offset..offset + 4)
.ok_or_else(|| self.fail(self.at, tag, "LONG abgeschnitten"))?
.try_into()
.unwrap()
)
),
"sng" => f32::from_le_bytes(
data.get(offset..offset + 4)
.ok_or_else(|| self.fail(self.at, tag, "SINGLE abgeschnitten"))?
.try_into()
.unwrap(),
)
.to_string(),
"dbl" => f64::from_le_bytes(
data.get(offset..offset + 8)
.ok_or_else(|| self.fail(self.at, tag, "DOUBLE abgeschnitten"))?
.try_into()
.unwrap(),
)
.to_string(),
"label" => {
let name = self.ident(word()?)?;
if name.bytes().all(|byte| byte.is_ascii_digit()) {
format!("{name} ")
} else {
format!("{name}:")
}
}
"call" => self.call(data, false)?,
"call()" => self.call(data, true)?,
"procdecl" => self.proc_decl(data, offset, false)?,
"procdecl()" => self.proc_decl(data, offset, true)?,
"id(expr)" => self.array(data, false)?,
"id(decl)" => self.array(data, true)?,
"varargs" => self.varargs(data)?,
"proctype" => self.procedure_type.into(),
"blockname" => {
let id = word()?;
if id == u16::MAX {
String::new()
} else {
format!(" /{}/", self.ident(id)?)
}
}
"action-verb" => {
["OR", "AND", "PRESET", "PSET", "XOR"][usize::from(word()?.min(4))].into()
}
"open-args" => {
let flags = word()?;
[
match flags & 0x3f {
1 => "FOR INPUT",
2 => "FOR OUTPUT",
4 => "FOR RANDOM",
8 => "FOR APPEND",
32 => "FOR BINARY",
_ => "",
},
match (flags >> 8) & 3 {
1 => "ACCESS READ",
2 => "ACCESS WRITE",
3 => "ACCESS READ WRITE",
_ => "",
},
match (flags >> 12) & 7 {
1 => "LOCK READ WRITE",
2 => "LOCK WRITE",
3 => "LOCK READ",
4 => "SHARED",
_ => "",
},
]
.into_iter()
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
"keymode" => match word()? {
1 => "ON",
2 => "LIST",
_ => "OFF",
}
.into(),
// Die im Corpus verwendeten Varianten haben genau diese Form;
// die Stack-Platzhalter werden anschliessend normal aufgeloest.
"line-args" | "lock-args" => "{0}".into(),
"circle-args" => "{1}, {0}".into(),
"input-args" => String::new(),
"defxxx" => {
let raw = data
.get(offset + 2..offset + 6)
.ok_or_else(|| self.fail(self.at, "DEF", "Tokendaten abgeschnitten"))?;
let mask = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
let kind = match mask & 7 {
1 => "INT",
2 => "LNG",
3 => "SNG",
4 => "DBL",
5 => "STR",
_ => "",
};
let letters = mask & 0xffff_ffc0;
if mask & 7 == 3 {
self.modified_defaults &= !letters;
} else {
self.modified_defaults |= letters;
}
format!("DEF{kind} {}", letter_ranges(letters))
}
other => {
return Err(self.fail(
self.at,
format!("Token-Tag {other}"),
"unbekannte P-Code-Struktur",
))
}
})
}
fn execute(&mut self, mut rule: String, data: &[u8]) -> Result<bool, FormError> {
let mut kind = self.pcode.to_string();
if let Some((left, right)) = rule.split_once("::=") {
kind = left.into();
rule = right.into();
}
if rule.starts_with('.') {
self.pending
.extend(self.stack.drain(..).map(|item| item.text));
rule.remove(0);
}
let initial = self.stack.len();
let mut deepest = None::<usize>;
let mut relative = Vec::<(String, String, isize)>::new();
let mut out = String::new();
let mut cursor = 0;
while let Some(open) = rule[cursor..].find('{').map(|value| cursor + value) {
let Some(close) = rule[open + 1..].find('}').map(|value| open + 1 + value) else {
return Err(self.fail(self.at, "Tokenregel", "offene Ersetzung"));
};
out.push_str(&rule[cursor..open]);
let tag = &rule[open + 1..close];
if let Some(tag) = tag.strip_prefix("##") {
let (tag, offset, _) = self.split_tag(tag);
let replacement = self.data_tag(tag, offset.max(0) as usize, data)?;
rule.replace_range(open..=close, &replacement);
cursor = open;
continue;
}
if let Some(tag) = tag.strip_prefix('#') {
let (tag, offset, _) = self.split_tag(tag);
if tag == "method" {
if Self::word(data, 0) != Some(offset as u16) {
return Ok(false);
}
cursor = close + 1;
continue;
}
out.push_str(&self.data_tag(tag, offset.max(0) as usize, data)?);
} else {
let (expected, offset, is_relative) = self.split_tag(tag);
if is_relative {
let marker = format!("\u{b}{}\u{c}", relative.len());
out.push_str(&marker);
relative.push((marker, expected.into(), offset));
} else {
if !self.valid(expected, offset, &kind) {
return Ok(false);
}
if let Some(item) = usize::try_from(offset)
.ok()
.and_then(|offset| self.peek(offset))
{
out.push_str(&item.text);
}
deepest = deepest.max(usize::try_from(offset).ok());
}
}
cursor = close + 1;
}
out.push_str(&rule[cursor..]);
let mut consume = deepest.map_or(0, |offset| offset + 1);
for (marker, expected, offset) in relative {
let actual = consume as isize + offset;
if !self.valid(&expected, actual, &kind) {
return Ok(false);
}
let text = self
.peek(actual as usize)
.map(|item| item.text.as_str())
.unwrap_or("");
out = out.replace(&marker, text);
consume = consume.max(actual as usize + 1);
}
self.stack.truncate(initial.saturating_sub(consume));
self.stack.push(Item { kind, text: out });
Ok(true)
}
fn postprocess(&mut self, final_block: bool) {
while let Some(marker) = self.pending.find('\r') {
let kind = self.pending.as_bytes().get(marker + 1).copied();
if kind == Some(0) && marker + 3 < self.pending.len() {
let bytes = self.pending.as_bytes();
let column = u16::from_le_bytes([bytes[marker + 2], bytes[marker + 3]]) as usize;
let spaces = column.saturating_sub(marker).max(1);
self.pending
.replace_range(marker..marker + 4, &" ".repeat(spaces));
continue;
}
if matches!(kind, Some(1 | 2)) {
let line = self.pending[..marker].trim_end().to_owned();
self.lines.push(line);
self.pending.drain(..marker + 2);
continue;
}
if let (Some(count), Some(ch)) = (
self.pending.as_bytes().get(marker + 1).copied(),
self.pending.as_bytes().get(marker + 2).copied(),
) {
self.pending.replace_range(
marker..marker + 3,
&char::from(ch).to_string().repeat(count as usize),
);
continue;
}
break;
}
if final_block && !self.pending.trim().is_empty() {
self.lines.push(self.pending.trim_end().into());
self.pending.clear();
}
}
fn run(mut self) -> Result<(String, u32), FormError> {
while self.at < self.code.len() {
let token_at = self.at;
let word = read_u16(self.code, self.at)
.ok_or_else(|| self.fail(self.at, "P-Code", "Token abgeschnitten"))?;
self.at += 2;
self.pcode = word & 0x3ff;
self.hparam = word >> 10;
if self.pcode == 8 {
break;
}
let def = token_def(self.pcode).ok_or_else(|| {
self.fail(
token_at,
format!("Token 0x{:03x}", self.pcode),
"unbekannte P-Code-Struktur",
)
})?;
let len = if def.len < 0 {
let len = read_u16(self.code, self.at)
.ok_or_else(|| self.fail(self.at, "P-Code", "variable Laenge fehlt"))?
as usize;
self.at += 2;
len
} else {
def.len as usize
};
let data = self
.code
.get(self.at..self.at + len)
.ok_or_else(|| self.fail(self.at, "P-Code", "Tokendaten abgeschnitten"))?
.to_owned();
self.at += (len + 1) & !1;
if matches!(self.pcode, 0x065 | 0x066) {
let operands = usize::from(self.pcode == 0x066);
let split = self.stack.len().saturating_sub(operands);
if split > 0
&& self.stack[split - 1].kind != "newline"
&& !self.stack[split - 1].text.ends_with(" THEN ")
&& !self.stack[split - 1].text.ends_with(" ELSE ")
{
self.stack.insert(
split,
Item {
kind: "newline".into(),
text: "\r\u{1}".into(),
},
);
}
}
if matches!(self.pcode, 0x1c5 | 0x1c6) {
let first = self
.stack
.len()
.checked_sub(3)
.ok_or_else(|| self.fail(token_at, "MSGBOX", "Argumente fehlen"))?;
if self.stack[first..]
.iter()
.any(|item| !matches!(item.kind.as_str(), "expr" | "nularg"))
{
return Err(self.fail(token_at, "MSGBOX", "ungueltige Argumentstruktur"));
}
let mut args = self.stack.drain(first..).collect::<Vec<_>>();
while args.last().is_some_and(|item| item.kind == "nularg") {
args.pop();
}
let call = format!(
"MSGBOX{}{}",
if self.pcode == 0x1c6 { "(" } else { " " },
args.iter()
.map(|item| item.text.as_str())
.collect::<Vec<_>>()
.join(", ")
) + if self.pcode == 0x1c6 { ")" } else { "" };
self.stack.push(Item {
kind: if self.pcode == 0x1c6 {
"expr"
} else {
"statement"
}
.into(),
text: call,
});
self.postprocess(false);
continue;
}
let mut accepted = false;
for rule in def.rules {
if self.execute((*rule).into(), &data)? {
accepted = true;
break;
}
}
if !accepted {
let stack = self
.stack
.iter()
.map(|item| item.kind.as_str())
.collect::<Vec<_>>()
.join(", ");
return Err(self.fail(
token_at,
format!("Token 0x{:03x}", self.pcode),
format!("keine passende P-Code-Regel; Stapel: {stack}"),
));
}
self.postprocess(false);
}
self.pending
.extend(self.stack.drain(..).map(|item| item.text));
self.postprocess(true);
let output = if self.lines.is_empty() {
String::new()
} else {
self.lines.join("\n") + "\n"
};
Ok((output, self.modified_defaults))
}
}
type ModuleLayout = (usize, usize, Vec<(usize, usize)>);
fn candidate_layout(bytes: &[u8], base: usize) -> Option<ModuleLayout> {
let symbol_len = usize::from(read_u16(bytes, base)?);
let symbol_start = base + 2;
let main_header = symbol_start.checked_add(symbol_len)?;
let main_len = usize::from(read_u16(bytes, main_header)?);
if main_len & 1 != 0 {
return None;
}
let main_start = main_header + 2;
let main_end = main_start.checked_add(main_len)?;
if main_end + 16 > bytes.len()
|| main_len != 0 && read_u16(bytes, main_end - 2).map(|word| word & 0x3ff) != Some(8)
{
return None;
}
let mut blocks = vec![(main_start, main_end)];
let mut at = main_end + 16;
while at < bytes.len() {
let name_len = usize::from(read_u16(bytes, at + 1)?);
let name = bytes.get(at + 3..at + 3 + name_len)?;
if bytes.get(at) != Some(&0)
|| !(1..=40).contains(&name_len)
|| !name.first().is_some_and(u8::is_ascii_alphabetic)
|| !name
.iter()
.all(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
{
return None;
}
let code_len = usize::from(read_u16(bytes, at + 6 + name_len)?);
let start = at + 8 + name_len;
let end = start.checked_add(code_len)?;
if code_len & 1 != 0
|| end + 16 > bytes.len()
|| code_len != 0 && read_u16(bytes, end - 2).map(|word| word & 0x3ff) != Some(8)
{
return None;
}
blocks.push((start, end));
at = end + 16;
}
(at == bytes.len()).then_some((symbol_start, main_header, blocks))
}
fn module_layout(bytes: &[u8], after_catalog: usize) -> Option<ModuleLayout> {
for base in after_catalog..after_catalog.saturating_add(24).min(bytes.len()) {
if let Some(layout) = candidate_layout(bytes, base) {
return Some(layout);
}
}
None
}
pub(super) fn decode(file: &str, bytes: &[u8], after_catalog: usize) -> Result<String, FormError> {
let (symbol_start, symbol_end, blocks) = module_layout(bytes, after_catalog)
.ok_or_else(|| error(file, after_catalog, "Codeteil", "unerkannte Struktur"))?;
let sym = &bytes[symbol_start..symbol_end];
let mut output = String::new();
let mut pending_reset = 0;
for (start, end) in blocks {
let (block, block_reset) = Decoder::new(file, sym, &bytes[start..end], start).run()?;
if block.is_empty() {
pending_reset |= block_reset;
continue;
}
if !output.is_empty() && !block.is_empty() {
output.push('\n');
if pending_reset != 0 {
output.push_str("DEFSNG ");
output.push_str(&letter_ranges(pending_reset));
output.push('\n');
}
}
output.push_str(&block);
pending_reset = block_reset;
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
fn symbols(names: &[&str]) -> (Vec<u8>, Vec<u16>) {
let mut table = Vec::new();
let mut offsets = Vec::new();
for name in names {
offsets.push(table.len() as u16);
table.extend_from_slice(&[0, 0, 0, name.len() as u8]);
table.extend_from_slice(name.as_bytes());
}
(table, offsets)
}
#[test]
fn decodes_vbdos_declarations_without_losing_type_or_bounds() {
let (sym, ids) = symbols(&["PrepVal", "StringToPrep", "Oldcontents"]);
let mut decoder = Decoder::new("test.frm", &sym, &[], 0);
let mut proc = Vec::new();
for word in [ids[0], 0x02c7, 1, ids[1], 0x2000, 9, 0] {
proc.extend_from_slice(&word.to_le_bytes());
}
decoder.pcode = 0x058;
assert_eq!(
decoder.proc_decl(&proc, 0, true).unwrap(),
"FUNCTION PrepVal$ (StringToPrep AS CONTROL)"
);
decoder.hparam = 0;
assert_eq!(
decoder.data_tag("id+", 0, &ids[0].to_le_bytes()).unwrap(),
"PrepVal$"
);
let mut decoder = Decoder::new("test.frm", &sym, &[], 0);
decoder.stack = vec![
Item {
kind: "expr".into(),
text: "0".into(),
},
Item {
kind: "expr".into(),
text: "6".into(),
},
];
let mut array = Vec::new();
array.extend_from_slice(&2u16.to_le_bytes());
array.extend_from_slice(&ids[2].to_le_bytes());
assert_eq!(
decoder.array(&array, true).unwrap(),
"Oldcontents({1} TO {0})"
);
assert_eq!(
decoder.array(&array, false).unwrap(),
"Oldcontents({1}, {0})"
);
array[..2].copy_from_slice(&0u16.to_le_bytes());
assert_eq!(decoder.array(&array, false).unwrap(), "Oldcontents()");
}
#[test]
fn next_trennt_eine_vorangehende_anweisung_auch_ohne_newline_token() {
let (sym, ids) = symbols(&["i"]);
let mut code = Vec::new();
code.extend_from_slice(&0u16.to_le_bytes());
code.extend_from_slice(&0x00au16.to_le_bytes());
code.extend_from_slice(&9u16.to_le_bytes());
code.extend_from_slice(&[0, 0]);
code.extend_from_slice(b"PRINT 1");
code.push(0);
code.extend_from_slice(&(0x0400u16 | 0x00b).to_le_bytes());
code.extend_from_slice(&ids[0].to_le_bytes());
code.extend_from_slice(&0x066u16.to_le_bytes());
code.extend_from_slice(&[0xff, 0xff, 1, 0]);
code.extend_from_slice(&0u16.to_le_bytes());
code.extend_from_slice(&8u16.to_le_bytes());
let (decoded, _) = Decoder::new("test.frm", &sym, &code, 0).run().unwrap();
assert!(decoded.contains("PRINT 1\nNEXT i%\n"), "{decoded:?}");
}
#[test]
fn decodes_defint_and_vbdos_isam_tokens() {
let mut decoder = Decoder::new("test.frm", &[], &[], 0);
assert_eq!(
decoder
.data_tag("defxxx", 0, &[0xff, 0xff, 0xc1, 0xff, 0xff, 0xff])
.unwrap(),
"DEFINT A-Z"
);
assert!(token_def(0x188)
.unwrap()
.rules
.contains(&"expr::=GETINDEX$({0})"));
assert!(token_def(0x181).unwrap().rules.contains(&"expr::=CURDIR$"));
assert!(token_def(0x184)
.unwrap()
.rules
.contains(&"expr::=DIR$({0})"));
assert!(token_def(0x1c3)
.unwrap()
.rules
.contains(&"expr::=INPUTBOX$({2}, {1}, {0})"));
assert!(token_def(0x1a8).unwrap().rules.contains(&"REDIM {0}"));
}
#[test]
fn resets_block_local_default_types_before_the_next_block() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&0u16.to_le_bytes());
let first = [
0x1b, 0x00, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0x00, 0x00, 0x08, 0x00,
];
bytes.extend_from_slice(&(first.len() as u16).to_le_bytes());
bytes.extend_from_slice(&first);
bytes.extend_from_slice(&[0; 16]);
let second = [
0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, b'X', 0x00, 0x00, 0x00, 0x08, 0x00,
];
bytes.extend_from_slice(&[0, 1, 0, b'P', 0, 0, 0]);
bytes.extend_from_slice(&(second.len() as u16).to_le_bytes());
bytes.extend_from_slice(&second);
bytes.extend_from_slice(&[0; 16]);
assert_eq!(
decode("test.frm", &bytes, 0).unwrap(),
"DEFINT A-Z\n\nDEFSNG A-Z\nX\n"
);
}
}