2725 lines
81 KiB
Rust
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"
|
|
);
|
|
}
|
|
}
|