diff --git a/README.md b/README.md index 215a812..25294a8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Compiler tested under Windows and MacOS operating system. It should work under L ## Usage -timu6502 is terminal based compiler and there is no UI yet available. So, basic usage is: +timu6502 is terminal based compiler. So, basic usage is: ```bash timu6502asm test.asm --target test.bin timu6502asm test.asm --binary-dump @@ -47,7 +47,7 @@ timu6502asm test.asm --token-dump timu6502asm test.asm --token-dump --slient timu6502asm --help ``` -If the compilation operation failed, process exit code will be **1**. +If the compilation operation failed, process exit code will be **1** and print error descriptions if silent mode is off. ## Data types Compiler works with primative data types. @@ -83,7 +83,7 @@ $CC33 ; in hexadecimal format ### Ascii It takes up different sizes of space depending on the definition. The text must be written between double quotes. ```assembly -"Hello world" ; in decimal format +"Hello world" ``` ## Available directives diff --git a/src/ast.rs b/src/ast.rs index a2e2184..33da23e 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -9,10 +9,33 @@ use thiserror::Error; use crate::{context::Context, directive::{DirectiveEnum, DirectiveType, DirectiveValue, SYSTEM_DIRECTIVES}, opcode::{ModeType, BRANCH_INSTS, INSTS_SIZE, JUMP_INSTS}, parser::{Parser, Token, TokenType}, tool::print_error}; +#[derive(Debug, PartialEq)] +pub enum InstrValue { + Byte(u8), + Word(u16), + Reference(String), + LocalReference(String) +} + +#[derive(Debug, PartialEq)] +pub enum InstrInfoRegister { + None, + X, + Y +} + +#[derive(Debug, PartialEq)] +pub struct InstrInfo { + pub value: InstrValue, + pub is_immediate: bool, + pub in_parenthesis: bool, + pub register: InstrInfoRegister +} + #[derive(Debug, Copy, Clone)] pub enum BranchType { Generic, - Next + Local } #[derive(Debug)] @@ -20,7 +43,7 @@ pub enum Ast { InstrImplied(usize), InstrBranch(usize, String), InstrJump(usize, String), - Instr(usize, u16, ModeType), + Instr(usize, InstrInfo), Branch(String, BranchType), Directive(DirectiveEnum, Vec) } @@ -63,7 +86,7 @@ impl AstGeneratorError { #[derive(Debug)] pub struct AstGenerator { pub index: Cell, - pub size: Cell, + pub(crate) size: Cell, pub include_asm: RefCell> } @@ -114,76 +137,13 @@ impl AstGenerator { } fn cleanup_space(&self, context: &Context) -> Result<(), AstGeneratorError> { - let token_index = self.peek()?; - let token = &context.tokens.borrow()[token_index]; - if let Token::Space(_) = token.token { - let _ = self.eat(); - } - Ok(()) - } - - fn eat_if_number(&self, context: &Context) -> Option<(u16, ModeType)> { - - if let Ok(mut position) = self.peek() { - let tokens = context.tokens.borrow(); - let mut immediate = false; - let mut mode = ModeType::ZeroPage; - let mut number = 0_u16; - let index = self.index.get(); - - if let Token::Sharp = &tokens[position].token { - let _ = self.eat(); - immediate = true; - if let Ok(new_position) = self.peek() { - position = new_position; - } else { - self.index.set(index); - return None; - } - } - - if let Token::Byte(byte) = &tokens[position].token { - let _ = self.eat(); - mode = ModeType::ZeroPage; - number = *byte as u16; - } - - else if let Token::Word(word) = &tokens[position].token { - let _ = self.eat(); - mode = ModeType::Absolute; - number = *word; - } - - else if let Token::Keyword(keyword) = &tokens[position].token { - let references = context.references.borrow(); - let values = references.get(keyword)?; - if values.len() != 1 { - self.index.set(index); - return None - } - - let first_value = &values[0]; - (number, mode) = match first_value { - DirectiveValue::Byte(number) => (*number as u16, ModeType::ZeroPage), - DirectiveValue::Word(number) => ({ *number }, ModeType::Absolute), - _ => { - self.index.set(index); - return None - } - }; + if let Ok(token_index) = self.peek() { + let token = &context.tokens.borrow()[token_index]; + if let Token::Space(_) = token.token { let _ = self.eat(); } - - return match immediate { - true => Some((number, ModeType::Immediate)), - false => match mode == ModeType::Absolute { - true => Some((number, ModeType::Absolute)), - false => Some(((number as u8) as u16, ModeType::ZeroPage)), - }, - }; } - - None + Ok(()) } fn eat_assign(&self, context: &Context) -> Result<(), AstGeneratorError> { @@ -200,6 +160,7 @@ impl AstGenerator { let token = &context.tokens.borrow()[token_index]; match &token.token { Token::Keyword(text) => Ok(text.clone()), + Token::LocalBranch(text) => Ok(text.clone()), _ => Err(AstGeneratorError::syntax_issue(context, token_index, "Expected text".to_string())) } } @@ -235,7 +196,6 @@ impl AstGenerator { Some(Token::Word(number)) => { values.push(DirectiveValue::Word(*number)); token_found = true; }, Some(Token::Byte(number)) => { values.push(DirectiveValue::Byte(*number)); token_found = true; }, Some(Token::String(string)) => { values.push(DirectiveValue::String(string.clone())); token_found = true; }, - Some(Token::BranchNext(name)) => { values.push(DirectiveValue::Reference(name.clone())); token_found = true; }, Some(Token::NewLine(_)) => finish = true, Some(Token::Comment(_)) => finish = true, Some(Token::End) => finish = true, @@ -363,100 +323,119 @@ impl AstGenerator { Ok(()) } - pub(crate) fn try_parse_number(&self, context: &Context) -> Result<(u16, ModeType), AstGeneratorError> { + pub(crate) fn parse_instr_value(&self, context: &Context) -> Result { self.cleanup_space(context)?; let tokens = context.tokens.borrow(); - let token_index = self.peek()?; - let token = &tokens[token_index]; + + let token_index = self.eat()?; + let mut token = &tokens[token_index]; + + let mut inst_info = InstrInfo { + in_parenthesis: false, + is_immediate: false, + register: InstrInfoRegister::None, + value: InstrValue::Byte(0) + }; + + let mut parenthesis_open = false; if let Token::OpenParenthesis = token.token { - let mut mode = ModeType::Indirect; - let mut parenthesis_closed = false; - self.eat()?; - self.cleanup_space(context)?; - - let Some((number, _)) = self.eat_if_number(context) else { - return Err(AstGeneratorError::syntax_issue(context, token_index, "Invalid numbering number format".to_string())); - }; + inst_info.in_parenthesis = true; + parenthesis_open = true; self.cleanup_space(context)?; + let token_index = self.eat()?; + token = &tokens[token_index]; + } - let token_index = self.peek()?; - let token = &tokens[token_index]; - if let Token::OpenParenthesis = token.token { - self.eat()?; - parenthesis_closed = true; + if let Token::Sharp = &token.token { + inst_info.is_immediate = true; + + let token_index = self.eat()?; + token = &tokens[token_index]; + } + + match &token.token { + Token::Keyword(keyword) => { + let references = context.references.borrow(); + if let Some(values) = references.get(keyword) { + if values.len() != 1 { + return Err(AstGeneratorError::syntax_issue(context, token_index, "Only one token required".to_string())) + } + + let first_value = &values[0]; + match first_value { + DirectiveValue::Byte(byte) => inst_info.value = InstrValue::Byte(*byte), + DirectiveValue::Word(word) => inst_info.value = InstrValue::Word(*word), + _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Invalid token for number".to_string())) + }; + } else { + inst_info.value = InstrValue::Reference(keyword.to_owned()); + } + }, + Token::Byte(byte) => inst_info.value = InstrValue::Byte(*byte), + Token::Word(word) => inst_info.value = InstrValue::Word(*word), + _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Invalid numbering number format".to_string())) + }; + + self.cleanup_space(context)?; + + if let Ok(token_index) = self.peek() { + token = &tokens[token_index]; + if let Token::CloseParenthesis = token.token { + let _ = self.eat()?; + parenthesis_open = false; + self.cleanup_space(context)?; + + let token_index = self.peek()?; + token = &tokens[token_index]; } - self.cleanup_space(context)?; - let token_index = self.peek()?; - let token = &tokens[token_index]; if let Token::Comma = token.token { self.eat()?; self.cleanup_space(context)?; - + let token_index = self.peek()?; - let token = &tokens[token_index]; - - mode = match &token.token { - Token::Keyword(value) if value == "x" || value == "X" => ModeType::IndirectX, - Token::Keyword(value) if value == "y" || value == "Y" => ModeType::IndirectY, + token = &tokens[token_index]; + + match &token.token { + Token::Keyword(value) if value == "x" || value == "X" => inst_info.register = InstrInfoRegister::X, + Token::Keyword(value) if value == "y" || value == "Y" => inst_info.register = InstrInfoRegister::Y, _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Expected X or Y".to_string())) }; - + + if parenthesis_open && inst_info.register == InstrInfoRegister::Y { + return Err(AstGeneratorError::syntax_issue(context, token_index, "Expected X".to_string())) + + } else if !parenthesis_open && inst_info.in_parenthesis && inst_info.register == InstrInfoRegister::X { + return Err(AstGeneratorError::syntax_issue(context, token_index, "Expected Y".to_string())) + } self.eat()?; } + } + + self.cleanup_space(context)?; - self.cleanup_space(context)?; - - if !parenthesis_closed { - self.eat_expected(context, TokenType::CloseParenthesis, AstGeneratorError::syntax_issue(context, token_index, "Expected ')'".to_string()))?; - } - - Ok((number, mode)) - - } else { - self.cleanup_space(context)?; - - let Some((number, mut mode)) = self.eat_if_number(context) else { - return Err(AstGeneratorError::syntax_issue(context, token_index, "Invalid numbering number format".to_string())); - }; + if parenthesis_open { + self.eat_expected(context, TokenType::CloseParenthesis, AstGeneratorError::syntax_issue(context, token_index, "Expected ')'".to_string()))?; + } - if mode == ModeType::Immediate { - return Ok((number, mode)); + if inst_info.is_immediate && !inst_info.in_parenthesis && inst_info.register == InstrInfoRegister::None { + if let InstrValue::Word(word) = inst_info.value { + inst_info.value = InstrValue::Byte(word as u8); } + } - self.cleanup_space(context)?; - let token_index = self.peek()?; - let token = &tokens[token_index]; - if let Token::Comma = token.token { - self.eat()?; - self.cleanup_space(context)?; - - let token_index = self.peek()?; - let token = &tokens[token_index]; - - mode = match &token.token { - Token::Keyword(value) if value == "x" || value == "X" => match mode { - ModeType::Absolute => ModeType::AbsoluteX, - ModeType::ZeroPage => ModeType::ZeroPageX, - _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Invalid usage".to_string())) - }, - Token::Keyword(value) if value == "y" || value == "Y" => match mode { - ModeType::Absolute => ModeType::AbsoluteY, - ModeType::ZeroPage => ModeType::ZeroPageY, - _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Invalid usage".to_string())) - }, - _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Expected X or Y".to_string())) - }; - self.eat()?; + if !inst_info.is_immediate && inst_info.in_parenthesis && inst_info.register != InstrInfoRegister::None { + if let InstrValue::Word(word) = inst_info.value { + inst_info.value = InstrValue::Byte(word as u8); } - - Ok((number, mode)) } + + Ok(inst_info) } - + fn generate_code_block(&self, context: &Context, token_index: usize, positon: usize) -> Result<(), AstGeneratorError> { if INSTS_SIZE[positon] == 1 { @@ -475,8 +454,8 @@ impl AstGenerator { self.eat_space(context)?; let index = self.index.get(); - if let Ok((number, mode)) = self.try_parse_number(context) { - context.add_ast(token_index, Ast::Instr(positon, number, mode)); + if let Ok(value) = self.parse_instr_value(context) { + context.add_ast(token_index, Ast::Instr(positon, value)); return Ok(()) } @@ -494,8 +473,8 @@ impl AstGenerator { else { self.eat_space(context)?; - let (number, mode) = self.try_parse_number(context)?; - context.add_ast(token_index, Ast::Instr(positon, number, mode)); + let value = self.parse_instr_value(context)?; + context.add_ast(token_index, Ast::Instr(positon, value)); } Ok(()) @@ -526,7 +505,8 @@ impl AstGenerator { Some(Token::Assign) => return Err(AstGeneratorError::syntax_issue(context, token_index, "'=' not expected".to_string())), Some(Token::Comma) => return Err(AstGeneratorError::syntax_issue(context, token_index, "',' not expected".to_string())), Some(Token::String(_)) => return Err(AstGeneratorError::syntax_issue(context, token_index, "String not expected".to_string())), - Some(Token::BranchNext(name)) => self.generate_branch(context, token_index, name, BranchType::Next)?, + Some(Token::LocalKeyword(_)) => return Err(AstGeneratorError::syntax_issue(context, token_index, "Unexpected local branch name".to_string())), + Some(Token::LocalBranch(name)) => self.generate_branch(context, token_index, name, BranchType::Local)?, Some(Token::End) => break, None => return Err(AstGeneratorError::InternalError) } diff --git a/src/code_gen.rs b/src/code_gen.rs index f6e6d80..eda3771 100644 --- a/src/code_gen.rs +++ b/src/code_gen.rs @@ -9,6 +9,7 @@ use log::{info, warn}; // Use log crate when building application use std::{println as info, println as warn}; // Workaround to use prinltn! for logs. use thiserror::Error; +use crate::ast::{InstrInfo, InstrValue, InstrInfoRegister}; use crate::context::Context; use crate::tool::print_error; use crate::{ast::{Ast, BranchType}, opcode::{ModeType, MODES}, directive::{DirectiveEnum, DirectiveValue}}; @@ -46,8 +47,10 @@ pub struct CodeGenerator { pub start_point: u16, pub fillvalue : u8, pub branches: HashMap, + pub local_branches: HashMap, pub unresolved_branches: Vec<(String, usize, usize)>, - pub unresolved_jumps: Vec<(String, usize, usize)> + pub unresolved_jumps: Vec<(String, usize, usize)>, + pub unresolved_local_branches: Vec<(String, usize, usize)> } impl CodeGenerator { @@ -59,6 +62,8 @@ impl CodeGenerator { start_point: Default::default(), fillvalue: 0x00, branches: Default::default(), + local_branches: Default::default(), + unresolved_local_branches: Default::default(), unresolved_branches: Default::default(), unresolved_jumps: Default::default(), } @@ -93,18 +98,71 @@ impl CodeGenerator { Ok(()) } - fn generate_instr(&mut self, target: &mut Vec, instr: usize, number: u16, mode: ModeType) -> Result<(), CodeGeneratorError> { + fn generate_instr(&mut self, target: &mut Vec, ast_index: usize, instr: usize, value: &InstrInfo) -> Result<(), CodeGeneratorError> { let modes = MODES[instr]; let mut found = false; + + let (number, mut possible_mode) = match &value.value { + InstrValue::Byte(byte) => (*byte as u16, ModeType::ZeroPage), + InstrValue::Word(word) => (*word, ModeType::Absolute), + InstrValue::Reference(reference) => match self.branches.get(reference) { + Some(branch_position) => { + let distance_position = *branch_position as i8 - (target.len() + 2) as i8; + (distance_position as u16, ModeType::Absolute) + }, + None => { + self.unresolved_jumps.push((reference.clone(), target.len() + 1, ast_index)); + (0, ModeType::Absolute) + } + }, + InstrValue::LocalReference(reference) => match self.local_branches.get(reference) { + Some(branch_position) => { + let distance_position = *branch_position as i8 - (target.len() + 2) as i8; + (distance_position as u16, ModeType::Absolute) + }, + None => { + self.unresolved_local_branches.push((reference.clone(), target.len() + 1, ast_index)); + (0, ModeType::Absolute) + } + } + }; + + if value.in_parenthesis { + possible_mode = match value.register { + InstrInfoRegister::None => ModeType::Indirect, + InstrInfoRegister::X => ModeType::IndirectX, + InstrInfoRegister::Y => ModeType::IndirectY, + }; + } else { + possible_mode = match value.register { + InstrInfoRegister::None => possible_mode, + InstrInfoRegister::X => match possible_mode { + ModeType::ZeroPage => ModeType::ZeroPageX, + _ => ModeType::AbsoluteX, + }, + InstrInfoRegister::Y => match possible_mode { + ModeType::ZeroPage => ModeType::ZeroPageY, + _ => ModeType::AbsoluteY, + } + }; + } + + if value.is_immediate { + possible_mode = ModeType::Immediate; + } + for search_mode in modes.iter() { - if search_mode.mode == mode { + if search_mode.mode == possible_mode { target.push(search_mode.opcode); - self.push_number(target, number, mode)?; + self.push_number(target, number, possible_mode)?; found = true; break; } } + println!("modes: {:?}", &modes); + println!("target: {:?}", &target); + if !found { return Err(CodeGeneratorError::IllegalOpcode) } @@ -156,9 +214,17 @@ impl CodeGenerator { Ok(()) } - fn generate_branch(&mut self, target: &mut [u8], name: &str, _: BranchType) -> Result<(), CodeGeneratorError> { - self.branches.insert(name.to_owned(), target.len()); - //self.references.insert(name, ReferenceValue::AbsoluteAddress(0)); + fn generate_branch(&mut self, target: &mut [u8], name: &str, branch_type: BranchType) -> Result<(), CodeGeneratorError> { + match branch_type { + BranchType::Generic => { + self.branches.insert(name.to_owned(), target.len()); + self.local_branches.clear(); + }, + BranchType::Local => { + self.local_branches.insert(name.to_owned(), target.len()); + self.build_unresolved_local_branches(target)?; + } + }; Ok(()) } @@ -173,6 +239,16 @@ impl CodeGenerator { Ok(()) } + fn build_unresolved_local_branches(&mut self, target: &mut [u8]) -> Result<(), CodeGeneratorError> { + for (branch_name, position, _) in self.unresolved_local_branches.iter() { + if let Some(branch_position) = self.local_branches.get(branch_name) { + target[*position] = (*branch_position as i8 - *position as i8 - 1) as u8; + }; + } + + Ok(()) + } + fn build_unresolved_jumps(&mut self, target: &mut [u8]) -> Result<(), CodeGeneratorError> { for (branch_name, position, _) in self.unresolved_jumps.iter() { match self.branches.get(branch_name) { @@ -336,7 +412,7 @@ impl CodeGenerator { Some(Ast::InstrImplied(position)) => self.generate_implied(&mut context.target, *position)?, Some(Ast::InstrBranch(position, branch)) => self.generate_instr_branch(&mut context.target, ast_index, *position, branch)?, Some(Ast::InstrJump(position, branch)) => self.generate_instr_jump(&mut context.target, ast_index, *position, branch)?, - Some(Ast::Instr(position, number, mode)) => self.generate_instr(&mut context.target, *position, *number, *mode)?, + Some(Ast::Instr(position, value)) => self.generate_instr(&mut context.target, ast_index, *position, value)?, Some(Ast::Branch(name, branch_type)) => self.generate_branch(&mut context.target, name, *branch_type)?, Some(Ast::Directive(option, values)) => self.generate_directive(&mut context.target, *option, values)?, None => return Err(CodeGeneratorError::InternalError) diff --git a/src/parser.rs b/src/parser.rs index 76e53d6..048c3b2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -42,6 +42,7 @@ pub struct Parser<'a> { pub enum Token { Instr(usize), Keyword(String), + LocalKeyword(String), String(String), Directive(String), Comment(String), @@ -51,7 +52,7 @@ pub enum Token { CloseParenthesis, Sharp, Branch(String), - BranchNext(String), + LocalBranch(String), Byte(u8), Word(u16), NewLine(usize), @@ -89,6 +90,9 @@ pub enum ParseError { #[error("Invalid keyword")] InvalidKeyword, + #[error("Missing colon")] + MissingColon, + #[error("Invalid directive")] InvalidDirective, @@ -217,6 +221,7 @@ impl<'a> Parser<'a> { b'%' => self.parse_binary(), b'0'..=b'9' => self.parse_absolute_decimal(), b'#' => self.parse_sharp(), + b'@' => self.parse_local_branch(), b'a'..=b'z' | b'A'..=b'Z' => self.parse_keyword(), b'.' => self.parse_directive(), b'"' => self.parse_string(), @@ -386,6 +391,43 @@ impl<'a> Parser<'a> { Ok(Token::Keyword(str::from_utf8(&self.data[start..self.index])?.to_string())) } + fn parse_local_branch(&mut self) -> Result { + self.eat_expected(b'@', ParseError::UnknownToken)?; + let start = self.index; + + let mut valid = false; + + loop { + match self.peek() { + Ok(byte) => { + match byte { + b'0'..=b'9' => (), + b'a'..=b'z' => valid = true, + b'A'..=b'Z' => valid = true, + b'_' => (), + b':' | b'\t' => break, + b'\n' | b'\r' => break, + _ => return Err(ParseError::InvalidKeyword), + }; + self.eat()?; + } + Err(ParseError::OutOfScope) => break, + _ => return Err(ParseError::InvalidKeyword), + }; + } + + if !valid { + return Err(ParseError::InvalidKeyword); + } + + if let Ok(b':') = self.peek() { + self.eat()?; + return Ok(Token::LocalBranch(str::from_utf8(&self.data[start..self.index-1])?.to_string())) + } + + return Ok(Token::LocalKeyword(str::from_utf8(&self.data[start..self.index])?.to_string())); + } + fn parse_string(&mut self) -> Result { self.eat_expected(b'"', ParseError::InvalidString)?; let start = self.index; @@ -446,7 +488,7 @@ impl<'a> Parser<'a> { } if branch { - return Ok(Token::BranchNext(str::from_utf8(&self.data[start..self.index - 1])?.to_string())); + return Ok(Token::Branch(str::from_utf8(&self.data[start..self.index - 1])?.to_string())); } Ok(Token::Directive(str::from_utf8(&self.data[start..self.index])?.to_string())) @@ -527,9 +569,10 @@ impl<'a> Parser<'a> { Token::Space(_) => "SPACE", Token::End => "END", Token::String(_) => "STRING", - Token::BranchNext(_) => "BRANCHNEXT", Token::Assign => "ASSIGN", Token::Comma => "COMMA", + Token::LocalBranch(_) => "LOCAL BR", + Token::LocalKeyword(_) => "LOCAL KEY" }; if ast.line != line { diff --git a/src/tests/generic.rs b/src/tests/generic.rs index 0dd20db..f3b70d0 100644 --- a/src/tests/generic.rs +++ b/src/tests/generic.rs @@ -3,7 +3,7 @@ use std::{fs::File, io::Read, path::PathBuf}; use rstest::*; use crate::{ - ast::AstGenerator, + ast::{AstGenerator, InstrInfo, InstrValue, InstrInfoRegister}, code_gen::{CodeGenerator, CodeGeneratorError}, context::Context, parser::Parser, @@ -78,6 +78,39 @@ fn compile_test(#[case] data: &'_ [u8]) { generator.dump(&context); } +#[rstest] +#[case(br#"#$08"#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: true, in_parenthesis: false, register: InstrInfoRegister::None })] +#[case(br#"#$0008"#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: true, in_parenthesis: false, register: InstrInfoRegister::None })] +#[case(br#"$08"#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: false, in_parenthesis: false, register: InstrInfoRegister::None })] +#[case(br#"$0800"#, InstrInfo { value: InstrValue::Word(0x0800), is_immediate: false, in_parenthesis: false, register: InstrInfoRegister::None })] +#[case(br#"($0800)"#, InstrInfo { value: InstrValue::Word(0x0800), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::None })] +#[case(br#"($0008, X)"#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::X })] +#[case(br#"($0008) , Y"#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::Y })] +#[case(br#"( $08, X ) "#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::X })] +#[case(br#"( $08 ) , Y "#, InstrInfo { value: InstrValue::Byte(0x08), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::Y })] +#[case(br#"( test ) , Y "#, InstrInfo { value: InstrValue::Reference("test".to_string()), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::Y })] +#[case(br#"(test),Y"#, InstrInfo { value: InstrValue::Reference("test".to_string()), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::Y })] +#[case(br#"#test"#, InstrInfo { value: InstrValue::Reference("test".to_string()), is_immediate: true, in_parenthesis: false, register: InstrInfoRegister::None })] +#[case(br#"(test)"#, InstrInfo { value: InstrValue::Reference("test".to_string()), is_immediate: false, in_parenthesis: true, register: InstrInfoRegister::None })] +#[case(br#"test"#, InstrInfo { value: InstrValue::Reference("test".to_string()), is_immediate: false, in_parenthesis: false, register: InstrInfoRegister::None })] +fn number_parsing_test(#[case] data: &'_ [u8], #[case] expected: InstrInfo) { + let context = Context::default(); + let path = PathBuf::from("main.asm"); + context.add_file(0, path); + context.code_files.borrow_mut()[0].data = data.to_vec(); + + let mut parser = Parser::new(0, data, context); + parser.parse().unwrap(); + parser.friendly_dump(); + + let context = parser.context; + + let ast_generator = AstGenerator::new(); + ast_generator.size.set(context.tokens.borrow().len()); + let info = ast_generator.parse_instr_value(&context).unwrap(); + assert_eq!(info, expected); +} + /* */ #[rstest] @@ -197,7 +230,7 @@ LDx IOREST"#, &[0xad, 0x4a, 0xff, 0xae, 0x3f, 0xff])] #[case(br#"AND $ffdd"#, &[0x2d, 0xdd, 0xff])] #[case(br#"AND ($ff, x)"#, &[0x21, 0xff])] #[case(br#"AND ($00ff, x)"#, &[0x21, 0xff])] -#[case(br#"AND ($ff,Y )"#, &[0x31, 0xff])] +#[case(br#"AND ($ff),Y"#, &[0x31, 0xff])] #[case(br#"LDX $ff,Y"#, &[0xb6, 0xff])] #[case(br#"AND $ff,x"#, &[0x35, 0xff])] #[case(br#"AND $ffdd , x"#, &[0x3d, 0xdd, 0xff])] @@ -271,6 +304,8 @@ fn parser_fail(#[case] data: &'_ [u8]) { #[case(br#"BNE "Hello""#)] #[case(br#"BNE = "Hello""#)] #[case(br#".fBNE = "Hello""#)] +#[case(br#"AND ($0008) , x"#)] +#[case(br#"AND ($0008 , Y)"#)] fn ast_generator_fail(#[case] data: &'_ [u8]) { let context = Context::default(); let path = PathBuf::from("main.asm"); @@ -365,3 +400,36 @@ fn fail_test(#[case] code_filename: &str) { let mut generator = CodeGenerator::new(); assert!(generator.generate(context).is_err()); } + +#[rstest] +#[case(br#"@decrement:"#)] +//#[case(br#"LDX #$08 +//decrement2: +// STX $0201 +//@decrement: +// DEX +// STX $0200 +// CPX #$03 +// BNE @decrement +// BNE decrement2 +// STX $0201 +// BRK"#)] +fn local_branch_test(#[case] data: &'_ [u8]) { + let context = Context::default(); + let path = PathBuf::from("main.asm"); + context.add_file(0, path); + context.code_files.borrow_mut()[0].data = data.to_vec(); + + let mut parser = Parser::new(0, data, context); + parser.parse().unwrap(); + parser.friendly_dump(); + + let context = parser.context; + + let ast_generator = AstGenerator::new(); + let context = ast_generator.generate(context).unwrap(); + + let mut generator = CodeGenerator::new(); + let context = generator.generate(context).unwrap(); + generator.dump(&context); +} \ No newline at end of file