diff --git a/Cargo.lock b/Cargo.lock index 4fe7c36..9a6b194 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,17 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] +version = 4 [[package]] name = "ariadne" @@ -22,17 +11,11 @@ dependencies = [ "yansi", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] @@ -45,13 +28,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "compact_str" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5138945395949e7dfba09646dc9e766b548ff48e23deb5246890e6b64ae9e1b9" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", + "cfg-if", "itoa", + "rustversion", "ryu", + "static_assertions", ] [[package]] @@ -64,42 +50,40 @@ dependencies = [ ] [[package]] -name = "encoding_rs" -version = "0.8.31" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "getrandom" -version = "0.2.8" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "cfg-if", - "libc", - "wasi", + "foldhash", ] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "indexmap" -version = "1.9.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ - "autocfg", - "hashbrown", + "equivalent", + "hashbrown 0.16.1", ] [[package]] @@ -108,12 +92,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" -[[package]] -name = "libc" -version = "0.2.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" - [[package]] name = "memchr" version = "2.5.0" @@ -122,21 +100,44 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "object" -version = "0.29.0" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", - "hashbrown", + "hashbrown 0.15.5", "indexmap", "memchr", ] [[package]] -name = "once_cell" -version = "1.16.0" +name = "phf" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "powerpc-asm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6279a5e74397a442815266b75cba540630860f62cc306e324079e3056a27225b" +dependencies = [ + "phf", + "thiserror", +] [[package]] name = "ppcasm" @@ -144,8 +145,8 @@ version = "0.1.0" dependencies = [ "ariadne", "compact_str", - "encoding_rs", "object", + "powerpc-asm", "pratt", "smallvec", "unicode-ident", @@ -157,6 +158,24 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17e0a4425d076f0718b820673a38fbf3747080c61017eeb0dd79bc7e472b8bb8" +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -169,30 +188,90 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 0a8a5e3..432d4e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -compact_str = "0.6.1" -object = { version = "0.29.0", features = ["write_std", "elf"], default-features = false } -smallvec = { version = "1.10.0", features = ["const_generics", "const_new", "union"] } -encoding_rs = "0.8.31" +compact_str = "0.8" +object = { version = "0.36", features = ["write_std", "elf"], default-features = false } +smallvec = { version = "1", features = ["const_generics", "const_new", "union"] } pratt = "0.4.0" ariadne = "0.1.5" -unicode-ident = "1.0.5" +unicode-ident = "1" +powerpc-asm = "0.4.1" diff --git a/src/main.rs b/src/main.rs index 7026cff..4530d82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,28 @@ -#![feature(test)] extern crate core; -extern crate test; use std::{ collections::{hash_map::Entry, HashMap}, error::Error, - fmt::{Display, Formatter}, fs, fs::File, - io::{BufRead, BufReader, BufWriter, Read}, + io::BufWriter, mem::{discriminant, take}, - path::Path, - str::Chars, + path::PathBuf, + process, }; -use ariadne::{Color, ColorGenerator, Fmt, Label, Report, ReportKind, Source}; +use ariadne::{Color, Label, Report, ReportKind, Source}; use compact_str::CompactString; -use encoding_rs::SHIFT_JIS; use object::{ - elf::{R_PPC_ADDR16_HA, R_PPC_ADDR16_LO, R_PPC_ADDR32, R_PPC_EMB_SDA21}, + elf::{ + R_PPC_ADDR16_HA, R_PPC_ADDR16_HI, R_PPC_ADDR16_LO, R_PPC_ADDR32, R_PPC_EMB_SDA21, + R_PPC_REL14, R_PPC_REL24, + }, write::{SectionId, SymbolId}, - Architecture, BinaryFormat, Endianness, + Architecture, BinaryFormat, Endianness, RelocationFlags, }; use parser::Parser; +use powerpc_asm::{Argument, Arguments}; use crate::{ errors::{ParseError, ParseErrorDiagnostic, SourceInfo}, @@ -40,6 +40,7 @@ enum Visibility { Local, Global, Weak, + Hidden, } #[derive(Debug, Eq, PartialEq)] @@ -67,7 +68,19 @@ struct Relocation { kind: RelocationKind, section: CompactString, offset: u32, - size: u8, +} + +#[derive(Debug)] +struct BranchFixup { + section: CompactString, + offset: u32, + target_sym: CompactString, + addend: i32, +} + +struct SectionInfo { + kind: object::SectionKind, + align: u64, } struct Analyzer { @@ -76,7 +89,11 @@ struct Analyzer { replacements: HashMap, symbols: HashMap, section_data: HashMap>, + section_info: HashMap, + section_order: Vec, relocations: Vec, + branch_fixups: Vec, + file_name: Option, } type AnalyzerResult = Result; @@ -85,27 +102,131 @@ enum ExpressionResult { Number(i64), Float(f32), Double(f64), - // Symbol(CompactString), Relocation(CompactString, i32), } impl Analyzer { fn new() -> Self { let mut replacements = HashMap::::new(); + // General-purpose and floating-point registers for i in 0..=31 { replacements.insert(format!("r{}", i).into(), Operand::Number(i, SourceInfo::new(0))); replacements.insert(format!("f{}", i).into(), Operand::Number(i, SourceInfo::new(0))); } + // Paired-singles quantization registers for i in 0..=7 { replacements.insert(format!("qr{}", i).into(), Operand::Number(i, SourceInfo::new(0))); } + // CR fields + for i in 0..=7 { + replacements.insert(format!("cr{}", i).into(), Operand::Number(i, SourceInfo::new(0))); + } + // CR bits: cr0lt=0, cr0gt=1, cr0eq=2, cr0un=3, ..., cr7un=31 + let cr_bit_names = ["lt", "gt", "eq", "un", "so"]; + for cr in 0..=7 { + for (bit, name) in cr_bit_names.iter().enumerate() { + let bit_num = cr * 4 + bit as i64; + // "so" is an alias for "un" (bit 3) + let bit_num = if *name == "so" { cr * 4 + 3 } else { bit_num }; + replacements.insert( + format!("cr{}{}", cr, name).into(), + Operand::Number(bit_num, SourceInfo::new(0)), + ); + } + } + // Bare CR bit names (aliases for cr0 bits) + replacements.insert("lt".into(), Operand::Number(0, SourceInfo::new(0))); + replacements.insert("gt".into(), Operand::Number(1, SourceInfo::new(0))); + replacements.insert("eq".into(), Operand::Number(2, SourceInfo::new(0))); + replacements.insert("so".into(), Operand::Number(3, SourceInfo::new(0))); + replacements.insert("un".into(), Operand::Number(3, SourceInfo::new(0))); + // SPRs + let sprs: &[(&str, i64)] = &[ + ("XER", 1), + ("LR", 8), + ("CTR", 9), + ("DSISR", 18), + ("DAR", 19), + ("DEC", 22), + ("SDR1", 25), + ("SRR0", 26), + ("SRR1", 27), + ("SPRG0", 272), + ("SPRG1", 273), + ("SPRG2", 274), + ("SPRG3", 275), + ("EAR", 282), + ("TBL", 284), + ("TBU", 285), + ("PVR", 287), + ("IBAT0U", 528), + ("IBAT0L", 529), + ("IBAT1U", 530), + ("IBAT1L", 531), + ("IBAT2U", 532), + ("IBAT2L", 533), + ("IBAT3U", 534), + ("IBAT3L", 535), + ("DBAT0U", 536), + ("DBAT0L", 537), + ("DBAT1U", 538), + ("DBAT1L", 539), + ("DBAT2U", 540), + ("DBAT2L", 541), + ("DBAT3U", 542), + ("DBAT3L", 543), + ("GQR0", 912), + ("GQR1", 913), + ("GQR2", 914), + ("GQR3", 915), + ("GQR4", 916), + ("GQR5", 917), + ("GQR6", 918), + ("GQR7", 919), + ("HID0", 1008), + ("HID1", 1009), + ("HID2", 920), + ("WPAR", 921), + ("DMA_U", 922), + ("DMA_L", 923), + ("UMMCR0", 936), + ("UPMC1", 937), + ("UPMC2", 938), + ("USIA", 939), + ("UMMCR1", 940), + ("UPMC3", 941), + ("UPMC4", 942), + ("USDA", 943), + ("MMCR0", 952), + ("PMC1", 953), + ("PMC2", 954), + ("SIA", 955), + ("MMCR1", 956), + ("PMC3", 957), + ("PMC4", 958), + ("SDA", 959), + ("IABR", 1010), + ("DABR", 1013), + ("L2CR", 1017), + ("ICTC", 1019), + ("THRM1", 1020), + ("THRM2", 1021), + ("THRM3", 1022), + ]; + for &(name, value) in sprs { + replacements.insert(name.into(), Operand::Number(value, SourceInfo::new(0))); + } Self { current_section: None, section_offset: 0, replacements, symbols: Default::default(), section_data: Default::default(), + section_info: Default::default(), + section_order: Vec::new(), relocations: Default::default(), + branch_fixups: Default::default(), + file_name: None, } } @@ -116,71 +237,352 @@ impl Analyzer { if sym.starts_with('.') { match sym.as_str() { ".include" => { - // TODO + // Silently ignore (built-in replacements cover macros.inc) Ok(()) } ".section" => self.section(args, source), + ".text" => self.switch_section_named(".text"), + ".data" => self.switch_section_named(".data"), + ".rodata" => self.switch_section_named(".rodata"), + ".bss" => self.switch_section_named(".bss"), + ".sbss" => self.switch_section_named(".sbss"), + ".sdata" => self.switch_section_named(".sdata"), + ".sdata2" => self.switch_section_named(".sdata2"), ".4byte" => self.byte4(args, source), ".2byte" => self.byte2(args, source), ".byte" => self.byte(args, source), ".balign" => self.balign(args, source), - ".global" => self.global(args, source), + ".global" | ".globl" => self.global(args, source), + ".local" => self.local(args, source), + ".weak" => self.weak(args, source), ".float" => self.float(args, source), ".double" => self.double(args, source), ".lcomm" => self.lcomm(args, source), ".comm" => self.comm(args, source), ".skip" | ".space" => self.space(args, source), - ".asciz" => self.asciz(args, source), - _ => todo!("{}", sym), + ".asciz" | ".string" => self.asciz(args, source), + ".ascii" => self.ascii(args, source), + ".hidden" => self.hidden(args, source), + ".fn" => self.dir_fn(args, source), + ".endfn" => self.dir_endfn(args, source), + ".obj" => self.dir_obj(args, source), + ".endobj" => self.dir_endobj(args, source), + ".sym" => self.dir_sym(args, source), + ".endsym" => self.dir_endsym(args, source), + ".set" => self.dir_set(args, source), + ".type" => self.dir_type(args, source), + ".size" => self.dir_size(args, source), + ".file" => self.dir_file(args, source), + ".rel" => self.dir_rel(args, source), + _ => Err(ParseError { + message: format!("unknown directive '{}'", sym), + diagnostics: vec![ParseErrorDiagnostic { + source, + message: format!("unknown directive"), + color: Color::Red, + }], + note: None, + }), } } else { - // TODO - // println!("Stub for {}", sym); - for arg in args { - match arg.arg { - Arg::Offset(base, offs) => { - let base_result = self.evaluate(base)?; - let offs_result = self.evaluate(offs)?; - } - Arg::Relocation(base, kind) => { - let base_result = self.evaluate(base)?; - } - Arg::RelocationWithOffset(base, kind, offs) => { - let base_result = self.evaluate(base)?; - let offs_result = self.evaluate(offs)?; - } - Arg::Expression(expr) => { - let result = self.evaluate(expr)?; - } - } - } - self.fill_data(4, 0)?; - Ok(()) + self.assemble_instruction(&sym, args, source) } } } } + fn assemble_instruction( + &mut self, + mnemonic: &str, + args: StatementArgs, + source: SourceInfo, + ) -> AnalyzerResult<()> { + let instruction_offset = self.section_offset; + let mut asm_args: Arguments = [Argument::None; 5]; + let mut arg_idx = 0; + let mut pending_branch_reloc: Option<(CompactString, i32)> = None; + + for arg in args { + match arg.arg { + Arg::Expression(expr) => { + let result = self.evaluate(expr)?; + match result { + ExpressionResult::Number(n) => { + if n < 0 { + asm_args[arg_idx] = Argument::Signed(n as i32); + } else { + asm_args[arg_idx] = Argument::Unsigned(n as u32); + } + arg_idx += 1; + } + ExpressionResult::Relocation(sym, addend) => { + let is_local_label = sym.starts_with(".L"); + if is_local_label { + // Local label: try to resolve inline + if let Some(offset) = + self.resolve_same_section(&sym, addend, instruction_offset) + { + asm_args[arg_idx] = Argument::Signed(offset); + arg_idx += 1; + } else { + // Forward ref to local label: fixup later + asm_args[arg_idx] = Argument::Signed(0); + arg_idx += 1; + pending_branch_reloc = Some((sym, addend)); + } + } else { + // Named symbol: always emit relocation + asm_args[arg_idx] = Argument::Signed(0); + arg_idx += 1; + pending_branch_reloc = Some((sym, addend)); + } + } + ExpressionResult::Float(_) | ExpressionResult::Double(_) => { + return Err(ParseError { + message: format!("float not valid in instruction argument"), + diagnostics: vec![], + note: None, + }); + } + } + } + Arg::Offset(disp, reg) => { + let disp_result = self.evaluate(disp)?; + let reg_result = self.evaluate(reg)?; + match disp_result { + ExpressionResult::Number(n) => { + if n < 0 { + asm_args[arg_idx] = Argument::Signed(n as i32); + } else { + asm_args[arg_idx] = Argument::Unsigned(n as u32); + } + } + _ => { + return Err(ParseError { + message: format!("expected numeric displacement"), + diagnostics: vec![], + note: None, + }); + } + } + arg_idx += 1; + match reg_result { + ExpressionResult::Number(n) => { + asm_args[arg_idx] = Argument::Unsigned(n as u32); + } + _ => { + return Err(ParseError { + message: format!("expected register"), + diagnostics: vec![], + note: None, + }); + } + } + arg_idx += 1; + } + Arg::Relocation(expr, kind) => { + let (sym, addend) = self.evaluate_reloc(expr)?; + match kind { + RelocationKind::Sda21 => { + // Bare @sda21 without register: only add the immediate + // value (0). The linker patches both the register field + // and offset via R_PPC_EMB_SDA21. + asm_args[arg_idx] = Argument::Signed(0); + arg_idx += 1; + self.relocations.push(Relocation { + sym, + addend, + kind, + section: self.current_section.clone().unwrap(), + offset: instruction_offset, + }); + } + RelocationKind::Ha | RelocationKind::H | RelocationKind::L => { + asm_args[arg_idx] = Argument::Signed(0); + arg_idx += 1; + self.relocations.push(Relocation { + sym, + addend, + kind, + section: self.current_section.clone().unwrap(), + offset: instruction_offset + 2, + }); + } + _ => { + return Err(ParseError { + message: format!("unexpected relocation kind in instruction"), + diagnostics: vec![], + note: None, + }); + } + } + } + Arg::RelocationWithOffset(expr, kind, offs_expr) => { + let (sym, addend) = self.evaluate_reloc(expr)?; + let offs_result = self.evaluate(offs_expr)?; + let reg = match offs_result { + ExpressionResult::Number(n) => n as u32, + _ => { + return Err(ParseError { + message: format!("expected register"), + diagnostics: vec![], + note: None, + }); + } + }; + match kind { + RelocationKind::Sda21 => { + asm_args[arg_idx] = Argument::Signed(0); + arg_idx += 1; + asm_args[arg_idx] = Argument::Unsigned(reg); + arg_idx += 1; + self.relocations.push(Relocation { + sym, + addend, + kind, + section: self.current_section.clone().unwrap(), + offset: instruction_offset, + }); + } + RelocationKind::Ha | RelocationKind::H | RelocationKind::L => { + asm_args[arg_idx] = Argument::Signed(0); + arg_idx += 1; + asm_args[arg_idx] = Argument::Unsigned(reg); + arg_idx += 1; + self.relocations.push(Relocation { + sym, + addend, + kind, + section: self.current_section.clone().unwrap(), + offset: instruction_offset + 2, + }); + } + _ => { + return Err(ParseError { + message: format!("unexpected relocation kind"), + diagnostics: vec![], + note: None, + }); + } + } + } + } + } + + // Assemble the instruction + let encoded = powerpc_asm::assemble(mnemonic, &asm_args).map_err(|e| ParseError { + message: format!("assembly error for '{}': {:?}", mnemonic, e), + diagnostics: vec![ParseErrorDiagnostic { + source, + message: format!("failed to assemble"), + color: Color::Red, + }], + note: None, + })?; + + self.write_data(&encoded.to_be_bytes())?; + + // Handle unresolved branch relocations + if let Some((sym, addend)) = pending_branch_reloc { + let opcode = (encoded >> 26) & 0x3F; + let _reloc_kind = match opcode { + 18 => RelocationKind::Rel24, + 16 => RelocationKind::Rel14, + _ => RelocationKind::Absolute, + }; + self.branch_fixups.push(BranchFixup { + section: self.current_section.clone().unwrap(), + offset: instruction_offset, + target_sym: sym, + addend, + }); + } + + Ok(()) + } + + /// Try to resolve a symbol reference to a same-section relative offset. + /// Returns Some(offset) if the symbol is defined in the same section. + fn resolve_same_section( + &self, + sym: &CompactString, + addend: i32, + instruction_offset: u32, + ) -> Option { + let defsym = self.symbols.get(sym)?; + let sym_section = defsym.section.as_ref()?; + let cur_section = self.current_section.as_ref()?; + if sym_section != cur_section { + return None; + } + let addr = defsym.address?; + Some((addr as i64 - instruction_offset as i64 + addend as i64) as i32) + } + + /// Resolve branch fixups after all statements have been processed. + fn resolve_branch_fixups(&mut self) -> AnalyzerResult<()> { + let fixups = take(&mut self.branch_fixups); + for fixup in fixups { + let is_local_label = fixup.target_sym.starts_with(".L"); + + // Determine the relocation kind from the encoded instruction + let data = match self.section_data.get(&fixup.section) { + Some(d) => d, + None => continue, + }; + let off = fixup.offset as usize; + let insn = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]); + let opcode = (insn >> 26) & 0x3F; + let reloc_kind = match opcode { + 18 => RelocationKind::Rel24, + 16 => RelocationKind::Rel14, + _ => RelocationKind::Absolute, + }; + + // For local labels in the same section: patch inline, no relocation + if is_local_label { + if let Some(defsym) = self.symbols.get(&fixup.target_sym) { + if let (Some(sym_section), Some(addr)) = (&defsym.section, defsym.address) { + if *sym_section == fixup.section { + let offset = + (addr as i64 - fixup.offset as i64 + fixup.addend as i64) as i32; + let data = self.section_data.get_mut(&fixup.section).unwrap(); + let patched = match opcode { + 18 => (insn & !0x03FFFFFC) | ((offset as u32) & 0x03FFFFFC), + 16 => (insn & !0x0000FFFC) | ((offset as u32) & 0x0000FFFC), + _ => insn, + }; + let bytes = patched.to_be_bytes(); + data[off] = bytes[0]; + data[off + 1] = bytes[1]; + data[off + 2] = bytes[2]; + data[off + 3] = bytes[3]; + continue; + } + } + } + } + + // Named symbols or unresolved: emit relocation + self.relocations.push(Relocation { + sym: fixup.target_sym, + addend: fixup.addend, + kind: reloc_kind, + section: fixup.section, + offset: fixup.offset, + }); + } + Ok(()) + } + fn label(&mut self, sym: CompactString, source: SourceInfo) -> AnalyzerResult<()> { if let Some(defsym) = self.symbols.get_mut(&sym) { if defsym.address.is_some() { return Err(errors::symbol_redefinition(defsym, source)); } - // println!( - // "Updating {} to address {} (section {})", - // sym, - // self.section_offset, - // self.current_section.as_ref().unwrap() - // ); defsym.section = self.current_section.clone(); defsym.address = Some(self.section_offset); } else { - // println!( - // "Creating {} at address {} (section {})", - // sym, - // self.section_offset, - // self.current_section.as_ref().unwrap() - // ); self.symbols.insert(sym.clone(), DefSym { name: sym, section: self.current_section.clone(), @@ -194,39 +596,68 @@ impl Analyzer { Ok(()) } - fn section(&mut self, mut args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + fn section(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { let mut name = CompactString::default(); let mut flags = CompactString::default(); - let mut kind = CompactString::default(); + let mut sec_type = CompactString::default(); + let mut unique_id: Option = None; + let mut saw_unique = false; for (idx, arg) in args.into_iter().enumerate() { + if saw_unique { + if let Ok(n) = self.expect_absolute(arg) { + unique_id = Some(n); + } + saw_unique = false; + continue; + } match idx { 0 => name = self.expect_symbol(arg)?, - 1 => flags = self.expect_string(arg)?, - 2 => kind = self.expect_constant(arg)?, - _ => return Err(errors::extra_arg(".section", &arg)), + 1 => { + if let Ok(s) = self.expect_string(arg) { + flags = s; + } + } + _ => { + match arg.arg { + Arg::Expression(Expression::Operand(Operand::Symbol( + Symbol::Regular(ref sym), + _, + ))) => { + if sym.as_str() == "unique" { + saw_unique = true; + } else { + // @nobits, @progbits, etc. + sec_type = sym.clone(); + } + } + _ => {} + } + } } } - // TODO use flags, type - self.switch_section(name)?; + // For unique sections, use an internal key that distinguishes instances + let internal_name = if let Some(id) = unique_id { + CompactString::from(format!("{}\0{}", name, id)) + } else { + name.clone() + }; + // Determine section info from flags/type, falling back to name + let info = if !flags.is_empty() { + section_info_from_flags(&flags, &sec_type) + } else { + section_info_from_name(&name) + }; + self.section_info.insert(internal_name.clone(), info); + self.switch_section(internal_name)?; Ok(()) } - fn byte4(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + fn byte4(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { if args.is_empty() { - // Defaults to 0 return self.write_data(&[0u8; 4]); } for value in args { match value.arg { - Arg::Offset(expr, offs) => { - todo!() - } - Arg::Relocation(expr, kind) => { - todo!() - } - Arg::RelocationWithOffset(expr, kind, offs) => { - todo!() - } Arg::Expression(expr) => { let result = self.evaluate(expr)?; match result { @@ -236,7 +667,6 @@ impl Analyzer { ExpressionResult::Float(_) | ExpressionResult::Double(_) => { return Err(ParseError { message: format!("float value not permitted for .4byte"), - // TODO diagnostics: vec![], note: None, }); @@ -248,33 +678,26 @@ impl Analyzer { kind: RelocationKind::Absolute, section: self.current_section.clone().unwrap(), offset: self.section_offset, - size: 4, }); self.write_data(&[0u8; 4])?; } } } + _ => { + return Err(ParseError { + message: format!("unexpected argument type for .4byte"), + diagnostics: vec![], + note: None, + }); + } } } Ok(()) } - fn byte2(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { - // if args.is_empty() { - // // Defaults to 0 - // return self.write_data(&[0u8; 2]); - // } + fn byte2(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { for value in args { match value.arg { - Arg::Offset(expr, offs) => { - todo!() - } - Arg::Relocation(expr, kind) => { - todo!() - } - Arg::RelocationWithOffset(expr, kind, offs) => { - todo!() - } Arg::Expression(expr) => { let result = self.evaluate(expr)?; match result { @@ -284,7 +707,6 @@ impl Analyzer { ExpressionResult::Float(_) | ExpressionResult::Double(_) => { return Err(ParseError { message: format!("float value not permitted for .2byte"), - // TODO diagnostics: vec![], note: None, }); @@ -296,43 +718,35 @@ impl Analyzer { kind: RelocationKind::Absolute, section: self.current_section.clone().unwrap(), offset: self.section_offset, - size: 2, }); self.write_data(&[0u8; 2])?; } } } + _ => { + return Err(ParseError { + message: format!("unexpected argument type for .2byte"), + diagnostics: vec![], + note: None, + }); + } } } Ok(()) } - fn byte(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { - // if args.is_empty() { - // // Defaults to 0 - // return self.write_data(&[0u8; 1]); - // } + fn byte(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { for value in args { match value.arg { - Arg::Offset(expr, offs) => { - todo!() - } - Arg::Relocation(expr, kind) => { - todo!() - } - Arg::RelocationWithOffset(expr, kind, offs) => { - todo!() - } Arg::Expression(expr) => { let result = self.evaluate(expr)?; match result { ExpressionResult::Number(n) => { - self.write_data(&(n as u8).to_be_bytes())?; + self.write_data(&[n as u8])?; } ExpressionResult::Float(_) | ExpressionResult::Double(_) => { return Err(ParseError { message: format!("float value not permitted for .byte"), - // TODO diagnostics: vec![], note: None, }); @@ -344,46 +758,33 @@ impl Analyzer { kind: RelocationKind::Absolute, section: self.current_section.clone().unwrap(), offset: self.section_offset, - size: 1, }); self.write_data(&[0u8; 1])?; } } } + _ => { + return Err(ParseError { + message: format!("unexpected argument type for .byte"), + diagnostics: vec![], + note: None, + }); + } } } Ok(()) } - fn float(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + fn float(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { for value in args { match value.arg { Arg::Expression(expr) => { - let result = self.evaluate(expr)?; - match result { - ExpressionResult::Number(n) => { - self.write_data(&(n as f32).to_be_bytes())?; - } - ExpressionResult::Float(n) => { - self.write_data(&n.to_be_bytes())?; - } - ExpressionResult::Double(n) => { - self.write_data(&(n as f32).to_be_bytes())?; - } - ExpressionResult::Relocation(_, _) => { - return Err(ParseError { - message: format!("expected float value"), - // TODO - diagnostics: vec![], - note: None, - }); - } - } + let f = self.evaluate_as_f64(expr)?; + self.write_data(&(f as f32).to_be_bytes())?; } _ => { return Err(ParseError { message: format!("expected float value"), - // TODO diagnostics: vec![], note: None, }); @@ -393,35 +794,16 @@ impl Analyzer { Ok(()) } - fn double(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + fn double(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { for value in args { match value.arg { Arg::Expression(expr) => { - let result = self.evaluate(expr)?; - match result { - ExpressionResult::Number(n) => { - self.write_data(&(n as f64).to_be_bytes())?; - } - ExpressionResult::Float(n) => { - self.write_data(&(n as f64).to_be_bytes())?; - } - ExpressionResult::Double(n) => { - self.write_data(&n.to_be_bytes())?; - } - ExpressionResult::Relocation(_, _) => { - return Err(ParseError { - message: format!("expected double value"), - // TODO - diagnostics: vec![], - note: None, - }); - } - } + let f = self.evaluate_as_f64(expr)?; + self.write_data(&f.to_be_bytes())?; } _ => { return Err(ParseError { message: format!("expected double value"), - // TODO diagnostics: vec![], note: None, }); @@ -431,19 +813,55 @@ impl Analyzer { Ok(()) } - fn asciz(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + fn asciz(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { for value in args { let str = self.expect_string(value)?; - self.write_data(str.as_bytes())?; + // Convert chars to raw bytes: each char U+0000-U+00FF maps to a single byte. + // This is necessary because octal/hex escapes like \203 produce U+0083 in the + // Rust string, but we need the raw byte 0x83 in the output. + let bytes: Vec = str.chars().map(|c| c as u8).collect(); + self.write_data(&bytes)?; self.fill_data(1, 0)?; } Ok(()) } + fn ascii(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { + for value in args { + let str = self.expect_string(value)?; + let bytes: Vec = str.chars().map(|c| c as u8).collect(); + self.write_data(&bytes)?; + // No null terminator (unlike .asciz/.string) + } + Ok(()) + } + + fn hidden(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + for arg in args { + let sym = self.expect_symbol(arg)?; + match self.symbols.entry(sym.clone()) { + Entry::Occupied(mut entry) => { + entry.get_mut().visibility = Visibility::Hidden; + } + Entry::Vacant(entry) => { + entry.insert(DefSym { + name: sym, + section: None, + address: None, + size: None, + kind: None, + visibility: Visibility::Hidden, + source, + }); + } + } + } + Ok(()) + } + fn balign(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { const USAGE: &str = "usage: .balign [align[, fill]]"; let add_usage = |mut e: ParseError| { - // e.note = Some(USAGE.into()); e.diagnostics.push(ParseErrorDiagnostic { source, message: USAGE.into(), @@ -461,9 +879,11 @@ impl Analyzer { _ => return Err(errors::extra_arg(".balign", &arg)), } } - let count = ((self.section_offset + align - 1) & !(align - 1)) - self.section_offset; - if count > 0 { - self.fill_data(count, fill)?; + if align > 0 { + let count = ((self.section_offset + align - 1) & !(align - 1)) - self.section_offset; + if count > 0 { + self.fill_data(count, fill)?; + } } Ok(()) } @@ -494,6 +914,52 @@ impl Analyzer { Ok(()) } + fn local(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + for arg in args { + let sym = self.expect_symbol(arg)?; + match self.symbols.entry(sym.clone()) { + Entry::Occupied(mut entry) => { + entry.get_mut().visibility = Visibility::Local; + } + Entry::Vacant(entry) => { + entry.insert(DefSym { + name: sym, + section: None, + address: None, + size: None, + kind: None, + visibility: Visibility::Local, + source, + }); + } + } + } + Ok(()) + } + + fn weak(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + for arg in args { + let sym = self.expect_symbol(arg)?; + match self.symbols.entry(sym.clone()) { + Entry::Occupied(mut entry) => { + entry.get_mut().visibility = Visibility::Weak; + } + Entry::Vacant(entry) => { + entry.insert(DefSym { + name: sym, + section: None, + address: None, + size: None, + kind: None, + visibility: Visibility::Weak, + source, + }); + } + } + } + Ok(()) + } + fn lcomm(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { let mut sym = CompactString::default(); let mut size = 0u32; @@ -510,7 +976,14 @@ impl Analyzer { } } let prev_section = self.current_section.clone(); - self.switch_section(".bss".into())?; + self.switch_section_named(".bss")?; + // Align the current offset before placing the symbol + if align > 1 { + let padding = ((self.section_offset + align - 1) & !(align - 1)) - self.section_offset; + if padding > 0 { + self.fill_data(padding, 0)?; + } + } match self.symbols.entry(sym.clone()) { Entry::Occupied(mut entry) => { let defsym = entry.get_mut(); @@ -556,40 +1029,37 @@ impl Analyzer { _ => return Err(errors::extra_arg(".comm", &arg)), } } - let prev_section = self.current_section.clone(); - self.switch_section(".comm".into())?; + // Common symbols don't live in a section; the linker allocates space. + // st_value = alignment, st_size = size, st_shndx = SHN_COMMON match self.symbols.entry(sym.clone()) { Entry::Occupied(mut entry) => { let defsym = entry.get_mut(); if defsym.address.is_some() { return Err(errors::symbol_redefinition(defsym, source)); } - defsym.address = Some(self.section_offset); - defsym.section = self.current_section.clone(); + defsym.address = Some(align); + defsym.size = Some(size); + defsym.kind = Some(SymbolKind::Common); } Entry::Vacant(entry) => { entry.insert(DefSym { name: sym, - section: self.current_section.clone(), - address: Some(self.section_offset), + section: None, + address: Some(align), size: Some(size), - kind: None, + kind: Some(SymbolKind::Common), visibility: Visibility::Global, source, }); } } - self.fill_data(size, 0)?; - if let Some(section) = prev_section { - self.switch_section(section)?; - } Ok(()) } fn space(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { let mut size = 0u32; let mut fill = 0u8; - if args.len() < 1 { + if args.is_empty() { return Err(errors::missing_arg(".space", 1, &args, source)); } for (idx, arg) in args.into_iter().enumerate() { @@ -603,13 +1073,246 @@ impl Analyzer { Ok(()) } + // .fn name, visibility + fn dir_fn(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + let mut vis = Visibility::Local; + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + 1 => { + let v = self.expect_symbol(arg)?; + vis = parse_visibility(&v); + } + _ => return Err(errors::extra_arg(".fn", &arg)), + } + } + self.set_symbol_vis_and_kind(&name, vis, Some(SymbolKind::Function), source)?; + self.label(name, source) + } + + fn dir_endfn(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + _ => return Err(errors::extra_arg(".endfn", &arg)), + } + } + self.set_symbol_size(&name, source) + } + + // .obj name, visibility + fn dir_obj(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + let mut vis = Visibility::Local; + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + 1 => { + let v = self.expect_symbol(arg)?; + vis = parse_visibility(&v); + } + _ => return Err(errors::extra_arg(".obj", &arg)), + } + } + self.set_symbol_vis_and_kind(&name, vis, Some(SymbolKind::Object), source)?; + self.label(name, source) + } + + fn dir_endobj(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + _ => return Err(errors::extra_arg(".endobj", &arg)), + } + } + self.set_symbol_size(&name, source) + } + + // .sym name, visibility + fn dir_sym(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + let mut vis = Visibility::Local; + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + 1 => { + let v = self.expect_symbol(arg)?; + vis = parse_visibility(&v); + } + _ => return Err(errors::extra_arg(".sym", &arg)), + } + } + self.set_symbol_vis_and_kind(&name, vis, None, source)?; + self.label(name, source) + } + + fn dir_endsym(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + _ => return Err(errors::extra_arg(".endsym", &arg)), + } + } + self.set_symbol_size(&name, source) + } + + // .set name, value + fn dir_set(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + let mut value = 0i64; + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + 1 => value = self.expect_absolute(arg)?, + _ => return Err(errors::extra_arg(".set", &arg)), + } + } + self.replacements.insert(name, Operand::Number(value, SourceInfo::new(0))); + Ok(()) + } + + // .type name, @function/@object + fn dir_type(&mut self, args: StatementArgs, source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + let mut type_str = CompactString::default(); + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + 1 => type_str = self.expect_constant(arg)?, + _ => return Err(errors::extra_arg(".type", &arg)), + } + } + let kind = match type_str.as_str() { + "@function" => Some(SymbolKind::Function), + "@object" => Some(SymbolKind::Object), + _ => None, + }; + if let Some(defsym) = self.symbols.get_mut(&name) { + defsym.kind = kind; + } else { + self.symbols.insert(name.clone(), DefSym { + name, + section: None, + address: None, + size: None, + kind, + visibility: Visibility::Local, + source, + }); + } + Ok(()) + } + + // .size name, expr + fn dir_size(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { + let mut name = CompactString::default(); + let mut size_val = 0u32; + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => name = self.expect_symbol(arg)?, + 1 => size_val = self.expect_absolute(arg)? as u32, + _ => return Err(errors::extra_arg(".size", &arg)), + } + } + if let Some(defsym) = self.symbols.get_mut(&name) { + defsym.size = Some(size_val); + } + Ok(()) + } + + // .file "name" + fn dir_file(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => { + let name = self.expect_string(arg)?; + self.file_name = Some(name); + } + _ => {} // ignore extra args + } + } + Ok(()) + } + + // .rel sym1, sym2 - emit a 4-byte relocation targeting sym1 with + // addend = sym2_addr - sym1_addr (decomp-toolkit convention) + fn dir_rel(&mut self, args: StatementArgs, _source: SourceInfo) -> AnalyzerResult<()> { + let mut sym1 = CompactString::default(); + let mut sym2 = CompactString::default(); + for (idx, arg) in args.into_iter().enumerate() { + match idx { + 0 => sym1 = self.expect_symbol(arg)?, + 1 => sym2 = self.expect_symbol(arg)?, + _ => return Err(errors::extra_arg(".rel", &arg)), + } + } + let addend = match (self.symbols.get(&sym1), self.symbols.get(&sym2)) { + (Some(def1), Some(def2)) => match (def1.address, def2.address) { + (Some(a1), Some(a2)) => (a2 as i32) - (a1 as i32), + _ => 0, + }, + _ => 0, + }; + self.relocations.push(Relocation { + sym: sym1, + addend, + kind: RelocationKind::Absolute, + section: self.current_section.clone().unwrap(), + offset: self.section_offset, + }); + self.write_data(&[0u8; 4])?; + Ok(()) + } + + fn set_symbol_vis_and_kind( + &mut self, + name: &CompactString, + vis: Visibility, + kind: Option, + source: SourceInfo, + ) -> AnalyzerResult<()> { + match self.symbols.entry(name.clone()) { + Entry::Occupied(mut entry) => { + let defsym = entry.get_mut(); + defsym.visibility = vis; + if kind.is_some() { + defsym.kind = kind; + } + } + Entry::Vacant(entry) => { + entry.insert(DefSym { + name: name.clone(), + section: None, + address: None, + size: None, + kind, + visibility: vis, + source, + }); + } + } + Ok(()) + } + + fn set_symbol_size(&mut self, name: &CompactString, _source: SourceInfo) -> AnalyzerResult<()> { + if let Some(defsym) = self.symbols.get_mut(name) { + if let Some(addr) = defsym.address { + defsym.size = Some(self.section_offset - addr); + } + } + Ok(()) + } + fn write_data(&mut self, bytes: &[u8]) -> AnalyzerResult<()> { let curr_section = self.current_section.clone().ok_or(ParseError { message: "[internal] write_data called without a section".into(), diagnostics: vec![], note: None, })?; - // println!("Writing {} bytes to {}", bytes.len(), curr_section); self.section_data.entry(curr_section).or_default().extend_from_slice(bytes); self.section_offset += bytes.len() as u32; Ok(()) @@ -621,7 +1324,6 @@ impl Analyzer { diagnostics: vec![], note: None, })?; - // println!("Filling {} bytes in {}", count, curr_section); let data = self.section_data.entry(curr_section).or_default(); data.resize(data.len() + count as usize, value); self.section_offset += count; @@ -643,9 +1345,7 @@ impl Analyzer { BinaryOpKind::Or => Ok(ExpressionResult::Number(l | r)), BinaryOpKind::And => Ok(ExpressionResult::Number(l & r)), BinaryOpKind::Xor => Ok(ExpressionResult::Number(l ^ r)), - BinaryOpKind::Nor => { - todo!() - } + BinaryOpKind::Nor => Ok(ExpressionResult::Number(!(l | r))), BinaryOpKind::Add => Ok(ExpressionResult::Number(l + r)), BinaryOpKind::Sub => Ok(ExpressionResult::Number(l - r)), BinaryOpKind::Eq => { @@ -686,7 +1386,6 @@ impl Analyzer { "can't perform {:?} between relocation and number", kind, ), - // TODO diagnostics: vec![], note: None, }), @@ -697,15 +1396,53 @@ impl Analyzer { BinaryOpKind::Add => { Ok(ExpressionResult::Relocation(sym, (l + r as i64) as i32)) } + _ => Err(ParseError { + message: format!( + "can't perform {:?} between number and relocation", + kind, + ), + diagnostics: vec![], + note: None, + }), + } + } + ( + ExpressionResult::Relocation(sym_l, add_l), + ExpressionResult::Relocation(sym_r, add_r), + ) => { + match kind { BinaryOpKind::Sub => { - Ok(ExpressionResult::Relocation(sym, (l - r as i64) as i32)) + // sym_l - sym_r: if both in same section, resolves to a number + if let (Some(def_l), Some(def_r)) = + (self.symbols.get(&sym_l), self.symbols.get(&sym_r)) + { + if let (Some(sec_l), Some(sec_r), Some(addr_l), Some(addr_r)) = ( + &def_l.section, + &def_r.section, + def_l.address, + def_r.address, + ) { + if sec_l == sec_r { + return Ok(ExpressionResult::Number( + addr_l as i64 - addr_r as i64 + add_l as i64 + - add_r as i64, + )); + } + } + } + Err(ParseError { + message: format!( + "can't subtract relocations from different sections" + ), + diagnostics: vec![], + note: None, + }) } _ => Err(ParseError { message: format!( - "can't perform {:?} between relocation and number", + "can't perform {:?} between two relocations", kind, ), - // TODO diagnostics: vec![], note: None, }), @@ -718,7 +1455,6 @@ impl Analyzer { discriminant(&left), discriminant(&right) ), - // TODO diagnostics: vec![], note: None, }), @@ -737,7 +1473,6 @@ impl Analyzer { UnaryOpKind::Neg => Ok(ExpressionResult::Float(-n)), _ => Err(ParseError { message: format!("can't perform {:?} on float", kind), - // TODO diagnostics: vec![], note: None, }), @@ -747,39 +1482,39 @@ impl Analyzer { UnaryOpKind::Neg => Ok(ExpressionResult::Double(-n)), _ => Err(ParseError { message: format!("can't perform {:?} on float", kind), - // TODO diagnostics: vec![], note: None, }), }, - ExpressionResult::Relocation(_, _) => Err(ParseError { - message: format!("can't perform {:?} on relocation", kind), - // TODO - diagnostics: vec![], - note: None, - }), + ExpressionResult::Relocation(sym, addend) => match kind { + // +reloc is a no-op (handles branch hints like beq+ label) + UnaryOpKind::Pos => Ok(ExpressionResult::Relocation(sym, addend)), + // -reloc makes sense for same-section diffs (handled elsewhere) + UnaryOpKind::Neg => Ok(ExpressionResult::Relocation(sym, -addend)), + _ => Err(ParseError { + message: format!("can't perform {:?} on relocation", kind), + diagnostics: vec![], + note: None, + }), + }, } } Expression::Operand(operand) => { match operand { - Operand::Number(n, source) => Ok(ExpressionResult::Number(n)), - Operand::Float(n, source) => Ok(ExpressionResult::Float(n)), - Operand::Double(n, source) => Ok(ExpressionResult::Double(n)), - Operand::Symbol(sym, source) => { + Operand::Number(n, _source) => Ok(ExpressionResult::Number(n)), + Operand::Float(n, _source) => Ok(ExpressionResult::Float(n)), + Operand::Double(n, _source) => Ok(ExpressionResult::Double(n)), + Operand::Symbol(sym, _source) => { let sym = match sym { Symbol::Regular(sym) => { - match sym.as_str() { - "." => { - return Ok(ExpressionResult::Number( - self.section_offset as i64, - )) - } - _ => {} + if sym.as_str() == "." { + return Ok(ExpressionResult::Number( + self.section_offset as i64, + )); } if let Some(replacement) = self.replacements.get(&sym) { return match replacement { Operand::Number(n, _) => Ok(ExpressionResult::Number(*n)), - // TODO: just number?? Operand::Float(n, _) => Ok(ExpressionResult::Float(*n)), Operand::Double(n, _) => Ok(ExpressionResult::Double(*n)), Operand::Symbol(_, _) => unreachable!(), @@ -790,7 +1525,13 @@ impl Analyzer { } Symbol::Quoted(sym) => sym, }; + // Always return Relocation for symbols that have a section + // (labels), so call sites can decide how to handle them. + // Symbols without a section and with an address are .set constants. if let Some(defsym) = self.symbols.get(&sym) { + if defsym.section.is_some() { + return Ok(ExpressionResult::Relocation(sym, 0)); + } if let Some(addr) = defsym.address { return Ok(ExpressionResult::Number(addr as i64)); } @@ -803,6 +1544,54 @@ impl Analyzer { } } + /// Evaluate an expression as f64, preserving negative zero. + /// Used by .float and .double directives. + fn evaluate_as_f64(&mut self, expr: Expression) -> AnalyzerResult { + // Handle unary negation specially to preserve -0.0 + if let Expression::UnaryOp(UnaryOpKind::Neg, inner) = expr { + let val = self.evaluate_as_f64(*inner)?; + return Ok(-val); + } + let result = self.evaluate(expr)?; + match result { + ExpressionResult::Number(n) => Ok(n as f64), + ExpressionResult::Float(n) => Ok(n as f64), + ExpressionResult::Double(n) => Ok(n), + ExpressionResult::Relocation(_, _) => Err(ParseError { + message: format!("expected numeric value for float/double"), + diagnostics: vec![], + note: None, + }), + } + } + + /// Evaluate an expression in a relocation context. + /// Unlike regular evaluate(), this treats bare symbol names as relocations + /// even if they match a replacement (register name), since `sym@ha` means + /// "the symbol named sym", not "register number". + fn evaluate_reloc(&mut self, expr: Expression) -> AnalyzerResult<(CompactString, i32)> { + // For simple symbol operands, return the name directly + match &expr { + Expression::Operand(Operand::Symbol(Symbol::Regular(sym), _)) => { + return Ok((sym.clone(), 0)); + } + Expression::Operand(Operand::Symbol(Symbol::Quoted(sym), _)) => { + return Ok((sym.clone(), 0)); + } + _ => {} + } + // For complex expressions, evaluate and expect a Relocation result + let result = self.evaluate(expr)?; + match result { + ExpressionResult::Relocation(sym, addend) => Ok((sym, addend)), + _ => Err(ParseError { + message: format!("expected symbol for relocation"), + diagnostics: vec![], + note: None, + }), + } + } + /// Expect an absolute value fn expect_absolute(&mut self, value: ArgWithSource) -> AnalyzerResult { let result = match value.arg { @@ -845,71 +1634,172 @@ impl Analyzer { } } + /// Switch to a well-known section by name, registering its info from defaults. + fn switch_section_named(&mut self, name: &str) -> AnalyzerResult<()> { + let name: CompactString = name.into(); + if !self.section_info.contains_key(&name) { + self.section_info.insert(name.clone(), section_info_from_name(&name)); + } + self.switch_section(name) + } + fn switch_section(&mut self, name: CompactString) -> AnalyzerResult<()> { self.section_offset = self.section_data.get(&name).map(|v| v.len() as u32).unwrap_or_default(); - // println!("Entering section {} (offset {:#X})", name, self.section_offset); + if !self.section_order.contains(&name) { + self.section_order.push(name.clone()); + } self.current_section = Some(name); Ok(()) } } +/// Determine section kind and alignment from flags and type. +/// `flags` is the GAS flags string (e.g. "ax", "wa", "a"). +/// `sec_type` is the GAS type (e.g. "@nobits", "@progbits", or empty). +fn section_info_from_flags(flags: &str, sec_type: &str) -> SectionInfo { + let is_nobits = sec_type == "@nobits"; + let has_x = flags.contains('x'); + let has_w = flags.contains('w'); + + let kind = if is_nobits { + object::SectionKind::UninitializedData + } else if has_x { + object::SectionKind::Text + } else if has_w { + object::SectionKind::Data + } else { + object::SectionKind::ReadOnlyData + }; + + let align = if has_x { 4 } else { 8 }; + SectionInfo { kind, align } +} + +/// Determine section kind and alignment from a well-known section name. +fn section_info_from_name(name: &str) -> SectionInfo { + match name { + ".text" | ".init" => SectionInfo { kind: object::SectionKind::Text, align: 4 }, + ".data" | ".sdata" => SectionInfo { kind: object::SectionKind::Data, align: 8 }, + ".rodata" | ".sdata2" => SectionInfo { kind: object::SectionKind::ReadOnlyData, align: 8 }, + ".bss" | ".sbss" | ".sbss2" => { + SectionInfo { kind: object::SectionKind::UninitializedData, align: 8 } + } + ".ctors" | ".dtors" => SectionInfo { kind: object::SectionKind::Data, align: 4 }, + _ => SectionInfo { kind: object::SectionKind::Data, align: 4 }, + } +} + +fn parse_visibility(s: &str) -> Visibility { + match s { + "global" => Visibility::Global, + "weak" => Visibility::Weak, + _ => Visibility::Local, + } +} + fn main() -> Result<(), Box> { - let path = Path::new("/home/lstreet/Development/prime/asm/MetroidPrime/Player/CPlayerGun.s"); - let filename = - path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or("[unknown]".into()); - let result = fs::read_to_string(path)?; + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: ppcasm [-o output.o]"); + process::exit(1); + } + + let input_path = PathBuf::from(&args[1]); + let output_path = if let Some(pos) = args.iter().position(|a| a == "-o") { + if pos + 1 < args.len() { + PathBuf::from(&args[pos + 1]) + } else { + eprintln!("Error: -o requires an argument"); + process::exit(1); + } + } else { + input_path.with_extension("o") + }; + + let filename = input_path + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or("[unknown]".into()); + let result = fs::read_to_string(&input_path)?; let mut parser = Parser::new(result.as_str()); let mut analyzer = Analyzer::new(); - loop { + let mut had_error = false; + while !parser.at_end() { let stmt = match parser.statement() { Ok(stmt) => stmt, Err(e) => { - print_error(e, filename.as_str(), result.as_str())?; + print_error(&e, filename.as_str(), result.as_str())?; + had_error = true; break; } }; - // println!("{:?}", stmt); match analyzer.process(stmt) { Ok(_) => {} Err(e) => { - print_error(e, filename.as_str(), result.as_str())?; + print_error(&e, filename.as_str(), result.as_str())?; + had_error = true; break; } } } + if had_error { + process::exit(1); + } + // Resolve forward branch references + analyzer.resolve_branch_fixups()?; + + // Build ELF object let mut obj = object::write::Object::new(BinaryFormat::Elf, Architecture::PowerPc, Endianness::Big); + + // Add file symbol if .file was encountered + if let Some(ref file_name) = analyzer.file_name { + obj.add_file_symbol(file_name.as_bytes().to_vec()); + } + let mut section_map = HashMap::::new(); - for (section_name, data) in analyzer.section_data { - let (kind, align) = match section_name.as_str() { - ".text" => (object::SectionKind::Text, 4), - ".data" => (object::SectionKind::Data, 8), - ".rodata" => (object::SectionKind::ReadOnlyData, 8), - ".sdata" => (object::SectionKind::Data, 8), - ".sdata2" => (object::SectionKind::ReadOnlyData, 8), - ".bss" => (object::SectionKind::UninitializedData, 8), - ".sbss" => (object::SectionKind::UninitializedData, 8), - ".sbss2" => (object::SectionKind::UninitializedData, 8), - ".ctors" => (object::SectionKind::Data, 4), - ".comm" => continue, - _ => todo!("{}", section_name), + for section_key in &analyzer.section_order { + let data = match analyzer.section_data.get(section_key) { + Some(d) => d, + None => continue, }; + // Extract the original section name (strip unique ID suffix after \0) + let section_name = match section_key.find('\0') { + Some(pos) => §ion_key[..pos], + None => section_key.as_str(), + }; + if section_name == ".comm" { + continue; + } + let info = analyzer + .section_info + .get(section_key) + .unwrap_or(&SectionInfo { kind: object::SectionKind::Data, align: 4 }); + let kind = info.kind; + let align = info.align; let id = obj.add_section(Vec::new(), section_name.as_bytes().to_vec(), kind); - section_map.insert(section_name, id); + section_map.insert(section_key.clone(), id); let section = obj.section_mut(id); match kind { object::SectionKind::UninitializedData => { section.append_bss(data.len() as u64, align); } _ => { - section.set_data(data, align); + section.set_data(data.clone(), align); } } } - for (_, defsym) in analyzer.symbols { - obj.add_symbol(object::write::Symbol { + + // Add symbols (skip local labels like .L_xxx) + let mut symbol_ids = HashMap::::new(); + for (name, defsym) in &analyzer.symbols { + // Skip local labels - they don't appear in the output symbol table + if name.starts_with(".L") && defsym.visibility == Visibility::Local { + continue; + } + let sym_id = obj.add_symbol(object::write::Symbol { name: defsym.name.as_bytes().to_vec(), value: defsym.address.unwrap_or_default() as u64, size: defsym.size.unwrap_or_default() as u64, @@ -921,14 +1811,14 @@ fn main() -> Result<(), Box> { }, scope: match defsym.visibility { Visibility::Local => object::SymbolScope::Compilation, - Visibility::Global => object::SymbolScope::Compilation, - Visibility::Weak => object::SymbolScope::Compilation, + Visibility::Global | Visibility::Weak => object::SymbolScope::Dynamic, + Visibility::Hidden => object::SymbolScope::Linkage, }, weak: defsym.visibility == Visibility::Weak, section: match defsym.kind { Some(SymbolKind::Common) => object::write::SymbolSection::Common, _ => { - match defsym.section.and_then(|name| section_map.get(name.as_str()).cloned()) { + match defsym.section.as_ref().and_then(|name| section_map.get(name).cloned()) { None => object::write::SymbolSection::Undefined, Some(id) => object::write::SymbolSection::Section(id), } @@ -936,93 +1826,71 @@ fn main() -> Result<(), Box> { }, flags: object::SymbolFlags::None, }); + symbol_ids.insert(name.clone(), sym_id); } - for reloc in analyzer.relocations { + + // Sort relocations by offset for deterministic output + analyzer.relocations.sort_by_key(|r| r.offset); + + // Add relocations + for reloc in &analyzer.relocations { let section_id = match section_map.get(reloc.section.as_str()).cloned() { Some(id) => id, - None => continue, // TODO warn + None => continue, }; - let symbol_id = match obj.symbol_id(reloc.sym.as_bytes()) { - // TODO: why not hit? - Some(id) => id, - None => obj.add_symbol(object::write::Symbol { - name: reloc.sym.as_bytes().to_vec(), - value: 0, - size: 0, - kind: object::SymbolKind::Unknown, - scope: object::SymbolScope::Unknown, - weak: false, - section: object::write::SymbolSection::Undefined, - flags: object::SymbolFlags::None, - }), + let symbol_id = match symbol_ids.get(&reloc.sym) { + Some(&id) => id, + None => { + // External symbol - add as undefined (and cache it) + let id = obj.add_symbol(object::write::Symbol { + name: reloc.sym.as_bytes().to_vec(), + value: 0, + size: 0, + kind: object::SymbolKind::Unknown, + scope: object::SymbolScope::Unknown, + weak: false, + section: object::write::SymbolSection::Undefined, + flags: object::SymbolFlags::None, + }); + symbol_ids.insert(reloc.sym.clone(), id); + id + } + }; + let r_type = match reloc.kind { + RelocationKind::Absolute => R_PPC_ADDR32, + RelocationKind::Sda21 => R_PPC_EMB_SDA21, + RelocationKind::Ha => R_PPC_ADDR16_HA, + RelocationKind::H => R_PPC_ADDR16_HI, + RelocationKind::L => R_PPC_ADDR16_LO, + RelocationKind::Rel24 => R_PPC_REL24, + RelocationKind::Rel14 => R_PPC_REL14, }; obj.add_relocation(section_id, object::write::Relocation { offset: reloc.offset as u64, - size: reloc.size, - kind: match reloc.kind { - RelocationKind::Absolute => object::RelocationKind::Elf(R_PPC_ADDR32), - RelocationKind::Sda21 => object::RelocationKind::Elf(R_PPC_EMB_SDA21), - RelocationKind::Ha => object::RelocationKind::Elf(R_PPC_ADDR16_HA), - RelocationKind::L => object::RelocationKind::Elf(R_PPC_ADDR16_LO), - }, - encoding: object::RelocationEncoding::Generic, symbol: symbol_id, addend: reloc.addend as i64, + flags: RelocationFlags::Elf { r_type }, })?; - // println!("reloc: {:?}", reloc); } - obj.write_stream(BufWriter::new(File::create("test.o")?))?; - // for i in 0..100 { - // let mut parser = Parser::new(result.as_str()); - // loop { - // let stmt = match parser.statement() { - // Ok(stmt) => stmt, - // Err(e) => { - // // print_error(e, filename.as_str(), result.as_str())?; - // break; - // } - // }; - // } - // } + obj.write_stream(BufWriter::new(File::create(&output_path)?))?; Ok(()) } -fn print_error(e: ParseError, filename: &str, contents: &str) -> std::io::Result<()> { +fn print_error(e: &ParseError, filename: &str, contents: &str) -> std::io::Result<()> { let begin = e.diagnostics.first().map(|d| d.source.range().start).unwrap_or_default(); - let mut report = Report::build(ReportKind::Error, filename, begin).with_message(e.message); - for (i, diag) in e.diagnostics.into_iter().enumerate() { + let mut report = + Report::build(ReportKind::Error, filename, begin).with_message(e.message.clone()); + for (i, diag) in e.diagnostics.iter().enumerate() { report.add_label( Label::new((filename, diag.source.range())) - .with_message(diag.message) + .with_message(diag.message.clone()) .with_color(diag.color) .with_order(i as i32), ); } - if let Some(note) = e.note { - report = report.with_note(note); + if let Some(ref note) = e.note { + report = report.with_note(note.clone()); } report.finish().print((filename, Source::from(contents))) } - -#[cfg(test)] -mod tests { - use test::Bencher; - - use super::*; - - #[bench] - fn bench_parse(b: &mut Bencher) { - let path = - Path::new("/home/lstreet/Development/prime/asm/MetroidPrime/Player/CPlayerGun.s"); - let result = fs::read_to_string(path).unwrap(); - b.iter(|| { - let mut parser = Parser::new(result.as_str()); - loop { - if parser.statement().is_err() { - break; - } - } - }); - } -} diff --git a/src/parser.rs b/src/parser.rs index e85fb8a..139f04d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,13 +1,11 @@ use core::mem::take; use std::{ - error::Error, fmt::{Display, Formatter}, iter::Peekable, - ops::Range, str::Chars, }; -use ariadne::{Color, ColorGenerator, Fmt}; +use ariadne::{ColorGenerator, Fmt}; use compact_str::CompactString; use pratt::{Affix, Associativity, PrattParser, Precedence, Result as PrattResult}; use smallvec::SmallVec; @@ -109,12 +107,15 @@ enum ExpressionToken { Operand(Operand), } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone)] pub(crate) enum RelocationKind { Absolute, Sda21, Ha, + H, L, + Rel24, + Rel14, } #[derive(Debug, PartialEq)] @@ -170,6 +171,12 @@ impl<'a> Parser<'a> { Self { inner: s.chars().peekable(), peek_char: None, position: 0 } } + /// Returns true if there are no more statements to parse. + pub(crate) fn at_end(&mut self) -> bool { + self.skip_newlines(); + self.peek().is_none() + } + fn next(&mut self) -> Option { if let Some(c) = self.peek_char.take() { return Some(c); @@ -278,7 +285,7 @@ impl<'a> Parser<'a> { fn symbol(&mut self) -> ParseResult { self.skip(); - let mut source = self.begin_source(); + let source = self.begin_source(); let c = self.next().ok_or_else(|| { let mut colors = ColorGenerator::new(); let a = colors.next(); @@ -327,8 +334,42 @@ impl<'a> Parser<'a> { self.advance(); result.push('"'); } - Some('0') => todo!("octal"), - Some('x') => todo!("hex"), + Some(c @ '0'..='7') => { + self.advance(); + let mut val = c as u32 - '0' as u32; + for _ in 0..2 { + match self.peek() { + Some(d @ '0'..='7') => { + self.advance(); + val = val * 8 + (d as u32 - '0' as u32); + } + _ => break, + } + } + result.push(char::from(val as u8)); + } + Some('x') => { + self.advance(); + let mut val = 0u32; + for _ in 0..2 { + match self.peek() { + Some(d @ '0'..='9') => { + self.advance(); + val = val * 16 + (d as u32 - '0' as u32); + } + Some(d @ 'a'..='f') => { + self.advance(); + val = val * 16 + (d as u32 - 'a' as u32 + 10); + } + Some(d @ 'A'..='F') => { + self.advance(); + val = val * 16 + (d as u32 - 'A' as u32 + 10); + } + _ => break, + } + } + result.push(char::from(val as u8)); + } Some(c) => { let mut colors = ColorGenerator::new(); let a = colors.next(); @@ -422,6 +463,17 @@ impl<'a> Parser<'a> { self.skip_newlines(); let mut source = self.begin_source(); let mut symbol = self.symbol()?; + // Absorb branch prediction hints (+/-) that follow the mnemonic + // e.g. "beq+" or "bne-" + match self.peek() { + Some(c @ '+') | Some(c @ '-') => { + if let Symbol::Regular(ref mut s) = symbol { + s.push(c); + self.advance(); + } + } + _ => {} + } self.end_source(&mut source); self.skip(); match self.peek() { @@ -484,6 +536,7 @@ impl<'a> Parser<'a> { let kind = match symbol.string().to_ascii_lowercase().as_str() { "sda21" => RelocationKind::Sda21, "ha" => RelocationKind::Ha, + "h" => RelocationKind::H, "l" => RelocationKind::L, _ => { let mut colors = ColorGenerator::new(); @@ -514,7 +567,7 @@ impl<'a> Parser<'a> { }; Ok(match self.peek() { Some('(') => { - let mut source = self.begin_source(); + let source = self.begin_source(); self.advance(); let offset_expr = self.expression()?; match self.peek() { @@ -589,7 +642,7 @@ impl<'a> Parser<'a> { let mut source = self.begin_source(); match self.peek() { Some('(') => { - let mut source = self.begin_source(); + let source = self.begin_source(); self.advance(); let expr = self.expression()?; self.skip(); @@ -710,6 +763,12 @@ impl<'a> Parser<'a> { if let Ok(v) = i64::from_str_radix(result.as_str(), radix) { return Ok(Operand::Number(v, source)); } + // If i64 overflows for a decimal number, try f64 + if radix == 10 { + if let Ok(v) = result.parse::() { + return Ok(Operand::Double(v, source)); + } + } } return Ok(Operand::Symbol(Symbol::Regular(result), source)); }