pub mod delayed_command;
pub mod file;
pub mod function;
pub mod list;
pub mod listing_output;
pub mod r#macro;
pub mod matrix;
pub mod page_info;
pub mod report;
pub mod save_command;
pub mod stable_ticker;
pub mod symbols_output;
pub mod processed_token;
use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fmt::{Debug, Display};
use std::io::Write;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use std::time::Instant;
use cpclib_basic::*;
use cpclib_common::bitvec::prelude::BitVec;
use cpclib_common::itertools::Itertools;
use cpclib_common::lazy_static::__Deref;
#[cfg(not(target_arch = "wasm32"))]
use cpclib_common::rayon::prelude::*;
use cpclib_common::smallvec::SmallVec;
use cpclib_sna::*;
use cpclib_tokens::ToSimpleToken;
use self::function::{Function, FunctionBuilder, HardCodedFunction};
use self::listing_output::*;
use self::processed_token::ProcessedToken;
use self::report::SavedFile;
use self::symbols_output::SymbolOutputGenerator;
use crate::assembler::processed_token::visit_processed_tokens;
use crate::delayed_command::*;
use crate::page_info::PageInformation;
use crate::preamble::*;
use crate::report::Report;
use crate::save_command::*;
use crate::stable_ticker::*;
use crate::{AssemblingOptions, PhysicalAddress};
const MAX_SIZE: usize = 4;
const REPEAT_START_VALUE: i32 = 1;
const MMR_PAGES_SELECTION: [u8; 9] = [
0xC0,
0b11_000_0_01,
0b11_001_0_01,
0b11_010_0_01,
0b11_011_0_01,
0b11_100_0_01,
0b11_101_0_01,
0b11_110_0_01,
0b11_111_0_01
];
#[allow(missing_docs)]
pub type Bytes = SmallVec<[u8; MAX_SIZE]>;
fn add_index(m: &mut Bytes, idx: i32) -> Result<(), AssemblerError> {
if idx < -127 || idx > 128 {
eprintln!("Index error {}", idx);
}
let val = (idx & 0xFF) as u8;
add_byte(m, val);
Ok(())
}
fn add_byte(m: &mut Bytes, b: u8) {
m.push(b);
}
fn add_word(m: &mut Bytes, w: u16) {
m.push((w % 256) as u8);
m.push((w / 256) as u8);
}
fn add_index_register_code(m: &mut Bytes, r: IndexRegister16) {
add_byte(m, indexed_register16_to_code(r));
}
const DD: u8 = 0xDD;
const FD: u8 = 0xFD;
trait MyDefault {
fn default() -> Self;
}
type Bank = [u8; 0x1_0000];
impl MyDefault for Bank {
fn default() -> Bank {
[0; 0x1_0000]
}
}
const NB_BANKS: usize = 9;
type Banks = Vec<Bank>;
impl MyDefault for Banks {
fn default() -> Self {
vec![Bank::default()]
}
}
#[derive(Clone, Copy, Debug)]
pub enum AssemblingPass {
Uninitialized,
FirstPass,
SecondPass, Finished,
ListingPass }
impl fmt::Display for AssemblingPass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let content = match self {
AssemblingPass::Uninitialized => "Uninitialized",
AssemblingPass::FirstPass => "1",
AssemblingPass::SecondPass => "2",
AssemblingPass::Finished => "Finished",
AssemblingPass::ListingPass => "Listing"
};
write!(f, "{}", content)
}
}
#[allow(missing_docs)]
#[allow(unused)]
impl AssemblingPass {
fn is_uninitialized(self) -> bool {
match self {
AssemblingPass::Uninitialized => true,
_ => false
}
}
pub fn is_finished(self) -> bool {
match self {
AssemblingPass::Finished => true,
_ => false
}
}
pub fn is_first_pass(self) -> bool {
match self {
AssemblingPass::FirstPass => true,
_ => false
}
}
pub fn is_second_pass(self) -> bool {
match self {
AssemblingPass::SecondPass => true,
_ => false
}
}
pub fn is_listing_pass(self) -> bool {
match self {
AssemblingPass::ListingPass => true,
_ => false
}
}
fn next_pass(self) -> Self {
match self {
AssemblingPass::Uninitialized => AssemblingPass::FirstPass,
AssemblingPass::FirstPass => AssemblingPass::SecondPass,
AssemblingPass::SecondPass => AssemblingPass::Finished,
AssemblingPass::Finished | AssemblingPass::ListingPass => panic!()
}
}
}
pub trait Visited {
fn visited(&self, env: &mut Env) -> Result<(), AssemblerError>;
}
impl Visited for Token {
fn visited(&self, env: &mut Env) -> Result<(), AssemblerError> {
visit_token(self, env)
}
}
impl Visited for LocatedToken {
fn visited(&self, env: &mut Env) -> Result<(), AssemblerError> {
visit_located_token(self, env).map_err(|e| e.locate(self.span().clone()))
}
}
type AssemblerWarning = AssemblerError;
#[derive(Clone)]
struct CrunchedSectionState {
crunched_section_start: Option<Z80Span>
}
impl CrunchedSectionState {
pub fn new(span: Option<Z80Span>) -> Self {
CrunchedSectionState {
crunched_section_start: span
}
}
}
#[derive(Clone)]
pub struct CharsetEncoding {
lut: std::collections::HashMap<char, i32>
}
impl CharsetEncoding {
pub fn new() -> Self {
let mut enc = Self {
lut: Default::default()
};
enc.reset();
enc
}
pub fn reset(&mut self) {
self.lut.clear()
}
pub fn update(&mut self, spec: &CharsetFormat, env: &Env) -> Result<(), AssemblerError> {
match spec {
CharsetFormat::Reset => self.reset(),
CharsetFormat::CharsList(l, s) => {
let mut s = env.resolve_expr_must_never_fail(s)?.int()?;
for c in l.iter() {
self.lut.insert(*c, s);
s += 1;
}
}
CharsetFormat::Char(c, i) => {
let c = env.resolve_expr_must_never_fail(c)?.char()?;
let i = env.resolve_expr_must_never_fail(i)?.int()?;
self.lut.insert(c, i);
}
CharsetFormat::Interval(a, b, s) => {
let a = env.resolve_expr_must_never_fail(a)?.char()?;
let b = env.resolve_expr_must_never_fail(b)?.char()?;
let mut s = env.resolve_expr_must_never_fail(s)?.int()?;
for c in a..=b {
self.lut.insert(c, s);
s += 1;
}
}
}
Ok(())
}
pub fn transform_char(&self, c: char) -> u8 {
self.lut.get(&c).cloned().unwrap_or(c as i32) as _
}
pub fn transform_string(&self, s: &str) -> Vec<u8> {
s.chars().map(|c| self.transform_char(c)).collect_vec()
}
}
#[derive(Clone, Debug)]
struct Section {
name: String,
start: u16,
stop: u16,
mmr: u8,
output_adr: u16,
code_adr: u16
}
impl Section {
fn new(name: &str, start: u16, stop: u16, mmr: u8) -> Self {
Section {
mmr,
name: name.to_owned(),
start,
stop,
output_adr: start,
code_adr: start
}
}
fn contains(&self, addr: u16) -> bool {
addr >= self.start && addr <= self.stop
}
fn new_pass(&mut self) {
self.output_adr = self.start;
self.code_adr = self.start;
}
}
#[allow(missing_docs)]
pub struct Env {
pass: AssemblingPass,
pub(crate) ctx: ParserContext,
real_nb_passes: usize,
can_skip_next_passes: RwLock<bool>,
request_additional_pass: RwLock<bool>,
requested_additional_pass: bool,
crunched_section_state: Option<CrunchedSectionState>,
stable_counters: StableTickerCounters,
ga_mmr: u8,
output_address: u16,
pages_info_sna: Vec<PageInformation>,
sna: cpclib_sna::Snapshot,
sna_version: cpclib_sna::SnapshotVersion,
banks: Vec<(Bank, PageInformation, BitVec)>,
selected_bank: Option<usize>,
macro_seed: usize,
charset_encoding: CharsetEncoding,
written_bytes: BitVec,
byte_written: bool,
symbols: SymbolsTableCaseDependent,
return_value: Option<ExprResult>,
functions: BTreeMap<String, Arc<Function>>,
run_options: Option<(u16, Option<u16>)>,
output_trigger: Option<ListingOutputTrigger>,
symbols_output: SymbolOutputGenerator,
string_warning_done: bool,
warnings: Vec<AssemblerWarning>,
nested_rorg: usize,
sections: HashMap<String, Arc<RwLock<Section>>>,
current_section: Option<Arc<RwLock<Section>>>,
saved_files: Option<Vec<SavedFile>>,
previous_pass_discarded_errors: HashSet<String>,
current_pass_discarded_errors: HashSet<String>,
if_token_adr_to_used_decision: HashMap<usize, bool>,
if_token_adr_to_unused_decision: HashMap<usize, bool>,
included_paths: HashSet<PathBuf>,
extra_print_from_function: RwLock<Vec<PrintOrPauseCommand>>,
extra_failed_assert_from_function: RwLock<Vec<FailedAssertCommand>>
}
impl Clone for Env {
fn clone(&self) -> Self {
Self {
ctx: self.ctx.clone(),
can_skip_next_passes: (*self.can_skip_next_passes.read().unwrap().deref()).into(),
request_additional_pass: (*self.request_additional_pass.read().unwrap().deref()).into(),
pass: self.pass.clone(),
real_nb_passes: self.real_nb_passes.clone(),
crunched_section_state: self.crunched_section_state.clone(),
stable_counters: self.stable_counters.clone(),
ga_mmr: self.ga_mmr.clone(),
output_address: self.output_address.clone(),
pages_info_sna: self.pages_info_sna.clone(),
sna: self.sna.clone(),
sna_version: self.sna_version.clone(),
banks: self.banks.clone(),
selected_bank: self.selected_bank.clone(),
macro_seed: self.macro_seed.clone(),
charset_encoding: self.charset_encoding.clone(),
written_bytes: self.written_bytes.clone(),
byte_written: self.byte_written.clone(),
symbols: self.symbols.clone(),
run_options: self.run_options.clone(),
output_trigger: self.output_trigger.clone(),
symbols_output: self.symbols_output.clone(),
string_warning_done: self.string_warning_done.clone(),
warnings: self.warnings.clone(),
nested_rorg: self.nested_rorg.clone(),
sections: self.sections.clone(),
current_section: self.current_section.clone(),
saved_files: self.saved_files.clone(),
if_token_adr_to_used_decision: self.if_token_adr_to_used_decision.clone(),
if_token_adr_to_unused_decision: self.if_token_adr_to_unused_decision.clone(),
requested_additional_pass: self.requested_additional_pass,
functions: self.functions.clone(),
return_value: self.return_value.clone(),
current_pass_discarded_errors: self.current_pass_discarded_errors.clone(),
previous_pass_discarded_errors: self.previous_pass_discarded_errors.clone(),
included_paths: self.included_paths.clone(),
extra_print_from_function: self
.extra_print_from_function
.read()
.unwrap()
.clone()
.into(),
extra_failed_assert_from_function: self
.extra_failed_assert_from_function
.read()
.unwrap()
.clone()
.into()
}
}
}
impl fmt::Debug for Env {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Env{{ pass: {:?}, symbols {:?} }}",
self.pass,
self.symbols()
)
}
}
impl Default for Env {
fn default() -> Self {
Self {
pass: AssemblingPass::Uninitialized,
stable_counters: StableTickerCounters::default(),
ctx: Default::default(),
pages_info_sna: vec![Default::default(); 2],
ga_mmr: 0xC0,
macro_seed: 0,
charset_encoding: CharsetEncoding::new(),
sna: {
let mut sna = Snapshot::new_6128().unwrap();
sna.unwrap_memory_chunks();
sna
},
sna_version: cpclib_sna::SnapshotVersion::V3,
symbols: SymbolsTableCaseDependent::default(),
run_options: None,
written_bytes: BitVec::repeat(false, 0x4000 * 2 * 4),
byte_written: false,
output_trigger: None,
symbols_output: Default::default(),
crunched_section_state: None,
string_warning_done: false,
warnings: Vec::new(),
nested_rorg: 0,
sections: HashMap::<String, Arc<RwLock<Section>>>::default(),
current_section: None,
output_address: 0,
banks: Vec::new(),
selected_bank: None,
real_nb_passes: 0,
saved_files: None,
can_skip_next_passes: true.into(),
request_additional_pass: false.into(),
if_token_adr_to_used_decision: HashMap::default(),
if_token_adr_to_unused_decision: HashMap::default(),
requested_additional_pass: false,
functions: Default::default(),
return_value: None,
current_pass_discarded_errors: HashSet::default(),
previous_pass_discarded_errors: HashSet::default(),
included_paths: HashSet::default(),
extra_print_from_function: Vec::new().into(),
extra_failed_assert_from_function: Vec::new().into()
}
}
}
impl Env {
pub fn symbols(&self) -> &SymbolsTableCaseDependent {
&self.symbols
}
pub fn symbols_mut(&mut self) -> &mut SymbolsTableCaseDependent {
&mut self.symbols
}
pub fn resolve_expr_may_fail_in_first_pass<E: ExprEvaluationExt>(
&self,
exp: &E
) -> Result<ExprResult, AssemblerError> {
match exp.resolve(self) {
Ok(value) => Ok(value),
Err(e) => {
if self.pass.is_first_pass() {
*self.can_skip_next_passes.write().unwrap() = false;
Ok(0.into())
}
else {
Err(e)
}
}
}
}
fn resolve_expr_must_never_fail<E: ExprEvaluationExt>(
&self,
exp: &E
) -> Result<ExprResult, AssemblerError> {
match exp.resolve(self) {
Ok(value) => Ok(value),
Err(e) => {
if self.pass.is_first_pass() {
*self.can_skip_next_passes.write().unwrap() = false;
Err(e)
}
else {
Err(e)
}
}
}
}
pub(crate) fn add_function_parameter_to_symbols_table<S: Into<Symbol>, V: Into<Value>>(
&mut self,
symbol: S,
value: V
) -> Result<(), AssemblerError> {
let symbol = symbol.into();
self.symbols.set_symbol_to_value(symbol, value)?;
Ok(())
}
fn add_symbol_to_symbol_table<E: Into<Value>>(
&mut self,
label: &str,
value: E
) -> Result<(), AssemblerError> {
let already_present = self.symbols().contains_symbol(label)?;
let value = value.into();
match (already_present, self.pass) {
(true, AssemblingPass::FirstPass) => Err(AssemblerError::SymbolAlreadyExists {
symbol: label.to_string(),
}),
(false, AssemblingPass::SecondPass ) => {
if ! self.requested_additional_pass &&
! *self.request_additional_pass.read().unwrap()
{
Err(AssemblerError::IncoherentCode{msg: format!(
"Label {} is not present in the symbol table in pass {}. There is an issue with some conditional code.",
label, self.pass
)})
} else {
self.symbols_mut()
.set_symbol_to_value(label, value)?;
Ok(())
}
},
(false, AssemblingPass::ListingPass) => Err(AssemblerError::IncoherentCode{msg: format!(
"Label {} is not present in the symbol table in pass {}. There is an issue with some conditional code.",
label, self.pass
)}),
(false, AssemblingPass::FirstPass) | (false, AssemblingPass::Uninitialized) => {
self.symbols_mut()
.set_symbol_to_value(label, value)?;
Ok(())
}
(true, AssemblingPass::SecondPass | AssemblingPass::ListingPass) => {
self.symbols_mut()
.update_symbol_to_value(&label.to_owned(), value)?;
Ok(())
}
(_, _) => panic!(
"add_symbol_to_symbol_table / unmanaged case {}, {}, {} {:#?}",
self.pass, label, already_present, value
),
}
}
fn track_used_symbols(&mut self, e: &Expr) {
e.symbols_used()
.into_iter()
.for_each(|symbol| self.symbols.use_symbol(symbol))
}
}
impl Env {
pub fn report(&self, start: &Instant) -> Report {
Report::from((self, start))
}
}
impl Env {
fn has_included(&self, path: &PathBuf) -> bool {
self.included_paths.contains(path)
}
fn mark_included(&mut self, path: PathBuf) {
self.included_paths.insert(path);
}
}
impl Env {
pub fn add_error_discardable_one_pass(
&mut self,
e: AssemblerError
) -> Result<(), AssemblerError> {
let repr = SimplerAssemblerError(&e).to_string();
if self.previous_pass_discarded_errors.contains(&repr) {
return Err(e);
}
else {
self.current_pass_discarded_errors.insert(repr);
return Ok(());
}
}
}
impl Env {
fn enter_namespace(&mut self, namespace: &str) -> Result<(), AssemblerError> {
if namespace.contains(".") {
return Err(AssemblerError::AssemblingError {
msg: format!("Invalid namespace \"{}\"", namespace)
});
}
self.symbols_mut().enter_namespace(namespace);
Ok(())
}
fn leave_namespace(&mut self) -> Result<Symbol, AssemblerError> {
self.symbols_mut().leave_namespace().map_err(|e| e.into())
}
}
#[allow(missing_docs)]
impl Env {
pub fn with_table(symbols: &SymbolsTable) -> Self {
let mut env = Self::default();
env.symbols.set_table(symbols.clone());
env.pass = AssemblingPass::SecondPass;
env
}
pub fn with_table_case_dependent(symbols: &SymbolsTableCaseDependent) -> Self {
let mut env = Self::default();
env.symbols = symbols.clone();
env.pass = AssemblingPass::SecondPass;
env
}
pub fn warnings(&self) -> &[AssemblerWarning] {
&self.warnings
}
fn handle_output_trigger(&mut self, new: &LocatedToken) {
if self.pass.is_listing_pass() && self.output_trigger.is_some() {
let addr = self.logical_output_address();
let trigg = self.output_trigger.as_mut().unwrap();
trigg.new_token(
new,
addr as _,
if self.crunched_section_state.is_some() {
AddressKind::CrunchedArea
}
else {
AddressKind::Address
}
);
}
}
pub(crate) fn start_new_pass(&mut self) {
self.requested_additional_pass |= !self.current_pass_discarded_errors.is_empty();
let mut can_change_request = true;
if !self.pass.is_listing_pass() {
self.pass = if self.real_nb_passes == 0
|| !*self.can_skip_next_passes.read().unwrap().deref()
{
if *self.request_additional_pass.read().unwrap() {
if self.pass.is_first_pass() {
can_change_request = false;
}
AssemblingPass::SecondPass
}
else {
self.pass.next_pass()
}
}
else {
if !*self.request_additional_pass.read().unwrap() {
AssemblingPass::Finished
}
else {
AssemblingPass::SecondPass
}
};
}
if !self.pass.is_finished() || self.pass.is_listing_pass() {
if !self.pass.is_listing_pass() {
self.real_nb_passes += 1;
}
std::mem::swap(
&mut self.current_pass_discarded_errors,
&mut self.previous_pass_discarded_errors
);
self.current_pass_discarded_errors.clear();
self.stable_counters = StableTickerCounters::default();
self.run_options = None;
self.written_bytes().fill(false);
self.warnings.retain(|elem| !elem.is_override_memory());
self.pages_info_sna.iter_mut().for_each(|p| p.new_pass());
self.sections
.iter_mut()
.for_each(|s| s.1.write().unwrap().new_pass());
self.current_section = None;
self.banks.iter_mut().for_each(|bank| {
bank.1.new_pass();
bank.2.fill(false);
});
self.selected_bank = None;
self.output_address = 0;
let page_info = self.active_page_info_mut();
page_info.logical_outputadr = 0;
page_info.logical_codeadr = 0;
self.update_dollar();
self.ga_mmr = 0xC0;
self.macro_seed = 0;
self.charset_encoding.reset();
self.sna_version = cpclib_sna::SnapshotVersion::V3;
self.can_skip_next_passes = true.into();
if can_change_request {
self.request_additional_pass = false.into();
}
self.symbols.new_pass();
}
}
fn handle_post_actions(&mut self) -> Result<Vec<SavedFile>, AssemblerError> {
self.handle_assert()?;
self.handle_print()?;
self.handle_breakpoints()?;
self.handle_file_save()
}
fn handle_breakpoints(&mut self) -> Result<(), AssemblerError> {
let mut winape_raw = Vec::new();
let pages_mmr = MMR_PAGES_SELECTION;
for (activepage, _page) in pages_mmr[0..self.pages_info_sna.len()].iter().enumerate() {
for brk in self.pages_info_sna[activepage].collect_breakpoints() {
let info = AssemblerError::RelocatedInfo {
info: Box::new(AssemblerError::AssemblingError {
msg: format!("Add a breakpoint in 0x{:04x}", brk.address)
}),
span: brk.span.clone()
};
eprint!("{}", info);
winape_raw.extend(brk.winape_raw());
}
}
assert_eq!(winape_raw.len() % 5, 0);
let winape_chunk = WinapeBreakPointChunk::from([b'B', b'R', b'K', b'S'], winape_raw);
if winape_chunk.nb_breakpoints() > 0 {
self.sna.add_chunk(winape_chunk);
}
Ok(())
}
fn handle_assert(&mut self) -> Result<(), AssemblerError> {
let backup = self.ga_mmr;
let pages_mmr = MMR_PAGES_SELECTION;
let mut assert_failures: Option<AssemblerError> = None;
for (activepage, page) in pages_mmr[0..self.pages_info_sna.len()].iter().enumerate() {
self.ga_mmr = *page;
let mut l_errors = self.pages_info_sna[activepage].collect_assert_failure();
match (&mut assert_failures, &mut l_errors) {
(_, Ok(_)) => {
}
(
Some(AssemblerError::MultipleErrors { errors: e1 }),
Err(AssemblerError::MultipleErrors { errors: e2 })
) => {
e1.append(e2);
}
(None, Err(l_errors)) => {
assert_failures = Some(l_errors.clone());
}
_ => unreachable!()
}
}
for bank in self.banks.iter() {
let mut l_errors = bank.1.collect_assert_failure();
match (&mut assert_failures, &mut l_errors) {
(_, Ok(_)) => {
}
(
Some(AssemblerError::MultipleErrors { errors: e1 }),
Err(AssemblerError::MultipleErrors { errors: e2 })
) => {
e1.append(e2);
}
(None, Err(l_errors)) => {
assert_failures = Some(l_errors.clone());
}
_ => unreachable!()
}
}
self.ga_mmr = backup;
if let Some(errors) = assert_failures {
Err(errors)
}
else {
Ok(())
}
}
fn handle_print(&mut self) -> Result<(), AssemblerError> {
let backup = self.ga_mmr;
let pages_mmr = MMR_PAGES_SELECTION;
let mut print_errors: Option<AssemblerError> = None;
let mut writer = std::io::stdout();
for (activepage, page) in pages_mmr[0..self.pages_info_sna.len()].iter().enumerate() {
self.ga_mmr = *page;
let mut l_errors = self.pages_info_sna[activepage].execute_print_or_pause(&mut writer);
match (&mut print_errors, &mut l_errors) {
(_, Ok(_)) => {
}
(
Some(AssemblerError::MultipleErrors { errors: e1 }),
Err(AssemblerError::MultipleErrors { errors: e2 })
) => {
e1.append(e2);
}
(None, Err(l_errors)) => {
print_errors = Some(l_errors.clone());
}
_ => unreachable!()
}
}
for bank in self.banks.iter() {
let mut l_errors = bank.1.execute_print_or_pause(&mut writer);
match (&mut print_errors, &mut l_errors) {
(_, Ok(_)) => {
}
(
Some(AssemblerError::MultipleErrors { errors: e1 }),
Err(AssemblerError::MultipleErrors { errors: e2 })
) => {
e1.append(e2);
}
(None, Err(l_errors)) => {
print_errors = Some(l_errors.clone());
}
_ => unreachable!()
}
}
self.ga_mmr = backup;
if let Some(errors) = print_errors {
Err(errors)
}
else {
Ok(())
}
}
fn handle_file_save(&mut self) -> Result<Vec<SavedFile>, AssemblerError> {
let backup = self.ga_mmr;
let pages_mmr = MMR_PAGES_SELECTION;
let mut saved_files = Vec::new();
for (activepage, page) in pages_mmr[0..self.pages_info_sna.len()].iter().enumerate() {
self.ga_mmr = *page;
let mut saved = self.pages_info_sna[activepage].execute_save(self)?;
saved_files.append(&mut saved);
}
self.ga_mmr = 0xC0;
#[cfg(not(target_arch = "wasm32"))]
let iter = self.banks.par_iter();
#[cfg(target_arch = "wasm32")]
let iter = self.banks.iter();
let mut saved = iter
.map(|bank| bank.1.execute_save(self))
.collect::<Result<Vec<_>, AssemblerError>>()?;
for s in &mut saved {
saved_files.append(s);
}
self.ga_mmr = backup;
Ok(saved_files)
}
}
impl Env {
fn active_page_info(&self) -> &PageInformation {
match &self.selected_bank {
Some(idx) => &self.banks[*idx].1,
None => {
let active_page =
self.logical_to_physical_address(self.output_address).page() as usize;
&self.pages_info_sna[active_page]
}
}
}
fn active_page_info_mut(&mut self) -> &mut PageInformation {
match &self.selected_bank {
Some(idx) => &mut self.banks[*idx].1,
None => {
let active_page =
self.logical_to_physical_address(self.output_address).page() as usize;
&mut self.pages_info_sna[active_page]
}
}
}
fn written_bytes(&mut self) -> &mut BitVec {
match &self.selected_bank {
Some(idx) => &mut self.banks[*idx].2,
None => &mut self.written_bytes
}
}
pub fn logical_output_address(&self) -> u16 {
self.active_page_info().logical_outputadr
}
pub fn logical_code_address(&self) -> u16 {
self.active_page_info().logical_codeadr
}
pub fn limit_address(&self) -> u16 {
self.active_page_info().limit
}
pub fn start_address(&self) -> Option<u16> {
self.active_page_info().startadr
}
pub fn maximum_address(&self) -> u16 {
self.active_page_info().maxadr
}
pub fn update_dollar(&mut self) {
let addr = self.logical_to_physical_address(self.logical_code_address());
self.symbols.set_current_address(addr);
let addr = self.logical_to_physical_address(self.logical_output_address());
self.symbols.set_current_output_address(addr);
}
pub fn memory(&self, start: u16, size: u16) -> Vec<u8> {
let mut mem = Vec::new();
for pos in start..(start + size) {
let address = self.logical_to_physical_address(pos as _);
mem.push(self.peek(&address));
}
mem
}
pub fn produced_bytes(&self) -> Vec<u8> {
let (start, length) = match self.start_address() {
Some(start) => {
if start > self.maximum_address() {
(0, 0)
}
else {
(start, self.maximum_address() as usize - start as usize + 1)
}
}
None => (0, 0)
};
self.memory(start, length as _)
}
pub fn loading_address(&self) -> Option<u16> {
self.start_address()
}
pub fn execution_address(&self) -> Option<u16> {
self.start_address()
}
pub fn output(&mut self, v: u8) -> Result<bool, AssemblerError> {
if self.logical_output_address() != self.output_address {
return Err(AssemblerError::BugInAssembler {
msg: format!(
"Sync issue with output address (0x{:x} != 0x{:x})",
self.logical_output_address(),
self.output_address
)
});
}
let physical_address =
self.logical_to_physical_address(self.logical_output_address() as u16);
if self.logical_code_address() > self.limit_address()
|| (self.active_page_info().fail_next_write_if_zero && self.logical_code_address() == 0)
{
return Err(AssemblerError::OutputExceedsLimits(
physical_address,
self.limit_address() as _
));
}
for protected_area in &self.active_page_info().protected_areas {
if protected_area.contains(&(self.logical_code_address() as u16)) {
return Err(AssemblerError::OutputProtected {
area: protected_area.clone(),
address: self.logical_code_address() as _
});
}
}
self.byte_written = true;
self.active_page_info_mut().maxadr =
self.maximum_address().max(self.logical_output_address());
if self.active_page_info().startadr.is_none() {
self.active_page_info_mut().startadr = Some(self.logical_output_address());
}
let abstract_address = physical_address.offset_in_cpc();
let already_used = *self.written_bytes().get(abstract_address as usize).unwrap();
let r#override = if already_used {
let r#override = AssemblerError::OverrideMemory(physical_address.clone(), 1);
if self.allow_memory_override() {
self.warnings.push(r#override);
true
}
else {
return Err(r#override);
}
}
else {
false
};
if self.selected_bank.is_none() {
if let Some(section) = &self.current_section {
let section = section.read().unwrap();
if !section.contains(physical_address.address()) {
return Err(AssemblerError::AssemblingError {
msg: format!(
"SECTION error: write address 0x{:x} out of range [Ox{:}-Ox{:}]",
physical_address.address(),
section.start,
section.stop
)
});
}
}
}
match &self.selected_bank {
Some(idx) => {
self.banks[*idx].0[self.output_address as usize] = v;
}
None => {
self.sna.set_byte(abstract_address, v);
}
}
self.written_bytes().set(abstract_address as _, true);
if self.pass.is_listing_pass() && self.output_trigger.is_some() {
self.output_trigger.as_mut().unwrap().write_byte(v);
}
self.active_page_info_mut().logical_outputadr =
self.logical_output_address().wrapping_add(1);
self.output_address = self.logical_output_address();
self.active_page_info_mut().logical_codeadr = self.logical_code_address().wrapping_add(1);
if self.logical_output_address() == 0 {
self.active_page_info_mut().fail_next_write_if_zero = true;
}
{
let (output, code) = (
self.active_page_info().logical_outputadr,
self.active_page_info().logical_codeadr
);
if let Some(section) = &mut self.current_section {
section.write().unwrap().output_adr = output;
section.write().unwrap().code_adr = code;
}
}
self.update_dollar();
Ok(r#override)
}
pub fn allow_memory_override(&self) -> bool {
true }
pub fn output_bytes(&mut self, bytes: &[u8]) -> Result<(), AssemblerError> {
let mut previously_overrided = false;
for b in bytes.iter() {
let currently_overrided = self.output(*b)?;
match (previously_overrided, currently_overrided) {
(true, true) => {
let extra_override_idx = self
.warnings
.iter_mut()
.rev()
.position(|w| {
if let AssemblerError::OverrideMemory(..) = w {
true
}
else {
false
}
})
.unwrap(); self.warnings
.remove(self.warnings.len() - 1 - extra_override_idx);
let r#override = self
.warnings
.iter_mut()
.rev()
.find(|w| {
if let AssemblerError::OverrideMemory(..) = w {
true
}
else {
false
}
})
.unwrap();
match r#override {
AssemblerError::OverrideMemory(_, ref mut size) => {
*size += 1;
}
_ => unreachable!()
};
}
_ => {
}
}
previously_overrided = currently_overrided;
}
Ok(())
}
pub fn peek(&self, address: &PhysicalAddress) -> u8 {
let address = address.offset_in_cpc();
match &self.selected_bank {
Some(idx) => self.banks[*idx].0[address as usize],
None => self.sna.get_byte(address)
}
}
pub fn poke(&mut self, byte: u8, address: &PhysicalAddress) {
let address = address.offset_in_cpc();
match &self.selected_bank {
Some(idx) => {
self.banks[*idx].0[address as usize] = byte;
}
None => {
self.sna.set_byte(address, byte);
}
}
}
pub fn size(&self) -> u16 {
if self.start_address().is_none() {
panic!("Unable to compute size now");
}
else {
(self.logical_output_address() - self.start_address().unwrap()) as u16
}
}
pub fn eval(&self, expr: &Expr) -> Result<ExprResult, AssemblerError> {
expr.resolve(self)
}
pub fn sna(&self) -> &cpclib_sna::Snapshot {
&self.sna
}
pub fn sna_version(&self) -> cpclib_sna::SnapshotVersion {
self.sna_version
}
pub fn save_sna<P: AsRef<std::path::Path>>(&self, fname: P) -> Result<(), std::io::Error> {
self.sna().save(fname, self.sna_version())
}
fn absolute_to_relative_may_fail_in_first_pass(
&self,
address: i32,
opcode_delta: i32
) -> Result<u8, AssemblerError> {
match absolute_to_relative(address, opcode_delta, self.symbols()) {
Ok(value) => Ok(value),
Err(error) => {
if self.pass.is_first_pass() {
Ok(0)
}
else {
Err(AssemblerError::RelativeAddressUncomputable {
address,
pass: self.pass,
error: Box::new(error)
})
}
}
}
}
}
impl Env {
fn visit_breakpoint(
&mut self,
exp: Option<&Expr>,
span: Option<Z80Span>
) -> Result<(), AssemblerError> {
if exp.is_some() {
return Err(AssemblerError::BugInAssembler {
msg: format!("Breakpoint with an expression is not yet implemented")
});
}
let current_address = self.logical_code_address();
let page = match self.logical_to_physical_address(current_address).page() {
0 => 0,
1 => 1,
_ => {
return Err(AssemblerError::BugInAssembler {
msg: format!(
"Page selection not handled 0x{:x}",
self.logical_to_physical_address(current_address).page()
)
})
}
};
let brk = BreakpointCommand::new(current_address, page, span.unwrap());
self.active_page_info_mut().add_breakpoint_command(brk);
Ok(())
}
}
#[allow(missing_docs)]
impl Env {
pub fn generate_symbols_output<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
self.symbols_output.generate(w, self.symbols())
}
pub fn visit_listing<T: ListingElement + Visited + MayHaveSpan>(
&mut self,
listing: &[T]
) -> Result<(), AssemblerError> {
for token in listing.iter() {
let _res = token.visited(self)?;
}
Ok(())
}
fn visit_limit(&mut self, exp: &Expr) -> Result<(), AssemblerError> {
let value = self.resolve_expr_must_never_fail(exp)?.int()?;
self.active_page_info_mut().limit = value as _;
if self.limit_address() <= self.maximum_address() {
return Err(AssemblerError::OutputAlreadyExceedsLimits(
self.limit_address() as _
));
}
if self.limit_address() == 0 {
eprintln!("[WARNING] Do you really want to set a limit of 0 ?");
}
Ok(())
}
fn visit_label(&mut self, label: &str) -> Result<(), AssemblerError> {
let label = self.symbols().normalize_symbol(label);
let label = label.value();
let res = if self.symbols().contains_symbol(label)?
&& (self.pass.is_first_pass()
|| !(self.symbols().kind(label)? == "address"
|| self.symbols().kind(label)? == "any"))
{
Err(AssemblerError::AlreadyDefinedSymbol {
symbol: label.into(),
kind: self.symbols().kind(label)?.into()
})
}
else {
if !label.starts_with('.') {
self.symbols_mut().set_current_label(label)?;
}
let value = match self.symbols().current_address() {
Ok(address) => address,
Err(_) => 0
};
let addr = self.logical_to_physical_address(value);
self.add_symbol_to_symbol_table(label, addr)
};
if let Err(AssemblerError::AlreadyDefinedSymbol { symbol: _, kind }) = &res {
if kind == "macro" || kind == "struct" {
return Err(AssemblerError::AssemblingError {
msg:
"Use (void) for macros with no parameters to disambiguate them with labels"
.to_owned()
});
}
else {
res
}
}
else {
res
}
}
fn visit_noexport<S: Borrow<str> + Display>(
&mut self,
labels: &[S]
) -> Result<(), AssemblerError> {
if labels.is_empty() {
self.symbols_output.forbid_all_symbols();
}
else {
labels
.iter()
.for_each(|l| self.symbols_output.forbid_symbol(l.borrow()));
}
Ok(())
}
fn visit_export<S: Borrow<str> + Display>(
&mut self,
labels: &[S]
) -> Result<(), AssemblerError> {
if labels.is_empty() {
self.symbols_output.allow_all_symbols();
}
else {
labels
.iter()
.for_each(|l| self.symbols_output.allow_symbol(l.borrow()));
}
Ok(())
}
fn visit_multi_pushes(&mut self, regs: &[DataAccess]) -> Result<(), AssemblerError> {
let result = regs
.iter()
.map(|reg| assemble_push(reg))
.collect::<Result<Vec<_>, AssemblerError>>()?;
let result = result.into_iter().flatten().collect_vec();
self.output_bytes(&result)
}
fn visit_multi_pops(&mut self, regs: &[DataAccess]) -> Result<(), AssemblerError> {
let result = regs
.iter()
.map(|reg| assemble_pop(reg))
.collect::<Result<Vec<_>, AssemblerError>>()?;
let result = result.into_iter().flatten().collect_vec();
self.output_bytes(&result)
}
pub fn visit_macro_definition(
&mut self,
name: &str,
arguments: &[&str],
code: &str,
source: Option<&Z80Span>
) -> Result<(), AssemblerError> {
if self.pass.is_first_pass() && self.symbols().contains_symbol(name)? {
return Err(AssemblerError::SymbolAlreadyExists {
symbol: name.to_owned()
});
}
let source = source.map(|s| s.into());
self.symbols_mut().set_symbol_to_value(
name,
Macro::new(name.into(), arguments, code.to_owned(), source)
)?;
Ok(())
}
pub fn visit_waitnops(&mut self, count: &Expr) -> Result<(), AssemblerError> {
let bytes = self.assemble_nop(Mnemonic::Nop, Some(count))?;
self.output_bytes(&bytes)?;
self.stable_counters
.update_counters(self.resolve_expr_may_fail_in_first_pass(count)?.int()? as _);
Ok(())
}
pub fn visit_struct_definition<T: ListingElement + ToSimpleToken, S: Borrow<str>>(
&mut self,
name: &str,
content: &[(S, T)],
source: Option<&Z80Span>
) -> Result<(), AssemblerError> {
if self.pass.is_first_pass() && self.symbols().contains_symbol(name)? {
return Err(AssemblerError::SymbolAlreadyExists {
symbol: name.to_owned()
});
}
let r#struct = Struct::new(name, content, source.map(|s| s.into()));
let mut index = 0;
for (f, s) in r#struct.fields_size(self.symbols()) {
self.symbols_mut()
.set_symbol_to_value(format!("{}.{}", name, f), index)?;
index += s;
}
self.symbols_mut().set_symbol_to_value(name, r#struct)?;
Ok(())
}
pub fn visit_buildsna(
&mut self,
version: Option<&SnapshotVersion>
) -> Result<(), AssemblerError> {
self.sna_version = version.cloned().unwrap_or(SnapshotVersion::V3);
Ok(())
}
pub fn visit_align(
&mut self,
boundary: &Expr,
fill: Option<&Expr>
) -> Result<(), AssemblerError> {
let boundary = self.resolve_expr_must_never_fail(boundary)?.int()? as u16;
let fill = match fill {
Some(fill) => self.resolve_expr_may_fail_in_first_pass(fill)?.int()? as u8,
None => 0
};
while self.logical_output_address() as u16 % boundary != 0 {
self.output(fill)?;
}
Ok(())
}
fn visit_section(&mut self, name: &str) -> Result<(), AssemblerError> {
let section = match self.sections.get(name) {
Some(section) => section,
None => {
return Err(AssemblerError::AssemblingError {
msg: format!("Section '{}' does not exists", name)
});
}
};
let (output_adr, code_adr, mmr) = {
let section = section.read().unwrap();
if section.mmr != self.ga_mmr {
self.warnings.push(AssemblerError::AssemblingError{
msg: format!("Gate Array configuration is not coherent with the section. We manually set it (0x{:x} expected instead of 0x{:x})", section.mmr, self.ga_mmr)
});
}
(section.output_adr, section.code_adr, section.mmr)
};
self.current_section = Some(Arc::clone(section));
self.ga_mmr = mmr;
self.output_address = output_adr;
self.active_page_info_mut().logical_outputadr = output_adr;
self.active_page_info_mut().logical_codeadr = code_adr;
self.update_dollar();
self.output_trigger
.as_mut()
.map(|o| o.replace_address(code_adr.into()));
Ok(())
}
fn visit_range(&mut self, name: &str, start: &Expr, stop: &Expr) -> Result<(), AssemblerError> {
let start = self.resolve_expr_must_never_fail(start)?.int()? as u16;
let stop = self.resolve_expr_must_never_fail(stop)?.int()? as u16;
let mmr = self.ga_mmr;
if let Some(section) = self.sections.get(name) {
let section = section.read().unwrap();
if start != section.start
|| stop != section.stop
|| name != section.name
|| mmr != section.mmr
{
return Err(AssemblerError::AssemblingError {
msg: format!(
"Section '{}' is already defined from 0x{:x} to 0x{:x} in 0x{:x}",
section.name, section.start, section.stop, section.mmr
)
});
}
}
else {
let section = Arc::new(RwLock::new(Section::new(name, start, stop, mmr)));
self.sections.insert(name.to_owned(), section);
}
Ok(())
}
fn visit_next_and_co(
&mut self,
destination: &str,
source: &str,
delta: Option<&Expr>,
can_override: bool
) -> Result<(), AssemblerError> {
if !can_override && self.symbols.contains_symbol(destination)? && self.pass.is_first_pass()
{
let kind = self.symbols().kind(Symbol::from(destination))?;
return Err(AssemblerError::AlreadyDefinedSymbol {
symbol: destination.into(),
kind: kind.into()
});
}
let value = self.resolve_expr_must_never_fail(&Expr::Label(source.into()))?;
if can_override {
self.symbols_mut()
.assign_symbol_to_value(destination, value.clone())?;
}
else {
self.add_symbol_to_symbol_table(destination, value.clone())?;
}
self.output_trigger
.as_mut()
.map(|o| o.replace_address(value.clone()));
let delta = match delta {
Some(delta) => self.resolve_expr_must_never_fail(delta)?,
None => 1.into()
};
let value = (value + delta)?;
self.symbols_mut().assign_symbol_to_value(source, value)?;
Ok(())
}
pub fn logical_to_physical_address(&self, address: u16) -> PhysicalAddress {
PhysicalAddress::new(address, self.ga_mmr)
}
fn visit_bank(&mut self, exp: Option<&Expr>) -> Result<(), AssemblerError> {
if self.nested_rorg > 0 {
return Err(AssemblerError::NotAllowed);
}
match exp {
Some(exp) => {
let mmr = self.resolve_expr_must_never_fail(exp)?.int()?;
if mmr < 0xC0 || mmr > 0xC7 {
return Err(AssemblerError::MMRError { value: mmr });
}
let mmr = mmr as u8;
self.ga_mmr = mmr;
}
None => {
if self.pass.is_first_pass() {
self.selected_bank = Some(self.banks.len());
self.banks.push((
Bank::default(),
PageInformation::default(),
BitVec::repeat(false, 0x4000 * 4)
));
}
else {
self.selected_bank = self.selected_bank.map(|v| v + 1).or(Some(0));
if *self.selected_bank.as_ref().unwrap() >= self.banks.len() {
return Err(AssemblerError::AssemblingError {
msg: "There were less banks in previous pass".to_owned()
});
}
}
self.ga_mmr = 0xC0;
self.output_address = 0;
let page_info = self.active_page_info_mut();
page_info.logical_outputadr = 0;
page_info.logical_codeadr = 0;
}
}
Ok(())
}
fn visit_bankset(&mut self, exp: &Expr) -> Result<(), AssemblerError> {
if self.nested_rorg > 0 {
return Err(AssemblerError::NotAllowed);
}
let page = self.resolve_expr_must_never_fail(exp)?.int()? as u8;
eprintln!("Warning need to code sna memory extension if needed");
self.select_page(page)?;
Ok(())
}
fn select_page(&mut self, page: u8) -> Result<(), AssemblerError> {
if self.nested_rorg > 0 {
return Err(AssemblerError::NotAllowed);
}
if
page >= 8 {
return Err(AssemblerError::InvalidArgument {
msg: format!(
"{} is invalid. BANKSET only accept values from 0 to 7",
page
)
.into()
});
}
if page == 0 {
self.ga_mmr = 0b11_000_0_00;
}
else {
self.ga_mmr = 0b11_000_0_10 + ((page - 1) << 3);
}
let page = page as usize;
let nb_pages = self.pages_info_sna.len();
let expected_nb = nb_pages.max(page + 1);
if expected_nb > nb_pages {
self.pages_info_sna.resize(expected_nb, Default::default());
self.written_bytes().resize(expected_nb * 0x1_0000, false);
}
self.output_address = self.logical_output_address();
self.update_dollar();
Ok(())
}
pub fn visit_undef(&mut self, label: &str) -> Result<(), AssemblerError> {
match self.symbols_mut().remove_symbol(label)? {
Some(_) => Ok(()),
None => {
Err(AssemblerError::UnknownSymbol {
symbol: label.into(),
closest: self.symbols().closest_symbol(label, SymbolFor::Number)?
})
}
}
}
pub fn visit_protect(&mut self, start: &Expr, stop: &Expr) -> Result<(), AssemblerError> {
if self.pass.is_first_pass() {
let start = self.resolve_expr_must_never_fail(start)?.int()? as u16;
let stop = self.resolve_expr_must_never_fail(stop)?.int()? as u16;
self.active_page_info_mut()
.protected_areas
.push(start..=stop);
}
Ok(())
}
fn build_string_from_formatted_expression(
&self,
info: &[FormattedExpr]
) -> Result<String, AssemblerError> {
let mut repr = String::default();
for (_idx, current) in info.iter().enumerate() {
match current {
FormattedExpr::Raw(Expr::String(string)) => {
repr += string;
}
FormattedExpr::Raw(Expr::Char(char)) => {
repr += &char.to_string();
}
FormattedExpr::Raw(expr) => {
let value = self.resolve_expr_may_fail_in_first_pass(expr)?;
repr += &value.to_string();
}
FormattedExpr::Formatted(format, expr) => {
let value = self.resolve_expr_may_fail_in_first_pass(expr)?.int()? as i32;
repr += &format.string_representation(value);
}
}
}
Ok(repr)
}
pub fn visit_print(&mut self, info: &[FormattedExpr], span: Option<Z80Span>) {
let print_or_error = match self.build_string_from_formatted_expression(info) {
Ok(msg) => either::Either::Left(msg),
Err(error) => either::Either::Right(error)
};
self.active_page_info_mut().add_print_command(PrintCommand {
span,
print_or_error
})
}
pub fn visit_pause(&mut self, span: Option<Z80Span>) {
self.active_page_info_mut().add_pause_command(span.into());
}
pub fn visit_fail(&self, info: Option<&[FormattedExpr]>) -> Result<(), AssemblerError> {
let repr = info
.map(|info| self.build_string_from_formatted_expression(info))
.unwrap_or_else(|| Ok("".to_owned()))?;
dbg!(Err(AssemblerError::Fail { msg: repr }))
}
pub fn visit_save(
&mut self,
filename: &str,
address: Option<&Expr>,
size: Option<&Expr>,
save_type: Option<&SaveType>,
dsk_filename: Option<&String>,
_side: Option<&Expr>
) -> Result<(), AssemblerError> {
if cfg!(target_arch = "wasm32") {
return Err(AssemblerError::AssemblingError {
msg: "SAVE directive is not allowed in a web-based assembling.".to_owned()
});
}
let from = match address {
Some(address) => Some(self.resolve_expr_must_never_fail(address)?.int()?),
None => None
};
let size = match size {
Some(size) => Some(self.resolve_expr_must_never_fail(size)?.int()?),
None => None
};
let page_info = self.active_page_info_mut();
page_info.add_save_command(SaveCommand::new(
from,
size,
filename.to_owned(),
save_type.cloned(),
dsk_filename.cloned()
));
Ok(())
}
pub fn visit_charset(&mut self, format: &CharsetFormat) -> Result<(), AssemblerError> {
let mut new_charset = CharsetEncoding::new();
std::mem::swap(&mut new_charset, &mut self.charset_encoding);
new_charset.update(format, self)?;
std::mem::swap(&mut new_charset, &mut self.charset_encoding); Ok(())
}
pub fn visit_snainit(&mut self, fname: &str) -> Result<(), AssemblerError> {
if self.byte_written {
return Err(AssemblerError::AssemblingError {
msg: format!(
"Some bytes has alrady been produced; you cannot import the snapshot {}.",
fname
)
});
}
self.sna = Snapshot::load(fname).map_err(|e| {
AssemblerError::AssemblingError {
msg: format!("Error while loading snapshot. {}", e)
}
})?;
dbg!(self.sna.memory_size_header());
dbg!(self
.sna
.chunks()
.iter()
.map(|c| String::from_utf8_lossy(c.code()))
.collect_vec());
self.sna.unwrap_memory_chunks();
dbg!(self.sna.memory_size_header());
dbg!(self
.sna
.chunks()
.iter()
.map(|c| String::from_utf8_lossy(c.code()))
.collect_vec());
Ok(())
}
pub fn visit_snaset(
&mut self,
flag: &cpclib_sna::SnapshotFlag,
value: &cpclib_sna::FlagValue
) -> Result<(), AssemblerError> {
self.sna
.set_value(*flag, value.as_u16().unwrap())
.map_err(|e| e.into())
}
pub fn visit_incbin(&mut self, data: &[u8]) -> Result<(), AssemblerError> {
self.output_bytes(data)
}
pub fn visit_crunched_section<'tokens, T: Visited + ListingElement + MayHaveSpan + Sync>(
&mut self,
kind: &CrunchType,
lst: &mut [ProcessedToken<'tokens, T>],
previous_bytes: &mut Option<Vec<u8>>,
previous_crunched_bytes: &mut Option<Vec<u8>>,
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
ProcessedToken<'tokens, T>: FunctionBuilder,
<<T as cpclib_tokens::ListingElement>::TestKind as cpclib_tokens::TestKindElement>::Expr:
ExprEvaluationExt
{
let could_display_warning_message = self.active_page_info().limit != 0xFFFF
|| !self.active_page_info().protected_areas.is_empty();
let mut crunched_env = self.clone();
crunched_env.crunched_section_state = CrunchedSectionState::new(span.cloned()).into();
crunched_env.active_page_info_mut().logical_outputadr = 0;
crunched_env.active_page_info_mut().startadr = None; crunched_env.active_page_info_mut().maxadr = 0;
crunched_env.active_page_info_mut().limit = 0xFFFF; crunched_env.active_page_info_mut().protected_areas.clear(); crunched_env.output_address = 0;
self.output_trigger
.as_mut()
.map(|t| t.enter_crunched_section());
visit_processed_tokens(lst, &mut crunched_env).map_err(|e| {
dbg!(&self.pass, &crunched_env.pass);
let e = AssemblerError::CrunchedSectionError { error: e.into() };
match span {
Some(span) => {
AssemblerError::RelocatedError {
error: e.into(),
span: span.clone()
}
}
None => e
}
})?;
self.output_trigger
.as_mut()
.map(|t| t.leave_crunched_section());
let bytes = crunched_env.produced_bytes();
let must_crunch = previous_bytes
.as_ref()
.map(|b| b.as_slice() != bytes.as_slice())
.unwrap_or(true);
if must_crunch {
let crunched: Vec<u8> = if bytes.is_empty() {
Vec::new()
}
else {
kind.crunch(&bytes).map_err(|e| {
match span {
Some(span) => {
AssemblerError::RelocatedError {
error: e.into(),
span: span.clone()
}
}
None => e
}
})?
};
previous_crunched_bytes.replace(crunched);
previous_bytes.replace(bytes);
}
else {
}
let _bytes = previous_bytes.as_ref().unwrap();
let crunched = previous_crunched_bytes.as_ref().unwrap();
self.visit_incbin(&crunched).map_err(|e| {
match span {
Some(span) => {
AssemblerError::RelocatedError {
error: e.into(),
span: span.clone()
}
}
None => e
}
})?;
std::mem::swap(self.symbols_mut(), crunched_env.symbols_mut());
let can_skip_next_passes = (*self.can_skip_next_passes.read().unwrap().deref()
& *crunched_env.can_skip_next_passes.read().unwrap())
.into(); let request_additional_pass = (*self.request_additional_pass.read().unwrap().deref()
| *crunched_env.request_additional_pass.read().unwrap())
.into();
*self.can_skip_next_passes.write().unwrap() = can_skip_next_passes;
*self.request_additional_pass.write().unwrap() = request_additional_pass;
self.macro_seed = crunched_env.macro_seed;
if could_display_warning_message {
self.warnings.push(
AssemblerWarning::AssemblingError{
msg: "Memory protection systems are disabled in crunched section. If you want to keep them, explicitely use LIMIT or PROTECT directives in the crunched section.".to_owned()
}
);
}
Ok(())
}
}
impl Env {
fn assemble_nop(&self, kind: Mnemonic, count: Option<&Expr>) -> Result<Bytes, AssemblerError> {
let count = match count {
Some(count) => self.resolve_expr_must_never_fail(count)?.int()?,
None => 1
};
let mut bytes = Bytes::new();
for _i in 0..count {
match kind {
Mnemonic::Nop => {
bytes.push(0);
}
Mnemonic::Nop2 => {
bytes.push(0xED);
bytes.push(0xFF);
}
_ => unreachable!()
}
}
Ok(bytes)
}
}
pub fn visit_tokens_all_passes<
'token,
T: 'token + Visited + ToSimpleToken + Debug + Sync + ListingElement + MayHaveSpan
>(
tokens: &'token [T],
ctx: &ParserContext
) -> Result<Env, AssemblerError>
where
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as cpclib_tokens::TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let options = AssemblingOptions::default();
visit_tokens_all_passes_with_options(tokens, &options, ctx)
}
impl Env {
pub fn new(options: &AssemblingOptions, ctx: &ParserContext) -> Self {
let mut env = Env::default();
env.ctx = ctx.clone();
env.symbols =
SymbolsTableCaseDependent::new(options.symbols().clone(), options.case_sensitive());
if let Some(builder) = &options.output_builder {
env.output_trigger = ListingOutputTrigger {
token: None,
bytes: Vec::new(),
builder: builder.clone(),
start: 0
}
.into();
}
env
}
pub fn pass(&self) -> &AssemblingPass {
&self.pass
}
}
impl Env {
pub fn visit_return(&mut self, e: &Expr) -> Result<(), AssemblerError> {
debug_assert!(self.return_value.is_none());
self.return_value = Some(self.resolve_expr_must_never_fail(e)?);
Ok(())
}
pub fn user_defined_function(&self, name: &str) -> Result<&Function, AssemblerError> {
match self.functions.get(name) {
Some(f) => Ok(f),
None => Err(AssemblerError::FunctionUnknown(name.to_owned()))
}
}
pub fn any_function<'res>(
&'res self,
name: &'res str
) -> Result<&'res Function, AssemblerError> {
match HardCodedFunction::by_name(name) {
Some(f) => Ok(f),
None => self.user_defined_function(name)
}
}
}
pub fn visit_tokens_all_passes_with_options<'token, T>(
tokens: &'token [T],
options: &AssemblingOptions,
ctx: &ParserContext
) -> Result<Env, AssemblerError>
where
T: Visited + ToSimpleToken + Debug + Sync + ListingElement + MayHaveSpan,
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr: ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let mut env = Env::new(options, ctx);
let mut tokens = processed_token::build_processed_tokens_list(tokens, &mut env);
loop {
env.start_new_pass();
if env.pass.is_finished() {
break;
}
processed_token::visit_processed_tokens(&mut tokens, &mut env)?;
}
if options.output_builder.is_some() {
env.pass = AssemblingPass::ListingPass;
env.start_new_pass();
processed_token::visit_processed_tokens(&mut tokens, &mut env)
.map_err(|e| eprintln!("{}", e))
.expect("No error can arise in listing output mode; there is a bug somewhere");
}
if let Some(trigger) = env.output_trigger.as_mut() {
trigger.finish()
}
env.saved_files = Some(env.handle_post_actions()?);
Ok(env)
}
#[deprecated(note = "use visit_tokens_one_pass")]
pub fn visit_tokens<T: Visited>(tokens: &[T]) -> Result<Env, AssemblerError> {
visit_tokens_one_pass(tokens)
}
pub fn visit_tokens_one_pass<T: Visited>(tokens: &[T]) -> Result<Env, AssemblerError> {
let mut env = Env::default();
for token in tokens.iter() {
token.visited(&mut env)?;
}
Ok(env)
}
pub fn visit_located_token(
outer_token: &LocatedToken,
env: &mut Env
) -> Result<(), AssemblerError> {
let nb_warnings = env.warnings.len();
let outer_token = unsafe { (outer_token as *const LocatedToken).as_ref().unwrap() };
env.handle_output_trigger(outer_token);
let span = outer_token.span();
match outer_token {
LocatedToken::Standard { token, span } => {
match token {
Token::Assert(exp, txt) => {
visit_assert(exp, txt.as_ref(), env, Some(span.clone()))?;
Ok(())
}
Token::Breakpoint(expr) => env.visit_breakpoint(expr.as_ref(), Some(span.clone())),
Token::Macro(_name, _arguments, _code) => {
panic!("Should delegate it to ProcessedToken")
}
Token::MacroCall(..) => {
panic!("Should not be called")
}
Token::Pause => {
env.visit_pause(Some(span.clone()));
Ok(())
}
Token::Print(ref exp) => {
env.visit_print(exp.as_ref(), Some(span.clone()));
Ok(())
}
Token::Struct(name, content) => {
env.visit_struct_definition(name, content.as_slice(), Some(span))
.map_err(|e| e.locate(span.clone()))
}
Token::Incbin { .. } | Token::Include(..) => {
panic!("Should never be called");
}
_ => {
token.visited(env).map_err(|err| {
AssemblerError::RelocatedError {
error: Box::new(err),
span: span.clone()
}
})
}
}
}
LocatedToken::CrunchedSection(..) => {
panic!("Should be handled by ProcessedToken")
}
LocatedToken::Function(..) => {
panic!("Should be handled by ProcessedToken")
}
LocatedToken::If(..) => {
panic!("Should be handled by ProcessedToken")
}
LocatedToken::Module(name, code, span) => {
env.enter_namespace(name)
.map_err(|e| e.locate(span.clone()))?;
env.visit_listing(code)?;
env.leave_namespace().map_err(|e| e.locate(span.clone()))?;
Ok(())
}
LocatedToken::Repeat(..) | LocatedToken::RepeatUntil(..) | LocatedToken::Rorg(..) => {
panic!("Should be handled by ProcessedToken")
}
LocatedToken::Switch(value, cases, default, span) => {
env.visit_switch(
value,
cases.iter().map(|c| (&c.0, &c.1[..], c.2)),
default.as_ref().map(|l| &l[..]),
Some(span)
)
}
LocatedToken::While(cond, inner, span) => env.visit_while(cond, inner, Some(span.clone())),
LocatedToken::Iterate(..) => {
panic!("Should never be called")
}
LocatedToken::For { .. } => {
panic!("Should never be called")
}
LocatedToken::Label(label) => {
env.visit_label(label.as_str())
.map_err(|e| e.locate(label.clone()))
}
LocatedToken::MacroCall(_name, _args, _span) => {
panic!("Should never be called")
}
LocatedToken::Struct(name, args, span) => {
env.visit_struct_definition(name.as_str(), args, Some(span))
}
LocatedToken::Defb(l, span) => {
visit_db_or_dw_or_str(DbLikeKind::Defb, l.as_ref(), env)
.map_err(|e| e.locate(span.clone()))
}
LocatedToken::Defw(l, span) => {
visit_db_or_dw_or_str(DbLikeKind::Defw, l.as_ref(), env)
.map_err(|e| e.locate(span.clone()))
}
LocatedToken::Str(l, span) => {
visit_db_or_dw_or_str(DbLikeKind::Str, l.as_ref(), env)
.map_err(|e| e.locate(span.clone()))
}
LocatedToken::Include(..) => panic!("Should never been called"),
LocatedToken::Incbin { .. } => panic!("Should never been called"),
LocatedToken::Macro {
name: _,
params: _,
content: _,
span: _
} => panic!("Should never been called")
}?;
let nb_additional_warnings = env.warnings.len() - nb_warnings;
for i in 0..nb_additional_warnings {
let warning = &mut env.warnings[i + nb_warnings];
if !warning.is_located() {
*warning = AssemblerError::RelocatedWarning {
warning: Box::new(warning.clone()),
span: span.clone()
};
}
}
env.move_delayed_commands_of_functions();
Ok(())
}
fn visit_token(token: &Token, env: &mut Env) -> Result<(), AssemblerError> {
env.update_dollar();
match token {
Token::Align(ref boundary, ref fill) => env.visit_align(boundary, fill.as_ref()),
Token::Assert(ref exp, ref txt) => {
visit_assert(exp, txt.as_ref(), env, None)?;
Ok(())
}
Token::Basic(ref variables, ref hidden_lines, ref code) => {
env.visit_basic(variables.as_ref(), hidden_lines.as_ref(), code)
}
Token::BuildSna(ref v) => env.visit_buildsna(v.as_ref()),
Token::Bank(ref exp) => env.visit_bank(exp.as_ref()),
Token::Bankset(ref v) => env.visit_bankset(v),
Token::Breakpoint(ref exp) => env.visit_breakpoint(exp.as_ref(), None),
Token::Org(ref address, ref address2) => visit_org(address, address2.as_ref(), env),
Token::Defb(l) => visit_db_or_dw_or_str(DbLikeKind::Defb, l.as_ref(), env),
Token::Defw(l) => visit_db_or_dw_or_str(DbLikeKind::Defw, l.as_ref(), env),
Token::Str(l) => visit_db_or_dw_or_str(DbLikeKind::Str, l.as_ref(), env),
Token::Defs(_) => visit_defs(token, env),
Token::End => visit_end(env),
Token::OpCode(ref mnemonic, ref arg1, ref arg2, ref arg3) => {
visit_opcode(*mnemonic, &arg1, &arg2, &arg3, env)?;
if !env.stable_counters.is_empty() {
let duration = token.estimated_duration()?;
env.stable_counters.update_counters(duration);
}
Ok(())
}
Token::Comment(_) => Ok(()), Token::List => {
env.output_trigger.as_mut().map(|l| {
l.on();
});
Ok(())
}
Token::NoList => {
env.output_trigger.as_mut().map(|l| {
l.off();
});
Ok(())
}
Token::Include(_, _namespace, _once) => {
panic!("ERROR - Should never be executed");
Ok(())
}
Token::Include(_fname, _namespace, _once) => {
panic!("ERROR - Should never be executed");
}
Token::Incbin {
fname: _,
offset: _,
length: _,
extended_offset: _,
off: _,
transformation: _
} => panic!("Error - should never be called"),
Token::If(ref _cases, ref _other) => {
panic!("Should be handled by ProcessedToken")
}
Token::Label(ref label) => env.visit_label(label),
Token::Limit(ref exp) => env.visit_limit(exp),
Token::MultiPush(ref regs) => env.visit_multi_pushes(regs),
Token::MultiPop(ref regs) => env.visit_multi_pops(regs),
Token::NoExport(ref labels) => env.visit_noexport(labels.as_slice()),
Token::Export(ref labels) => env.visit_export(labels.as_slice()),
Token::Equ(ref label, ref exp) => visit_equ(label, exp, env),
Token::Assign(ref label, ref exp, op) => visit_assign(label, exp, op.as_ref(), env),
Token::Pause => {
env.visit_pause(None);
Ok(())
}
Token::Protect(ref start, ref end) => env.visit_protect(start, end),
Token::Print(ref exp) => {
env.visit_print(exp.as_ref(), None);
Ok(())
}
Token::Fail(ref exp) => env.visit_fail(exp.as_ref().map(|v| v.as_slice())),
Token::Repeat(..) => {
panic!("Should never be called")
}
Token::Run(address, gate_array) => env.visit_run(address, gate_array.as_ref()),
Token::Rorg(ref _exp, ref _code) => panic!("Is delegated to ProcessedToken"),
Token::Save {
filename,
address,
size,
save_type,
dsk_filename,
side
} => {
env.visit_save(
filename,
address.as_ref(),
size.as_ref(),
save_type.as_ref(),
dsk_filename.as_ref(),
side.as_ref()
)
}
Token::Charset(format) => env.visit_charset(format),
Token::SnaSet(flag, value) => env.visit_snaset(flag, value),
Token::StableTicker(ref ticker) => visit_stableticker(ticker, env),
Token::Undef(ref label) => env.visit_undef(label),
Token::Macro(_name, _arguments, _code) => {
panic!("Is delegated to ProcessedToken")
}
Token::MacroCall(_name, _parameters) => panic!("Should never been called"),
Token::Struct(name, content) => env.visit_struct_definition(name, content.as_slice(), None),
Token::WaitNops(count) => env.visit_waitnops(count),
Token::Next(label, source, delta) => {
env.visit_next_and_co(label, source, delta.as_ref(), false)
}
Token::SnaInit(fname) => env.visit_snainit(fname),
Token::SetN(label, source, delta) => {
env.visit_next_and_co(label, source, delta.as_ref(), true)
}
Token::Range(name, start, stop) => env.visit_range(name, start, stop),
Token::Section(name) => env.visit_section(name),
Token::Return(exp) => env.visit_return(exp),
_ => unimplemented!("{:?}", token)
}?;
env.move_delayed_commands_of_functions();
Ok(())
}
fn visit_assert(
exp: &Expr,
txt: Option<&Vec<FormattedExpr>>,
env: &mut Env,
span: Option<Z80Span>
) -> Result<bool, AssemblerError> {
let res = match env.resolve_expr_must_never_fail(exp) {
Err(e) => Err(e),
Ok(value) => {
if !value.bool()? {
let _symbols = env.symbols();
let oper = |left: &Expr, right: &Expr, oper: &str| -> String {
let res_left = left.resolve(env).unwrap();
let res_right = right.resolve(env).unwrap();
match (&res_left, &res_right) {
(ExprResult::Char(c1), ExprResult::Char(c2)) => {
format!("['{}' {} '{}']", *c1 as char, oper, *c2 as char)
}
_ => {
format!("[{} {} {}] ", res_left, oper, res_right)
+ &format!("[0x{:x} {} 0x{:x}] ", res_left, oper, res_right)
}
}
};
let prefix = match exp {
Expr::BinaryOperation(BinaryOperation::Equal, box left, box right) => {
oper(left, right, "==")
}
Expr::BinaryOperation(BinaryOperation::LowerOrEqual, box left, box right) => {
oper(left, right, "<=")
}
Expr::BinaryOperation(BinaryOperation::GreaterOrEqual, box left, box right) => {
oper(left, right, ">=")
}
Expr::BinaryOperation(BinaryOperation::StrictlyLower, box left, box right) => {
oper(left, right, "<")
}
Expr::BinaryOperation(
BinaryOperation::StrictlyGreater,
box left,
box right
) => oper(left, right, ">"),
_ => "".to_string()
};
Err(AssemblerError::AssertionFailed {
msg: prefix
+ &if txt.is_some() {
env.build_string_from_formatted_expression(txt.unwrap())?
}
else {
"".to_owned()
},
test: exp.to_string(),
guidance: env.to_assert_string(exp)
})
}
else {
Ok(())
}
}
};
if let Err(assert_error) = res {
let assert_error = if let Some(span) = span {
assert_error.locate(span)
}
else {
assert_error
};
env.active_page_info_mut()
.add_failed_assert_command(assert_error.into());
Ok(false)
}
else {
Ok(true)
}
}
impl Env {
pub fn visit_while<
E: ExprEvaluationExt,
T: ListingElement<Expr = E> + Visited + MayHaveSpan
>(
&mut self,
cond: &E,
code: &[T],
span: Option<Z80Span>
) -> Result<(), AssemblerError> {
while self.resolve_expr_must_never_fail(cond)?.bool()? {
self.visit_listing(code).map_err(|e| {
AssemblerError::WhileIssue {
error: Box::new(e),
span: span.clone()
}
})?;
}
Ok(())
}
pub fn visit_switch<'a, T, E>(
&mut self,
value: &E,
cases: impl Iterator<Item = (&'a E, &'a [T], bool)>,
default: Option<&'a [T]>,
_span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
E: ExprEvaluationExt,
E: 'a,
T: 'a + ListingElement<Expr = E> + Visited + MayHaveSpan
{
let value = self.resolve_expr_must_never_fail(value)?;
let mut met = false;
let mut broken = false;
for (case, listing, r#break) in cases {
let case = self.resolve_expr_must_never_fail(case)?;
met |= case == value;
if met {
self.visit_listing(listing)?;
if r#break {
broken = true;
break;
}
}
}
if !met || !broken {
if let Some(default) = default {
self.visit_listing(default)?;
}
}
Ok(())
}
pub fn visit_iterate<
'token,
E: ExprEvaluationExt,
T: ListingElement<Expr = E> + Visited + MayHaveSpan + Sync
>(
&mut self,
counter_name: &str,
values: either::Either<&Vec<E>, &E>,
code: &mut [ProcessedToken<'token, T>],
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let counter_name = format!("{{{}}}", counter_name);
let counter_name = counter_name.as_str();
if self.symbols().contains_symbol(counter_name)? {
return Err(AssemblerError::RepeatIssue {
error: AssemblerError::ExpressionError(ExpressionError::OwnError(
box AssemblerError::AssemblingError {
msg: format!("Counter {} already exists", counter_name)
}
))
.into(),
span: span.cloned(),
repetition: 0
});
}
match values {
either::Either::Left(values) => {
for (i, value) in values.iter().enumerate() {
let counter_value = self.resolve_expr_must_never_fail(value).map_err(|e| {
AssemblerError::RepeatIssue {
error: Box::new(e),
span: span.cloned(),
repetition: i as _
}
})?;
self.inner_visit_repeat(
Some(counter_name),
Some(counter_value),
i as _,
code,
span
)?;
}
}
either::Either::Right(values) => {
match self.resolve_expr_must_never_fail(values)? {
ExprResult::List(values) => {
for (i, counter_value) in values.into_iter().enumerate() {
self.inner_visit_repeat(
Some(counter_name),
Some(counter_value),
i as _,
code,
span.clone()
)?;
}
}
_ => {
return Err(AssemblerError::AssemblingError {
msg: format!("REPEAT issue: {} is not a list", values)
})
}
}
}
}
self.symbols_mut().remove_symbol(counter_name)?;
Ok(())
}
pub fn visit_rorg<'token, T, E>(
&mut self,
address: &E,
code: &mut [ProcessedToken<'token, T>],
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
E: ExprEvaluationExt,
T: ListingElement<Expr = E> + Visited + MayHaveSpan + Sync,
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let address = self
.resolve_expr_must_never_fail(address)
.map_err(|error| {
match span {
Some(span) => {
AssemblerError::RelocatedError {
error: Box::new(error),
span: span.clone()
}
}
None => error
}
})?
.int()?;
{
let page_info = self.active_page_info_mut();
page_info.logical_codeadr = address as _;
}
self.update_dollar();
let value = self.active_page_info().logical_codeadr;
self.output_trigger
.as_mut()
.map(|o| o.replace_address(value.into()));
self.nested_rorg += 1; visit_processed_tokens(code, self)?;
self.nested_rorg -= 1;
let page_info = self.active_page_info_mut();
page_info.logical_codeadr = page_info.logical_outputadr;
Ok(())
}
pub fn visit_for<'token, E: ExprEvaluationExt, T>(
&mut self,
label: &str,
start: &E,
stop: &E,
step: Option<&E>,
code: &mut [ProcessedToken<'token, T>],
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
T: ListingElement<Expr = E> + Visited + MayHaveSpan + Sync,
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let counter_name = format!("{{{}}}", label);
if self.symbols().contains_symbol(&counter_name)? {
return Err(AssemblerError::ForIssue {
error: AssemblerError::ExpressionError(ExpressionError::OwnError(
box AssemblerError::AssemblingError {
msg: format!("Counter {} already exists", &counter_name)
}
))
.into(),
span: span.cloned()
});
}
let mut counter_value = self.resolve_expr_must_never_fail(start)?;
let stop = self.resolve_expr_must_never_fail(stop)?;
let step = match step {
Some(step) => self.resolve_expr_must_never_fail(step)?,
None => 1i32.into()
};
let zero = ExprResult::from(0i32);
if step == zero {
return Err(AssemblerError::ForIssue {
error: AssemblerError::ExpressionError(ExpressionError::OwnError(
box AssemblerError::AssemblingError {
msg: format!("Infinite loop")
}
))
.into(),
span: span.cloned()
});
}
if step < zero {
return Err(AssemblerError::ForIssue {
error: AssemblerError::ExpressionError(ExpressionError::OwnError(
box AssemblerError::AssemblingError {
msg: format!("Negative step is not yet handled")
}
))
.into(),
span: span.cloned()
});
}
let mut i = 1;
while counter_value <= stop {
self.inner_visit_repeat(
Some(counter_name.as_str()),
Some(counter_value.clone()),
i as _,
code,
span
)?;
counter_value = (counter_value + &step)?;
i += 1;
}
self.symbols_mut().remove_symbol(counter_name)?;
Ok(())
}
pub fn visit_repeat_until<'token, E, T>(
&mut self,
cond: &E,
code: &mut [ProcessedToken<'token, T>],
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
E: ExprEvaluationExt,
T: ListingElement<Expr = E> + Visited + MayHaveSpan + Sync,
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let mut i = 0;
loop {
i = i + 1;
self.inner_visit_repeat(None, None, i as _, code, span.clone())?;
let res = self.resolve_expr_must_never_fail(cond)?;
if res.bool()? {
break;
}
}
Ok(())
}
pub fn visit_repeat<'token, T, E>(
&mut self,
count: &E,
code: &mut [ProcessedToken<'token, T>],
counter: Option<&str>,
counter_start: Option<&E>,
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
E: ExprEvaluationExt,
T: ListingElement<Expr = E> + Visited + MayHaveSpan + Sync,
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
let count = self.resolve_expr_must_never_fail(count)?.int()?;
let counter_name = counter.as_ref().map(|counter| format!("{{{}}}", counter));
let counter_name = counter_name.as_ref().map(|s| s.as_str());
if let Some(counter_name) = counter_name {
if self.symbols().contains_symbol(counter_name)? {
return Err(AssemblerError::RepeatIssue {
error: AssemblerError::ExpressionError(ExpressionError::OwnError(
box AssemblerError::AssemblingError {
msg: format!("Counter {} already exists", counter_name)
}
))
.into(),
span: span.cloned(),
repetition: 0
});
}
}
let mut counter_value = counter_start
.map(|start| self.resolve_expr_must_never_fail(start))
.unwrap_or(Ok(REPEAT_START_VALUE.into()))?;
for i in 0..count {
self.inner_visit_repeat(
counter_name,
Some(counter_value.clone()),
i as _,
code,
span.clone()
)?;
counter_value += 1i32.into();
}
if let Some(counter_name) = counter_name {
self.symbols_mut().remove_symbol(counter_name)?;
}
Ok(())
}
fn inner_visit_repeat<'token, T: ListingElement + Visited + MayHaveSpan + Sync>(
&mut self,
counter_name: Option<&str>,
counter_value: Option<ExprResult>,
iteration: i32,
code: &mut [ProcessedToken<'token, T>],
span: Option<&Z80Span>
) -> Result<(), AssemblerError>
where
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
ExprEvaluationExt,
ProcessedToken<'token, T>: FunctionBuilder
{
{
self.macro_seed += 1;
let seed = self.macro_seed;
self.symbols_mut().push_seed(seed);
}
if let Some(counter_name) = counter_name {
self.symbols_mut()
.set_symbol_to_value(counter_name, counter_value.clone().unwrap())?;
}
visit_processed_tokens(code, self).map_err(|e| {
let e = if let AssemblerError::RelocatedError {
error: box AssemblerError::UnknownSymbol { closest: _, symbol },
span
} = &e
{
dbg!(symbol, counter_name);
if let Some(counter_name) = counter_name {
if counter_name == &format!("{{{}}}", symbol) {
AssemblerError::RelocatedError {
error: box AssemblerError::UnknownSymbol {
closest: Some(counter_name.into()),
symbol: symbol.clone()
},
span: span.clone()
}
}
else {
e
}
}
else {
e
}
}
else {
e
};
AssemblerError::RepeatIssue {
error: Box::new(e),
span: span.cloned(),
repetition: iteration as _
}
})?;
self.symbols_mut().pop_seed();
Ok(())
}
fn to_assert_string(&self, exp: &Expr) -> String {
let format = |oper, left, right| {
format!(
"0x{:x} {} 0x{:x}",
self.resolve_expr_must_never_fail(left).unwrap(),
oper,
self.resolve_expr_must_never_fail(right).unwrap(),
)
};
if exp.is_binary_operation() {
let code = match exp.binary_operation() {
BinaryOperation::Equal => Some("=="),
BinaryOperation::GreaterOrEqual => Some(">="),
BinaryOperation::StrictlyGreater => Some(">"),
BinaryOperation::StrictlyLower => Some("<"),
BinaryOperation::LowerOrEqual => Some("<="),
_ => None
};
match code {
Some(code) => {
let left = exp.arg1();
let right = exp.arg2();
format(code, left, right)
}
None => format!("0x{:x}", self.resolve_expr_must_never_fail(exp).unwrap())
}
}
else {
format!("0x{:x}", self.resolve_expr_must_never_fail(exp).unwrap())
}
}
fn visit_run(&mut self, address: &Expr, ga: Option<&Expr>) -> Result<(), AssemblerError> {
let address = self.resolve_expr_may_fail_in_first_pass(address)?.int()?;
self.output_trigger
.as_mut()
.map(|o| o.replace_address(address.into()));
if self.run_options.is_some() {
return Err(AssemblerError::RunAlreadySpecified);
}
self.sna
.set_value(cpclib_sna::SnapshotFlag::Z80_PC, address as _)?;
match ga {
None => {
self.run_options = Some((address as _, None));
}
Some(ga_expr) => {
let ga_expr = self.resolve_expr_may_fail_in_first_pass(ga_expr)?.int()?;
self.sna.set_value(SnapshotFlag::GA_RAMCFG, address as _)?;
self.run_options = Some((address as _, Some(ga_expr as _)));
}
}
Ok(())
}
}
impl Env {
pub fn inc_macro_seed(&mut self) {
self.macro_seed += 1;
}
pub fn macro_seed(&self) -> usize {
self.macro_seed
}
}
fn visit_equ(label: &str, exp: &Expr, env: &mut Env) -> Result<(), AssemblerError> {
if env.symbols().contains_symbol(label)? && env.pass.is_first_pass() {
Err(AssemblerError::AlreadyDefinedSymbol {
symbol: label.into(),
kind: env.symbols().kind(label)?.into()
})
}
else {
let value = env.resolve_expr_may_fail_in_first_pass(exp)?;
env.output_trigger
.as_mut()
.map(|o| o.replace_address(value.clone()));
env.add_symbol_to_symbol_table(label, value)
}
}
fn visit_assign(
label: &str,
exp: &Expr,
op: Option<&BinaryOperation>,
env: &mut Env
) -> Result<(), AssemblerError> {
let value = if let Some(op) = op {
let new_exp = Expr::BinaryOperation(*op, box Expr::Label(label.into()), box exp.clone());
env.resolve_expr_must_never_fail(&new_exp)?
}
else {
env.resolve_expr_may_fail_in_first_pass(exp)?
};
env.output_trigger
.as_mut()
.map(|o| o.replace_address(value.clone()));
env.symbols_mut().assign_symbol_to_value(label, value)?;
Ok(())
}
fn visit_defs(token: &Token, env: &mut Env) -> Result<(), AssemblerError> {
match token {
Token::Defs(l) => {
for (e, f) in l.iter() {
let bytes = assemble_defs_item(e, f.as_ref(), env)?;
env.output_bytes(&bytes)?;
}
Ok(())
}
_ => unreachable!()
}
}
fn visit_end(_env: &mut Env) -> Result<(), AssemblerError> {
eprintln!("END directive is not implemented");
Ok(())
}
pub enum DbLikeKind {
Defb,
Defw,
Str
}
impl From<&Token> for DbLikeKind {
fn from(token: &Token) -> Self {
match token {
Token::Defb(..) => Self::Defb,
Token::Defw(..) => Self::Defw,
Token::Str(..) => Self::Str,
_ => unreachable!()
}
}
}
impl DbLikeKind {
fn mask(&self) -> u16 {
match self {
DbLikeKind::Defb => 0xFF,
DbLikeKind::Defw => 0xFFFF,
DbLikeKind::Str => 0xFF
}
}
}
pub fn visit_db_or_dw_or_str<E: ExprEvaluationExt + ExprElement>(
kind: DbLikeKind,
exprs: &[E],
env: &mut Env
) -> Result<(), AssemblerError> {
let mask = kind.mask();
let output = |env: &mut Env, val: i32, mask: u16| -> Result<(), AssemblerError> {
if mask == 0xFF {
env.output(val as u8)?;
}
else {
let high = ((val & 0xFF00) >> 8) as u8;
let low = (val & 0xFF) as u8;
env.output(low)?;
env.output(high)?;
}
Ok(())
};
let output_expr_result = |env: &mut Env, expr: ExprResult, mask: u16| {
match &expr {
ExprResult::Float(_)
| ExprResult::Value(_)
| ExprResult::Bool(_)
| ExprResult::Char(_) => output(env, expr.int()?, mask),
ExprResult::String(s) => {
for c in s.chars() {
output(env, c as _, mask)?;
}
Ok(())
}
ExprResult::List(l) => {
for c in l {
output(env, c.int()?, mask)?;
}
Ok(())
}
ExprResult::Matrix { .. } => {
for row in expr.matrix_rows() {
for c in row.list_content() {
output(env, c.int()?, mask)?;
}
}
Ok(())
}
}
};
let backup_address = env.logical_output_address();
for exp in exprs.iter() {
if exp.is_string() {
let s = exp.string();
let bytes = env.charset_encoding.transform_string(s);
for b in &bytes {
output(env, *b as _, mask)?
}
env.update_dollar();
}
else if exp.is_char() {
let c = exp.char();
let b = env.charset_encoding.transform_char(c);
output(env, b as _, mask)?;
env.update_dollar();
}
else {
let val = env.resolve_expr_may_fail_in_first_pass(exp)?;
output_expr_result(env, val, mask)?;
env.update_dollar();
}
}
if matches!(kind, DbLikeKind::Str) && backup_address < env.logical_output_address() {
let last_address = env.logical_output_address() - 1;
let last_address = env.logical_to_physical_address(last_address as _);
let last_value = env.peek(&last_address);
env.poke(last_value | 0x80, &last_address);
}
Ok(())
}
impl Env {
fn move_delayed_commands_of_functions(&mut self) {
{
let prints = self.extra_print_from_function.read().unwrap().clone();
for print in prints.into_iter() {
self.active_page_info_mut()
.add_print_or_pause_command(print);
}
self.extra_print_from_function.write().unwrap().clear();
}
{
let asserts = self
.extra_failed_assert_from_function
.read()
.unwrap()
.clone();
for assert in asserts.into_iter() {
self.active_page_info_mut()
.add_failed_assert_command(assert);
}
self.extra_failed_assert_from_function
.write()
.unwrap()
.clear();
}
}
}
#[allow(missing_docs)]
impl Env {
pub fn visit_basic<S: Borrow<str> + Display>(
&mut self,
variables: Option<&Vec<S>>,
hidden_lines: Option<&Vec<u16>>,
code: &str
) -> Result<(), AssemblerError> {
let bytes = self.assemble_basic(variables, hidden_lines, code)?;
if self.start_address().is_none() {
self.active_page_info_mut().logical_outputadr = 0x170;
self.active_page_info_mut().logical_codeadr = self.logical_output_address();
self.active_page_info_mut().startadr = Some(self.logical_output_address());
self.output_address = 0x170;
}
self.output_bytes(&bytes)
}
pub fn assemble_basic<S: Borrow<str> + Display>(
&mut self,
variables: Option<&Vec<S>>,
hidden_lines: Option<&Vec<u16>>,
code: &str
) -> Result<Vec<u8>, AssemblerError> {
let basic_src = {
let mut basic = code.to_owned();
match variables {
None => {}
Some(arguments) => {
for argument in arguments {
let key = format!("{{{}}}", argument);
let value = format!(
"&{:X}",
self.resolve_expr_may_fail_in_first_pass(&Expr::from(
argument.borrow()
))?
);
basic = basic.replace(&key, &value);
}
}
}
basic
};
let mut basic = BasicProgram::parse(basic_src)?;
if hidden_lines.is_some() {
basic.hide_lines(hidden_lines.unwrap())?;
}
Ok(basic.as_bytes())
}
}
pub fn visit_repeat(rept: &Token, env: &mut Env) -> Result<(), AssemblerError> {
let tokens = rept.unroll(env).unwrap()?;
for token in &tokens {
visit_token(token, env)?;
}
Ok(())
}
#[allow(clippy::cast_possible_wrap)]
pub fn visit_stableticker(
ticker: &StableTickerAction,
env: &mut Env
) -> Result<(), AssemblerError> {
match ticker {
StableTickerAction::Start(ref name) => {
env.stable_counters.add_counter(name)?;
Ok(())
}
StableTickerAction::Stop => {
match env.stable_counters.release_last_counter() {
None => Err(AssemblerError::NoActiveCounter),
Some((label, count)) => env.add_symbol_to_symbol_table(&label, count)
}
}
}
}
pub fn assemble_defs_item(
expr: &Expr,
fill: Option<&Expr>,
env: &mut Env
) -> Result<Bytes, AssemblerError> {
let count = match env.resolve_expr_must_never_fail(expr) {
Ok(amount) => amount.int()?,
Err(e) => {
env.add_error_discardable_one_pass(e)?;
*env.request_additional_pass.write().unwrap() = true; 0.into()
}
};
let value = if fill.is_none() {
0
}
else {
let value = env
.resolve_expr_may_fail_in_first_pass(fill.unwrap())?
.int()?;
(value & 0xFF) as u8
};
let mut bytes = Bytes::with_capacity(count as usize);
bytes.resize_with(count as _, || value);
Ok(bytes)
}
pub fn assemble_align(
expr: &Expr,
fill: Option<&Expr>,
env: &Env
) -> Result<Bytes, AssemblerError> {
let expression = env.resolve_expr_must_never_fail(expr)?.int()? as u16;
let current = env.symbols().current_address()?;
let value = if fill.is_none() {
0
}
else {
let value = env
.resolve_expr_may_fail_in_first_pass(fill.unwrap())?
.int()?;
(value & 0xFF) as u8
};
let mut until = current;
while until % expression != 0 {
until += 1;
}
let hole = (until - current) as usize;
let mut bytes = Bytes::with_capacity(hole);
for _i in 0..hole {
bytes.push(value);
}
Ok(bytes)
}
pub(crate) fn visit_opcode(
mnemonic: Mnemonic,
arg1: &Option<DataAccess>,
arg2: &Option<DataAccess>,
arg3: &Option<Register8>,
env: &mut Env
) -> Result<(), AssemblerError> {
let bytes = assemble_opcode(mnemonic, arg1, arg2, arg3, env)?;
for b in bytes.iter() {
env.output(*b)?;
}
Ok(())
}
pub fn assemble_opcode(
mnemonic: Mnemonic,
arg1: &Option<DataAccess>,
arg2: &Option<DataAccess>,
arg3: &Option<Register8>,
env: &mut Env
) -> Result<Bytes, AssemblerError> {
match mnemonic {
Mnemonic::And | Mnemonic::Or | Mnemonic::Xor => {
assemble_logical_operator(mnemonic, arg1.as_ref().unwrap(), env)
}
Mnemonic::Add | Mnemonic::Adc => {
assemble_add_or_adc(
mnemonic,
arg1.as_ref().unwrap(),
arg2.as_ref().unwrap(),
env
)
}
Mnemonic::Cp => env.assemble_cp(arg1.as_ref().unwrap()),
Mnemonic::ExMemSp => assemble_ex_memsp(arg1.as_ref().unwrap()),
Mnemonic::Dec | Mnemonic::Inc => assemble_inc_dec(mnemonic, arg1.as_ref().unwrap(), env),
Mnemonic::Djnz => assemble_djnz(arg1.as_ref().unwrap(), env),
Mnemonic::In => assemble_in(arg1.as_ref().unwrap(), &arg2.as_ref().unwrap(), env),
Mnemonic::Ld => assemble_ld(arg1.as_ref().unwrap(), &arg2.as_ref().unwrap(), env),
Mnemonic::Ldi
| Mnemonic::Ldd
| Mnemonic::Ldir
| Mnemonic::Lddr
| Mnemonic::Outi
| Mnemonic::Outd
| Mnemonic::Ei
| Mnemonic::Di
| Mnemonic::ExAf
| Mnemonic::ExHlDe
| Mnemonic::Exx
| Mnemonic::Halt
| Mnemonic::Ind
| Mnemonic::Indr
| Mnemonic::Ini
| Mnemonic::Inir
| Mnemonic::Rla
| Mnemonic::Rlca
| Mnemonic::Rrca
| Mnemonic::Rra
| Mnemonic::Reti
| Mnemonic::Retn
| Mnemonic::Scf
| Mnemonic::Ccf
| Mnemonic::Cpd
| Mnemonic::Cpdr
| Mnemonic::Cpi
| Mnemonic::Cpir
| Mnemonic::Cpl
| Mnemonic::Daa
| Mnemonic::Neg
| Mnemonic::Otdr
| Mnemonic::Otir
| Mnemonic::Rld
| Mnemonic::Rrd => assemble_no_arg(mnemonic),
Mnemonic::Out => assemble_out(arg1.as_ref().unwrap(), &arg2.as_ref().unwrap(), env),
Mnemonic::Jr | Mnemonic::Jp | Mnemonic::Call => {
assemble_call_jr_or_jp(mnemonic, arg1.as_ref(), arg2.as_ref().unwrap(), env)
}
Mnemonic::Pop => assemble_pop(arg1.as_ref().unwrap()),
Mnemonic::Push => assemble_push(arg1.as_ref().unwrap()),
Mnemonic::Bit | Mnemonic::Res | Mnemonic::Set => {
assemble_bit_res_or_set(
mnemonic,
arg1.as_ref().unwrap(),
arg2.as_ref().unwrap(),
arg3.as_ref(),
env
)
}
Mnemonic::Ret => assemble_ret(arg1),
Mnemonic::Rst => assemble_rst(arg1.as_ref().unwrap(), env),
Mnemonic::Im => assemble_im(arg1.as_ref().unwrap(), env),
Mnemonic::Nop => env.assemble_nop(Mnemonic::Nop, arg1.as_ref().map(|v| v.expr().unwrap())),
Mnemonic::Nop2 => env.assemble_nop(Mnemonic::Nop2, None),
Mnemonic::Sub => env.assemble_sub(arg1.as_ref().unwrap()),
Mnemonic::Sbc => env.assemble_sbc(arg1.as_ref().unwrap(), arg2.as_ref().unwrap()),
Mnemonic::Sla
| Mnemonic::Sra
| Mnemonic::Srl
| Mnemonic::Sl1
| Mnemonic::Rl
| Mnemonic::Rr
| Mnemonic::Rlc
| Mnemonic::Rrc => env.assemble_shift(mnemonic, arg1.as_ref().unwrap(), arg2.as_ref())
}
}
fn visit_org(address: &Expr, address2: Option<&Expr>, env: &mut Env) -> Result<(), AssemblerError> {
let code_adr = if address2.is_none() && address == &"$".into() {
if env.start_address().is_none() {
return Err(AssemblerError::InvalidArgument {
msg: "ORG: $ cannot be used now".into()
});
}
env.logical_output_address() as i32
}
else {
env.resolve_expr_must_never_fail(address)?.int()?
};
let output_adr = if address2.is_some() {
env.resolve_expr_must_never_fail(address2.unwrap())?.int()?
}
else {
code_adr.clone()
};
let page_info = env.active_page_info_mut();
page_info.logical_outputadr = output_adr as _;
page_info.logical_codeadr = code_adr as _;
page_info.fail_next_write_if_zero = false;
env.active_page_info_mut().startadr = match env.start_address() {
Some(val) => val.min(env.logical_output_address()),
None => env.logical_output_address()
}
.into();
env.output_address = output_adr as _;
assert_eq!(env.logical_output_address(), env.output_address);
env.update_dollar();
Ok(())
}
fn assemble_no_arg(mnemonic: Mnemonic) -> Result<Bytes, AssemblerError> {
let bytes: &[u8] = match mnemonic {
Mnemonic::Ldi => &[0xED, 0xA0],
Mnemonic::Ldd => &[0xED, 0xA8],
Mnemonic::Lddr => &[0xED, 0xB8],
Mnemonic::Ldir => &[0xED, 0xB0],
Mnemonic::Di => &[0xF3],
Mnemonic::ExAf => &[0x08],
Mnemonic::ExHlDe => &[0xEB],
Mnemonic::Exx => &[0xD9],
Mnemonic::Ei => &[0xFB],
Mnemonic::Halt => &[0x76],
Mnemonic::Ind => &[0xED, 0xAA],
Mnemonic::Indr => &[0xED, 0xBA],
Mnemonic::Ini => &[0xED, 0xA2],
Mnemonic::Inir => &[0xED, 0xB2],
Mnemonic::Outd => &[0xED, 0xAB],
Mnemonic::Outi => &[0xED, 0xA3],
Mnemonic::Rla => &[0x17],
Mnemonic::Rlca => &[0x07],
Mnemonic::Rrca => &[0x0F],
Mnemonic::Rra => &[0x1F],
Mnemonic::Reti => &[0xED, 0x4D],
Mnemonic::Retn => &[0xED, 0x45],
Mnemonic::Scf => &[0x37],
Mnemonic::Ccf => &[0x3F],
Mnemonic::Cpd => &[0xED, 0xA9],
Mnemonic::Cpdr => &[0xED, 0xB9],
Mnemonic::Cpi => &[0xED, 0xA1],
Mnemonic::Cpir => &[0xED, 0xB1],
Mnemonic::Cpl => &[0x2F],
Mnemonic::Daa => &[0x27],
Mnemonic::Neg => &[0xED, 0x44],
Mnemonic::Otdr => &[0xED, 0xBB],
Mnemonic::Otir => &[0xED, 0xB3],
Mnemonic::Rld => &[0xED, 0x6F],
Mnemonic::Rrd => &[0xED, 0x67],
_ => {
return Err(AssemblerError::BugInAssembler {
msg: format!("{} not treated", mnemonic)
});
}
};
Ok(Bytes::from_slice(bytes))
}
fn assemble_inc_dec(mne: Mnemonic, arg1: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let is_inc = match mne {
Mnemonic::Inc => true,
Mnemonic::Dec => false,
_ => panic!("Impossible case")
};
match arg1 {
DataAccess::Register16(ref reg) => {
let base = if is_inc { 0b0000_0011 } else { 0b0000_1011 };
let byte = base | (register16_to_code_with_sp(*reg) << 4);
bytes.push(byte);
}
DataAccess::IndexRegister16(ref reg) => {
bytes.push(indexed_register16_to_code(*reg));
bytes.push(if is_inc { 0x23 } else { 0x2B });
}
DataAccess::Register8(ref reg) => {
bytes.push(
if is_inc { 0b0000_0100 } else { 0b0000_0101 } | (register8_to_code(*reg) << 3)
);
}
DataAccess::IndexRegister8(ref reg) => {
bytes.push(indexed_register16_to_code(reg.complete()));
bytes.push(
if is_inc { 0b0000_0100 } else { 0b0000_0101 }
| (indexregister8_to_code(*reg) << 3)
);
}
DataAccess::MemoryRegister16(Register16::Hl) => {
bytes.push(if is_inc { 0x34 } else { 0x35 });
}
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(indexed_register16_to_code(*reg));
bytes.push(if is_inc { 0x34 } else { 0x35 });
bytes.push(val);
}
_ => {
return Err(AssemblerError::BugInAssembler {
msg: format!(
"{}: not implemented for {:?}",
mne.to_string().to_owned(),
arg1
)
});
}
}
Ok(bytes)
}
pub fn absolute_to_relative<T: AsRef<SymbolsTable>>(
address: i32,
opcode_delta: i32,
sym: T
) -> Result<u8, AssemblerError> {
match sym.as_ref().current_address() {
Err(_msg) => Err(AssemblerError::UnknownAssemblingAddress),
Ok(root) => {
let delta = (address - i32::from(root)) - opcode_delta;
if delta > 128 || delta < -127 {
Err(AssemblerError::InvalidArgument {
msg: format!(
"Address 0x{:x} relative to 0x{:x} is too far {}",
address, root, delta
)
})
}
else {
let res = (delta & 0xFF) as u8;
Ok(res)
}
}
}
}
fn assemble_ret(arg1: &Option<DataAccess>) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if arg1.is_some() {
if let Some(&DataAccess::FlagTest(ref test)) = arg1.as_ref() {
let flag = flag_test_to_code(*test);
bytes.push(0b1100_0000 | (flag << 3));
}
else {
return Err(AssemblerError::InvalidArgument {
msg: format!("RET: wrong argument for ret")
});
}
}
else {
bytes.push(0xC9);
};
Ok(bytes)
}
fn assemble_rst(arg1: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let val = env
.resolve_expr_may_fail_in_first_pass(arg1.get_expression().unwrap())?
.int()?;
let p = match val {
0x00 => 0b000,
0x08 => 0b001,
0x10 => 0b010,
0x18 => 0b011,
0x20 => 0b100,
0x28 => 0b101,
0x30 => 0b110,
0x38 => 0b111,
10 => 0b010,
18 => 0b011,
20 => 0b100,
28 => 0b101,
30 => 0b110,
38 => 0b111,
_ => {
return Err(AssemblerError::InvalidArgument {
msg: format!("RST cannot take {} as argument.", val)
})
}
};
bytes.push(0b11000111 | p << 3);
Ok(bytes)
}
fn assemble_im(arg1: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let val = env
.resolve_expr_may_fail_in_first_pass(arg1.get_expression().unwrap())?
.int()?;
let code = match val {
0x00 => 0x46,
0x01 => 0x56,
0x02 => 0x5E,
_ => {
return Err(AssemblerError::InvalidArgument {
msg: format!("IM cannot take {} as argument.", val)
})
}
};
bytes.push(0xED);
bytes.push(code);
Ok(bytes)
}
pub fn assemble_call_jr_or_jp(
mne: Mnemonic,
arg1: Option<&DataAccess>,
arg2: &DataAccess,
env: &mut Env
) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let is_jr = match mne {
Mnemonic::Jr => true,
Mnemonic::Jp | Mnemonic::Call => false,
_ => unreachable!()
};
let is_call = match mne {
Mnemonic::Call => true,
Mnemonic::Jp | Mnemonic::Jr => false,
_ => unreachable!()
};
let is_jp = !(is_call || is_jr);
let flag_code = if arg1.is_some() {
match arg1.as_ref() {
Some(DataAccess::FlagTest(ref test)) => Some(flag_test_to_code(*test)),
_ => {
return Err(AssemblerError::InvalidArgument {
msg: format!(
"{}: wrong flag argument",
mne.to_string().to_ascii_uppercase()
)
})
}
}
}
else {
None
};
if let DataAccess::Expression(ref e) = arg2 {
let address = env.resolve_expr_may_fail_in_first_pass(e)?.int()?;
if is_jr {
let relative = if e.is_relative() {
address as u8
}
else {
env.absolute_to_relative_may_fail_in_first_pass(address, 2)? as u8
};
if flag_code.is_some() {
add_byte(&mut bytes, 0b0010_0000 | (flag_code.unwrap() << 3));
}
else {
add_byte(&mut bytes, 0b0001_1000);
}
add_byte(&mut bytes, relative);
}
else if is_call {
match flag_code {
Some(flag) => add_byte(&mut bytes, 0b1100_0100 | (flag << 3)),
None => add_byte(&mut bytes, 0xCD)
}
add_word(&mut bytes, address as u16);
}
else {
if flag_code.is_some() {
add_byte(&mut bytes, 0b1100_0010 | (flag_code.unwrap() << 3))
}
else {
add_byte(&mut bytes, 0xC3);
}
add_word(&mut bytes, address as u16);
}
env.track_used_symbols(e);
}
else if let DataAccess::MemoryRegister16(Register16::Hl) = arg2 {
assert!(is_jp);
add_byte(&mut bytes, 0xE9);
}
else if let DataAccess::MemoryIndexRegister16(ref reg) = arg2 {
assert!(is_jp);
add_byte(&mut bytes, indexed_register16_to_code(*reg));
add_byte(&mut bytes, 0xE9);
}
else {
return Err(AssemblerError::BugInAssembler {
msg: format!("{}: parameter {:?} not treated", mne, arg2)
});
}
Ok(bytes)
}
fn assemble_djnz(arg1: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
if let DataAccess::Expression(ref expr) = arg1 {
let mut bytes = Bytes::new();
let address = env.resolve_expr_may_fail_in_first_pass(expr)?.int()?;
let relative = if expr.is_relative() {
address as u8
}
else {
env.absolute_to_relative_may_fail_in_first_pass(address, 1 + 1)? as u8
};
bytes.push(0x10);
bytes.push(relative);
Ok(bytes)
}
else {
unreachable!()
}
}
#[allow(missing_docs)]
impl Env {
pub fn assemble_cp(&mut self, arg: &DataAccess) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
match arg {
DataAccess::Register8(ref reg) => {
add_byte(&mut bytes, 0b1011_1000 + register8_to_code(*reg));
}
DataAccess::IndexRegister8(ref reg) => {
add_byte(&mut bytes, indexed_register16_to_code(reg.complete()));
add_byte(&mut bytes, 0b1011_1000 + indexregister8_to_code(*reg));
}
DataAccess::Expression(ref exp) => {
add_byte(&mut bytes, 0xFE);
add_byte(
&mut bytes,
self.resolve_expr_may_fail_in_first_pass(exp)?.int()? as _
);
}
DataAccess::MemoryRegister16(Register16::Hl) => {
add_byte(&mut bytes, 0xBE);
}
DataAccess::IndexRegister16WithIndex(ref reg, ref idx) => {
add_byte(&mut bytes, indexed_register16_to_code(*reg));
add_byte(&mut bytes, 0xBE);
add_byte(
&mut bytes,
self.resolve_expr_may_fail_in_first_pass(idx)?.int()? as _
);
}
_ => unreachable!()
}
Ok(bytes)
}
pub fn assemble_sub(&mut self, arg: &DataAccess) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
match arg {
DataAccess::Expression(ref exp) => {
let val = (self.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0xD6);
bytes.push(val);
}
DataAccess::Register8(ref reg) => {
bytes.push(0b10010000 + (register8_to_code(*reg)));
}
DataAccess::IndexRegister8(ref reg) => {
bytes.push(indexed_register16_to_code(reg.complete()));
bytes.push(0b10010000 + (indexregister8_to_code(*reg)));
}
DataAccess::MemoryRegister16(Register16::Hl) => {
bytes.push(0x96);
}
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
let val = (self.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(indexed_register16_to_code(*reg));
bytes.push(0x96);
bytes.push(val);
}
_ => {
unreachable!();
}
}
Ok(bytes)
}
pub fn assemble_sbc(
&mut self,
arg1: &DataAccess,
arg2: &DataAccess
) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if arg1.is_register_a() {
match arg2 {
DataAccess::Register8(ref reg) => {
bytes.push(0b10011000 + register8_to_code(*reg));
}
DataAccess::IndexRegister8(ref reg) => {
bytes.push(indexed_register16_to_code(reg.complete()));
bytes.push(0b10011000 + indexregister8_to_code(*reg));
}
DataAccess::Expression(ref exp) => {
let val = self.resolve_expr_may_fail_in_first_pass(exp)?.int()? as u8;
bytes.push(0xDE);
bytes.push(val);
}
DataAccess::MemoryRegister16(Register16::Hl) => {
bytes.push(0x9E);
}
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
bytes.push(indexed_register16_to_code(*reg));
bytes.push(0x9E);
let val = self.resolve_expr_may_fail_in_first_pass(exp)?.int()? as u8;
bytes.push(val);
}
_ => unreachable!()
}
}
else {
assert!(arg1.is_register_hl());
match arg2 {
DataAccess::Register16(ref reg) => {
bytes.push(0xED);
bytes.push(0b0100_0010 | register16_to_code_with_sp(*reg) << 4);
}
_ => unreachable!()
}
}
Ok(bytes)
}
pub fn assemble_shift(
&mut self,
mne: Mnemonic,
target: &DataAccess,
hidden: Option<&DataAccess>
) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if let DataAccess::Register8(ref reg) = target {
add_byte(&mut bytes, 0xCB);
let byte = if mne.is_sla() {
0b0010_0000
}
else if mne.is_sra() {
0b0010_1000
}
else if mne.is_srl() {
0b0011_1000
}
else if mne.is_rlc() {
0b0000_0000
}
else if mne.is_rrc() {
0b0000_1000
}
else if mne.is_rl() {
0b0001_0000
}
else if mne.is_rr() {
0b0001_1000
}
else if mne.is_sl1() {
0b0011_0000
}
else {
unreachable!()
} + register8_to_code(*reg);
add_byte(&mut bytes, byte);
}
else {
assert!(match target {
DataAccess::MemoryRegister16(Register16::Hl) => true,
DataAccess::IndexRegister16WithIndex(..) => true,
_ => false
});
match target {
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
let val = self.resolve_expr_may_fail_in_first_pass(exp)?.int()? as u8;
bytes.push(indexed_register16_to_code(*reg));
add_byte(&mut bytes, 0xCB);
bytes.push(val);
}
DataAccess::MemoryRegister16(Register16::Hl) => {
add_byte(&mut bytes, 0xCB);
}
_ => {
return Err(AssemblerError::InvalidArgument {
msg: format!("{} cannot take {} as argument", mne, target)
})
}
};
let mut byte: u8 = if mne.is_sla() {
0x26
}
else if mne.is_sra() {
0x2E
}
else if mne.is_srl() {
0x3E
}
else if mne.is_rlc() {
0x06
}
else if mne.is_rrc() {
0x0E
}
else if mne.is_rl() {
0x16
}
else if mne.is_rr() {
0x1E
}
else if mne.is_sl1() {
0x36
}
else {
unreachable!()
};
if hidden.is_some() {
let delta: i8 = match hidden.unwrap().get_register8().unwrap() {
Register8::A => 1,
Register8::L => -1,
Register8::H => -2,
Register8::E => -3,
Register8::D => -4,
Register8::C => -5,
Register8::B => -6
};
if delta < 0 {
byte -= delta.abs() as u8;
}
else {
byte += delta as u8;
}
}
bytes.push(byte);
}
Ok(bytes)
}
}
fn assemble_ld(arg1: &DataAccess, arg2: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if let DataAccess::Register8(ref dst) = arg1 {
let dst = register8_to_code(*dst);
match arg2 {
DataAccess::Register8(ref src) => {
let src = register8_to_code(*src);
let code = 0b0100_0000 + (dst << 3) + src;
bytes.push(code);
}
DataAccess::IndexRegister8(ref src) => {
bytes.push(indexed_register16_to_code(src.complete()));
let src = indexregister8_to_code(*src);
let code = 0b0100_0000 + (dst << 3) + src;
bytes.push(code);
}
DataAccess::Expression(ref exp) => {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0b0000_0110 | (dst << 3));
bytes.push(val);
}
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
let val = env.resolve_expr_may_fail_in_first_pass(exp)?.int()?;
add_index_register_code(&mut bytes, *reg);
add_byte(&mut bytes, 0b0100_0110 | (dst << 3));
add_index(&mut bytes, val)?;
}
DataAccess::MemoryRegister16(Register16::Hl) => {
add_byte(&mut bytes, 0b0100_0110 | (dst << 3));
}
DataAccess::MemoryIndexRegister16(reg) => {
add_index_register_code(&mut bytes, *reg);
add_byte(&mut bytes, 0b0100_0110 | (dst << 3));
}
DataAccess::MemoryRegister16(ref memreg) if arg1.is_register_a() => {
let byte = match memreg {
Register16::Bc => 0x0A,
Register16::De => 0x1A,
_ => unreachable!()
};
add_byte(&mut bytes, byte);
}
DataAccess::Memory(ref expr) => {
let val = env.resolve_expr_may_fail_in_first_pass(expr)?.int()?;
add_byte(&mut bytes, 0x3A);
add_word(&mut bytes, val as _);
}
DataAccess::SpecialRegisterI => {
assert!(arg1.is_register_a());
bytes.push(0xED);
bytes.push(0x57);
}
DataAccess::SpecialRegisterR => {
assert!(arg1.is_register_a());
bytes.push(0xED);
bytes.push(0x5F);
}
_ => {
return Err(AssemblerError::BugInAssembler {
msg: format!("LD: not properly implemented for '{:?}, {:?}'", arg1, arg2)
});
}
}
}
else if let DataAccess::Register16(ref dst) = arg1 {
let dst_code = register16_to_code_with_sp(*dst);
match arg2 {
DataAccess::Expression(ref exp) => {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFFFF) as u16;
add_byte(&mut bytes, 0b0000_0001 | (dst_code << 4));
add_word(&mut bytes, val);
}
DataAccess::Register16(Register16::Hl) if dst.is_sp() => {
add_byte(&mut bytes, 0xF9);
}
DataAccess::IndexRegister16(ref reg) if dst.is_sp() => {
add_byte(&mut bytes, indexed_register16_to_code(*reg));
add_byte(&mut bytes, 0xF9);
}
DataAccess::Register16(ref src) => {
println!("{:?}, {:?}", dst.split(), src.split());
let bytes_high = assemble_ld(
&DataAccess::Register8(dst.high().unwrap()),
&DataAccess::Register8(src.high().unwrap()),
env
)
.unwrap();
let bytes_low = assemble_ld(
&DataAccess::Register8(dst.low().unwrap()),
&DataAccess::Register8(src.low().unwrap()),
env
)
.unwrap();
bytes.extend_from_slice(&bytes_low);
bytes.extend_from_slice(&bytes_high);
}
DataAccess::Memory(ref expr) => {
let val = (env.resolve_expr_may_fail_in_first_pass(expr)?.int()? & 0xFFFF) as u16;
if let Register16::Hl = dst {
add_byte(&mut bytes, 0x2A);
add_word(&mut bytes, val);
}
else {
add_byte(&mut bytes, 0xED);
add_byte(
&mut bytes,
(register16_to_code_with_sp(*dst) << 4) + 0b0100_1011
);
add_word(&mut bytes, val);
}
}
_ => {}
}
}
else if let DataAccess::IndexRegister8(ref dst) = arg1 {
add_byte(&mut bytes, indexed_register16_to_code(dst.complete()));
match arg2 {
DataAccess::Expression(ref exp) => {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0b0000_0110 | (indexregister8_to_code(*dst) << 3));
bytes.push(val);
}
DataAccess::Register8(ref src) => {
let code = register8_to_code(*src);
let code = if dst.is_high() {
0b0110_0000 + code
}
else {
0x68 + code
};
bytes.push(code);
}
DataAccess::IndexRegister8(ref src) => {
assert_eq!(dst.complete(), src.complete());
let byte = match (dst.is_low(), src.is_low()) {
(false, false) => 0x64,
(false, true) => 0x65,
(true, false) => 0x6C,
(true, true) => 0x6D
};
bytes.push(byte)
}
_ => unreachable!()
}
}
else if let DataAccess::IndexRegister16(ref dst) = arg1 {
let code = indexed_register16_to_code(*dst);
match arg2 {
DataAccess::Expression(ref exp) => {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFFFF) as u16;
add_byte(&mut bytes, code);
add_byte(&mut bytes, 0x21);
add_word(&mut bytes, val);
}
DataAccess::Memory(ref exp) => {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFFFF) as u16;
add_byte(&mut bytes, code);
add_byte(&mut bytes, 0x2A);
add_word(&mut bytes, val);
}
_ => {}
}
}
else if let DataAccess::MemoryRegister16(ref dst) = arg1 {
match dst {
Register16::Hl => {
if let DataAccess::Register8(ref src) = arg2 {
let src = register8_to_code(*src);
let code = 0b0111_0000 | src;
bytes.push(code);
}
else if let DataAccess::Expression(ref exp) = arg2 {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0x36);
bytes.push(val);
}
}
Register16::De if DataAccess::Register8(Register8::A) == *arg2 => {
bytes.push(0b0001_0010);
}
Register16::Bc if DataAccess::Register8(Register8::A) == *arg2 => {
bytes.push(0b0000_0010);
}
_ => {}
}
}
else if let DataAccess::MemoryIndexRegister16(ref dst) = arg1 {
add_index_register_code(&mut bytes, *dst);
add_byte(&mut bytes, indexed_register16_to_code(*dst));
if let DataAccess::Register8(ref src) = arg2 {
let src = register8_to_code(*src);
let code = 0b0111_0000 | src;
bytes.push(code);
}
else if let DataAccess::Expression(ref exp) = arg2 {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0x36);
bytes.push(val);
}
}
else if let DataAccess::IndexRegister16WithIndex(ref reg, ref exp) = arg1 {
add_byte(&mut bytes, indexed_register16_to_code(*reg));
let delta = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
match arg2 {
DataAccess::Expression(ref exp) => {
let value = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
add_byte(&mut bytes, 0x36);
add_byte(&mut bytes, delta);
add_byte(&mut bytes, value);
}
DataAccess::Register8(ref src) => {
add_byte(&mut bytes, 0x70 + register8_to_code(*src));
add_byte(&mut bytes, delta);
}
_ => {
bytes.clear();
}
}
}
else if let DataAccess::Memory(ref exp) = arg1 {
let address = env.resolve_expr_may_fail_in_first_pass(exp)?.int()?;
match arg2 {
DataAccess::IndexRegister16(IndexRegister16::Ix) => {
bytes.push(0xDD);
bytes.push(0b0010_0010);
add_word(&mut bytes, address as _);
}
DataAccess::IndexRegister16(IndexRegister16::Iy) => {
bytes.push(0xFD);
bytes.push(0b0010_0010);
add_word(&mut bytes, address as _);
}
DataAccess::Register16(Register16::Hl) => {
bytes.push(0b0010_0010);
add_word(&mut bytes, address as _);
}
DataAccess::Register16(ref reg) => {
bytes.push(0xED);
bytes.push(0b0100_0011 | (register16_to_code_with_sp(*reg) << 4));
add_word(&mut bytes, address as _);
}
DataAccess::Register8(Register8::A) => {
bytes.push(0x32);
add_word(&mut bytes, address as _);
}
_ => {}
}
}
else if let DataAccess::SpecialRegisterI = arg1 {
if let DataAccess::Register8(Register8::A) = arg2 {
bytes.push(0xED);
bytes.push(0x47)
}
else {
unreachable!();
}
}
else if let DataAccess::SpecialRegisterR = arg1 {
if let DataAccess::Register8(Register8::A) = arg2 {
bytes.push(0xED);
bytes.push(0x4F)
}
else {
unreachable!();
}
}
if bytes.is_empty() {
match (arg1, arg2) {
(DataAccess::Register16(dst), DataAccess::Register16(src)) => {
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.low().unwrap()),
&DataAccess::Register8(src.low().unwrap()),
env
)?
.iter()
.cloned()
);
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.high().unwrap()),
&DataAccess::Register8(src.high().unwrap()),
env
)?
.iter()
.cloned()
);
}
(DataAccess::Register16(Register16::Hl), DataAccess::IndexRegister16(_))
| (DataAccess::IndexRegister16(_), DataAccess::Register16(Register16::Hl))
| (DataAccess::IndexRegister16(_), DataAccess::IndexRegister16(_)) => {
bytes.extend(assemble_push(arg2)?);
bytes.extend(assemble_pop(arg1)?);
}
(DataAccess::Register16(dst), DataAccess::IndexRegister16(src)) => {
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.low().unwrap()),
&DataAccess::IndexRegister8(src.low()),
env
)?
.iter()
.cloned()
);
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.high().unwrap()),
&DataAccess::IndexRegister8(src.high()),
env
)?
.iter()
.cloned()
);
}
(DataAccess::IndexRegister16(dst), DataAccess::Register16(src)) => {
bytes.extend(
assemble_ld(
&DataAccess::IndexRegister8(dst.low()),
&DataAccess::Register8(src.low().unwrap()),
env
)?
.iter()
.cloned()
);
bytes.extend(
assemble_ld(
&DataAccess::IndexRegister8(dst.high()),
&DataAccess::Register8(src.high().unwrap()),
env
)?
.iter()
.cloned()
);
}
(DataAccess::Register16(dst), DataAccess::IndexRegister16WithIndex(src, index)) => {
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.low().unwrap()),
&DataAccess::IndexRegister16WithIndex(src.clone(), index.clone()),
env
)?
.iter()
.cloned()
);
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.high().unwrap()),
&DataAccess::IndexRegister16WithIndex(src.clone(), index.add(1)),
env
)?
.iter()
.cloned()
);
}
(DataAccess::IndexRegister16WithIndex(dst, index), DataAccess::Register16(src)) => {
bytes.extend(
assemble_ld(
&DataAccess::IndexRegister16WithIndex(dst.clone(), index.clone()),
&DataAccess::Register8(src.low().unwrap()),
env
)?
.iter()
.cloned()
);
bytes.extend(
assemble_ld(
&DataAccess::IndexRegister16WithIndex(dst.clone(), index.add(1)),
&DataAccess::Register8(src.high().unwrap()),
env
)?
.iter()
.cloned()
);
}
(DataAccess::Register16(dst), DataAccess::MemoryRegister16(Register16::Hl)) => {
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.low().unwrap()),
&DataAccess::MemoryRegister16(Register16::Hl),
env
)?
.iter()
.cloned()
);
bytes.extend(assemble_inc_dec(
Mnemonic::Inc,
&DataAccess::Register16(Register16::Hl),
env
)?);
bytes.extend(
assemble_ld(
&DataAccess::Register8(dst.high().unwrap()),
&DataAccess::MemoryRegister16(Register16::Hl),
env
)?
.iter()
.cloned()
);
bytes.extend(assemble_inc_dec(
Mnemonic::Dec,
&DataAccess::Register16(Register16::Hl),
env
)?);
}
(DataAccess::MemoryRegister16(Register16::Hl), DataAccess::Register16(src)) => {
bytes.extend(
assemble_ld(
&DataAccess::MemoryRegister16(Register16::Hl),
&DataAccess::Register8(src.low().unwrap()),
env
)?
.iter()
.cloned()
);
bytes.extend(assemble_inc_dec(
Mnemonic::Inc,
&DataAccess::Register16(Register16::Hl),
env
)?);
bytes.extend(
assemble_ld(
&DataAccess::MemoryRegister16(Register16::Hl),
&DataAccess::Register8(src.high().unwrap()),
env
)?
.iter()
.cloned()
);
bytes.extend(assemble_inc_dec(
Mnemonic::Dec,
&DataAccess::Register16(Register16::Hl),
env
)?);
}
_ => {}
}
}
if bytes.is_empty() {
Err(AssemblerError::BugInAssembler {
msg: format!("LD: not properly implemented for '{:?}, {:?}'", arg1, arg2)
})
}
else {
Ok(bytes)
}
}
fn assemble_in(arg1: &DataAccess, arg2: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if *arg1 == DataAccess::Expression(0.into()) {
assert_eq!(arg2, &DataAccess::PortC);
bytes.push(0xED);
bytes.push(0x70);
}
else {
match arg2 {
DataAccess::PortC => {
match arg1 {
DataAccess::Register8(ref reg) => {
bytes.push(0xED);
bytes.push(0b0100_0000 | (register8_to_code(*reg) << 3))
}
_ => panic!()
}
}
DataAccess::PortN(ref exp) => {
if let DataAccess::Register8(Register8::A) = arg1 {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0xDB);
bytes.push(val);
}
}
_ => panic!("{:?}", arg2)
};
}
if bytes.is_empty() {
Err(AssemblerError::BugInAssembler {
msg: format!("IN: not properly implemented for '{:?}, {:?}'", arg1, arg2)
})
}
else {
Ok(bytes)
}
}
fn assemble_out(arg1: &DataAccess, arg2: &DataAccess, env: &Env) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if *arg2 == DataAccess::Expression(0.into()) {
assert_eq!(arg1, &DataAccess::PortC);
bytes.push(0xED);
bytes.push(0x71);
}
else {
match arg1 {
DataAccess::PortC => {
if let DataAccess::Register8(ref reg) = arg2 {
bytes.push(0xED);
bytes.push(0b0100_0001 | (register8_to_code(*reg) << 3))
}
if let DataAccess::Expression(Expr::Value(0)) = arg2 {
bytes.push(0xED);
bytes.push(0x71);
}
}
DataAccess::PortN(ref exp) => {
if let DataAccess::Register8(Register8::A) = arg2 {
let val = (env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF) as u8;
bytes.push(0xD3);
bytes.push(val);
}
}
_ => {}
};
}
if bytes.is_empty() {
Err(AssemblerError::BugInAssembler {
msg: format!("OUT: not properly implemented for '{:?}, {:?}'", arg1, arg2)
})
}
else {
Ok(bytes)
}
}
fn assemble_pop(arg1: &DataAccess) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
match arg1 {
DataAccess::Register16(ref reg) => {
let byte = 0b1100_0001 | (register16_to_code_with_af(*reg) << 4);
bytes.push(byte);
}
DataAccess::IndexRegister16(ref reg) => {
bytes.push(indexed_register16_to_code(*reg));
bytes.push(0xE1);
}
_ => {
return Err(AssemblerError::InvalidArgument {
msg: format!("POP: not implemented for {:?}", arg1)
});
}
}
Ok(bytes)
}
fn assemble_push(arg1: &DataAccess) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
match arg1 {
DataAccess::Register16(ref reg) => {
let byte = 0b1100_0101 | (register16_to_code_with_af(*reg) << 4);
bytes.push(byte);
}
DataAccess::IndexRegister16(ref reg) => {
bytes.push(indexed_register16_to_code(*reg));
bytes.push(0xE5);
}
_ => {
return Err(AssemblerError::InvalidArgument {
msg: format!("PUSH: not implemented for {:?}", arg1)
});
}
}
Ok(bytes)
}
fn assemble_logical_operator(
mnemonic: Mnemonic,
arg1: &DataAccess,
env: &Env
) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let memory_code = || {
match mnemonic {
Mnemonic::And => 0xA6,
Mnemonic::Or => 0xB6,
Mnemonic::Xor => 0xAE,
_ => unreachable!()
}
};
match arg1 {
DataAccess::Register8(ref reg) => {
let base = match mnemonic {
Mnemonic::And => 0b1010_0000,
Mnemonic::Or => 0b1011_0000,
Mnemonic::Xor => 0b1010_1000,
_ => unreachable!()
};
bytes.push(base + register8_to_code(*reg));
}
DataAccess::IndexRegister8(ref reg) => {
bytes.push(indexed_register16_to_code(reg.complete()));
let base = match mnemonic {
Mnemonic::And => 0b1010_0000,
Mnemonic::Or => 0b1011_0000,
Mnemonic::Xor => 0b1010_1000,
_ => unreachable!()
};
bytes.push(base + indexregister8_to_code(*reg));
}
DataAccess::Expression(ref exp) => {
let base = match mnemonic {
Mnemonic::And => 0xE6,
Mnemonic::Or => 0xF6,
Mnemonic::Xor => 0xEE,
_ => unreachable!()
};
let value = env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF;
bytes.push(base);
bytes.push(value as u8);
}
DataAccess::MemoryRegister16(Register16::Hl) => {
bytes.push(memory_code());
}
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
let value = env.resolve_expr_may_fail_in_first_pass(exp)?.int()? & 0xFF;
bytes.push(indexed_register16_to_code(*reg));
bytes.push(memory_code());
bytes.push(value as u8);
}
_ => unreachable!()
}
Ok(bytes)
}
fn assemble_ex_memsp(arg1: &DataAccess) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
if let DataAccess::IndexRegister16(ref reg) = arg1 {
bytes.push(indexed_register16_to_code(*reg));
}
bytes.push(0xE3);
Ok(bytes)
}
fn assemble_add_or_adc(
mnemonic: Mnemonic,
arg1: &DataAccess,
arg2: &DataAccess,
env: &Env
) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let is_add = match mnemonic {
Mnemonic::Add => true,
Mnemonic::Adc => false,
_ => panic!("Impossible case")
};
match arg1 {
DataAccess::Register8(Register8::A) => {
match arg2 {
DataAccess::MemoryRegister16(Register16::Hl) => {
if is_add {
bytes.push(0b1000_0110);
}
else {
bytes.push(0b1000_1110);
}
}
DataAccess::IndexRegister16WithIndex(ref reg, ref exp) => {
let val = env.resolve_expr_may_fail_in_first_pass(exp)?.int()?;
bytes.push(indexed_register16_to_code(*reg));
if is_add {
bytes.push(0b1000_0110);
}
else {
bytes.push(0x8E);
}
add_index(&mut bytes, val)?;
}
DataAccess::Expression(ref exp) => {
let val = env.resolve_expr_may_fail_in_first_pass(exp)?.int()? as u8;
if is_add {
bytes.push(0b1100_0110);
}
else {
bytes.push(0xCE);
}
bytes.push(val);
}
DataAccess::Register8(ref reg) => {
let base = if is_add { 0b1000_0000 } else { 0b1000_1000 };
bytes.push(base | register8_to_code(*reg));
}
DataAccess::IndexRegister8(ref reg) => {
bytes.push(indexed_register16_to_code(reg.complete()));
let base = if is_add { 0b1000_0000 } else { 0b1000_1000 };
bytes.push(base | indexregister8_to_code(*reg));
}
_ => {}
}
}
DataAccess::Register16(Register16::Hl) => {
if let DataAccess::Register16(ref reg) = arg2 {
let base = if is_add {
0b0000_1001
}
else {
bytes.push(0xED);
0b0100_1010
};
bytes.push(base | (register16_to_code_with_sp(*reg) << 4));
}
}
DataAccess::IndexRegister16(ref reg1) => {
match arg2 {
DataAccess::Register16(ref reg2) => {
bytes.push(indexed_register16_to_code(*reg1));
let base = if is_add {
0b0000_1001
}
else {
panic!();
};
bytes.push(
base | (register16_to_code_with_indexed(&DataAccess::Register16(*reg2))
<< 4)
)
}
DataAccess::IndexRegister16(ref reg2) => {
if reg1 != reg2 {
return Err(AssemblerError::InvalidArgument {
msg: "Unable to add different indexed registers".to_owned()
});
}
bytes.push(indexed_register16_to_code(*reg1));
let base = if is_add {
0b0000_1001
}
else {
panic!();
};
bytes.push(
base | (register16_to_code_with_indexed(&DataAccess::IndexRegister16(
*reg2
)) << 4)
)
}
_ => {}
}
}
_ => {}
}
if bytes.is_empty() {
Err(AssemblerError::BugInAssembler {
msg: format!("{:?} not implemented for {:?} {:?}", mnemonic, arg1, arg2)
})
}
else {
Ok(bytes)
}
}
fn assemble_bit_res_or_set(
mnemonic: Mnemonic,
arg1: &DataAccess,
arg2: &DataAccess,
hidden: Option<&Register8>,
env: &Env
) -> Result<Bytes, AssemblerError> {
let mut bytes = Bytes::new();
let bit = match arg1 {
DataAccess::Expression(ref e) => {
let bit = (env.resolve_expr_may_fail_in_first_pass(e)?.int()? & 0xFF) as u8;
if bit > 7 {
return Err(AssemblerError::InvalidArgument {
msg: format!("{}: {} is an invalid value", mnemonic.to_string(), bit)
});
}
bit
}
_ => unreachable!()
};
let code = match mnemonic {
Mnemonic::Res => 0b1000_0000,
Mnemonic::Set => 0b1100_0000,
Mnemonic::Bit => 0b0100_0000,
_ => unreachable!()
};
if let DataAccess::Register8(ref reg) = arg2 {
bytes.push(0xCB);
bytes.push(code | (bit << 3) | register8_to_code(*reg))
}
else {
assert!(match arg2 {
DataAccess::MemoryRegister16(Register16::Hl) => true,
DataAccess::IndexRegister16WithIndex(..) => true,
_ => false
});
let mut code = code + 0b0110;
if let DataAccess::IndexRegister16WithIndex(ref reg, delta) = arg2 {
bytes.push(indexed_register16_to_code(*reg));
add_byte(&mut bytes, 0xCB);
let delta = (env.resolve_expr_may_fail_in_first_pass(delta)?.int()? & 0xFF) as u8;
add_byte(&mut bytes, delta);
if hidden.is_some() {
let fix: i8 = match hidden.unwrap() {
Register8::A => 1,
Register8::L => -1,
Register8::H => -2,
Register8::E => -3,
Register8::D => -4,
Register8::C => -5,
Register8::B => -6
};
if fix < 0 {
code -= fix.abs() as u8;
}
else {
code += fix as u8;
}
}
}
else {
bytes.push(0xCB);
}
bytes.push(code | (bit << 3));
}
Ok(bytes)
}
fn indexed_register16_to_code(reg: IndexRegister16) -> u8 {
match reg {
IndexRegister16::Ix => DD,
IndexRegister16::Iy => FD
}
}
#[inline]
fn register8_to_code(reg: Register8) -> u8 {
match reg {
Register8::A => 0b111,
Register8::B => 0b000,
Register8::C => 0b001,
Register8::D => 0b010,
Register8::E => 0b011,
Register8::H => 0b100,
Register8::L => 0b101
}
}
#[inline]
fn indexregister8_to_code(reg: IndexRegister8) -> u8 {
match reg {
IndexRegister8::Ixh | IndexRegister8::Iyh => register8_to_code(Register8::H),
IndexRegister8::Ixl | IndexRegister8::Iyl => register8_to_code(Register8::L)
}
}
fn register16_to_code_with_af(reg: Register16) -> u8 {
match reg {
Register16::Bc => 0b00,
Register16::De => 0b01,
Register16::Hl => 0b10,
Register16::Af => 0b11,
_ => panic!("no mapping for {:?}", reg)
}
}
fn register16_to_code_with_sp(reg: Register16) -> u8 {
match reg {
Register16::Bc => 0b00,
Register16::De => 0b01,
Register16::Hl => 0b10,
Register16::Sp => 0b11,
_ => panic!("no mapping for {:?}", reg)
}
}
fn register16_to_code_with_indexed(reg: &DataAccess) -> u8 {
match reg {
DataAccess::Register16(Register16::Bc) => 0b00,
DataAccess::Register16(Register16::De) => 0b01,
DataAccess::IndexRegister16(_) => 0b10,
DataAccess::Register16(Register16::Sp) => 0b11,
_ => panic!("no mapping for {:?}", reg)
}
}
fn flag_test_to_code(flag: FlagTest) -> u8 {
match flag {
FlagTest::NZ => 0b000,
FlagTest::Z => 0b001,
FlagTest::NC => 0b010,
FlagTest::C => 0b011,
FlagTest::PO => 0b100,
FlagTest::PE => 0b101,
FlagTest::P => 0b110,
FlagTest::M => 0b111
}
}
#[cfg(test)]
#[allow(deprecated)]
mod test {
use super::processed_token::build_processed_token;
use super::*;
fn visit_token(token: &Token, env: &mut Env) -> Result<(), AssemblerError> {
let mut processed = build_processed_token(token, env);
processed.visited(env)
}
fn visit_tokens(tokens: &[Token]) -> Result<Env, AssemblerError> {
let mut env = Env::default();
for t in tokens {
visit_token(t, &mut env)?;
}
Ok(env)
}
#[test]
pub fn test_inc_b() {
let mut env = Env::default();
let res = assemble_inc_dec(
Mnemonic::Inc,
&DataAccess::Register8(Register8::B),
&mut env
)
.unwrap();
assert_eq!(res.len(), 1);
assert_eq!(res[0], 0x04);
}
#[test]
pub fn test_pop() {
let res = assemble_pop(&DataAccess::Register16(Register16::Af)).unwrap();
assert_eq!(res.len(), 1);
assert_eq!(res[0], 0b1111_0001);
}
#[test]
fn test_jump() {
let res = assemble_call_jr_or_jp(
Mnemonic::Jp,
Some(&DataAccess::FlagTest(FlagTest::Z)),
&DataAccess::Expression(Expr::Value(0x1234)),
&mut Env::default()
)
.unwrap();
assert_eq!(res.len(), 3);
assert_eq!(res[0], 0b1100_1010);
assert_eq!(res[1], 0x34);
assert_eq!(res[2], 0x12);
}
#[test]
pub fn test_assert() {
let mut env = Env::default();
env.start_new_pass();
assert!(visit_assert(
&Expr::BinaryOperation(
BinaryOperation::Equal,
Box::new(0i32.into()),
Box::new(0i32.into())
),
None,
&mut env,
None
)
.unwrap());
assert!(!visit_assert(
&Expr::BinaryOperation(
BinaryOperation::Equal,
Box::new(1i32.into()),
Box::new(0i32.into())
),
None,
&mut env,
None
)
.unwrap());
}
#[test]
pub fn test_undef() {
let mut env = Env::default();
env.start_new_pass();
env.visit_label("toto").unwrap();
assert!(env.symbols().contains_symbol("toto").unwrap());
env.visit_undef("toto").unwrap();
assert!(!env.symbols().contains_symbol("toto").unwrap());
assert!(env.visit_undef("toto").is_err());
}
#[test]
pub fn test_inc_dec() {
let env = Env::default();
let res =
assemble_inc_dec(Mnemonic::Inc, &DataAccess::Register16(Register16::De), &env).unwrap();
assert_eq!(res.len(), 1);
assert_eq!(res[0], 0x13);
let res =
assemble_inc_dec(Mnemonic::Dec, &DataAccess::Register8(Register8::B), &env).unwrap();
assert_eq!(res.len(), 1);
assert_eq!(res[0], 0x05);
}
#[test]
pub fn test_res() {
let env = Env::default();
let res = assemble_bit_res_or_set(
Mnemonic::Res,
&DataAccess::Expression(0.into()),
&DataAccess::Register8(Register8::B),
None,
&env
)
.unwrap();
assert_eq!(res.as_ref(), &[0xCB, 0b10000000]);
let env = Env::default();
let res = assemble_bit_res_or_set(
Mnemonic::Res,
&DataAccess::Expression(2.into()),
&DataAccess::Register8(Register8::C),
None,
&env
)
.unwrap();
assert_eq!(res.as_ref(), &[0xCB, 0b10010001]);
let env = Env::default();
let res = assemble_bit_res_or_set(
Mnemonic::Res,
&DataAccess::Expression(2.into()),
&DataAccess::MemoryRegister16(Register16::Hl),
None,
&env
)
.unwrap();
assert_eq!(res.as_ref(), &[0xCB, 0b10010110]);
let env = Env::default();
let res = assemble_bit_res_or_set(
Mnemonic::Res,
&DataAccess::Expression(2.into()),
&DataAccess::IndexRegister16WithIndex(IndexRegister16::Ix, 3.into()),
None,
&env
)
.unwrap();
assert_eq!(res.as_ref(), &[0xDD, 0xCB, 3, 0b10010110]);
let env = Env::default();
let res = assemble_bit_res_or_set(
Mnemonic::Res,
&DataAccess::Expression(2.into()),
&DataAccess::IndexRegister16WithIndex(IndexRegister16::Ix, 3.into()),
Some(&Register8::B),
&env
)
.unwrap();
assert_eq!(res.as_ref(), &[0xDD, 0xCB, 3, 0b10010000]);
}
#[test]
pub fn test_ld() {
let res = assemble_ld(
&DataAccess::Register16(Register16::De),
&DataAccess::Expression(Expr::Value(0x1234)),
&Env::default()
)
.unwrap();
assert_eq!(res.len(), 3);
assert_eq!(res[0], 0x11);
assert_eq!(res[1], 0x34);
assert_eq!(res[2], 0x12);
}
#[test]
#[should_panic]
pub fn test_ld_fail() {
let _res = assemble_ld(
&DataAccess::Register16(Register16::Af),
&DataAccess::Expression(Expr::Value(0x1234)),
&Env::default()
)
.unwrap();
}
#[test]
pub fn test_ld_r16_r16() {
let res = assemble_ld(
&DataAccess::Register16(Register16::De),
&DataAccess::Register16(Register16::Hl),
&Env::default()
)
.unwrap();
assert_eq!(res.len(), 2);
}
#[test]
pub fn test_repeat() {
let tokens = vec![
Token::Org(0.into(), None),
Token::Repeat(
10.into(),
vec![Token::OpCode(Mnemonic::Nop, None, None, None)].into(),
None,
None
),
];
let count = visit_tokens(&tokens).unwrap().size();
assert_eq!(count, 10);
}
#[test]
pub fn test_double_repeat() {
let tokens = vec![
Token::Org(0.into(), None),
Token::Repeat(
10.into(),
vec![Token::Repeat(
10.into(),
vec![Token::OpCode(Mnemonic::Nop, None, None, None)].into(),
None,
None
)]
.into(),
None,
None
),
];
let count = visit_tokens(&tokens).unwrap().size();
assert_eq!(count, 100);
}
#[test]
pub fn test_assemble_logical_operator() {
let operators = [Mnemonic::And, Mnemonic::Or, Mnemonic::Xor];
let operands = [
DataAccess::Register8(Register8::A),
DataAccess::Expression(0.into()),
DataAccess::MemoryRegister16(Register16::Hl),
DataAccess::IndexRegister16WithIndex(IndexRegister16::Ix, 2.into())
];
for operator in &operators {
for operand in &operands {
let token = Token::OpCode(*operator, Some(operand.clone()), None, None);
visit_tokens(&[token]).unwrap();
}
}
}
#[test]
pub fn test_count() {
let tokens = vec![
Token::Org(0.into(), None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
Token::OpCode(Mnemonic::Nop, None, None, None),
];
let count = visit_tokens(&tokens).unwrap().size();
assert_eq!(count, 10);
}
#[test]
pub fn test_stableticker() {
let tokens = vec![
Token::StableTicker(StableTickerAction::Start("myticker".into())),
Token::OpCode(
Mnemonic::Inc,
Some(DataAccess::Register16(Register16::Hl)),
None,
None
),
Token::StableTicker(StableTickerAction::Stop),
];
let env = visit_tokens(&tokens);
assert!(env.is_ok());
let env = env.unwrap();
let val = env.symbols().int_value("myticker");
assert_eq!(val.unwrap().unwrap(), 2);
}
#[test]
pub fn basic_no_variable() {
let tokens = vec![Token::Basic(None, None, "10 PRINT &DEAD".to_owned())];
let env = visit_tokens(&tokens);
println!("{:?}", env);
assert!(env.is_ok());
}
#[test]
pub fn basic_variable_unset() {
let tokens = vec![Token::Basic(
Some(vec!["STUFF".into()]),
None,
"10 PRINT {STUFF}".to_owned()
)];
let env = visit_tokens(&tokens);
println!("{:?}", env);
assert!(env.is_err());
}
#[test]
pub fn basic_variable_set() {
let tokens = vec![
Token::Label("STUFF".into()),
Token::Basic(Some(vec!["STUFF".into()]), None, "10 PRINT {STUFF}".into()),
];
let env = visit_tokens(&tokens);
println!("{:?}", env);
assert!(env.is_ok());
}
#[test]
pub fn test_duration() {
let tokens = vec![Token::OpCode(
Mnemonic::Ld,
Some(DataAccess::Register8(Register8::A)),
Some(DataAccess::Expression(Expr::UnaryTokenOperation(
UnaryTokenOperation::Duration,
Box::new(Token::OpCode(
Mnemonic::Inc,
Some(DataAccess::Register16(Register16::Hl)),
None,
None
))
))),
None
)];
let env = visit_tokens(&tokens);
assert!(env.is_ok());
let env = env.unwrap();
let bytes = env.memory(0, 2);
assert_eq!(bytes[1], 2);
}
#[test]
pub fn test_opcode() {
let tokens = vec![Token::OpCode(
Mnemonic::Ld,
Some(DataAccess::Register8(Register8::A)),
Some(DataAccess::Expression(Expr::UnaryTokenOperation(
UnaryTokenOperation::Opcode,
Box::new(Token::OpCode(
Mnemonic::Inc,
Some(DataAccess::Register16(Register16::Hl)),
None,
None
))
))),
None
)];
let env = visit_tokens(&tokens);
assert!(env.is_ok());
let env = env.unwrap();
let bytes = env.memory(0, 2);
assert_eq!(
bytes[1],
assemble_inc_dec(Mnemonic::Inc, &DataAccess::Register16(Register16::Hl), &env).unwrap()
[0]
);
}
#[test]
pub fn test_bytes() {
let mut m = Bytes::new();
add_byte(&mut m, 2);
assert_eq!(m.len(), 1);
assert_eq!(m[0], 2);
add_word(&mut m, 0x1234);
assert_eq!(m.len(), 3);
assert_eq!(m[1], 0x34);
assert_eq!(m[2], 0x12);
}
#[test]
pub fn test_labels() {
let mut env = Env::default();
let res = visit_token(&Token::Org(0x4000.into(), None), &mut env);
assert!(res.is_ok());
assert!(!env.symbols().contains_symbol("hello").unwrap());
let res = visit_token(&Token::Label("hello".into()), &mut env);
assert!(res.is_ok());
assert!(env.symbols().contains_symbol("hello").unwrap());
assert_eq!(env.symbols().int_value("hello").unwrap(), 0x4000.into());
}
#[test]
pub fn test_jr() {
let res = dbg!(visit_tokens_all_passes(
&[
Token::Org(0x4000.into(), None),
Token::OpCode(
Mnemonic::Jr,
None,
Some(DataAccess::Expression(Expr::Label("$".into()))),
None,
),
],
ctx()
));
assert!(res.is_ok());
let env = res.unwrap();
assert_eq!(
env.memory(0x4000, 2),
&[0x18, 0u8.wrapping_sub(1).wrapping_sub(1)]
);
}
#[test]
pub fn label_exists() {
let res = visit_tokens_all_passes(
&[
Token::Org(0x4000.into(), None),
Token::Label("hello".into()),
Token::Label("hello".into())
],
ctx()
);
assert!(res.is_err());
}
#[test]
pub fn test_rorg() {
let res = visit_tokens_all_passes(
&[
Token::Org(0x4000i32.into(), None),
Token::Rorg(
0x8000i32.into(),
vec![Token::Defb(vec![Expr::Label("$".into())])].into()
)
],
ctx()
);
assert!(res.is_ok());
}
#[test]
pub fn test_two_passes() {
let tokens = vec![
Token::Org(0x123i32.into(), None),
Token::OpCode(
Mnemonic::Ld,
Some(DataAccess::Register16(Register16::Hl)),
Some(DataAccess::Expression(Expr::Label("test".into()))),
None
),
Token::Label("test".into()),
];
let env = visit_tokens(&tokens);
assert!(env.is_err());
let env = visit_tokens_all_passes(&tokens, ctx());
assert!(env.is_ok());
let env = env.ok().unwrap();
let count = env.size();
assert_eq!(count, 3);
assert_eq!(
env.symbols()
.int_value(&"test".to_owned())
.unwrap()
.unwrap(),
0x123 + 3
);
let buffer = env.memory(0x123, 3);
assert_eq!(buffer[1], 0x23 + 3);
assert_eq!(buffer[2], 0x1);
}
#[test]
fn test_read_bytes() {
let tokens = vec![
Token::Org(0x100.into(), None),
Token::Defb(vec![1.into(), 2.into()]),
Token::Defb(vec![3.into(), 4.into()]),
];
let env = visit_tokens(&tokens).unwrap();
let bytes = env.memory(0x100, 4);
assert_eq!(bytes, vec![1, 2, 3, 4]);
}
#[test]
pub fn test_undocumented_rlc() {
let res = visit_tokens_all_passes(
&[
Token::Org(0x100.into(), None),
Token::OpCode(
Mnemonic::Rlc,
Some(DataAccess::IndexRegister16WithIndex(
IndexRegister16::Iy,
2.into()
)),
Some(DataAccess::Register8(Register8::C)),
None
)
],
ctx()
);
assert!(res.is_ok());
let env = res.unwrap();
let bytes = env.memory(0x100, 4);
assert_eq!(bytes, vec![0xFD, 0xCB, 0x2, 0x1]);
}
#[test]
pub fn test_undocumented_res() {
let res = visit_tokens_all_passes(
&[
Token::Org(0x100.into(), None),
Token::OpCode(
Mnemonic::Res,
Some(DataAccess::Expression(4.into())),
Some(DataAccess::MemoryRegister16(Register16::Hl)),
None
)
],
ctx()
);
assert!(res.is_ok());
let env = res.unwrap();
let bytes = env.memory(0x100, 2);
assert_eq!(bytes, vec![0xCB, 0xA6]);
let res = visit_tokens_all_passes(
&[
Token::Org(0x100.into(), None),
Token::OpCode(
Mnemonic::Res,
Some(DataAccess::Expression(4.into())),
Some(DataAccess::IndexRegister16WithIndex(
IndexRegister16::Iy,
2.into()
)),
Some(Register8::A)
)
],
ctx()
);
assert!(res.is_ok());
let env = res.unwrap();
let bytes = env.memory(0x100, 4);
assert_eq!(bytes, vec![0xFD, 0xCB, 0x2, 0xA7]);
}
lazy_static::lazy_static! {
static ref CTX: ParserContext = Default::default();
}
fn ctx() -> &'static ParserContext {
&CTX
}
}