#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable
)]
use super::super::MMixAssembler;
use super::super::Rule;
use super::super::operands::ZForm;
use super::super::tree::Children;
use super::MMixInstruction;
use tracing::debug;
#[derive(Debug, Clone, Copy)]
struct RelativeField {
backward: bool,
field: u32,
}
impl MMixAssembler {
fn relative_field(
&self,
mnemonic: &str,
target: u64,
bits: u32,
(line, col): (usize, usize),
absolute_alternative: &str,
) -> Result<RelativeField, String> {
let delta = target.wrapping_sub(self.current_addr) as i64;
if delta % 4 != 0 {
return Err(format!(
"{}:{}:{}: {} target 0x{:X} is not 4-byte aligned relative to the current instruction (byte delta {})",
self.current_filename, line, col, mnemonic, target, delta
));
}
let tetras = delta / 4;
if let Some(forward_sibling) = mnemonic.strip_suffix('B')
&& tetras >= 0
{
return Err(format!(
"{}:{}:{}: {} target 0x{:X} is not behind the current instruction ({} only encodes backward addresses; use {} instead)",
self.current_filename, line, col, mnemonic, target, mnemonic, forward_sibling
));
}
let span = 1i64 << bits;
let out_of_range = |direction: &str, reach_tetras: i64| {
format!(
"{}:{}:{}: {} target 0x{:X} is out of range: {} byte delta {} exceeds {}'s {}-byte {} reach{}",
self.current_filename,
line,
col,
mnemonic,
target,
direction,
delta.unsigned_abs(),
mnemonic,
reach_tetras * 4,
direction,
absolute_alternative
)
};
if tetras >= 0 {
if tetras >= span {
return Err(out_of_range("forward", span - 1));
}
return Ok(RelativeField {
backward: false,
field: tetras as u32,
});
}
if -tetras > span {
return Err(out_of_range("backward", span));
}
Ok(RelativeField {
backward: true,
field: (span + tetras) as u32,
})
}
pub(super) fn parse_inst_branch(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let mnem = parts.required()?;
let operands = parts.required()?;
let mut ops = Children::of(operands);
let x = self.parse_register(ops.required()?)?;
let target = self.parse_number(ops.required()?)?;
type Branch = fn(u8, u16) -> MMixInstruction;
let mnem = mnem.as_str().to_uppercase();
let (forward, backward): (Branch, Branch) = match mnem.as_str() {
"BN" | "BNB" => (MMixInstruction::BN, MMixInstruction::BNB),
"BZ" | "BZB" => (MMixInstruction::BZ, MMixInstruction::BZB),
"BP" | "BPB" => (MMixInstruction::BP, MMixInstruction::BPB),
"BOD" | "BODB" => (MMixInstruction::BOD, MMixInstruction::BODB),
"BNN" | "BNNB" => (MMixInstruction::BNN, MMixInstruction::BNNB),
"BNZ" | "BNZB" => (MMixInstruction::BNZ, MMixInstruction::BNZB),
"BNP" | "BNPB" => (MMixInstruction::BNP, MMixInstruction::BNPB),
"BEV" | "BEVB" => (MMixInstruction::BEV, MMixInstruction::BEVB),
_ => return Err(format!("Unknown branch instruction: {mnem}")),
};
let resolved = self.relative_field(&mnem, target, 16, (line, col), "")?;
let field = resolved.field as u16;
Ok(if resolved.backward {
backward(x, field)
} else {
forward(x, field)
})
}
pub(super) fn parse_inst_jmp(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let mnem = parts.next();
let operands = parts.required()?;
let mut ops = Children::of(operands);
let target = self.parse_number(ops.required()?)?;
let mnem = mnem.map_or_else(|| "JMP".to_string(), |m| m.as_str().to_uppercase());
let resolved = self.relative_field(&mnem, target, 24, (line, col), " (use GO instead)")?;
Ok(if resolved.backward {
MMixInstruction::JMPB(resolved.field)
} else {
MMixInstruction::JMP(resolved.field)
})
}
pub(super) fn parse_inst_pbranch(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let mnem = parts.required()?;
let operands = parts.required()?;
let mut ops = Children::of(operands);
let x = self.parse_register(ops.required()?)?;
let target = self.parse_number(ops.required()?)?;
type ProbableBranch = fn(u8, u8, u8) -> MMixInstruction;
let mnem = mnem.as_str().to_uppercase();
let (forward, backward): (ProbableBranch, ProbableBranch) = match mnem.as_str() {
"PBN" | "PBNB" => (MMixInstruction::PBN, MMixInstruction::PBNB),
"PBZ" | "PBZB" => (MMixInstruction::PBZ, MMixInstruction::PBZB),
"PBP" | "PBPB" => (MMixInstruction::PBP, MMixInstruction::PBPB),
"PBOD" | "PBODB" => (MMixInstruction::PBOD, MMixInstruction::PBODB),
"PBNN" | "PBNNB" => (MMixInstruction::PBNN, MMixInstruction::PBNNB),
"PBNZ" | "PBNZB" => (MMixInstruction::PBNZ, MMixInstruction::PBNZB),
"PBNP" | "PBNPB" => (MMixInstruction::PBNP, MMixInstruction::PBNPB),
"PBEV" | "PBEVB" => (MMixInstruction::PBEV, MMixInstruction::PBEVB),
_ => return Err(format!("Unknown probable branch instruction: {mnem}")),
};
let resolved = self.relative_field(&mnem, target, 16, (line, col), "")?;
let y = (resolved.field >> 8) as u8;
let z = (resolved.field & 0xFF) as u8;
Ok(if resolved.backward {
backward(x, y, z)
} else {
forward(x, y, z)
})
}
pub(super) fn parse_inst_geta(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let _mnem = parts.next(); let operand = parts.required()?;
let mut operand_parts = Children::of(operand);
let reg_pair = operand_parts.required()?;
let addr_pair = operand_parts.required()?;
let x = self.parse_register(reg_pair)?;
let addr = self.parse_number(addr_pair)?;
debug!(
"GETA: current_addr=0x{:X}, target_addr=0x{:X}",
self.current_addr, addr
);
let resolved = self.relative_field(
"GETA",
addr,
16,
(line, col),
" (use SETI, or LDA against a GREG base, for longer-range addresses)",
)?;
let y = (resolved.field >> 8) as u8;
let z = (resolved.field & 0xFF) as u8;
debug!(
"GETA: backward={}, field=0x{:X}, y=0x{:X}, z=0x{:X}",
resolved.backward, resolved.field, y, z
);
Ok(if resolved.backward {
MMixInstruction::GETAB(x, y, z)
} else {
MMixInstruction::GETA(x, y, z)
})
}
pub(super) fn parse_inst_getab(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let _mnem = parts.next(); let operand = parts.required()?;
let mut operand_parts = Children::of(operand);
let reg_pair = operand_parts.required()?;
let addr_pair = operand_parts.required()?;
let x = self.parse_register(reg_pair)?;
let addr = self.parse_number(addr_pair)?;
let resolved = self.relative_field(
"GETAB",
addr,
16,
(line, col),
" (use SETI, or LDA against a GREG base, for longer-range addresses)",
)?;
let y = (resolved.field >> 8) as u8;
let z = (resolved.field & 0xFF) as u8;
Ok(MMixInstruction::GETAB(x, y, z))
}
pub(super) fn parse_inst_pushj(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let _mnem = parts.next();
let operand = parts.required()?;
let mut ops = Children::of(operand);
let x = self.parse_reg_or_byte(ops.required()?, "PUSHJ")?;
let addr = self.parse_number(ops.required()?)?;
let resolved = self.relative_field("PUSHJ", addr, 16, (line, col), "")?;
let (y, z) = Self::split_hi_lo_byte(resolved.field as u16);
Ok(if resolved.backward {
MMixInstruction::PUSHJB(x, y, z)
} else {
MMixInstruction::PUSHJ(x, y, z)
})
}
pub(super) fn parse_inst_pushjb(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let (line, col) = pair.line_col();
let mut parts = Children::of(pair);
let _mnem = parts.next();
let operand = parts.required()?;
let mut ops = Children::of(operand);
let x = self.parse_reg_or_byte(ops.required()?, "PUSHJB")?;
let addr = self.parse_number(ops.required()?)?;
let resolved = self.relative_field("PUSHJB", addr, 16, (line, col), "")?;
let (y, z) = Self::split_hi_lo_byte(resolved.field as u16);
Ok(MMixInstruction::PUSHJB(x, y, z))
}
pub(super) fn parse_inst_go_auto(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let mut parts = Children::of(pair);
let mnem = parts.required()?.as_str().to_uppercase();
let operands = parts.required()?;
let is_three = operands.as_rule() == Rule::operand_list_three;
let mut ops = Children::of(operands);
let x_pair = ops.required()?;
let x = if mnem == "PUSHGO" {
self.parse_reg_or_byte(x_pair, &mnem)?
} else {
self.parse_register(x_pair)?
};
let (y, z) = if is_three {
let y = self.parse_register(ops.required()?)?;
let z = self.lower_z_operand(ops.required()?, &mnem)?;
(y, z)
} else {
let (y, offset) = self.resolve_memory_base_operand(ops.required()?)?;
(y, ZForm::Imm(offset))
};
match (mnem.as_str(), z) {
("GO", ZForm::Reg(z)) => Ok(MMixInstruction::GO(x, y, z)),
("GO", ZForm::Imm(z)) => Ok(MMixInstruction::GOI(x, y, z)),
("PUSHGO", ZForm::Reg(z)) => Ok(MMixInstruction::PUSHGO(x, y, z)),
("PUSHGO", ZForm::Imm(z)) => Ok(MMixInstruction::PUSHGOI(x, y, z)),
_ => Err(format!("Unknown GO instruction: {}", mnem)),
}
}
pub(super) fn parse_inst_pushgo_rri(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
self.parse_rri(pair, "PUSHGOI", MMixInstruction::PUSHGOI)
}
pub(super) fn parse_inst_pop(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let mut parts = Children::of(pair);
let _mnem = parts.next();
let Some(operands) = parts.next() else {
return Ok(MMixInstruction::POP(0, 0, 0));
};
match operands.as_rule() {
Rule::operand_list_two => {
let mut ops = Children::of(operands);
let x = self.imm_byte(ops.required()?, "POP")?;
let yz = self.imm_wyde(ops.required()?, "POP")?;
let (y, z) = Self::split_hi_lo_byte(yz);
Ok(MMixInstruction::POP(x, y, z))
}
Rule::operand_list_one => {
let mut ops = Children::of(operands);
let xyz = self.imm_three_bytes(ops.required()?, "POP")?;
let (x, y, z) = Self::split_xyz_bytes(xyz);
Ok(MMixInstruction::POP(x, y, z))
}
_ => Err(parts.unexpected(&operands)),
}
}
pub(super) fn parse_inst_go_rri(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
self.parse_rri(pair, "GOI", MMixInstruction::GOI)
}
}