Added right and left shift operators
This commit is contained in:
parent
bc6fdaf8f2
commit
0357df9873
11 changed files with 2350 additions and 37 deletions
113
commands.md
113
commands.md
|
|
@ -225,33 +225,56 @@ NEXT
|
||||||
|
|
||||||
## FUNC
|
## FUNC
|
||||||
|
|
||||||
Defines a function with optional parameters.
|
Defines a function with optional parameters. Functions must be terminated with `FEND`.
|
||||||
|
|
||||||
|
**Modern syntax (recommended):** Use implicit declarations with curly braces for self-contained functions with local parameters.
|
||||||
|
|
||||||
|
**Legacy syntax:** Can use pre-declared global variables for direct memory access.
|
||||||
|
|
||||||
Parameter passing modes: `in:` (default, read-only), `out:` (write-only), `io:` (read-write)
|
Parameter passing modes: `in:` (default, read-only), `out:` (write-only), `io:` (read-write)
|
||||||
|
|
||||||
**Syntax:**
|
**Syntax:**
|
||||||
```
|
```
|
||||||
FUNC <n>
|
FUNC name # void function
|
||||||
FUNC <n>(<param1>[,<param2>,...])
|
FUNC name ( {BYTE param} ) # single parameter (modern)
|
||||||
|
FUNC name ( {BYTE a} {BYTE b} ) # multiple parameters (modern)
|
||||||
|
FUNC name ( in:{BYTE x} out:{BYTE y} io:{BYTE z} ) # all direction modifiers
|
||||||
|
FUNC name ( {BYTE param @ $fa} ) # parameter at absolute address
|
||||||
|
|
||||||
|
FUNC name ( param ) # single pre-declared variable (legacy)
|
||||||
|
FUNC name ( a b ) # multiple pre-declared vars (legacy)
|
||||||
|
FUNC name ( in:x out:y io:z ) # direction with pre-declared (legacy)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
```
|
```
|
||||||
FUNC initialize
|
// Modern: self-contained function with local parameters
|
||||||
BYTE temp = 0
|
FUNC add({BYTE a} {BYTE b} {BYTE result})
|
||||||
screen = temp
|
|
||||||
FEND
|
|
||||||
|
|
||||||
FUNC add(in:a,in:b,out:result)
|
|
||||||
result = a + b
|
result = a + b
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
FUNC process(value,{BYTE temp})
|
// Modern: with direction modifiers
|
||||||
temp = value + 1
|
FUNC process(in:{BYTE input} out:{BYTE output})
|
||||||
|
output = input + 1
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
FUNC swap(io:x,io:y)
|
// Modern: io parameter (read-write)
|
||||||
BYTE temp = x
|
FUNC increment(io:{BYTE counter})
|
||||||
|
counter = counter + 1
|
||||||
|
FEND
|
||||||
|
|
||||||
|
// Modern: absolute address parameter
|
||||||
|
FUNC read_byte({BYTE data @ $fa})
|
||||||
|
BYTE temp
|
||||||
|
temp = data // Reads from $fa
|
||||||
|
FEND
|
||||||
|
|
||||||
|
// Legacy: uses global variables directly
|
||||||
|
BYTE x
|
||||||
|
BYTE y
|
||||||
|
FUNC swap_legacy ( x y )
|
||||||
|
BYTE temp
|
||||||
|
temp = x
|
||||||
x = y
|
x = y
|
||||||
y = temp
|
y = temp
|
||||||
FEND
|
FEND
|
||||||
|
|
@ -578,6 +601,70 @@ LABEL checkValue
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## SHIFTL
|
||||||
|
|
||||||
|
Logical shift left operation.
|
||||||
|
|
||||||
|
**Syntax:**
|
||||||
|
```
|
||||||
|
SHIFTL <source> BY <amount> GIVING <dest>
|
||||||
|
SHIFTL <source> << <amount> -> <dest>
|
||||||
|
<dest> = <source> << <amount>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `source`: BYTE or WORD variable or constant expression
|
||||||
|
- `amount`: BYTE variable or constant expression (0-255)
|
||||||
|
- `dest`: BYTE or WORD variable (cannot be constant)
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Logical shift only (no sign extension)
|
||||||
|
- Shift amount of 0 copies source to dest unchanged
|
||||||
|
- Shift amount ≥ bit-width yields zero (≥8 for BYTE, ≥16 for WORD)
|
||||||
|
- BYTE→WORD conversion zero-extends before shifting
|
||||||
|
- WORD→BYTE conversion truncates low byte only
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```
|
||||||
|
result = value << 3
|
||||||
|
SHIFTL mask BY bits GIVING shifted
|
||||||
|
SHIFTR flags >> 2 -> masked
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SHIFTR
|
||||||
|
|
||||||
|
Logical shift right operation.
|
||||||
|
|
||||||
|
**Syntax:**
|
||||||
|
```
|
||||||
|
SHIFTR <source> BY <amount> GIVING <dest>
|
||||||
|
SHIFTR <source> >> <amount> -> <dest>
|
||||||
|
<dest> = <source> >> <amount>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `source`: BYTE or WORD variable or constant expression
|
||||||
|
- `amount`: BYTE variable or constant expression (0-255)
|
||||||
|
- `dest`: BYTE or WORD variable (cannot be constant)
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Logical shift only (no sign extension)
|
||||||
|
- Shift amount of 0 copies source to dest unchanged
|
||||||
|
- Shift amount ≥ bit-width yields zero (≥8 for BYTE, ≥16 for WORD)
|
||||||
|
- BYTE→WORD conversion zero-extends before shifting
|
||||||
|
- WORD→BYTE conversion truncates low byte only
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```
|
||||||
|
mask = flags >> 2
|
||||||
|
SHIFTR value BY shift GIVING result
|
||||||
|
SHIFTR data >> 4 -> nibble
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## SUBTR
|
## SUBTR
|
||||||
|
|
||||||
Subtracts second value from first.
|
Subtracts second value from first.
|
||||||
|
|
|
||||||
20
examples/shift_demo/cm.sh
Executable file
20
examples/shift_demo/cm.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Define filename as variable
|
||||||
|
PROGNAME="shift_demo"
|
||||||
|
# Only set C65LIBPATH if not already defined
|
||||||
|
if [ -z "$C65LIBPATH" ]; then
|
||||||
|
export C65LIBPATH=$(readlink -f "../../lib")
|
||||||
|
fi
|
||||||
|
# Compile - use absolute path to c65gm
|
||||||
|
c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Compilation terminated"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo assemble.
|
||||||
|
acme ${PROGNAME}.s
|
||||||
|
if [ -f ${PROGNAME}.prg ]; then
|
||||||
|
rm ${PROGNAME}.prg
|
||||||
|
fi
|
||||||
|
# main.bin ${PROGNAME}.prg
|
||||||
|
mv main.bin main.prg
|
||||||
410
examples/shift_demo/shift_demo.c65
Normal file
410
examples/shift_demo/shift_demo.c65
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Shift Operations Demo
|
||||||
|
// Practical examples of << and >> operators
|
||||||
|
// Shows real-world uses for bit manipulation
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
|
||||||
|
#INCLUDE <c64start.c65>
|
||||||
|
#INCLUDE <c64defs.c65>
|
||||||
|
#INCLUDE <cbmiolib.c65>
|
||||||
|
|
||||||
|
#PRAGMA _P_USE_CBM_STRINGS 1
|
||||||
|
|
||||||
|
GOTO start
|
||||||
|
|
||||||
|
// Variables for demos
|
||||||
|
BYTE demo_value
|
||||||
|
BYTE demo_result
|
||||||
|
BYTE demo_shift
|
||||||
|
WORD demo_word
|
||||||
|
WORD demo_word_result
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Wait for key press
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC wait_key
|
||||||
|
BYTE key
|
||||||
|
|
||||||
|
// Wait for no key
|
||||||
|
WHILE 1
|
||||||
|
key = PEEK $c5
|
||||||
|
IF key = 64
|
||||||
|
BREAK
|
||||||
|
ENDIF
|
||||||
|
WEND
|
||||||
|
|
||||||
|
// Wait for key press
|
||||||
|
WHILE 1
|
||||||
|
key = PEEK $c5
|
||||||
|
IF key != 64
|
||||||
|
BREAK
|
||||||
|
ENDIF
|
||||||
|
WEND
|
||||||
|
|
||||||
|
// Reset key buffer
|
||||||
|
POKE $c6 WITH 0
|
||||||
|
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Clear screen
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC clear_screen
|
||||||
|
lib_cbmio_cls()
|
||||||
|
lib_cbmio_home()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Print binary representation of byte
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC print_binary({BYTE val})
|
||||||
|
BYTE i = 7
|
||||||
|
BYTE mask
|
||||||
|
BYTE bit
|
||||||
|
|
||||||
|
lib_cbmio_print(" %")
|
||||||
|
|
||||||
|
WHILE i < 8 // Will break when i wraps around to 255
|
||||||
|
mask = 1 << i
|
||||||
|
bit = val & mask
|
||||||
|
IF bit != 0
|
||||||
|
lib_cbmio_print("1")
|
||||||
|
ELSE
|
||||||
|
lib_cbmio_print("0")
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Add space every 4 bits for readability
|
||||||
|
IF i = 4
|
||||||
|
lib_cbmio_print(" ")
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Decrement i, break if we go below 0
|
||||||
|
IF i = 0
|
||||||
|
BREAK
|
||||||
|
ENDIF
|
||||||
|
i = i - 1
|
||||||
|
WEND
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Demo 1: Basic shift operations
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC demo_basic_shifts
|
||||||
|
lib_cbmio_printlf("basic shift operations")
|
||||||
|
lib_cbmio_printlf("======================")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Constant left shift
|
||||||
|
demo_value = $03 // Binary: 00000011
|
||||||
|
demo_result = demo_value << 2
|
||||||
|
lib_cbmio_print("$03 << 2 = $")
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
print_binary(demo_result)
|
||||||
|
lib_cbmio_printlf(" (3 * 4 = 12)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Constant right shift
|
||||||
|
demo_value = $30 // Binary: 00110000
|
||||||
|
demo_result = demo_value >> 3
|
||||||
|
lib_cbmio_print("$30 >> 3 = $")
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
print_binary(demo_result)
|
||||||
|
lib_cbmio_printlf(" (48 / 8 = 6)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Variable shift amount
|
||||||
|
demo_value = $80 // Binary: 10000000
|
||||||
|
demo_shift = 3
|
||||||
|
demo_result = demo_value >> demo_shift
|
||||||
|
lib_cbmio_print("$80 >> ")
|
||||||
|
lib_cbmio_hexoutb(demo_shift)
|
||||||
|
lib_cbmio_print(" = $")
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
print_binary(demo_result)
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
wait_key()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Demo 2: Bit manipulation
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC demo_bit_manipulation
|
||||||
|
lib_cbmio_printlf("bit manipulation")
|
||||||
|
lib_cbmio_printlf("================")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Extract color components from C64 color byte
|
||||||
|
// C64 color: bits 7-4 = background, bits 3-0 = foreground
|
||||||
|
BYTE color = $3E // Background: 3, Foreground: E
|
||||||
|
BYTE background
|
||||||
|
BYTE foreground
|
||||||
|
|
||||||
|
background = color >> 4
|
||||||
|
foreground = color & $0F
|
||||||
|
|
||||||
|
lib_cbmio_print("color byte: $")
|
||||||
|
lib_cbmio_hexoutb(color)
|
||||||
|
print_binary(color)
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_print("background: $")
|
||||||
|
lib_cbmio_hexoutb(background)
|
||||||
|
lib_cbmio_printlf(" (>> 4)")
|
||||||
|
lib_cbmio_print("foreground: $")
|
||||||
|
lib_cbmio_hexoutb(foreground)
|
||||||
|
lib_cbmio_printlf(" (& $0f)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Create bit masks
|
||||||
|
lib_cbmio_printlf("bit masks:")
|
||||||
|
BYTE mask
|
||||||
|
|
||||||
|
mask = 1 << 0
|
||||||
|
lib_cbmio_print("1 << 0 = $")
|
||||||
|
lib_cbmio_hexoutb(mask)
|
||||||
|
lib_cbmio_print(" (bit 0)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
mask = 1 << 7
|
||||||
|
lib_cbmio_print("1 << 7 = $")
|
||||||
|
lib_cbmio_hexoutb(mask)
|
||||||
|
lib_cbmio_print(" (bit 7)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
mask = 1 | 2 // Bits 0 and 1
|
||||||
|
lib_cbmio_print("bits 0+1 = $")
|
||||||
|
lib_cbmio_hexoutb(mask)
|
||||||
|
print_binary(mask)
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
wait_key()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Demo 3: Multiplication and division
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC demo_multiply_divide
|
||||||
|
lib_cbmio_printlf("fast multiply/divide")
|
||||||
|
lib_cbmio_printlf("====================")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Multiplication by powers of 2
|
||||||
|
BYTE value = 7
|
||||||
|
|
||||||
|
lib_cbmio_print("7 * 2 = ")
|
||||||
|
demo_result = value << 1
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_printlf(" (7 << 1)")
|
||||||
|
|
||||||
|
lib_cbmio_print("7 * 4 = ")
|
||||||
|
demo_result = value << 2
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_printlf(" (7 << 2)")
|
||||||
|
|
||||||
|
lib_cbmio_print("7 * 8 = ")
|
||||||
|
demo_result = value << 3
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_printlf(" (7 << 3)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Division by powers of 2
|
||||||
|
value = 100
|
||||||
|
|
||||||
|
lib_cbmio_print("100 / 2 = ")
|
||||||
|
demo_result = value >> 1
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_printlf(" (100 >> 1)")
|
||||||
|
|
||||||
|
lib_cbmio_print("100 / 4 = ")
|
||||||
|
demo_result = value >> 2
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_printlf(" (100 >> 2)")
|
||||||
|
|
||||||
|
lib_cbmio_print("100 / 8 = ")
|
||||||
|
demo_result = value >> 3
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_printlf(" (100 >> 3)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Variable shift for scaling
|
||||||
|
value = 5
|
||||||
|
demo_shift = 3 // Multiply by 8
|
||||||
|
|
||||||
|
lib_cbmio_print("5 * 8 = ")
|
||||||
|
demo_result = value << demo_shift
|
||||||
|
lib_cbmio_hexoutb(demo_result)
|
||||||
|
lib_cbmio_print(" (5 << ")
|
||||||
|
lib_cbmio_hexoutb(demo_shift)
|
||||||
|
lib_cbmio_printlf(")")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
wait_key()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Demo 4: Word operations
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC demo_word_operations
|
||||||
|
lib_cbmio_printlf("16-bit word operations")
|
||||||
|
lib_cbmio_printlf("======================")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Word left shift (multiply by 2)
|
||||||
|
demo_word = $1234
|
||||||
|
demo_word_result = demo_word << 1
|
||||||
|
|
||||||
|
lib_cbmio_print("$1234 << 1 = $")
|
||||||
|
lib_cbmio_hexoutw(demo_word_result)
|
||||||
|
lib_cbmio_printlf(" ($1234 * 2 = $2468)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Word right shift (divide by 2)
|
||||||
|
demo_word = $ABCD
|
||||||
|
demo_word_result = demo_word >> 1
|
||||||
|
|
||||||
|
lib_cbmio_print("$abcd >> 1 = $")
|
||||||
|
lib_cbmio_hexoutw(demo_word_result)
|
||||||
|
lib_cbmio_printlf(" ($abcd / 2 = $55e6)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Shift between bytes
|
||||||
|
demo_word = $00FF
|
||||||
|
demo_word_result = demo_word << 8
|
||||||
|
|
||||||
|
lib_cbmio_print("$00ff << 8 = $")
|
||||||
|
lib_cbmio_hexoutw(demo_word_result)
|
||||||
|
lib_cbmio_printlf(" (low->high byte)")
|
||||||
|
|
||||||
|
demo_word = $FF00
|
||||||
|
demo_word_result = demo_word >> 8
|
||||||
|
|
||||||
|
lib_cbmio_print("$ff00 >> 8 = $")
|
||||||
|
lib_cbmio_hexoutw(demo_word_result)
|
||||||
|
lib_cbmio_printlf(" (high->low byte)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Byte to word conversion with shift
|
||||||
|
BYTE small = $81
|
||||||
|
WORD large
|
||||||
|
|
||||||
|
large = small << 2 // Zero-extends byte to word, then shifts
|
||||||
|
|
||||||
|
lib_cbmio_print("byte $81 << 2 = word $")
|
||||||
|
lib_cbmio_hexoutw(large)
|
||||||
|
lib_cbmio_printlf(" (zero-extended)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
wait_key()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Demo 5: Practical C64 example
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
FUNC demo_c64_example
|
||||||
|
lib_cbmio_printlf("c64 practical example")
|
||||||
|
lib_cbmio_printlf("=====================")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Simulate reading joystick port 2
|
||||||
|
// Bits: 0=up, 1=down, 2=left, 3=right, 4=fire
|
||||||
|
BYTE joystick = $17 // Binary: 00010111 (up, down, right, fire)
|
||||||
|
|
||||||
|
lib_cbmio_print("joystick port: $")
|
||||||
|
lib_cbmio_hexoutb(joystick)
|
||||||
|
print_binary(joystick)
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Check individual buttons using shifts
|
||||||
|
lib_cbmio_printlf("checking buttons:")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Fire button (bit 4)
|
||||||
|
BYTE fire_mask
|
||||||
|
BYTE fire_check
|
||||||
|
fire_mask = 1 << 4
|
||||||
|
fire_check = joystick & fire_mask
|
||||||
|
IF fire_check = 0
|
||||||
|
lib_cbmio_printlf("fire: pressed")
|
||||||
|
ELSE
|
||||||
|
lib_cbmio_printlf("fire: not pressed")
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Up button (bit 0)
|
||||||
|
BYTE up_mask
|
||||||
|
BYTE up_check
|
||||||
|
up_mask = 1 << 0
|
||||||
|
up_check = joystick & up_mask
|
||||||
|
IF up_check = 0
|
||||||
|
lib_cbmio_printlf("up: pressed")
|
||||||
|
ELSE
|
||||||
|
lib_cbmio_printlf("up: not pressed")
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Right button (bit 3)
|
||||||
|
BYTE right_mask
|
||||||
|
BYTE right_check
|
||||||
|
right_mask = 1 << 3
|
||||||
|
right_check = joystick & right_mask
|
||||||
|
IF right_check = 0
|
||||||
|
lib_cbmio_printlf("right: pressed")
|
||||||
|
ELSE
|
||||||
|
lib_cbmio_printlf("right: not pressed")
|
||||||
|
ENDIF
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Extract direction bits to nibble
|
||||||
|
BYTE direction
|
||||||
|
direction = joystick & $0F // Mask off fire button
|
||||||
|
|
||||||
|
lib_cbmio_print("direction bits: $")
|
||||||
|
lib_cbmio_hexoutb(direction)
|
||||||
|
print_binary(direction)
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
wait_key()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Main program
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
LABEL start
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
lib_cbmio_printlf("shift operators demo")
|
||||||
|
lib_cbmio_printlf("====================")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("press any key...")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
wait_key()
|
||||||
|
|
||||||
|
demo_basic_shifts()
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
demo_bit_manipulation()
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
demo_multiply_divide()
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
demo_word_operations()
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
demo_c64_example()
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
lib_cbmio_printlf("demo complete!")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("shift operators:")
|
||||||
|
lib_cbmio_printlf(" << left shift (multiply)")
|
||||||
|
lib_cbmio_printlf(" >> right shift (divide)")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
lib_cbmio_printlf("works with bytes and words")
|
||||||
|
lib_cbmio_printlf("")
|
||||||
|
|
||||||
|
// Infinite loop
|
||||||
|
LABEL loop
|
||||||
|
GOTO loop
|
||||||
1
examples/shift_demo/start_in_vice.sh
Normal file
1
examples/shift_demo/start_in_vice.sh
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
x64 -autostartprgmode 1 main.prg
|
||||||
348
internal/commands/shiftl.go
Normal file
348
internal/commands/shiftl.go
Normal file
|
|
@ -0,0 +1,348 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
"c65gm/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShiftLCommand handles logical shift left operations
|
||||||
|
// Syntax:
|
||||||
|
//
|
||||||
|
// SHIFTL <source> BY <amount> GIVING <dest> # old syntax with BY/GIVING
|
||||||
|
// SHIFTL <source> << <amount> -> <dest> # old syntax with <</->
|
||||||
|
// <dest> = <source> << <amount> # new syntax
|
||||||
|
type ShiftLCommand struct {
|
||||||
|
sourceVarName string
|
||||||
|
sourceVarKind compiler.VarKind
|
||||||
|
sourceValue uint16
|
||||||
|
sourceIsVar bool
|
||||||
|
|
||||||
|
amountVarName string
|
||||||
|
amountVarKind compiler.VarKind
|
||||||
|
amountValue uint16
|
||||||
|
amountIsVar bool
|
||||||
|
|
||||||
|
destVarName string
|
||||||
|
destVarKind compiler.VarKind
|
||||||
|
|
||||||
|
line preproc.Line // Store line info for warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShiftLCommand) WillHandle(line preproc.Line) bool {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil || len(params) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old syntax: SHIFTL ... (must have exactly 6 params)
|
||||||
|
if strings.ToUpper(params[0]) == "SHIFTL" && len(params) == 6 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// New syntax: <dest> = <source> << <amount>
|
||||||
|
if len(params) == 5 && params[1] == "=" && params[3] == "<<" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShiftLCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||||
|
// Clear state
|
||||||
|
c.sourceVarName = ""
|
||||||
|
c.sourceIsVar = false
|
||||||
|
c.sourceValue = 0
|
||||||
|
c.amountVarName = ""
|
||||||
|
c.amountIsVar = false
|
||||||
|
c.amountValue = 0
|
||||||
|
c.destVarName = ""
|
||||||
|
c.line = line // Store line for warnings
|
||||||
|
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
paramCount := len(params)
|
||||||
|
scope := ctx.CurrentScope()
|
||||||
|
|
||||||
|
// Create constant lookup function
|
||||||
|
constLookup := ctx.SymbolTable.ConstantLookupFunc(scope)
|
||||||
|
|
||||||
|
// Determine syntax and parse accordingly
|
||||||
|
if strings.ToUpper(params[0]) == "SHIFTL" {
|
||||||
|
// Old syntax: SHIFTL <source> BY/<< <amount> GIVING/-> <dest>
|
||||||
|
if paramCount != 6 {
|
||||||
|
return fmt.Errorf("SHIFTL: wrong number of parameters (%d), expected 6", paramCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
separator1 := strings.ToUpper(params[2])
|
||||||
|
if separator1 != "BY" && separator1 != "<<" {
|
||||||
|
return fmt.Errorf("SHIFTL: parameter #3 must be 'BY' or '<<', got %q", params[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
separator2 := strings.ToUpper(params[4])
|
||||||
|
if separator2 != "GIVING" && separator2 != "->" {
|
||||||
|
return fmt.Errorf("SHIFTL: parameter #5 must be 'GIVING' or '->', got %q", params[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination
|
||||||
|
destName := params[5]
|
||||||
|
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||||
|
if destSym == nil {
|
||||||
|
return fmt.Errorf("SHIFTL: unknown variable %q", destName)
|
||||||
|
}
|
||||||
|
if destSym.IsConst() {
|
||||||
|
return fmt.Errorf("SHIFTL: cannot assign to constant %q", destName)
|
||||||
|
}
|
||||||
|
c.destVarName = destSym.FullName()
|
||||||
|
c.destVarKind = destSym.GetVarKind()
|
||||||
|
|
||||||
|
// Parse source
|
||||||
|
var err error
|
||||||
|
c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[1], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTL: source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse amount
|
||||||
|
c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[3], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTL: amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// New syntax: <dest> = <source> << <amount>
|
||||||
|
if paramCount != 5 {
|
||||||
|
return fmt.Errorf("SHIFTL: wrong number of parameters (%d), expected 5", paramCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params[1] != "=" {
|
||||||
|
return fmt.Errorf("SHIFTL: expected '=' at position 2, got %q", params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params[3] != "<<" {
|
||||||
|
return fmt.Errorf("SHIFTL: expected '<<' at position 4, got %q", params[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination
|
||||||
|
destName := params[0]
|
||||||
|
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||||
|
if destSym == nil {
|
||||||
|
return fmt.Errorf("SHIFTL: unknown variable %q", destName)
|
||||||
|
}
|
||||||
|
if destSym.IsConst() {
|
||||||
|
return fmt.Errorf("SHIFTL: cannot assign to constant %q", destName)
|
||||||
|
}
|
||||||
|
c.destVarName = destSym.FullName()
|
||||||
|
c.destVarKind = destSym.GetVarKind()
|
||||||
|
|
||||||
|
// Parse source
|
||||||
|
var err error
|
||||||
|
c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[2], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTL: source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse amount
|
||||||
|
c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[4], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTL: amount: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate amount
|
||||||
|
if c.amountIsVar {
|
||||||
|
if c.amountVarKind == compiler.KindWord {
|
||||||
|
return fmt.Errorf("SHIFTL: amount must be BYTE variable, got WORD %q", c.amountVarName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.amountValue > 255 {
|
||||||
|
return fmt.Errorf("SHIFTL: amount constant %d out of BYTE range (0-255)", c.amountValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShiftLCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// Check if shift amount >= bit width (result will be zero)
|
||||||
|
bitWidth := 8
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
bitWidth = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
amountZero := false
|
||||||
|
if !c.amountIsVar {
|
||||||
|
// Constant amount
|
||||||
|
if c.amountValue >= uint16(bitWidth) {
|
||||||
|
amountZero = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if amountZero {
|
||||||
|
// Result is zero, just store zero
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Copy source to destination if needed
|
||||||
|
if c.sourceIsVar && c.sourceVarName == c.destVarName {
|
||||||
|
// Same variable, no copy needed
|
||||||
|
} else {
|
||||||
|
copyAsm := c.generateCopy()
|
||||||
|
asm = append(asm, copyAsm...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Apply shift
|
||||||
|
shiftAsm, err := c.generateShift(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
asm = append(asm, shiftAsm...)
|
||||||
|
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCopy generates assembly to copy source to destination
|
||||||
|
func (c *ShiftLCommand) generateCopy() []string {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// If source is literal, just load it
|
||||||
|
if !c.sourceIsVar {
|
||||||
|
lo := uint8(c.sourceValue & 0xFF)
|
||||||
|
hi := uint8((c.sourceValue >> 8) & 0xFF)
|
||||||
|
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
// Optimization: don't reload if lo == hi
|
||||||
|
if lo != hi {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||||
|
}
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
return asm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source is variable
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
// Destination is byte
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
// Destination is word
|
||||||
|
if c.sourceVarKind == compiler.KindByte {
|
||||||
|
// Byte -> Word (zero-extend)
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
} else {
|
||||||
|
// Word -> Word
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return asm
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateShift generates assembly to shift destination left by amount
|
||||||
|
func (c *ShiftLCommand) generateShift(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// Constant amount
|
||||||
|
if !c.amountIsVar {
|
||||||
|
amount := c.amountValue
|
||||||
|
if amount == 0 {
|
||||||
|
return asm, nil // No shift needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine bit width
|
||||||
|
bitWidth := 8
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
bitWidth = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn if shift amount >= bit width (but not for 0)
|
||||||
|
if amount >= uint16(bitWidth) {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= %d bits, value will be zero\n",
|
||||||
|
c.line.Filename, c.line.LineNo, amount, bitWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount >= uint16(bitWidth) {
|
||||||
|
// Shift all bits out -> zero
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unroll shift loop
|
||||||
|
for i := uint16(0); i < amount; i++ {
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable amount
|
||||||
|
// Generate labels
|
||||||
|
loopLabel := ctx.GeneralStack.Push()
|
||||||
|
ctx.GeneralStack.Pop()
|
||||||
|
doneLabel := ctx.GeneralStack.Push()
|
||||||
|
ctx.GeneralStack.Pop()
|
||||||
|
|
||||||
|
// Load amount into X
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
|
||||||
|
|
||||||
|
// Check for zero amount
|
||||||
|
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
|
||||||
|
|
||||||
|
// Shift loop
|
||||||
|
asm = append(asm, loopLabel)
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
asm = append(asm, "\tdex")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
|
||||||
|
|
||||||
|
asm = append(asm, doneLabel)
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
551
internal/commands/shiftl_test.go
Normal file
551
internal/commands/shiftl_test.go
Normal file
|
|
@ -0,0 +1,551 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShiftLCommand_WillHandle(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
text string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// Old syntax
|
||||||
|
{"old syntax BY/GIVING", "SHIFTL a BY b GIVING c", true},
|
||||||
|
{"old syntax <</->", "SHIFTL a << b -> c", true},
|
||||||
|
{"old syntax mixed case", "shiftl x by y giving z", true},
|
||||||
|
|
||||||
|
// New syntax
|
||||||
|
{"new syntax basic", "result = a << b", true},
|
||||||
|
{"new syntax with literals", "x = 10 << 3", true},
|
||||||
|
|
||||||
|
// Should not handle
|
||||||
|
{"not shiftl - shiftr", "SHIFTR a BY b GIVING c", false},
|
||||||
|
{"not shiftl - add", "result = a + b", false},
|
||||||
|
{"not shiftl - wrong params", "SHIFTL a b c", false},
|
||||||
|
{"empty", "", false},
|
||||||
|
{"just SHIFTL", "SHIFTL", false},
|
||||||
|
{"assignment without shift", "x = y", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := &ShiftLCommand{}
|
||||||
|
line := preproc.Line{Text: tt.text}
|
||||||
|
if got := cmd.WillHandle(line); got != tt.want {
|
||||||
|
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShiftLCommand_Interpret_OldSyntax(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*compiler.CompilerContext)
|
||||||
|
text string
|
||||||
|
wantErr bool
|
||||||
|
check func(*testing.T, *ShiftLCommand)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "byte << byte -> byte",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 3, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a BY b GIVING c",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if !cmd.sourceIsVar || cmd.sourceVarName != "a" {
|
||||||
|
t.Errorf("source should be var 'a'")
|
||||||
|
}
|
||||||
|
if !cmd.amountIsVar || cmd.amountVarName != "b" {
|
||||||
|
t.Errorf("amount should be var 'b'")
|
||||||
|
}
|
||||||
|
if cmd.destVarName != "c" || cmd.destVarKind != compiler.KindByte {
|
||||||
|
t.Errorf("dest should be byte 'c'")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word << byte -> word",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("n", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL x BY n GIVING result",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.sourceVarKind != compiler.KindWord {
|
||||||
|
t.Errorf("source should be word")
|
||||||
|
}
|
||||||
|
if cmd.destVarKind != compiler.KindWord {
|
||||||
|
t.Errorf("dest should be word")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "literal << var -> byte",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL $FF BY shift GIVING result",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.sourceIsVar {
|
||||||
|
t.Errorf("source should be literal")
|
||||||
|
}
|
||||||
|
if cmd.sourceValue != 0xFF {
|
||||||
|
t.Errorf("source value = %d, want 255", cmd.sourceValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "var << literal -> byte",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 1, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL value BY 3 GIVING result",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.amountIsVar {
|
||||||
|
t.Errorf("amount should be literal")
|
||||||
|
}
|
||||||
|
if cmd.amountValue != 3 {
|
||||||
|
t.Errorf("amount value = %d, want 3", cmd.amountValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow syntax",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a << b -> c",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.destVarName != "c" {
|
||||||
|
t.Errorf("dest should be c")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a BY b GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "assign to constant",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a BY b GIVING MAX",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word amount variable (error)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a BY b GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amount constant > 255 (error)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a BY 300 GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong separator #3",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a WITH b GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong separator #5",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTL a BY b INTO c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||||
|
if tt.setup != nil {
|
||||||
|
tt.setup(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &ShiftLCommand{}
|
||||||
|
line := preproc.Line{Text: tt.text}
|
||||||
|
|
||||||
|
err := cmd.Interpret(line, ctx)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Interpret() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr && tt.check != nil {
|
||||||
|
tt.check(t, cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShiftLCommand_Interpret_NewSyntax(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*compiler.CompilerContext)
|
||||||
|
text string
|
||||||
|
wantErr bool
|
||||||
|
check func(*testing.T, *ShiftLCommand)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "dest = var << var",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = a << b",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.destVarName != "result" {
|
||||||
|
t.Errorf("dest = %q, want 'result'", cmd.destVarName)
|
||||||
|
}
|
||||||
|
if !cmd.sourceIsVar || cmd.sourceVarName != "a" {
|
||||||
|
t.Errorf("source should be var 'a'")
|
||||||
|
}
|
||||||
|
if !cmd.amountIsVar || cmd.amountVarName != "b" {
|
||||||
|
t.Errorf("amount should be var 'b'")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dest = literal << literal",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = $01 << 3",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.sourceIsVar || cmd.amountIsVar {
|
||||||
|
t.Errorf("both params should be literals")
|
||||||
|
}
|
||||||
|
if cmd.sourceValue != 1 || cmd.amountValue != 3 {
|
||||||
|
t.Errorf("source=%d, amount=%d, want 1,3", cmd.sourceValue, cmd.amountValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word destination",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 1, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = value << shift",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftLCommand) {
|
||||||
|
if cmd.destVarKind != compiler.KindWord {
|
||||||
|
t.Errorf("dest should be word")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown dest variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = a << b",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong operator (not <<)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = a >> b",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||||
|
if tt.setup != nil {
|
||||||
|
tt.setup(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &ShiftLCommand{}
|
||||||
|
line := preproc.Line{Text: tt.text}
|
||||||
|
|
||||||
|
err := cmd.Interpret(line, ctx)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Interpret() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr && tt.check != nil {
|
||||||
|
tt.check(t, cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShiftLCommand_Generate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*compiler.CompilerContext) *ShiftLCommand
|
||||||
|
wantLines []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "constant folding - byte << 0",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0x55,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 0,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #$55",
|
||||||
|
"\tsta result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - byte << 3",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0x01,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 3,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #$01",
|
||||||
|
"\tsta result",
|
||||||
|
"\tasl result",
|
||||||
|
"\tasl result",
|
||||||
|
"\tasl result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - byte << 8 (zero)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0xFF,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 8,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #0",
|
||||||
|
"\tsta result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - word << 1",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0x1234,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 1,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindWord,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #$34",
|
||||||
|
"\tsta result",
|
||||||
|
"\tlda #$12",
|
||||||
|
"\tsta result+1",
|
||||||
|
"\tasl result",
|
||||||
|
"\trol result+1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - word << 16 (zero)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0xFFFF,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 16,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindWord,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #0",
|
||||||
|
"\tsta result",
|
||||||
|
"\tsta result+1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "byte variable << byte variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: true,
|
||||||
|
sourceVarName: "value",
|
||||||
|
sourceVarKind: compiler.KindByte,
|
||||||
|
amountIsVar: true,
|
||||||
|
amountVarName: "shift",
|
||||||
|
amountVarKind: compiler.KindByte,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda value",
|
||||||
|
"\tsta result",
|
||||||
|
"\tldx shift",
|
||||||
|
"\tbeq _L2",
|
||||||
|
"_L1",
|
||||||
|
"\tasl result",
|
||||||
|
"\tdex",
|
||||||
|
"\tbne _L1",
|
||||||
|
"_L2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word variable << byte variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 3, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: true,
|
||||||
|
sourceVarName: "value",
|
||||||
|
sourceVarKind: compiler.KindWord,
|
||||||
|
amountIsVar: true,
|
||||||
|
amountVarName: "shift",
|
||||||
|
amountVarKind: compiler.KindByte,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindWord,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda value",
|
||||||
|
"\tsta result",
|
||||||
|
"\tlda value+1",
|
||||||
|
"\tsta result+1",
|
||||||
|
"\tldx shift",
|
||||||
|
"\tbeq _L2",
|
||||||
|
"_L1",
|
||||||
|
"\tasl result",
|
||||||
|
"\trol result+1",
|
||||||
|
"\tdex",
|
||||||
|
"\tbne _L1",
|
||||||
|
"_L2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same source and dest",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftLCommand{
|
||||||
|
sourceIsVar: true,
|
||||||
|
sourceVarName: "value",
|
||||||
|
sourceVarKind: compiler.KindByte,
|
||||||
|
amountIsVar: true,
|
||||||
|
amountVarName: "shift",
|
||||||
|
amountVarKind: compiler.KindByte,
|
||||||
|
destVarName: "value",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tldx shift",
|
||||||
|
"\tbeq _L2",
|
||||||
|
"_L1",
|
||||||
|
"\tasl value",
|
||||||
|
"\tdex",
|
||||||
|
"\tbne _L1",
|
||||||
|
"_L2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||||
|
cmd := tt.setup(ctx)
|
||||||
|
|
||||||
|
got, err := cmd.Generate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(got) != len(tt.wantLines) {
|
||||||
|
t.Errorf("Generate() got %d lines, want %d lines\nGot:\n%s\nWant:\n%s",
|
||||||
|
len(got), len(tt.wantLines),
|
||||||
|
strings.Join(got, "\n"), strings.Join(tt.wantLines, "\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range got {
|
||||||
|
// Skip exact label comparison (they're generated dynamically)
|
||||||
|
if strings.HasPrefix(got[i], "_L") && strings.HasPrefix(tt.wantLines[i], "_L") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got[i] != tt.wantLines[i] {
|
||||||
|
t.Errorf("Line %d:\ngot: %q\nwant: %q", i, got[i], tt.wantLines[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
348
internal/commands/shiftr.go
Normal file
348
internal/commands/shiftr.go
Normal file
|
|
@ -0,0 +1,348 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
"c65gm/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShiftRCommand handles logical shift right operations
|
||||||
|
// Syntax:
|
||||||
|
//
|
||||||
|
// SHIFTR <source> BY <amount> GIVING <dest> # old syntax with BY/GIVING
|
||||||
|
// SHIFTR <source> >> <amount> -> <dest> # old syntax with >>/->
|
||||||
|
// <dest> = <source> >> <amount> # new syntax
|
||||||
|
type ShiftRCommand struct {
|
||||||
|
sourceVarName string
|
||||||
|
sourceVarKind compiler.VarKind
|
||||||
|
sourceValue uint16
|
||||||
|
sourceIsVar bool
|
||||||
|
|
||||||
|
amountVarName string
|
||||||
|
amountVarKind compiler.VarKind
|
||||||
|
amountValue uint16
|
||||||
|
amountIsVar bool
|
||||||
|
|
||||||
|
destVarName string
|
||||||
|
destVarKind compiler.VarKind
|
||||||
|
|
||||||
|
line preproc.Line // Store line info for warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShiftRCommand) WillHandle(line preproc.Line) bool {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil || len(params) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old syntax: SHIFTR ... (must have exactly 6 params)
|
||||||
|
if strings.ToUpper(params[0]) == "SHIFTR" && len(params) == 6 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// New syntax: <dest> = <source> >> <amount>
|
||||||
|
if len(params) == 5 && params[1] == "=" && params[3] == ">>" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShiftRCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||||
|
// Clear state
|
||||||
|
c.sourceVarName = ""
|
||||||
|
c.sourceIsVar = false
|
||||||
|
c.sourceValue = 0
|
||||||
|
c.amountVarName = ""
|
||||||
|
c.amountIsVar = false
|
||||||
|
c.amountValue = 0
|
||||||
|
c.destVarName = ""
|
||||||
|
c.line = line // Store line for warnings
|
||||||
|
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
paramCount := len(params)
|
||||||
|
scope := ctx.CurrentScope()
|
||||||
|
|
||||||
|
// Create constant lookup function
|
||||||
|
constLookup := ctx.SymbolTable.ConstantLookupFunc(scope)
|
||||||
|
|
||||||
|
// Determine syntax and parse accordingly
|
||||||
|
if strings.ToUpper(params[0]) == "SHIFTR" {
|
||||||
|
// Old syntax: SHIFTR <source> BY/>> <amount> GIVING/-> <dest>
|
||||||
|
if paramCount != 6 {
|
||||||
|
return fmt.Errorf("SHIFTR: wrong number of parameters (%d), expected 6", paramCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
separator1 := strings.ToUpper(params[2])
|
||||||
|
if separator1 != "BY" && separator1 != ">>" {
|
||||||
|
return fmt.Errorf("SHIFTR: parameter #3 must be 'BY' or '>>', got %q", params[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
separator2 := strings.ToUpper(params[4])
|
||||||
|
if separator2 != "GIVING" && separator2 != "->" {
|
||||||
|
return fmt.Errorf("SHIFTR: parameter #5 must be 'GIVING' or '->', got %q", params[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination
|
||||||
|
destName := params[5]
|
||||||
|
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||||
|
if destSym == nil {
|
||||||
|
return fmt.Errorf("SHIFTR: unknown variable %q", destName)
|
||||||
|
}
|
||||||
|
if destSym.IsConst() {
|
||||||
|
return fmt.Errorf("SHIFTR: cannot assign to constant %q", destName)
|
||||||
|
}
|
||||||
|
c.destVarName = destSym.FullName()
|
||||||
|
c.destVarKind = destSym.GetVarKind()
|
||||||
|
|
||||||
|
// Parse source
|
||||||
|
var err error
|
||||||
|
c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[1], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTR: source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse amount
|
||||||
|
c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[3], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTR: amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// New syntax: <dest> = <source> >> <amount>
|
||||||
|
if paramCount != 5 {
|
||||||
|
return fmt.Errorf("SHIFTR: wrong number of parameters (%d), expected 5", paramCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params[1] != "=" {
|
||||||
|
return fmt.Errorf("SHIFTR: expected '=' at position 2, got %q", params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params[3] != ">>" {
|
||||||
|
return fmt.Errorf("SHIFTR: expected '>>' at position 4, got %q", params[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination
|
||||||
|
destName := params[0]
|
||||||
|
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||||
|
if destSym == nil {
|
||||||
|
return fmt.Errorf("SHIFTR: unknown variable %q", destName)
|
||||||
|
}
|
||||||
|
if destSym.IsConst() {
|
||||||
|
return fmt.Errorf("SHIFTR: cannot assign to constant %q", destName)
|
||||||
|
}
|
||||||
|
c.destVarName = destSym.FullName()
|
||||||
|
c.destVarKind = destSym.GetVarKind()
|
||||||
|
|
||||||
|
// Parse source
|
||||||
|
var err error
|
||||||
|
c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[2], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTR: source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse amount
|
||||||
|
c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam(
|
||||||
|
params[4], ctx.SymbolTable, scope, constLookup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHIFTR: amount: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate amount
|
||||||
|
if c.amountIsVar {
|
||||||
|
if c.amountVarKind == compiler.KindWord {
|
||||||
|
return fmt.Errorf("SHIFTR: amount must be BYTE variable, got WORD %q", c.amountVarName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.amountValue > 255 {
|
||||||
|
return fmt.Errorf("SHIFTR: amount constant %d out of BYTE range (0-255)", c.amountValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShiftRCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// Check if shift amount >= bit width (result will be zero)
|
||||||
|
bitWidth := 8
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
bitWidth = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
amountZero := false
|
||||||
|
if !c.amountIsVar {
|
||||||
|
// Constant amount
|
||||||
|
if c.amountValue >= uint16(bitWidth) {
|
||||||
|
amountZero = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if amountZero {
|
||||||
|
// Result is zero, just store zero
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Copy source to destination if needed
|
||||||
|
if c.sourceIsVar && c.sourceVarName == c.destVarName {
|
||||||
|
// Same variable, no copy needed
|
||||||
|
} else {
|
||||||
|
copyAsm := c.generateCopy()
|
||||||
|
asm = append(asm, copyAsm...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Apply shift
|
||||||
|
shiftAsm, err := c.generateShift(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
asm = append(asm, shiftAsm...)
|
||||||
|
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCopy generates assembly to copy source to destination
|
||||||
|
func (c *ShiftRCommand) generateCopy() []string {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// If source is literal, just load it
|
||||||
|
if !c.sourceIsVar {
|
||||||
|
lo := uint8(c.sourceValue & 0xFF)
|
||||||
|
hi := uint8((c.sourceValue >> 8) & 0xFF)
|
||||||
|
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
if lo != hi {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||||
|
}
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
return asm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source is variable
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
// Destination is byte
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
// Destination is word
|
||||||
|
if c.sourceVarKind == compiler.KindByte {
|
||||||
|
// Byte -> Word (zero-extend)
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
} else {
|
||||||
|
// Word -> Word
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return asm
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateShift generates assembly to shift destination right by amount
|
||||||
|
func (c *ShiftRCommand) generateShift(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// Constant amount
|
||||||
|
if !c.amountIsVar {
|
||||||
|
amount := c.amountValue
|
||||||
|
if amount == 0 {
|
||||||
|
return asm, nil // No shift needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine bit width
|
||||||
|
bitWidth := 8
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
bitWidth = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn if shift amount >= bit width (but not for 0)
|
||||||
|
if amount >= uint16(bitWidth) {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= %d bits, value will be zero\n",
|
||||||
|
c.line.Filename, c.line.LineNo, amount, bitWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount >= uint16(bitWidth) {
|
||||||
|
// Shift all bits out -> zero
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unroll shift loop
|
||||||
|
for i := uint16(0); i < amount; i++ {
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlsr %s+1", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable amount
|
||||||
|
// Generate labels
|
||||||
|
loopLabel := ctx.GeneralStack.Push()
|
||||||
|
ctx.GeneralStack.Pop()
|
||||||
|
doneLabel := ctx.GeneralStack.Push()
|
||||||
|
ctx.GeneralStack.Pop()
|
||||||
|
|
||||||
|
|
||||||
|
// Load amount into X
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
|
||||||
|
|
||||||
|
// Check for zero amount
|
||||||
|
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
|
||||||
|
|
||||||
|
// Shift loop
|
||||||
|
asm = append(asm, loopLabel)
|
||||||
|
if c.destVarKind == compiler.KindByte {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlsr %s+1", c.destVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName))
|
||||||
|
}
|
||||||
|
asm = append(asm, "\tdex")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
|
||||||
|
|
||||||
|
asm = append(asm, doneLabel)
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
551
internal/commands/shiftr_test.go
Normal file
551
internal/commands/shiftr_test.go
Normal file
|
|
@ -0,0 +1,551 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShiftRCommand_WillHandle(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
text string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// Old syntax
|
||||||
|
{"old syntax BY/GIVING", "SHIFTR a BY b GIVING c", true},
|
||||||
|
{"old syntax >>/->", "SHIFTR a >> b -> c", true},
|
||||||
|
{"old syntax mixed case", "shiftr x by y giving z", true},
|
||||||
|
|
||||||
|
// New syntax
|
||||||
|
{"new syntax basic", "result = a >> b", true},
|
||||||
|
{"new syntax with literals", "x = 10 >> 3", true},
|
||||||
|
|
||||||
|
// Should not handle
|
||||||
|
{"not shiftr - shiftl", "SHIFTL a BY b GIVING c", false},
|
||||||
|
{"not shiftr - add", "result = a + b", false},
|
||||||
|
{"not shiftr - wrong params", "SHIFTR a b c", false},
|
||||||
|
{"empty", "", false},
|
||||||
|
{"just SHIFTR", "SHIFTR", false},
|
||||||
|
{"assignment without shift", "x = y", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := &ShiftRCommand{}
|
||||||
|
line := preproc.Line{Text: tt.text}
|
||||||
|
if got := cmd.WillHandle(line); got != tt.want {
|
||||||
|
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShiftRCommand_Interpret_OldSyntax(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*compiler.CompilerContext)
|
||||||
|
text string
|
||||||
|
wantErr bool
|
||||||
|
check func(*testing.T, *ShiftRCommand)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "byte >> byte -> byte",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 3, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a BY b GIVING c",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if !cmd.sourceIsVar || cmd.sourceVarName != "a" {
|
||||||
|
t.Errorf("source should be var 'a'")
|
||||||
|
}
|
||||||
|
if !cmd.amountIsVar || cmd.amountVarName != "b" {
|
||||||
|
t.Errorf("amount should be var 'b'")
|
||||||
|
}
|
||||||
|
if cmd.destVarName != "c" || cmd.destVarKind != compiler.KindByte {
|
||||||
|
t.Errorf("dest should be byte 'c'")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word >> byte -> word",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("n", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR x BY n GIVING result",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.sourceVarKind != compiler.KindWord {
|
||||||
|
t.Errorf("source should be word")
|
||||||
|
}
|
||||||
|
if cmd.destVarKind != compiler.KindWord {
|
||||||
|
t.Errorf("dest should be word")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "literal >> var -> byte",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR $FF BY shift GIVING result",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.sourceIsVar {
|
||||||
|
t.Errorf("source should be literal")
|
||||||
|
}
|
||||||
|
if cmd.sourceValue != 0xFF {
|
||||||
|
t.Errorf("source value = %d, want 255", cmd.sourceValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "var >> literal -> byte",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 8, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR value BY 3 GIVING result",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.amountIsVar {
|
||||||
|
t.Errorf("amount should be literal")
|
||||||
|
}
|
||||||
|
if cmd.amountValue != 3 {
|
||||||
|
t.Errorf("amount value = %d, want 3", cmd.amountValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow syntax",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a >> b -> c",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.destVarName != "c" {
|
||||||
|
t.Errorf("dest should be c")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a BY b GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "assign to constant",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a BY b GIVING MAX",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word amount variable (error)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a BY b GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amount constant > 255 (error)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a BY 300 GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong separator #3",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a WITH b GIVING c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong separator #5",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "SHIFTR a BY b INTO c",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||||
|
if tt.setup != nil {
|
||||||
|
tt.setup(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &ShiftRCommand{}
|
||||||
|
line := preproc.Line{Text: tt.text}
|
||||||
|
|
||||||
|
err := cmd.Interpret(line, ctx)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Interpret() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr && tt.check != nil {
|
||||||
|
tt.check(t, cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShiftRCommand_Interpret_NewSyntax(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*compiler.CompilerContext)
|
||||||
|
text string
|
||||||
|
wantErr bool
|
||||||
|
check func(*testing.T, *ShiftRCommand)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "dest = var >> var",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = a >> b",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.destVarName != "result" {
|
||||||
|
t.Errorf("dest = %q, want 'result'", cmd.destVarName)
|
||||||
|
}
|
||||||
|
if !cmd.sourceIsVar || cmd.sourceVarName != "a" {
|
||||||
|
t.Errorf("source should be var 'a'")
|
||||||
|
}
|
||||||
|
if !cmd.amountIsVar || cmd.amountVarName != "b" {
|
||||||
|
t.Errorf("amount should be var 'b'")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dest = literal >> literal",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = $08 >> 3",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.sourceIsVar || cmd.amountIsVar {
|
||||||
|
t.Errorf("both params should be literals")
|
||||||
|
}
|
||||||
|
if cmd.sourceValue != 8 || cmd.amountValue != 3 {
|
||||||
|
t.Errorf("source=%d, amount=%d, want 8,3", cmd.sourceValue, cmd.amountValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word destination",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 1, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = value >> shift",
|
||||||
|
wantErr: false,
|
||||||
|
check: func(t *testing.T, cmd *ShiftRCommand) {
|
||||||
|
if cmd.destVarKind != compiler.KindWord {
|
||||||
|
t.Errorf("dest should be word")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown dest variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = a >> b",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong operator (not >>)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) {
|
||||||
|
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
},
|
||||||
|
text: "result = a << b",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||||
|
if tt.setup != nil {
|
||||||
|
tt.setup(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &ShiftRCommand{}
|
||||||
|
line := preproc.Line{Text: tt.text}
|
||||||
|
|
||||||
|
err := cmd.Interpret(line, ctx)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Interpret() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr && tt.check != nil {
|
||||||
|
tt.check(t, cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShiftRCommand_Generate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*compiler.CompilerContext) *ShiftRCommand
|
||||||
|
wantLines []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "constant folding - byte >> 0",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0x55,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 0,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #$55",
|
||||||
|
"\tsta result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - byte >> 3",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0x08,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 3,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #$08",
|
||||||
|
"\tsta result",
|
||||||
|
"\tlsr result",
|
||||||
|
"\tlsr result",
|
||||||
|
"\tlsr result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - byte >> 8 (zero)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0xFF,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 8,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #0",
|
||||||
|
"\tsta result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - word >> 1",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0x1234,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 1,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindWord,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #$34",
|
||||||
|
"\tsta result",
|
||||||
|
"\tlda #$12",
|
||||||
|
"\tsta result+1",
|
||||||
|
"\tlsr result+1",
|
||||||
|
"\tror result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "constant folding - word >> 16 (zero)",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: false,
|
||||||
|
sourceValue: 0xFFFF,
|
||||||
|
amountIsVar: false,
|
||||||
|
amountValue: 16,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindWord,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda #0",
|
||||||
|
"\tsta result",
|
||||||
|
"\tsta result+1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "byte variable >> byte variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: true,
|
||||||
|
sourceVarName: "value",
|
||||||
|
sourceVarKind: compiler.KindByte,
|
||||||
|
amountIsVar: true,
|
||||||
|
amountVarName: "shift",
|
||||||
|
amountVarKind: compiler.KindByte,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda value",
|
||||||
|
"\tsta result",
|
||||||
|
"\tldx shift",
|
||||||
|
"\tbeq _L2",
|
||||||
|
"_L1",
|
||||||
|
"\tlsr result",
|
||||||
|
"\tdex",
|
||||||
|
"\tbne _L1",
|
||||||
|
"_L2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word variable >> byte variable",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 3, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: true,
|
||||||
|
sourceVarName: "value",
|
||||||
|
sourceVarKind: compiler.KindWord,
|
||||||
|
amountIsVar: true,
|
||||||
|
amountVarName: "shift",
|
||||||
|
amountVarKind: compiler.KindByte,
|
||||||
|
destVarName: "result",
|
||||||
|
destVarKind: compiler.KindWord,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tlda value",
|
||||||
|
"\tsta result",
|
||||||
|
"\tlda value+1",
|
||||||
|
"\tsta result+1",
|
||||||
|
"\tldx shift",
|
||||||
|
"\tbeq _L2",
|
||||||
|
"_L1",
|
||||||
|
"\tlsr result+1",
|
||||||
|
"\tror result",
|
||||||
|
"\tdex",
|
||||||
|
"\tbne _L1",
|
||||||
|
"_L2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same source and dest",
|
||||||
|
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
|
||||||
|
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||||
|
return &ShiftRCommand{
|
||||||
|
sourceIsVar: true,
|
||||||
|
sourceVarName: "value",
|
||||||
|
sourceVarKind: compiler.KindByte,
|
||||||
|
amountIsVar: true,
|
||||||
|
amountVarName: "shift",
|
||||||
|
amountVarKind: compiler.KindByte,
|
||||||
|
destVarName: "value",
|
||||||
|
destVarKind: compiler.KindByte,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantLines: []string{
|
||||||
|
"\tldx shift",
|
||||||
|
"\tbeq _L2",
|
||||||
|
"_L1",
|
||||||
|
"\tlsr value",
|
||||||
|
"\tdex",
|
||||||
|
"\tbne _L1",
|
||||||
|
"_L2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||||
|
cmd := tt.setup(ctx)
|
||||||
|
|
||||||
|
got, err := cmd.Generate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(got) != len(tt.wantLines) {
|
||||||
|
t.Errorf("Generate() got %d lines, want %d lines\nGot:\n%s\nWant:\n%s",
|
||||||
|
len(got), len(tt.wantLines),
|
||||||
|
strings.Join(got, "\n"), strings.Join(tt.wantLines, "\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range got {
|
||||||
|
// Skip exact label comparison (they're generated dynamically)
|
||||||
|
if strings.HasPrefix(got[i], "_L") && strings.HasPrefix(tt.wantLines[i], "_L") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got[i] != tt.wantLines[i] {
|
||||||
|
t.Errorf("Line %d:\ngot: %q\nwant: %q", i, got[i], tt.wantLines[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
41
language.md
41
language.md
|
|
@ -156,6 +156,8 @@ result = 100 - 5 // Subtraction
|
||||||
mask = value & $FF // Bitwise AND
|
mask = value & $FF // Bitwise AND
|
||||||
flags = flags | $01 // Bitwise OR
|
flags = flags | $01 // Bitwise OR
|
||||||
toggle = value ^ $FF // Bitwise XOR
|
toggle = value ^ $FF // Bitwise XOR
|
||||||
|
result = value << 3 // Shift left
|
||||||
|
mask = flags >> 2 // Shift right
|
||||||
```
|
```
|
||||||
|
|
||||||
### Increment and Decrement
|
### Increment and Decrement
|
||||||
|
|
@ -280,7 +282,7 @@ FUNC initialize
|
||||||
screen = temp
|
screen = temp
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
FUNC setColor(color, brightness)
|
FUNC setColor({BYTE color} {BYTE brightness})
|
||||||
borderColor = color
|
borderColor = color
|
||||||
backgroundColor = brightness
|
backgroundColor = brightness
|
||||||
FEND
|
FEND
|
||||||
|
|
@ -305,12 +307,13 @@ Parameters can have different access modes:
|
||||||
- `io:` - Read-write
|
- `io:` - Read-write
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
FUNC add(in:a, in:b, out:result)
|
FUNC add(in:{BYTE a} in:{BYTE b} out:{BYTE result})
|
||||||
result = a + b
|
result = a + b
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
FUNC swap(io:x, io:y)
|
FUNC swap(io:{BYTE x} io:{BYTE y})
|
||||||
BYTE temp = x
|
BYTE temp
|
||||||
|
temp = x
|
||||||
x = y
|
x = y
|
||||||
y = temp
|
y = temp
|
||||||
FEND
|
FEND
|
||||||
|
|
@ -321,27 +324,19 @@ FEND
|
||||||
Variables declared inside functions are local:
|
Variables declared inside functions are local:
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
FUNC calculate(value)
|
FUNC calculate({BYTE value})
|
||||||
BYTE local = 10 // Only exists inside this function
|
BYTE local = 10 // Only exists inside this function
|
||||||
WORD temp // Local temporary
|
WORD temp // Local temporary
|
||||||
temp = value + local
|
temp = value + local
|
||||||
FEND
|
FEND
|
||||||
```
|
```
|
||||||
|
|
||||||
### Curly Brace Syntax
|
|
||||||
|
|
||||||
Alternative parameter syntax using curly braces:
|
|
||||||
|
|
||||||
```c65
|
|
||||||
FUNC process({BYTE value} out:{BYTE result})
|
|
||||||
result = value + 1
|
|
||||||
FEND
|
|
||||||
```
|
|
||||||
|
|
||||||
### Returning from Functions
|
### Returning from Functions
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
FUNC checkValue(value)
|
FUNC checkValue({BYTE value})
|
||||||
IF value == 0
|
IF value == 0
|
||||||
EXIT // Return early
|
EXIT // Return early
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
@ -445,12 +440,12 @@ ENDASM
|
||||||
Local variables (inside FUNC) need pipe delimiters `|varname|`:
|
Local variables (inside FUNC) need pipe delimiters `|varname|`:
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
FUNC calculate(value)
|
FUNC calculate({BYTE value})
|
||||||
BYTE local = 10
|
BYTE local = 10
|
||||||
ASM
|
ASM
|
||||||
lda |local| // Reference local variable
|
lda |local| // Reference local variable
|
||||||
clc
|
clc
|
||||||
adc value // Global parameter
|
adc value // Function parameter
|
||||||
sta |local|
|
sta |local|
|
||||||
ENDASM
|
ENDASM
|
||||||
FEND
|
FEND
|
||||||
|
|
@ -719,7 +714,7 @@ FUNC lib_mylib_initialize
|
||||||
lib_mylib_buffer = $C000
|
lib_mylib_buffer = $C000
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
FUNC lib_mylib_process(value)
|
FUNC lib_mylib_process({BYTE value})
|
||||||
// Do something
|
// Do something
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
@ -804,7 +799,7 @@ FEND
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
// Print null-terminated string
|
// Print null-terminated string
|
||||||
FUNC printString(textPtr)
|
FUNC printString({WORD textPtr})
|
||||||
BYTE char
|
BYTE char
|
||||||
char = PEEK textPtr[0]
|
char = PEEK textPtr[0]
|
||||||
|
|
||||||
|
|
@ -827,7 +822,7 @@ BYTE spriteX @ VIC2+0
|
||||||
BYTE spriteY @ VIC2+1
|
BYTE spriteY @ VIC2+1
|
||||||
BYTE spriteEnable @ VIC2+21
|
BYTE spriteEnable @ VIC2+21
|
||||||
|
|
||||||
FUNC enableSprite(spriteNum)
|
FUNC enableSprite({BYTE spriteNum})
|
||||||
BYTE mask
|
BYTE mask
|
||||||
mask = 1
|
mask = 1
|
||||||
|
|
||||||
|
|
@ -846,7 +841,7 @@ For frequently accessed pointers, use zero page:
|
||||||
```c65
|
```c65
|
||||||
WORD fastPtr @ $FB // Zero page = fast indexed access
|
WORD fastPtr @ $FB // Zero page = fast indexed access
|
||||||
|
|
||||||
FUNC processBuffer(buffer, size)
|
FUNC processBuffer({WORD buffer} {BYTE size})
|
||||||
POINTER fastPtr TO buffer
|
POINTER fastPtr TO buffer
|
||||||
|
|
||||||
WHILE size > 0
|
WHILE size > 0
|
||||||
|
|
@ -920,7 +915,7 @@ ENDSCRIPT
|
||||||
### Delay Loops
|
### Delay Loops
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
FUNC delay(frames)
|
FUNC delay({BYTE frames})
|
||||||
BYTE raster @ $D012
|
BYTE raster @ $D012
|
||||||
BYTE oldRaster
|
BYTE oldRaster
|
||||||
|
|
||||||
|
|
@ -1007,7 +1002,7 @@ Always use include guards in library files:
|
||||||
|
|
||||||
```c65
|
```c65
|
||||||
// Explain what and why, not how
|
// Explain what and why, not how
|
||||||
FUNC calculateTrajectory(velocity, angle)
|
FUNC calculateTrajectory({WORD velocity} {BYTE angle})
|
||||||
// Use fixed-point math (8.8 format)
|
// Use fixed-point math (8.8 format)
|
||||||
// because we don't have floating point
|
// because we don't have floating point
|
||||||
WORD xVel
|
WORD xVel
|
||||||
|
|
@ -1061,7 +1056,7 @@ FOR i = 0 TO 10
|
||||||
NEXT
|
NEXT
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
FUNC myFunc(in:param1, out:result)
|
FUNC myFunc(in:{BYTE param1} out:{BYTE result})
|
||||||
result = param1 + 1
|
result = param1 + 1
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -112,6 +112,8 @@ func registerCommands(comp *compiler.Compiler) {
|
||||||
comp.Registry().Register(&commands.DefaultCommand{})
|
comp.Registry().Register(&commands.DefaultCommand{})
|
||||||
comp.Registry().Register(&commands.EndSwitchCommand{})
|
comp.Registry().Register(&commands.EndSwitchCommand{})
|
||||||
comp.Registry().Register(&commands.MacroCommand{})
|
comp.Registry().Register(&commands.MacroCommand{})
|
||||||
|
comp.Registry().Register(&commands.ShiftLCommand{})
|
||||||
|
comp.Registry().Register(&commands.ShiftRCommand{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeOutput(filename string, lines []string) error {
|
func writeOutput(filename string, lines []string) error {
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ ASM
|
||||||
sta temp
|
sta temp
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
FUNC calculate(value)
|
FUNC calculate({BYTE value})
|
||||||
BYTE local = 10
|
BYTE local = 10
|
||||||
ASM
|
ASM
|
||||||
lda |local| ; Reference local variable
|
lda |local| ; Reference local variable
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue