From 5e4aa72feef7e6ae457c90a00ffd077953b91a41 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Fri, 17 Apr 2026 23:33:05 +0800 Subject: [PATCH 01/13] lynx feature --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index b88550f..2447be2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ itertools = "0.13.0" [features] atari2600 = [] atari7800 = [] +atarilynx = [] From 3fefc87beb41c564614759a6aec7124ba9bbdbb2 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:57:56 +0800 Subject: [PATCH 02/13] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ From 68a492a5af06ecdd9f3645c601d7abb13e3499fb Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:58:35 +0800 Subject: [PATCH 03/13] Lynx memory model --- src/cc6502.pest | 3 +- src/compile.rs | 123 +++++++++++++++++-------- src/generate/generate_asm.rs | 18 ++++ src/tests/build.rs | 172 ++++++++++++++++++----------------- 4 files changed, 198 insertions(+), 118 deletions(-) diff --git a/src/cc6502.pest b/src/cc6502.pest index f8c1892..3d78462 100644 --- a/src/cc6502.pest +++ b/src/cc6502.pest @@ -30,13 +30,14 @@ decl = { enclosed_decl } enclosed_decl = { bank ~ "{" ~ decl+ ~ "}" } var_decl = { var_type ~ global_id ~ ("," ~ global_id)* ~ ";"+ } -var_type = ${ ((var_const | superchip | ramchip | ramplus | bank | aligned | display | frequency | reversed | scattered | holeydma | noholeydma | screencode | nopagecross) ~ WHITESPACE+ )* ~ (var_sign ~ WHITESPACE+)? ~ var_simple_type ~ WHITESPACE+ } +var_type = ${ ((var_const | superchip | zp | ramchip | ramplus | bank | aligned | display | frequency | reversed | scattered | holeydma | noholeydma | screencode | nopagecross) ~ WHITESPACE+ )* ~ (var_sign ~ WHITESPACE+)? ~ var_simple_type ~ WHITESPACE+ } local_var_decl = { local_var_decl_const | local_var_decl_mut } local_var_decl_const = { "const" ~ var_type ~ global_id ~ ("," ~ global_id)* ~ ";" } local_var_decl_mut = { local_var_type ~ local_id ~ ("," ~ local_id)* ~ ";" } local_var_type = ${ (var_sign ~ WHITESPACE+)? ~ var_simple_type ~ WHITESPACE+ } var_const = { "const" } superchip = { "superchip" } +zp = { "zp" } ramchip = { "ramchip" } ramplus = { "ramplus" } display = { "display" } diff --git a/src/compile.rs b/src/compile.rs index dfcc3c2..d230d90 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -49,6 +49,14 @@ pub enum VariableType { ShortPtr, } +#[cfg(feature = "atarilynx")] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum VariableMemory { + Zeropage, + RAM, +} + +#[cfg(not(feature = "atarilynx"))] #[derive(Debug, Copy, Clone, PartialEq)] pub enum VariableMemory { ROM(u32), @@ -184,7 +192,7 @@ pub(crate) enum Statement<'a> { #[derive(Debug, Clone)] pub struct StatementLoc<'a> { - pub(crate) pos: usize, + pub pos: usize, pub(crate) label: Option, pub(crate) statement: Statement<'a>, } @@ -390,7 +398,10 @@ impl<'a> CompilerState<'a> { Variable { order: self.variables.len(), signed: false, + #[cfg(not(feature = "atarilynx"))] memory: VariableMemory::ROM(0), + #[cfg(feature = "atarilynx")] + memory: VariableMemory::RAM, var_const: true, alignment: 1, def: VariableDefinition::Array(v), @@ -555,7 +566,10 @@ impl<'a> CompilerState<'a> { Variable { order: self.variables.len(), signed: false, + #[cfg(not(feature = "atarilynx"))] memory: VariableMemory::ROM(0), + #[cfg(feature = "atarilynx")] + memory: VariableMemory::RAM, var_const: true, alignment: 1, def: VariableDefinition::Array(v), @@ -1116,7 +1130,10 @@ impl<'a> CompilerState<'a> { let mut var_const_ex = !global; let mut signedness_specified = false; let mut signed = self.signed_chars; + #[cfg(not(feature = "atarilynx"))] let mut memory = VariableMemory::Zeropage; + #[cfg(feature = "atarilynx")] + let mut memory = VariableMemory::RAM; let mut alignment = 1; let mut reversed = false; let mut scattered = None; @@ -1131,6 +1148,7 @@ impl<'a> CompilerState<'a> { for p in pair.into_inner() { //debug!("{:?}", p); let start = p.as_span().start(); + #[cfg(not(feature = "atarilynx"))] match p.as_rule() { Rule::var_const => { var_const_ex = true; @@ -1220,6 +1238,10 @@ impl<'a> CompilerState<'a> { } _ => unreachable!(), } + #[cfg(feature = "atarilynx")] + if p.as_rule() == Rule::zp { + memory = VariableMemory::Zeropage; + } } } Rule::global_id => { @@ -1290,16 +1312,21 @@ impl<'a> CompilerState<'a> { start = px.as_span().start(); match px.as_rule() { Rule::calc_expr => { - if !set_const { - return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); - } - let vx = self.parse_calc(px.into_inner())?; - def = VariableDefinition::Value(VariableValue::Int(vx)); - if var_type == VariableType::CharPtr && vx > 0xff { - memory = VariableMemory::Ramchip; + #[cfg(not(feature = "atarilynx"))] + { + if !set_const { + return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); + } + let vx = self.parse_calc(px.into_inner())?; + def = VariableDefinition::Value(VariableValue::Int(vx)); + + if var_type == VariableType::CharPtr && vx > 0xff { + memory = VariableMemory::Ramchip; + } } } Rule::var_ptr => { + #[cfg(not(feature = "atarilynx"))] if !set_const { return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); } @@ -1338,7 +1365,11 @@ impl<'a> CompilerState<'a> { } } Rule::ptr_offset => { - let sign = if x.as_str().starts_with("-") { -1 } else { 1 }; + let sign = if x.as_str().starts_with("-") { + -1 + } else { + 1 + }; let offset = parse_int( x.into_inner() .next() @@ -1385,23 +1416,27 @@ impl<'a> CompilerState<'a> { }); } Rule::array_def => { - if !set_const { - return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); - } - memory = match memory { - VariableMemory::ROM(_) - | VariableMemory::Display - | VariableMemory::Frequency => memory, - _ => { - if let Some(bank) = self.function_bank { - VariableMemory::ROM(bank) - } else if let Some(bank) = self.default_bank { - VariableMemory::ROM(bank) - } else { - VariableMemory::ROM(0) - } + #[cfg(not(feature = "atarilynx"))] + { + if !set_const { + return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); } - }; + memory = match memory { + VariableMemory::ROM(_) + | VariableMemory::Display + | VariableMemory::Frequency => memory, + _ => { + if let Some(bank) = self.function_bank { + VariableMemory::ROM(bank) + } else if let Some(bank) = self.default_bank { + VariableMemory::ROM(bank) + } else { + VariableMemory::ROM(0) + } + } + }; + } + if var_type != VariableType::CharPtr && var_type != VariableType::CharPtrPtr && var_type != VariableType::ShortPtr @@ -1501,7 +1536,7 @@ impl<'a> CompilerState<'a> { match pxx.as_rule() { Rule::calc_expr => v.push(( "__address__".into(), - self.parse_calc(pxx.into_inner())? + self.parse_calc(pxx.into_inner())?, )), Rule::var_ptr => { let mut pxxx = pxx.into_inner(); @@ -1591,15 +1626,19 @@ impl<'a> CompilerState<'a> { } } Rule::quoted_string => { - if !set_const { - return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); + #[cfg(not(feature = "atarilynx"))] + { + if !set_const { + return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); + } + memory = match memory { + VariableMemory::ROM(_) + | VariableMemory::Display + | VariableMemory::Frequency => memory, + _ => VariableMemory::ROM(0), + }; } - memory = match memory { - VariableMemory::ROM(_) - | VariableMemory::Display - | VariableMemory::Frequency => memory, - _ => VariableMemory::ROM(0), - }; + if var_type != VariableType::CharPtr { return Err(self.syntax_error( "String provided for something not a char*", @@ -1643,6 +1682,7 @@ impl<'a> CompilerState<'a> { // If there is no definition, then it's not ROM, it's a variable in RAM on the cart if def == VariableDefinition::None { + #[cfg(not(feature = "atarilynx"))] if let VariableMemory::ROM(bank) = memory { memory = VariableMemory::MemoryOnChip(bank); } @@ -1717,7 +1757,10 @@ impl<'a> CompilerState<'a> { let var_const_ex = false; let mut signedness_specified = false; let mut signed = self.signed_chars; - let memory = VariableMemory::Zeropage; + #[cfg(not(feature = "atarilynx"))] + let mut memory = VariableMemory::Zeropage; + #[cfg(feature = "atarilynx")] + let memory = VariableMemory::RAM; let alignment = 1; let reversed = false; let scattered = None; @@ -1992,7 +2035,10 @@ impl<'a> CompilerState<'a> { Variable { order: self.variables.len(), signed: false, + #[cfg(not(feature = "atarilynx"))] memory: VariableMemory::Dummy, + #[cfg(feature = "atarilynx")] + memory: VariableMemory::RAM, var_const: true, alignment: 1, def: VariableDefinition::None, @@ -2049,7 +2095,10 @@ impl<'a> CompilerState<'a> { let mut var_const = false; let mut signedness_specified = false; let mut signed = self.signed_chars; - let memory = VariableMemory::Zeropage; + #[cfg(not(feature = "atarilynx"))] + let mut memory = VariableMemory::Zeropage; + #[cfg(feature = "atarilynx")] + let memory = VariableMemory::RAM; let alignment = 1; let reversed = false; let scattered = None; @@ -2381,6 +2430,8 @@ pub fn compile( context.define("__ATARI2600__", "1"); #[cfg(feature = "atari7800")] context.define("__ATARI7800__", "1"); + #[cfg(feature = "atarilynx")] + context.define("__ATARILYNX__", "1"); for i in &args.defines { let mut s = i.splitn(2, '='); let def = s.next().unwrap(); diff --git a/src/generate/generate_asm.rs b/src/generate/generate_asm.rs index 1804739..3b5e77c 100644 --- a/src/generate/generate_asm.rs +++ b/src/generate/generate_asm.rs @@ -148,6 +148,8 @@ impl<'a> GeneratorState<'a> { ExprType::Absolute(variable, eight_bits, off) => { let v = self.compiler_state.get_variable(variable); signed = v.signed; + + #[cfg(not(feature = "atarilynx"))] let offset = if v.memory == VariableMemory::Superchip { match mnemonic { STA | STX | STY => *off, @@ -170,6 +172,10 @@ impl<'a> GeneratorState<'a> { } else { *off }; + + #[cfg(feature = "atarilynx")] + let offset = *off; + match v.var_type { VariableType::Char => { if !*eight_bits { @@ -281,6 +287,8 @@ impl<'a> GeneratorState<'a> { let mut indirect = false; let v = self.compiler_state.get_variable(variable); signed = v.signed; + + #[cfg(not(feature = "atarilynx"))] let offset = if v.memory == VariableMemory::Superchip { match mnemonic { STA | STX | STY => 0, @@ -303,6 +311,10 @@ impl<'a> GeneratorState<'a> { } else { 0 }; + + #[cfg(feature = "atarilynx")] + let offset = 0; + if v.var_type == VariableType::CharPtrPtr || v.var_type == VariableType::ShortPtr { let off = offset + if high_byte { v.size } else { 0 }; if off > 0 { @@ -424,6 +436,8 @@ impl<'a> GeneratorState<'a> { ExprType::AbsoluteX(variable) => { let v = self.compiler_state.get_variable(variable); signed = v.signed; + + #[cfg(not(feature = "atarilynx"))] let offset = if v.memory == VariableMemory::Superchip { match mnemonic { STA | STX | STY => 0, @@ -446,6 +460,10 @@ impl<'a> GeneratorState<'a> { } else { 0 }; + + #[cfg(feature = "atarilynx")] + let offset = 0; + if v.var_type == VariableType::CharPtr && !v.var_const && v.size == 1 { return Err(self.compiler_state.syntax_error( "Y-Indirect adressing mode not available with X register", diff --git a/src/tests/build.rs b/src/tests/build.rs index 9ffdc91..9625a3a 100644 --- a/src/tests/build.rs +++ b/src/tests/build.rs @@ -68,6 +68,7 @@ pub fn simple_build(compiler_state: &CompilerState, writer: &mut dyn Write, args // Try to figure out what is the bankswitching method // Let's identitfy superchip + #[cfg(not(feature = "atarilynx"))] for v in compiler_state.sorted_variables().iter() { if !v.1.var_const && v.1.memory == VariableMemory::Superchip && v.1.def == VariableDefinition::None { superchip = true; @@ -289,6 +290,7 @@ pub fn simple_build(compiler_state: &CompilerState, writer: &mut dyn Write, args level += 1; } + #[cfg(not(feature = "atarilynx"))] if superchip { gstate.write("\n\tSEG.U SUPERVARS\n\tORG $1000\n\tRORG $1000\n")?; // Superchip variables @@ -317,6 +319,7 @@ pub fn simple_build(compiler_state: &CompilerState, writer: &mut dyn Write, args } // Generate RAM for 3E bankswitching scheme + #[cfg(not(feature = "atarilynx"))] if bankswitching_scheme == "3E" { for bank in 1..=512 { // Max 512ko let mut first = true; @@ -350,6 +353,7 @@ pub fn simple_build(compiler_state: &CompilerState, writer: &mut dyn Write, args } // Generate RAM for 3E+ bankswitching scheme + #[cfg(not(feature = "atarilynx"))] if bankswitching_scheme == "3EP" { for bank in 0..64 { // Max 32ko let mut first = true; @@ -491,98 +495,101 @@ Powerup } // Generate ROM tables - gstate.write("\n; Tables in ROM\n")?; - for v in compiler_state.sorted_variables().iter() { - if let VariableMemory::ROM(rom_bank) = v.1.memory { - if rom_bank == bank { - match &v.1.def { - VariableDefinition::Array(arr) => { - if v.1.alignment != 1 { - gstate.write(&format!("\n\talign {}\n", v.1.alignment))?; - } - gstate.write(v.0)?; - let mut counter = 0; - for vx in arr { - match vx { - VariableValue::Int(i) => { + #[cfg(not(feature = "atarilynx"))] + { + gstate.write("\n; Tables in ROM\n")?; + for v in compiler_state.sorted_variables().iter() { + if let VariableMemory::ROM(rom_bank) = v.1.memory { + if rom_bank == bank { + match &v.1.def { + VariableDefinition::Array(arr) => { + if v.1.alignment != 1 { + gstate.write(&format!("\n\talign {}\n", v.1.alignment))?; + } + gstate.write(v.0)?; + let mut counter = 0; + for vx in arr { + match vx { + VariableValue::Int(i) => { + if counter == 0 { + gstate.write("\n\thex ")?; + } + counter += 1; + if counter == 16 { counter = 0; } + gstate.write(&format!("{:02x}", i & 0xff)) + }, + VariableValue::LowPtr((s, offset)) => { + counter = 0; + if *offset != 0 { + gstate.write(&format!("\n\t.byte <({} + {})", s, offset)) + } else { + gstate.write(&format!("\n\t.byte <{}", s)) + } + }, + VariableValue::HiPtr((s, offset)) => { + counter = 0; + if *offset != 0 { + gstate.write(&format!("\n\t.byte >({} + {})", s, offset)) + } else { + gstate.write(&format!("\n\t.byte >{}", s)) + } + }, + }?; + } + if v.1.var_type == VariableType::ShortPtr { + for vx in arr { if counter == 0 { gstate.write("\n\thex ")?; } counter += 1; if counter == 16 { counter = 0; } - gstate.write(&format!("{:02x}", i & 0xff)) - }, - VariableValue::LowPtr((s, offset)) => { - counter = 0; - if *offset != 0 { - gstate.write(&format!("\n\t.byte <({} + {})", s, offset)) - } else { - gstate.write(&format!("\n\t.byte <{}", s)) - } - }, - VariableValue::HiPtr((s, offset)) => { - counter = 0; - if *offset != 0 { - gstate.write(&format!("\n\t.byte >({} + {})", s, offset)) - } else { - gstate.write(&format!("\n\t.byte >{}", s)) + if let VariableValue::Int(i) = vx { + gstate.write(&format!("{:02x}", (i >> 8) & 0xff))?; } - }, - }?; - } - if v.1.var_type == VariableType::ShortPtr { - for vx in arr { - if counter == 0 { - gstate.write("\n\thex ")?; + } + } + gstate.write("\n")?; + }, + VariableDefinition::ArrayOfPointers(arr) => { + if v.1.alignment != 1 { + gstate.write(&format!("\n\talign {}\n", v.1.alignment))?; + } + gstate.write(v.0)?; + + let mut counter = 0; + for i in arr { + if counter % 8 == 0 { + gstate.write("\n\t.byte ")?; } counter += 1; - if counter == 16 { counter = 0; } - if let VariableValue::Int(i) = vx { - gstate.write(&format!("{:02x}", (i >> 8) & 0xff))?; + if i.1 != 0 { + gstate.write(&format!("<({} + {})", i.0, i.1))?; + } else { + gstate.write(&format!("<{}", i.0))?; } + if counter % 8 != 0 { + gstate.write(", ")?; + } } - } - gstate.write("\n")?; - }, - VariableDefinition::ArrayOfPointers(arr) => { - if v.1.alignment != 1 { - gstate.write(&format!("\n\talign {}\n", v.1.alignment))?; - } - gstate.write(v.0)?; - - let mut counter = 0; - for i in arr { - if counter % 8 == 0 { - gstate.write("\n\t.byte ")?; - } - counter += 1; - if i.1 != 0 { - gstate.write(&format!("<({} + {})", i.0, i.1))?; - } else { - gstate.write(&format!("<{}", i.0))?; - } - if counter % 8 != 0 { - gstate.write(", ")?; - } - } - for i in arr { - if counter % 8 == 0 { - gstate.write("\n\t.byte ")?; - } - counter += 1; - if i.1 != 0 { - gstate.write(&format!(">({} + {})", i.0, i.1))?; - } else { - gstate.write(&format!(">{}", i.0))?; - } - if counter % 8 != 0 && counter < 2 * arr.len() { - gstate.write(", ")?; + for i in arr { + if counter % 8 == 0 { + gstate.write("\n\t.byte ")?; + } + counter += 1; + if i.1 != 0 { + gstate.write(&format!(">({} + {})", i.0, i.1))?; + } else { + gstate.write(&format!(">{}", i.0))?; + } + if counter % 8 != 0 && counter < 2 * arr.len() { + gstate.write(", ")?; + } } - } - gstate.write("\n")?; - }, - _ => () - }; + gstate.write("\n")?; + }, + _ => () + }; + } } } } @@ -660,6 +667,7 @@ Call{} let starting_code = if maxbank > 0 && bank != 0 { "Start" } else { "Powerup" }; + #[cfg(not(feature = "atarilynx"))] if b == maxbank && compiler_state.variables.get("PLUSROM_API").is_some() { let v = compiler_state.get_variable("PLUSROM_API"); let offset = match v.memory { @@ -713,6 +721,7 @@ Call{} } } + #[cfg(not(feature = "atarilynx"))] if bankswitching_scheme == "DPC" { gstate.write(" SEG DISPLAY @@ -752,6 +761,7 @@ Call{} ")?; } + #[cfg(not(feature = "atarilynx"))] if bankswitching_scheme == "DPC+" { gstate.write(" SEG DISPLAY From b2c7776a303336649661fb2825a54d3c3c0a4f71 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sat, 18 Apr 2026 21:13:06 +0800 Subject: [PATCH 04/13] Make use of the bank keyword for Lynx "file" loading --- src/compile.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index d230d90..b0e2b33 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -53,7 +53,7 @@ pub enum VariableType { #[derive(Debug, Copy, Clone, PartialEq)] pub enum VariableMemory { Zeropage, - RAM, + Bank(u32), } #[cfg(not(feature = "atarilynx"))] @@ -401,7 +401,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] memory: VariableMemory::ROM(0), #[cfg(feature = "atarilynx")] - memory: VariableMemory::RAM, + memory: VariableMemory::Bank(0), var_const: true, alignment: 1, def: VariableDefinition::Array(v), @@ -569,7 +569,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] memory: VariableMemory::ROM(0), #[cfg(feature = "atarilynx")] - memory: VariableMemory::RAM, + memory: VariableMemory::Bank(0), var_const: true, alignment: 1, def: VariableDefinition::Array(v), @@ -1133,7 +1133,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] let mut memory = VariableMemory::Zeropage; #[cfg(feature = "atarilynx")] - let mut memory = VariableMemory::RAM; + let mut memory = VariableMemory::Bank(0); let mut alignment = 1; let mut reversed = false; let mut scattered = None; @@ -1239,8 +1239,21 @@ impl<'a> CompilerState<'a> { _ => unreachable!(), } #[cfg(feature = "atarilynx")] - if p.as_rule() == Rule::zp { - memory = VariableMemory::Zeropage; + match p.as_rule() { + Rule::zp => { + memory = VariableMemory::Zeropage; + } + Rule::bank => { + memory = VariableMemory::Bank( + p.into_inner() + .next() + .unwrap() + .as_str() + .parse::() + .unwrap(), + ) + } + _ => (), } } } @@ -1314,12 +1327,12 @@ impl<'a> CompilerState<'a> { Rule::calc_expr => { #[cfg(not(feature = "atarilynx"))] { - if !set_const { + if !set_const { return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); } let vx = self.parse_calc(px.into_inner())?; def = VariableDefinition::Value(VariableValue::Int(vx)); - + if var_type == VariableType::CharPtr && vx > 0xff { memory = VariableMemory::Ramchip; } @@ -1419,7 +1432,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] { if !set_const { - return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); + return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); } memory = match memory { VariableMemory::ROM(_) @@ -1760,7 +1773,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] let mut memory = VariableMemory::Zeropage; #[cfg(feature = "atarilynx")] - let memory = VariableMemory::RAM; + let memory = VariableMemory::Bank(0); let alignment = 1; let reversed = false; let scattered = None; @@ -2038,7 +2051,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] memory: VariableMemory::Dummy, #[cfg(feature = "atarilynx")] - memory: VariableMemory::RAM, + memory: VariableMemory::Bank(0), var_const: true, alignment: 1, def: VariableDefinition::None, @@ -2098,7 +2111,7 @@ impl<'a> CompilerState<'a> { #[cfg(not(feature = "atarilynx"))] let mut memory = VariableMemory::Zeropage; #[cfg(feature = "atarilynx")] - let memory = VariableMemory::RAM; + let memory = VariableMemory::Bank(0); let alignment = 1; let reversed = false; let scattered = None; From f35d83047b441c491aee42d06fd2add914af6b6f Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:08:40 +0800 Subject: [PATCH 05/13] Lynx bank org declaration --- src/cc6502.pest | 2 ++ src/compile.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/cc6502.pest b/src/cc6502.pest index 3d78462..d9f1410 100644 --- a/src/cc6502.pest +++ b/src/cc6502.pest @@ -27,6 +27,7 @@ decl = { enclosed_decl | func_vec_decl | func_decl | included_assembler + | bank_org_decl } enclosed_decl = { bank ~ "{" ~ decl+ ~ "}" } var_decl = { var_type ~ global_id ~ ("," ~ global_id)* ~ ";"+ } @@ -35,6 +36,7 @@ local_var_decl = { local_var_decl_const | local_var_decl_mut } local_var_decl_const = { "const" ~ var_type ~ global_id ~ ("," ~ global_id)* ~ ";" } local_var_decl_mut = { local_var_type ~ local_id ~ ("," ~ local_id)* ~ ";" } local_var_type = ${ (var_sign ~ WHITESPACE+)? ~ var_simple_type ~ WHITESPACE+ } +bank_org_decl = { bank ~ "org" ~ int ~ ";" } var_const = { "const" } superchip = { "superchip" } zp = { "zp" } diff --git a/src/compile.rs b/src/compile.rs index b0e2b33..da6aef6 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -228,6 +228,8 @@ pub struct CompilerState<'a> { variable_counter: u32, default_bank: Option, function_bank: Option, + #[cfg(feature = "atarilynx")] + pub bank_orgs: HashMap, } impl<'a> CompilerState<'a> { @@ -2278,6 +2280,19 @@ impl<'a> CompilerState<'a> { self.included_assembler .push((str.into(), filename, codesize, bank)); } + #[cfg(feature = "atarilynx")] + Rule::bank_org_decl => { + let mut bank_number = 0; + let mut bank_org = 0; + for p in pair.into_inner() { + match p.as_rule() { + Rule::bank => bank_number = p.into_inner().next().unwrap().as_str().parse::().unwrap(), + Rule::int => bank_org = parse_int(p.into_inner().next().unwrap()) as u16, + _ => (), + } + } + self.bank_orgs.insert(bank_number, bank_org); + } _ => { debug!("What's this ? {:?}", pair); unreachable!() @@ -2478,6 +2493,8 @@ pub fn compile( variable_counter: 0, default_bank: None, function_bank: None, + #[cfg(feature = "atarilynx")] + bank_orgs: HashMap::new(), }; let r = Cc2600Parser::parse(Rule::program, preprocessed_utf8); From 8e09bfa1b7e6fee03eee3a8adb12d1226042337c Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:35:07 +0800 Subject: [PATCH 06/13] Lynx: disable "jsr Call{}" --- src/generate/generate_statements.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/generate/generate_statements.rs b/src/generate/generate_statements.rs index e24ee74..2d0f304 100644 --- a/src/generate/generate_statements.rs +++ b/src/generate/generate_statements.rs @@ -247,12 +247,20 @@ impl<'a> GeneratorState<'a> { )?; self.asm(JSR, &ExprType::Label(var.clone()), pos, false)?; } else { + #[cfg(not(feature = "atarilynx"))] self.asm( JSR, &ExprType::Label(format!("Call{}", *var)), pos, false, )?; + #[cfg(feature = "atarilynx")] + self.asm( + JSR, + &ExprType::Label(format!("{}", *var)), + pos, + false, + )?; } } else { return Err(self.compiler_state.syntax_error( From d10fb839c15bcc92f653ac549580620b4836c4b3 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:58:02 +0800 Subject: [PATCH 07/13] Disable bank check for lynx --- src/generate/generate_statements.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/generate/generate_statements.rs b/src/generate/generate_statements.rs index 2d0f304..c509e1c 100644 --- a/src/generate/generate_statements.rs +++ b/src/generate/generate_statements.rs @@ -207,6 +207,7 @@ impl<'a> GeneratorState<'a> { } else if f.bank == self.current_bank || self.bankswitching_scheme == "3EP" || (self.bankswitching_scheme.starts_with("SuperGame") + || cfg!(feature = "atarilynx") && (f.bank == 0 || f.bank == fixed_bank)) { self.asm(JSR, &ExprType::Label(var.clone()), pos, false)?; @@ -229,7 +230,8 @@ impl<'a> GeneratorState<'a> { } else { return Err(self.compiler_state.syntax_error("Banked code can only be called from bank 0 or same bank", pos)); } - } else if self.current_bank == 0 || self.current_bank == fixed_bank + } + else if self.current_bank == 0 || self.current_bank == fixed_bank || cfg!(feature = "atarilynx") { // Generate bankswitching call if self.bankswitching_scheme.starts_with("SuperGame") { From 2581d56d55b05ca9cb3681d450af540ee86c12c8 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:15:10 +0800 Subject: [PATCH 08/13] Enable var_const for Lynx --- src/compile.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index da6aef6..9b6de08 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1255,6 +1255,23 @@ impl<'a> CompilerState<'a> { .unwrap(), ) } + Rule::var_const => { + var_const_ex = true; + set_const_ex = true; + } + Rule::var_sign => { + signed = p.as_str().eq("signed"); + signedness_specified = true; + } + Rule::var_simple_type => { + if p.as_str().starts_with("short") || p.as_str().starts_with("int") + { + var_type_ex = VariableType::Short; + if !signedness_specified { + signed = true; + } + } + } _ => (), } } @@ -1327,14 +1344,15 @@ impl<'a> CompilerState<'a> { start = px.as_span().start(); match px.as_rule() { Rule::calc_expr => { - #[cfg(not(feature = "atarilynx"))] { + #[cfg(not(feature = "atarilynx"))] if !set_const { return Err(self.syntax_error("Non constant global variable can't be statically initialized", start)); } let vx = self.parse_calc(px.into_inner())?; def = VariableDefinition::Value(VariableValue::Int(vx)); + #[cfg(not(feature = "atarilynx"))] if var_type == VariableType::CharPtr && vx > 0xff { memory = VariableMemory::Ramchip; } @@ -2286,8 +2304,18 @@ impl<'a> CompilerState<'a> { let mut bank_org = 0; for p in pair.into_inner() { match p.as_rule() { - Rule::bank => bank_number = p.into_inner().next().unwrap().as_str().parse::().unwrap(), - Rule::int => bank_org = parse_int(p.into_inner().next().unwrap()) as u16, + Rule::bank => { + bank_number = p + .into_inner() + .next() + .unwrap() + .as_str() + .parse::() + .unwrap() + } + Rule::int => { + bank_org = parse_int(p.into_inner().next().unwrap()) as u16 + } _ => (), } } From 4e864b4b8d14d3ebe1946aa8ac2c75b5d45e17ba Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sun, 3 May 2026 19:18:16 +0800 Subject: [PATCH 09/13] Lynx disable default const on charptr --- src/compile.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 9b6de08..f8cd2c4 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1326,13 +1326,22 @@ impl<'a> CompilerState<'a> { } if var_type == VariableType::Char { var_type = VariableType::CharPtr; - var_const = true; + #[cfg(not(feature = "atarilynx"))] + { + var_const = true; + } } else if var_type == VariableType::CharPtr { var_type = VariableType::CharPtrPtr; - var_const = true; + #[cfg(not(feature = "atarilynx"))] + { + var_const = true; + } } else if var_type == VariableType::Short { var_type = VariableType::ShortPtr; - var_const = true; + #[cfg(not(feature = "atarilynx"))] + { + var_const = true; + } } else { return Err( self.syntax_error("Kind of array not available", start) From 5b62b0e456d6eb45c9899ea3777e728771ebec76 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Tue, 5 May 2026 16:42:38 +0800 Subject: [PATCH 10/13] Enable alignment on Lynx --- src/compile.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/compile.rs b/src/compile.rs index f8cd2c4..ec58abc 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1272,6 +1272,17 @@ impl<'a> CompilerState<'a> { } } } + Rule::aligned => { + let px = p.into_inner().next().unwrap(); + let a = self.parse_calc(px.into_inner())?; + if a > 0 { + alignment = a as usize + } else { + return Err( + self.syntax_error("Alignement must be positive", start) + ); + } + } _ => (), } } From a12ae45573b3df8a02ab655eef32501b8db2aadd Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Tue, 5 May 2026 17:08:16 +0800 Subject: [PATCH 11/13] Cargo.toml, remove mistakenly pushed default atarilynx feature --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 2447be2..1993df3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ regex = "1" itertools = "0.13.0" [features] +default = [] atari2600 = [] atari7800 = [] atarilynx = [] From 8137cb234b4c5a3c5b5ed761f5f09014b3680c14 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Tue, 5 May 2026 21:34:44 +0800 Subject: [PATCH 12/13] 65c02 jmp->bra opt, moved AsmLine distance calc to own function. --- Cargo.toml | 5 +- src/assemble.rs | 179 ++++++++++++++++++++++------------- src/generate/generate_asm.rs | 4 + 3 files changed, 122 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1993df3..308043b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ regex = "1" itertools = "0.13.0" [features] -default = [] +default = [] +65C02 = [] atari2600 = [] atari7800 = [] -atarilynx = [] +atarilynx = [ "65C02" ] diff --git a/src/assemble.rs b/src/assemble.rs index a87f713..c2e06a9 100644 --- a/src/assemble.rs +++ b/src/assemble.rs @@ -63,6 +63,8 @@ pub enum AsmMnemonic { DEX, DEY, JMP, + #[cfg(feature = "65C02")] + BRA, JSR, RTS, RTI, @@ -236,15 +238,114 @@ impl AssemblyCode { Ok(s) } - pub fn optimize(&mut self) -> u32 { + pub(crate) fn distance(&self, inst: &AsmInstruction, position: usize) -> i32 { + let mut bytes_above = 0; + let mut bytes_below = 0; + let mut index_above = position; + let mut index_below = position + 1; + let mut reached_above = false; + let above; + let mut notfound = 0; + loop { + if !reached_above { + match &self.code[index_above] { + AsmLine::Label(l) => { + debug!("Iter above: {:?}", l); + if *l == inst.dasm_operand { + above = true; + break; + } + } + AsmLine::Inline(_, s) => { + bytes_above += s; + } + AsmLine::Instruction(k) => { + debug!("Iter above: {:?}", k); + bytes_above += k.nb_bytes; + } + _ => (), + } + } + match self.code.get(index_below) { + Some(AsmLine::Label(l)) => { + debug!("Iter below: {:?}", l); + if *l == inst.dasm_operand { + above = false; + break; + } + } + Some(AsmLine::Inline(_, s)) => { + bytes_below += s; + } + Some(AsmLine::Instruction(k)) => { + debug!("Iter below: {:?}", k); + bytes_below += k.nb_bytes; + } + None => notfound |= 2, + _ => (), + } + if index_above == 0 { + reached_above = true; + notfound |= 1; + } else { + index_above -= 1; + } + index_below += 1; + if notfound == 3 { + error!("Label {} not found", inst.dasm_operand); + unreachable!() + }; + } + // Ok, now we have the distance in bytes + if above { bytes_above as i32 } else { -(bytes_below as i32) } + } + + + #[cfg(feature = "65C02")] + fn optimize_jmp_bra(&mut self) { + let to_replace: Vec = self + .code + .iter() + .enumerate() + .filter_map(|(pos, line)| { + if let AsmLine::Instruction(jmp) = line { + if jmp.mnemonic == AsmMnemonic::JMP + && !jmp.protected + && (-127..=128).contains(&self.distance(jmp, pos)) + { + Some(pos) + } else { + None + } + } else { + None + } + }) + .collect(); + + for pos in to_replace { + if let AsmLine::Instruction(jmp) = &self.code[pos] { + self.code[pos] = AsmLine::Instruction(AsmInstruction { + mnemonic: AsmMnemonic::BRA, + dasm_operand: jmp.dasm_operand.clone(), + cycles: 3, + cycles_alt: Some(4), + nb_bytes: 2, + protected: false, + }); + } + } + } + + fn general_6502_optimizations(&mut self) -> u32 { let mut removed_instructions = 0u32; let mut accumulator = None; let mut x_register = None; let mut y_register = None; + let mut flags = FlagsState::Unknown; let mut iter = itertools::multipeek(self.code.iter_mut()); let mut first = iter.next(); - let mut flags = FlagsState::Unknown; - + loop { match &first { None => return removed_instructions, @@ -785,6 +886,15 @@ impl AssemblyCode { } } + pub fn optimize(&mut self) -> u32 { + let removed_instructions = self.general_6502_optimizations(); + + #[cfg(feature = "65C02")] + self.optimize_jmp_bra(); + + removed_instructions + } + pub fn check_branches(&mut self) -> u32 { // Loop until there is no problematic branch instruction let mut restart = true; @@ -810,69 +920,10 @@ impl AssemblyCode { | AsmMnemonic::BPL | AsmMnemonic::BCS | AsmMnemonic::BCC => { - // Ok, let's try to find the label above and under and try to count the bytes - let mut bytes_above = 0; - let mut bytes_below = 0; - let mut index_above = position; - let mut index_below = position + 1; - let mut reached_above = false; - let above; - let mut notfound = 0; - loop { - if !reached_above { - match &self.code[index_above] { - AsmLine::Label(l) => { - debug!("Iter above: {:?}", l); - if *l == inst.dasm_operand { - above = true; - break; - } - } - AsmLine::Inline(_, s) => { - bytes_above += s; - } - AsmLine::Instruction(k) => { - debug!("Iter above: {:?}", k); - bytes_above += k.nb_bytes; - } - _ => (), - } - } - match self.code.get(index_below) { - Some(AsmLine::Label(l)) => { - debug!("Iter below: {:?}", l); - if *l == inst.dasm_operand { - above = false; - break; - } - } - Some(AsmLine::Inline(_, s)) => { - bytes_below += s; - } - Some(AsmLine::Instruction(k)) => { - debug!("Iter below: {:?}", k); - bytes_below += k.nb_bytes; - } - None => notfound |= 2, - _ => (), - } - if index_above == 0 { - reached_above = true; - notfound |= 1; - } else { - index_above -= 1; - } - index_below += 1; - if notfound == 3 { - error!("Label {} not found", inst.dasm_operand); - unreachable!() - }; - } - // Ok, now we have the distance in bytes - let distance = if above { bytes_above } else { bytes_below }; + let distance = self.distance(inst, position); //error!("distance = {:?}", distance); //if above {unreachable!();} - if distance > 127 { + if !(-127..=128).contains(&distance) { // OK. We have a problem here // This branch should be changed for a jump repair = true; diff --git a/src/generate/generate_asm.rs b/src/generate/generate_asm.rs index 3b5e77c..83ec133 100644 --- a/src/generate/generate_asm.rs +++ b/src/generate/generate_asm.rs @@ -103,10 +103,14 @@ impl<'a> GeneratorState<'a> { ExprType::Label(l) => { nb_bytes = match mnemonic { JMP | JSR => 3, + #[cfg(feature = "65C02")] + BRA => 2, _ => 2, }; cycles = match mnemonic { JMP => 3, + #[cfg(feature = "65C02")] + BRA => 3, JSR => 6, _ => { cycles_alt = Some(3); From d625c386d28103ca27735f6571293fa865805210 Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Wed, 6 May 2026 10:49:20 +0800 Subject: [PATCH 13/13] Add 65c02 STZ --- src/assemble.rs | 2 ++ src/generate/generate_assign.rs | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/assemble.rs b/src/assemble.rs index c2e06a9..30cb5d3 100644 --- a/src/assemble.rs +++ b/src/assemble.rs @@ -32,6 +32,8 @@ pub enum AsmMnemonic { STA, STX, STY, + #[cfg(feature = "65C02")] + STZ, TAX, TAY, TXA, diff --git a/src/generate/generate_assign.rs b/src/generate/generate_assign.rs index 649430e..e19adb4 100644 --- a/src/generate/generate_assign.rs +++ b/src/generate/generate_assign.rs @@ -376,6 +376,21 @@ impl<'a> GeneratorState<'a> { _ => { let mut acc_in_use = self.acc_in_use; let signed; + + #[cfg(feature = "65C02")] + if right == &ExprType::Immediate(0) { + match left { + ExprType::Absolute(_, _, _) + | ExprType::AbsoluteX(_) => { + self.asm(STZ, left, pos, high_byte)?; + self.flags = FlagsState::Unknown; + self.carry_flag_ok = false; + return Ok(ExprType::Nothing); + } + _ => (), + }; + } + match right { ExprType::Absolute(_, _, _) | ExprType::AbsoluteX(_)