@@ -8,17 +8,24 @@ A minimal 6502 assembler written in Rust for inline assembly and lightweight com
88 * Hexadecimal: ` $FF ` , ` 0xFF ` , ` 0xFFh `
99 * Binary: ` %11111111 ` , ` 0b11111111 `
1010 * Decimal: ` 255 `
11+ * Extended range: Values up to 4,294,967,295 (u32) for calculations
1112* ** Expression arithmetic:**
1213 * Addition: ` $10+5 ` , ` LABEL+1 `
1314 * Subtraction: ` $FF-10 ` , ` *-2 `
1415 * Multiplication: ` 10*2 `
1516 * Division: ` 100/5 `
1617 * Mixed formats: ` $10+10+%00000101 `
1718 * Operator precedence: ` *,/ ` before ` +,- `
19+ * Parentheses: ` ($10000-$100) `
20+ * ** Low/High byte extraction:**
21+ * ` <expr ` - Extract low byte (bits 0-7)
22+ * ` >expr ` - Extract high byte (bits 8-15)
23+ * Works with any expression: ` #<($10000-RAM_SIZE) `
1824* ** Constants:** Define reusable values with ` LABEL = value ` syntax
1925 * Simple: ` SCREEN = $0400 `
2026 * Expressions: ` OFFSET = BASE+$10 `
2127 * Current address: ` HERE = * ` , ` NEXT = *+1 `
28+ * Memory calculations: ` TOP_MEM = $10000-$100 `
2229* ** Modern directives:**
2330 * ` .byte ` - Comma-separated bytes
2431 * ` .word ` - 16-bit words (little-endian)
@@ -32,6 +39,7 @@ A minimal 6502 assembler written in Rust for inline assembly and lightweight com
3239 * ` <$80 ` → force Zero Page addressing
3340 * ` >$80 ` → force Absolute addressing
3441* ** Adaptive long-branch expansion:** Out-of-range branches automatically become ` BRANCH skip ` + ` JMP target `
42+ * ** Whitespace-friendly:** Spaces allowed in operands: ` LDA #<($10000 - $100) `
3543* ** Optional listing output:** Print to stdout and/or save to file (feature-gated)
3644* ** Symbol table & address mapping helpers**
3745
@@ -44,6 +52,35 @@ LDA #255 ; Decimal
4452LDA #%11111111 ; Binary
4553LDA #0xFF ; Alternative hex format
4654LDA #0b11111111 ; Alternative binary format
55+ LDA #$10000 ; Extended range for calculations (65536)
56+ ```
57+
58+ ### Low/High Byte Operators
59+ ``` asm
60+ ; Extract low byte (<)
61+ LDA #<$1234 ; Low byte = $34
62+ LDA #<SCREEN ; Low byte of address
63+
64+ ; Extract high byte (>)
65+ LDA #>$1234 ; High byte = $12
66+ LDA #>SCREEN ; High byte of address
67+
68+ ; Common pattern: Load 16-bit address
69+ BUFFER = $2000
70+ LDA #<BUFFER ; Low byte
71+ STA $FC
72+ LDA #>BUFFER ; High byte
73+ STA $FD
74+
75+ ; With expressions (note: no spaces in operands for best compatibility)
76+ LDA #<($10000-$100) ; Low byte of $FF00 = $00
77+ LDX #>($10000-$100) ; High byte of $FF00 = $FF
78+
79+ ; Real-world memory calculations
80+ RAM_SIZE = $0200
81+ TOP_ADDR = $10000-RAM_SIZE ; = $FE00
82+ LDA #<TOP_ADDR ; $00
83+ LDX #>TOP_ADDR ; $FE
4784```
4885
4986### Expressions
@@ -54,10 +91,15 @@ LDA #10*2 ; = 20
5491LDA #100/5 ; = 20
5592LDA #$FF-10 ; = $F5
5693
57- ; Complex expressions
94+ ; Complex expressions (spaces allowed in operands)
5895LDA #10*2+5 ; = 25 (precedence: * before +)
96+ LDA #($FF - $10) ; = $EF (parentheses supported)
5997LDA #$10+10+%00000101 ; Mixed formats = $1F
6098
99+ ; Extended range calculations
100+ LDA #<($10000 - RAM_SIZE) ; Top-of-memory calculations
101+ LDX #>($10000 - $500 + $100) ; Complex address math
102+
61103; With labels
62104LDA buffer+1 ; Address of buffer + 1
63105JMP start+3 ; Jump to start + 3 bytes
@@ -75,6 +117,16 @@ BASE = $1000
75117OFFSET = BASE+$10 ; = $1010
76118DOUBLE = SPRITE_X*2 ; = 200
77119
120+ ; Low/High byte constants
121+ SCREEN_LO = <SCREEN ; = $00
122+ SCREEN_HI = >SCREEN ; = $04
123+
124+ ; Memory calculations
125+ RAM_SIZE = $0200
126+ TOP_MEM = $10000-RAM_SIZE ; = $FE00 (top of memory minus size)
127+ RAM_LO = <TOP_MEM ; = $00
128+ RAM_HI = >TOP_MEM ; = $FE
129+
78130; Current address (*) constants
79131HERE = * ; Current program counter
80132NEXT = *+1 ; Current PC + 1
@@ -146,33 +198,47 @@ buffer:
146198 LDA buffer+1 ; Label arithmetic
147199```
148200
149- ### Complete Example
201+ ### Complete Example with Memory Calculations
150202``` asm
151- *=$0800
203+ *=$0801
152204
153- ; Constants
154- SCREEN = $0400
155- CHAR_CODE = 65 ; 'A'
205+ ; BASIC stub: SYS 2061
206+ .byte $0B,$08,$0A,$00,$9E,$32,$30,$36,$31,$00,$00,$00
207+
208+ ; Zero page pointers
209+ LZSA_SRC = $FC ; Source pointer (2 bytes)
210+ LZSA_DST = $FE ; Destination pointer (2 bytes)
156211
157- ; Data section
158- message:
159- .string "HELLO WORLD"
212+ ; Calculate sizes
213+ RAM_DATA_SIZE = $0150
214+ RELOCATED_SIZE = $00BD
215+
216+ ; Top-of-memory calculations
217+ TOP_MEM = $10000
218+ RAM_BLOCK = TOP_MEM - RAM_DATA_SIZE ; = $FEB0
219+ RELOC_ADDR = RAM_BLOCK + RELOCATED_SIZE ; = $FF6D
220+
221+ start:
222+ ; Load source address (little-endian)
223+ LDA #<data_block
224+ STA LZSA_SRC
225+ LDA #>data_block
226+ STA LZSA_SRC+1
160227
161- sprite_data:
162- .byte $00,$3C,$42,$42,$7E,$42,$42,$00
228+ ; Load destination at top of memory
229+ LDA #<RAM_BLOCK
230+ STA LZSA_DST
231+ LDA #>RAM_BLOCK
232+ STA LZSA_DST+1
163233
164- lookup_table:
165- .word $1000,$2000,$3000,$4000
234+ JSR decompress
235+ RTS
166236
167- ; Code section
168- start:
169- LDX #0
170- loop:
171- LDA message,X
172- STA SCREEN,X
173- INX
174- CPX #11
175- BNE loop
237+ data_block:
238+ .incbin "compressed.lzsa"
239+
240+ decompress:
241+ ; Decompression routine here...
176242 RTS
177243```
178244
@@ -251,6 +317,43 @@ fn main() -> Result<(), asm6502::AsmError> {
251317}
252318```
253319
320+ ### Using Low/High Byte Operators
321+
322+ ``` rust
323+ use asm6502 :: Assembler6502 ;
324+
325+ fn main () -> Result <(), asm6502 :: AsmError > {
326+ let mut asm = Assembler6502 :: new ();
327+
328+ let code = r # "
329+ *=$0800
330+
331+ ; Define 16-bit address
332+ SCREEN = $D800
333+
334+ ; Load address using low/high byte extraction
335+ load_address:
336+ LDA #<SCREEN ; Low byte = $00
337+ STA $FB
338+ LDA #>SCREEN ; High byte = $D8
339+ STA $FC
340+
341+ ; Top-of-memory calculations
342+ RAM_SIZE = $0200
343+ TOP_ADDR = $10000-RAM_SIZE ; = $FE00
344+
345+ LDA #<TOP_ADDR ; = $00
346+ LDX #>TOP_ADDR ; = $FE
347+ RTS
348+ " # ;
349+
350+ let bytes = asm . assemble_bytes (code )? ;
351+ println! (" Assembled {} bytes" , bytes . len ());
352+
353+ Ok (())
354+ }
355+ ```
356+
254357### Using New Directives
255358
256359``` rust
@@ -301,16 +404,23 @@ fn main() -> Result<(), asm6502::AsmError> {
301404 asm . set_origin (0x1000 );
302405
303406 let code = r # "
304- ; Constants
407+ ; Constants with u32 calculations
305408 BASE = $2000
306409 OFFSET = BASE+$100
307410 COUNT = 10*2
308411
412+ ; Top-of-memory calculations
413+ RAM_SIZE = $0500
414+ TOP_MEM = $10000-RAM_SIZE ; = $FB00
415+
309416 ; Code with expressions
310417 start:
311418 LDA #COUNT
312419 STA BASE
313- LDA OFFSET+5
420+ LDA #<TOP_MEM
421+ STA $FC
422+ LDA #>TOP_MEM
423+ STA $FD
314424 JMP start
315425 " # ;
316426
@@ -388,7 +498,9 @@ The test suite covers:
3884985 . Constants (` LABEL = value ` )
3894996 . Current address usage (` * ` )
3905007 . New directives (` .byte ` , ` .word ` , ` .string ` )
391- 8 . Complete 6502 program (all addressing modes)
501+ 8 . U32 support and memory calculations
502+ 9 . Low/high byte operators
503+ 10 . Complete 6502 program (all addressing modes)
392504
393505## API Overview
394506
@@ -445,6 +557,19 @@ fn save_listing(&self, items: &[Item], filename: &str) -> io::Result<()>
445557| `. incbin` | `. incbin " file" ` | Include binary file | `. incbin " data.bin" ` |
446558| `LABEL = ` | `CONST = $ 42 ` | Define constant | `SCREEN = $ 0400 ` |
447559
560+ ## Operator Reference
561+
562+ | Operator | Description | Example | Result |
563+ | ---------- | ------------- | --------- | -------- |
564+ | `+ ` | Addition | `$ 10 + 5 ` | `$ 15 ` |
565+ | `- ` | Subtraction | `$ FF - 10 ` | `$ F5 ` |
566+ | `* ` | Multiplication | `10 * 2 ` | `20 ` |
567+ | `/ ` | Division | `100 / 5 ` | `20 ` |
568+ | `<` | Low byte (bits 0 - 7 ) | `<$ 1234 ` | `$ 34 ` |
569+ | `>` | High byte (bits 8 - 15 ) | `>$ 1234 ` | `$ 12 ` |
570+ | `* ` | Current address | `LABEL = * ` | Current PC |
571+ | `()` | Grouping | `($ 10 + $ 20 )* 2 ` | `$ 60 ` |
572+
448573## Building & Docs
449574
450575Build the workspace :
@@ -478,6 +603,7 @@ This assembler is designed for:
478603- * * JIT compilation ** : Runtime generation of 6502 code
479604- * * Emulator testing ** : Dynamic test case generation
480605- * * Educational tools ** : Interactive 6502 learning
606+ - * * Real - world 6502 projects ** : With extended u32 support for memory calculations
481607- * * Simplicity ** : Minimal dependencies , clear code structure
482608
483609It intentionally * * does not ** include :
@@ -497,6 +623,9 @@ Constants and expressions are evaluated in-order, requiring definitions before u
497623- * * Word endianness : ** `. word` directive outputs little - endian (6502 native format )
498624- * * String encoding : ** `. string` uses standard ASCII encoding
499625- * * Binary inclusion : ** `. incbin` reads files relative to working directory
626+ - * * U32 support : ** Internal calculations use 32 - bit unsigned integers , automatically wrapping to 16 - bit for addresses
627+ - * * Memory calculations : ** Supports expressions like `$ 10000 - RAM_SIZE ` for top - of - memory calculations
628+ - * * Whitespace in operands : ** Spaces are allowed in operands (split on first whitespace only )
500629- * * Current address arithmetic : **
501630 - `LABEL = * ` captures the current program counter
502631 - `LABEL = *+ n ` useful for calculating addresses of upcoming instructions
@@ -505,7 +634,14 @@ Constants and expressions are evaluated in-order, requiring definitions before u
505634
506635## Version History
507636
508- ### v2 . 0 (Current )
637+ ### v2 . 1 (Current )
638+ - Added u32 expression support for values > 65535 (e . g. , `$ 10000 `)
639+ - Added low / high byte operators (`<` and `>`)
640+ - Improved lexer to allow whitespace in operands
641+ - Enhanced long branch expansion algorithm
642+ - Better support for real - world memory calculations
643+
644+ ### v2 . 0
509645- Added `. byte`, `. word`, `. string`, and `. incbin` directives
510646- Improved listing output for data directives
511647- Enhanced address mapping support
0 commit comments