diff --git a/Cargo.toml b/Cargo.toml index aa12561..c57acc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.16", features = ["derive"] } log = "0.4.22" simplelog = "^0.12.2" strum = "0.26.3" diff --git a/README.md b/README.md index deffaf8..49a6b21 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,22 @@ Expected output: 0610: fb 60 00 ``` +## Building +timu6502 builded with latest Rust Language. You have to install Rust Language. After installetion execute ```cargo build --release``` command. The executable will be located under _target/release/_ folder. +Compiler tested under Windows and MacOS operating system. It should work under Linux OS but not yet tested. + + +## Usage +timu6502 is terminal based compiler and there is no UI yet available. So, basic usage is: +```bash +timu6502asm test.asm --target test.bin +timu6502asm test.asm --binary-dump +timu6502asm test.asm --token-dump +timu6502asm test.asm --token-dump --slient +timu6502asm --help +``` +If the compilation operation failed, process exit code will be **1**. + ## Data types Compiler works with primative date types. @@ -75,7 +91,7 @@ It takes up different sizes of space depending on the definition. The text must ### .org Change reference locations. It is not changing where the codes are stored, it is changing jump and branch references. ```assembly -.ORG $0600 +.org $0600 .byte $11 ``` ``` @@ -136,31 +152,46 @@ Include a file as binary data. ### .warning Print warning message on compilation time. ```assembly -.warning "timu6502asm compiler works partial" +.warning "timu6502asm compiler works" ``` ``` -22:05:16 [WARN] timu6502asm compiler works partial +22:05:16 [WARN] timu6502asm compiler works +``` + +### .fail +The compilation process stops with an error message. +```assembly +.fail "Unsupported platform" ``` ### .include -Import another file. +Import another assembly file. All variable defitions will be imported and could be accessible from other files. ```assembly .include "header.asm" .include "body.asm" .include "footer.asm" ``` + +### .pad +Fill memory from the current address to a specified address. A fill value may also be specified. +```assembly +.pad $0600 ``` -22:05:16 [WARN] timu6502asm compiler works partial + +### .fillvalue +Change the default filler for **.pad**. +```assembly +.fillvalue $ff ``` There are many things to do. Here are the some todos: - - [ ] Case insensitivity - - [ ] Rom file generation + - [X] Case insensitivity + - [X] Binary file generation - [ ] Decompiler - - [ ] Human friendly prints + - [X] Human friendly prints - [X] Import different asm files - [ ] Performance measurement - [ ] Documentation - - [ ] Deploy on real hardware + - [ ] Deploy on real hardware/emulator - [ ] (stretch goal) Basic high-level programming language - [ ] (stretch goal) Basic emulator diff --git a/src/ast.rs b/src/ast.rs index cf92f48..a2e2184 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,6 +1,10 @@ -use std::{cell::{Cell, RefCell}, fs::File, io::Read}; +use std::{cell::{Cell, RefCell}, fs::File, io::Read, path::PathBuf}; -use log::info; +#[cfg(not(test))] +use log::{info, warn}; // Use log crate when building application + +#[cfg(test)] +use std::{println as info, println as warn}; // Workaround to use prinltn! for logs. 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}; @@ -297,23 +301,28 @@ impl AstGenerator { fn process_include(&self, context: &Context, token_index: usize) -> Result<(), AstGeneratorError> { let include_asm = self.include_asm.replace(None); + let mut file_path = PathBuf::new(); if let Some(item) = include_asm { - let file_path = match item { - DirectiveValue::String(name) => name, + match item { + DirectiveValue::String(name) => file_path.push(name), _ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Path expected as a string".to_string())) }; let mut tokens = context.tokens.borrow_mut(); let token = &tokens[token_index]; - let path = context.add_file(token.file_id, file_path.to_string()); + let path = context.add_file(token.file_id, file_path); - info!("Importing {:?}", &path.as_os_str()); + if !context.silent { + info!("Importing {:?}", &path.as_os_str()); + } + let mut file = File::open(&path)?; let mut code = Vec::new(); file.read_to_end(&mut code)?; + context.code_files.borrow_mut()[context.last_file_id()].data = code.clone(); code.push(b'\n'); // Add new lines to end of the code file @@ -458,13 +467,14 @@ impl AstGenerator { // Branch inst self.eat_space(context)?; let text = self.eat_text(context)?; - context.add_ast(token_index,Ast::InstrBranch(positon, text)); + context.add_ast(token_index, Ast::InstrBranch(positon, text)); } else if JUMP_INSTS.contains(&positon) { // Jump inst 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)); return Ok(()) @@ -534,7 +544,11 @@ impl AstGenerator { Err(error) => { let tokens = context.tokens.borrow(); let token = &tokens[self.index.get() - 1]; - print_error(&context.target, &error, token.line, token.column, token.end); + + if !context.silent { + let code_file = &context.code_files.borrow()[token.file_id]; + print_error(&code_file.data, &error, token.line, token.column, token.end); + } Err(error) } } diff --git a/src/code_gen.rs b/src/code_gen.rs index 1e9f1fe..f6e6d80 100644 --- a/src/code_gen.rs +++ b/src/code_gen.rs @@ -2,7 +2,11 @@ use std::{collections::HashMap, str::Utf8Error}; use std::fs::File; use std::io::BufReader; use std::io::Read; -use log::{info, warn}; +#[cfg(not(test))] +use log::{info, warn}; // Use log crate when building application + +#[cfg(test)] +use std::{println as info, println as warn}; // Workaround to use prinltn! for logs. use thiserror::Error; use crate::context::Context; @@ -28,15 +32,19 @@ pub enum CodeGeneratorError { #[error("Text convertion issue ({0})")] Utf8Error(#[from] Utf8Error), #[error("Expected {0}")] - ExpectedThis(&'static str) + ExpectedThis(&'static str), + #[error("{0}")] + ProgramFailed(String) } #[derive(Debug)] pub struct CodeGenerator { pub index: usize, pub size: usize, + pub silent: bool, pub start_point: u16, + pub fillvalue : u8, pub branches: HashMap, pub unresolved_branches: Vec<(String, usize, usize)>, pub unresolved_jumps: Vec<(String, usize, usize)> @@ -47,7 +55,9 @@ impl CodeGenerator { Self { index: 0, size: 0, + silent: false, start_point: Default::default(), + fillvalue: 0x00, branches: Default::default(), unresolved_branches: Default::default(), unresolved_jumps: Default::default(), @@ -258,8 +268,42 @@ impl CodeGenerator { _ => return Err(CodeGeneratorError::ExpectedThis("string")) }; } + + if !self.silent { + warn!("{}", message); + } + Ok(()) + } + + fn directive_fail(&mut self, values: &[DirectiveValue]) -> Result<(), CodeGeneratorError> { + let mut message = String::new(); + + for value in values.iter() { + match value { + DirectiveValue::String(string) => message += &string[..], + DirectiveValue::Word(word) => message += &format!("0x{:02X}", word), + DirectiveValue::Byte(byte) => message += &format!("0x{:02X}", byte), + _ => return Err(CodeGeneratorError::ExpectedThis("string")) + }; + } + Err(CodeGeneratorError::ProgramFailed(message)) + } + + fn directive_pad(&mut self, target: &mut Vec, values: &[DirectiveValue]) -> Result<(), CodeGeneratorError> { + let address = match &values[0] { + DirectiveValue::Word(address) => *address, + _ => return Err(CodeGeneratorError::ExpectedThis("word")) + }; - warn!("{}", message); + for _ in 0..(address as usize-target.len()) { + target.push(self.fillvalue); + } + + Ok(()) + } + + fn directive_fillvalue(&mut self, values: &[DirectiveValue]) -> Result<(), CodeGeneratorError> { + self.fillvalue = values[0].get_byte()?; Ok(()) } @@ -272,7 +316,10 @@ impl CodeGenerator { DirectiveEnum::Ascii => self.directive_ascii(target, values, false)?, DirectiveEnum::Asciiz => self.directive_ascii(target, values, true)?, DirectiveEnum::Warning => self.directive_warning(values)?, + DirectiveEnum::Fail => self.directive_fail(values)?, DirectiveEnum::Include => (), + DirectiveEnum::Pad => self.directive_pad(target, values)?, + DirectiveEnum::Fillvalue => self.directive_fillvalue(values)?, }; Ok(()) } @@ -309,7 +356,10 @@ impl CodeGenerator { Err(error) => { let asts = context.asts.borrow(); let ast = &asts[self.index - 1]; - print_error(&context.target, &error, ast.line, ast.column, ast.end); + if !context.silent { + let code_file = &context.code_files.borrow()[0]; + print_error(&code_file.data, &error, ast.line, ast.column, ast.end); + } Err(error) } } diff --git a/src/context.rs b/src/context.rs index 3f49145..2482281 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, path::PathBuf}; +use std::{cell::RefCell, collections::HashMap, default, path::PathBuf}; use crate::{ast::{Ast, AstInfo}, directive::DirectiveValue, parser::TokenInfo}; @@ -9,7 +9,16 @@ pub struct Context { pub asts: RefCell>, pub references: RefCell>>, pub files: RefCell>, - pub work_directory: PathBuf + pub work_directory: PathBuf, + pub silent: bool, + pub code_files: RefCell> +} + +#[derive(Debug)] +pub struct CodeFile { + pub path: PathBuf, + pub includes: Vec, + pub data: Vec } impl Context { @@ -26,8 +35,9 @@ impl Context { self.asts.borrow_mut().push(info); } - pub fn add_file(&self, base_file_id: usize, file: String) -> PathBuf { + pub fn add_file(&self, base_file_id: usize, file: PathBuf) -> PathBuf { let mut files = self.files.borrow_mut(); + let mut code_files = self.code_files.borrow_mut(); let path = match files.get(base_file_id) { Some(path) => path.parent().map(|parent| parent.to_owned()), @@ -40,6 +50,7 @@ impl Context { }; files.push(full_file_path.clone()); + code_files.push(CodeFile { path: full_file_path.clone(), includes: Vec::new(), data: Vec::new() }); full_file_path } @@ -79,7 +90,9 @@ impl Default for Context { tokens: Default::default(), asts: Default::default(), references: Default::default(), - files: Default::default() + files: Default::default(), + silent: false, + code_files: Default::default() } } } diff --git a/src/directive.rs b/src/directive.rs index a1cff48..87cb225 100644 --- a/src/directive.rs +++ b/src/directive.rs @@ -11,7 +11,10 @@ pub enum DirectiveEnum { Ascii, Asciiz, Warning, - Include + Fail, + Include, + Pad, + Fillvalue } #[derive(Debug, PartialEq, Clone)] @@ -32,6 +35,14 @@ impl DirectiveValue { _ => Err(CodeGeneratorError::ExpectedThis("Word information")) } } + + pub fn get_byte(&self) -> Result { + + match self { + DirectiveValue::Byte(number) => Ok(*number), + _ => Err(CodeGeneratorError::ExpectedThis("Byte information")) + } + } } #[derive(Debug, PartialEq, Copy, Clone)] @@ -50,14 +61,17 @@ pub struct DirectiveInfo { } pub const SYSTEM_DIRECTIVES: &[DirectiveInfo] = &[ - DirectiveInfo { name: "BYTE", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] }, - DirectiveInfo { name: "DB", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] }, - DirectiveInfo { name: "WORD", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] }, - DirectiveInfo { name: "DW", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] }, - DirectiveInfo { name: "ORG", directive: DirectiveEnum::Org, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Word] }, - DirectiveInfo { name: "INCBIN", directive: DirectiveEnum::Incbin, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] }, - DirectiveInfo { name: "ASCII", directive: DirectiveEnum::Ascii, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] }, - DirectiveInfo { name: "ASCIIZ", directive: DirectiveEnum::Asciiz, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] }, - DirectiveInfo { name: "WARNING", directive: DirectiveEnum::Warning, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String, DirectiveType::Word, DirectiveType::Byte] }, - DirectiveInfo { name: "INCLUDE", directive: DirectiveEnum::Include, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] }, + DirectiveInfo { name: "BYTE", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] }, + DirectiveInfo { name: "DB", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] }, + DirectiveInfo { name: "WORD", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] }, + DirectiveInfo { name: "DW", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] }, + DirectiveInfo { name: "ORG", directive: DirectiveEnum::Org, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Word] }, + DirectiveInfo { name: "INCBIN", directive: DirectiveEnum::Incbin, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] }, + DirectiveInfo { name: "ASCII", directive: DirectiveEnum::Ascii, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] }, + DirectiveInfo { name: "ASCIIZ", directive: DirectiveEnum::Asciiz, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] }, + DirectiveInfo { name: "WARNING", directive: DirectiveEnum::Warning, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String, DirectiveType::Word, DirectiveType::Byte] }, + DirectiveInfo { name: "FAIL", directive: DirectiveEnum::Fail , size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String, DirectiveType::Word, DirectiveType::Byte] }, + DirectiveInfo { name: "INCLUDE", directive: DirectiveEnum::Include, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] }, + DirectiveInfo { name: "PAD", directive: DirectiveEnum::Pad, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Word] }, + DirectiveInfo { name: "FILLVALUE", directive: DirectiveEnum::Fillvalue, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Byte] }, ]; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8bae3b2..9423334 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,37 +8,134 @@ mod context; #[cfg(test)] mod tests; -use log::{info, LevelFilter}; +use std::{fs::File, io::{Read, Write}, path::PathBuf}; + +use log::{error, info, LevelFilter}; use simplelog::*; -use ast::AstGenerator; -use code_gen::CodeGenerator; +use ast::{AstGenerator, AstGeneratorError}; +use code_gen::{CodeGenerator, CodeGeneratorError}; use context::Context; -use parser::Parser; +use parser::{ParseError, Parser}; -fn main() { - let _ = CombinedLogger::init(vec![TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed, ColorChoice::Auto)]); - info!("timu6502asm Compiler"); +use clap::{arg, command, Parser as ClapParser}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum StarterError { + #[error("IO Error: ({0})")] + IOError(#[from] std::io::Error), + + #[error("{0}")] + Generation(#[from] CodeGeneratorError), + + #[error("{0}")] + Parser(#[from] ParseError), + + #[error("{0}")] + Ast(#[from] AstGeneratorError), + + #[error("Please specify on of the argument [--target, --binary_dump, --token_dump]")] + InvalidArgument +} + +#[derive(ClapParser)] +#[command(version, about, long_about = None)] +struct Cli { + + /// Source .asm file + #[arg(value_name = "SOURCE-FILE")] + source: PathBuf, + + /// Target binary + #[arg(long, value_name = "TARGET-FILE")] + target: Option, + + /// Dump binary + #[clap(long, short='b', action)] + binary_dump: bool, + + /// Dump tokens + #[clap(long, short, action)] + token_dump: bool, + + /// Silent mode + #[clap(long, short, action)] + silent: bool, +} + + +fn read_file(path: PathBuf) -> Result, StarterError> { + let mut file = File::open(&path)?; + let mut code = Vec::new(); + file.read_to_end(&mut code)?; + Ok(code) +} + +fn execute(cli: &Cli) -> Result<(), StarterError> { + if !cli.binary_dump && !cli.token_dump && cli.target.is_none() { + return Err(StarterError::InvalidArgument); + } + + if !cli.silent { + info!("timu6502asm Compiler"); + } + + let mut context = Context::default(); + context.silent = cli.silent; + + if !cli.silent { + info!("Compiling {:?}", &cli.source.as_os_str()); + } - let data = br#" - .include "src/tests/asms/tables.asm" - .include "test2.asm" - ADC TEST - "#; + let data = read_file(cli.source.clone())?; - let context = Context::default(); - context.add_file(0, "".to_string()); + context.add_file(0, cli.source.clone()); + context.code_files.borrow_mut()[0].data = data.clone(); - let mut parser = Parser::new(0, data, context); - parser.parse().unwrap(); - parser.friendly_dump(); + let mut parser = Parser::new(0, &data, context); + parser.parse()?; + if cli.token_dump { + parser.friendly_dump(); + } let context = parser.context; let ast_generator = AstGenerator::new(); - let context = ast_generator.generate(context).unwrap(); + let context = ast_generator.generate(context)?; let mut generator = CodeGenerator::new(); - let context = generator.generate(context).unwrap(); - //generator.dump(&context); + generator.silent = cli.silent; + + let context = generator.generate(context)?; + + if cli.binary_dump { + generator.dump(&context); + } + + if let Some(target) = &cli.target { + let mut file = File::create(target)?; + file.write_all(&context.target)?; + } + + if !cli.silent { + info!("Compilation successfully finished. "); + } + + Ok(()) +} + +fn main() { + let _ = CombinedLogger::init(vec![TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed, ColorChoice::Auto)]); + + let cli: Cli = Cli::parse(); + + if let Err(error) = execute(&cli) { + if !cli.silent { + error!("Compilation failed."); + error!("Reason: {}", error); + } + + std::process::exit(1); + } } diff --git a/src/parser.rs b/src/parser.rs index 0832040..76e53d6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -152,7 +152,10 @@ impl<'a> Parser<'a> { match self.inner_parse() { Ok(_) => Ok(()), Err(error) => { - print_error(self.data, &error, self.line, self.column, self.end); + if !self.context.silent { + print_error(self.data, &error, self.line, self.column, self.end); + } + Err(error) } } diff --git a/src/tests/asms/fail-test.asm b/src/tests/asms/fail-test.asm new file mode 100644 index 0000000..9b0588c --- /dev/null +++ b/src/tests/asms/fail-test.asm @@ -0,0 +1 @@ +.fail "Fail test" \ No newline at end of file diff --git a/src/tests/asms/import-test.asm b/src/tests/asms/import-test.asm new file mode 100644 index 0000000..9dddfb3 --- /dev/null +++ b/src/tests/asms/import-test.asm @@ -0,0 +1,12 @@ +.include "sub-file.asm" + +.fillvalue $00 +.pad $0020 + +.fillvalue $11 +.pad $0040 + +.include "sub-2-file.asm" + +.fillvalue $22 +.pad $0060 diff --git a/src/tests/asms/sub-2-file.asm b/src/tests/asms/sub-2-file.asm new file mode 100644 index 0000000..58bcccd --- /dev/null +++ b/src/tests/asms/sub-2-file.asm @@ -0,0 +1 @@ +.byte $00, $11, $22, $33, $44, $55, $66, $77, $88, $99, $AA, $BB, $CC, $DD, $EE, $FF diff --git a/src/tests/asms/sub-file.asm b/src/tests/asms/sub-file.asm new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/bins/import-test.bin b/src/tests/bins/import-test.bin new file mode 100644 index 0000000..a34c96f Binary files /dev/null and b/src/tests/bins/import-test.bin differ diff --git a/src/tests/generic.rs b/src/tests/generic.rs index 7cd5826..0dd20db 100644 --- a/src/tests/generic.rs +++ b/src/tests/generic.rs @@ -1,4 +1,4 @@ -use std::{fs::File, io::Read}; +use std::{fs::File, io::Read, path::PathBuf}; use rstest::*; @@ -60,7 +60,9 @@ BRK"# )] fn compile_test(#[case] data: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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(); @@ -203,7 +205,9 @@ LDx IOREST"#, &[0xad, 0x4a, 0xff, 0xae, 0x3f, 0xff])] #[case(br#"JMP ($ffdd)"#, &[0x6c, 0xdd, 0xff])] // Only jump has indirect mode fn check_codes(#[case] data: &'_ [u8], #[case] codes: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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(); @@ -224,7 +228,9 @@ fn check_codes(#[case] data: &'_ [u8], #[case] codes: &'_ [u8]) { #[case(br#".INCBIN "src/tests/bins/test1.bin""#, &[0x00, 0x01, 0x02, 0x03])] fn binary_read(#[case] data: &'_ [u8], #[case] binary: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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(); @@ -249,7 +255,9 @@ fn binary_read(#[case] data: &'_ [u8], #[case] binary: &'_ [u8]) { #[case(br#"? :"#)] fn parser_fail(#[case] data: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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); assert!(parser.parse().is_err()); @@ -265,7 +273,9 @@ fn parser_fail(#[case] data: &'_ [u8]) { #[case(br#".fBNE = "Hello""#)] fn ast_generator_fail(#[case] data: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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(); @@ -280,7 +290,9 @@ fn ast_generator_fail(#[case] data: &'_ [u8]) { #[case(br#"AND ($ffdd)"#)] fn compile_failure(#[case] data: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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(); @@ -302,6 +314,7 @@ fn compile_failure(#[case] data: &'_ [u8]) { #[rstest] #[case("src/tests/asms/tables.asm", "src/tests/bins/tables.bin")] +#[case("src/tests/asms/import-test.asm", "src/tests/bins/import-test.bin")] fn test_file(#[case] code_filename: &str, #[case] expected_filename: &str) { let mut code = Vec::new(); let mut file = File::open(code_filename).unwrap(); @@ -312,7 +325,9 @@ fn test_file(#[case] code_filename: &str, #[case] expected_filename: &str) { file.read_to_end(&mut binary).unwrap(); let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + let path = PathBuf::from(code_filename); + context.add_file(0, path); + context.code_files.borrow_mut()[0].data = code.clone(); let mut parser = Parser::new(0, &code, context); parser.parse().unwrap(); @@ -326,3 +341,27 @@ fn test_file(#[case] code_filename: &str, #[case] expected_filename: &str) { let context = generator.generate(context).unwrap(); assert_eq!(context.target, binary); } + +#[rstest] +#[case("src/tests/asms/fail-test.asm")] +fn fail_test(#[case] code_filename: &str) { + let mut code = Vec::new(); + let mut file = File::open(code_filename).unwrap(); + file.read_to_end(&mut code).unwrap(); + + let context = Context::default(); + let path = PathBuf::from(code_filename); + context.add_file(0, path); + context.code_files.borrow_mut()[0].data = code.clone(); + + let mut parser = Parser::new(0, &code, context); + parser.parse().unwrap(); + + let context = parser.context; + + let ast_generator = AstGenerator::new(); + let context = ast_generator.generate(context).unwrap(); + + let mut generator = CodeGenerator::new(); + assert!(generator.generate(context).is_err()); +} diff --git a/src/tests/parser.rs b/src/tests/parser.rs index b9918d8..88b00c4 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use rstest::*; use crate::{ast::AstGenerator, context::Context, parser::{Parser, Token}}; @@ -15,7 +17,8 @@ use crate::{ast::AstGenerator, context::Context, parser::{Parser, Token}}; #[case(b"160", 0xa0)] fn number_check(#[case] data: &'_ [u8], #[case] expected: u16) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + let path = PathBuf::from("main.asm"); + context.add_file(0, path); let mut parser = Parser::new(0, data, context); parser.parse().unwrap(); @@ -51,7 +54,9 @@ fn number_check(#[case] data: &'_ [u8], #[case] expected: u16) { #[case(b"$a000)")] fn invalid_number_check(#[case] data: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + 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); @@ -69,7 +74,8 @@ fn invalid_number_check(#[case] data: &'_ [u8]) { #[case(b";;;;;;;;;;;;;")] fn check_comment(#[case] data: &'_ [u8]) { let context = Context::default(); - context.add_file(0, "main.asm".to_string()); + let path = PathBuf::from("main.asm"); + context.add_file(0, path); let mut parser = Parser::new(0, data, context); parser.parse().unwrap(); diff --git a/src/tool.rs b/src/tool.rs index e74eeb4..b810c78 100644 --- a/src/tool.rs +++ b/src/tool.rs @@ -8,7 +8,6 @@ pub fn upper_case_byte(byte: u8) -> u8 { } pub fn print_error(data: &'_ [u8], error: &T, line: usize, column: usize, end: usize) { - return; let mut line_index = 0; let mut start_index = 0; let mut end_index = data.len()-1;