Skip to content

Commit

Permalink
added support for negative values in expressions (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkamprath committed Jul 14, 2023
1 parent 0cb1168 commit 7fe201c
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Changes that are planned but not implemented yet:
* unknown labels

## [Unreleased]
* Added support for negative values in numeric expressions, data type initializations, and constant values

## [0.4.1]
* added `.asciiz` as an equivalent data directive to `.cstr`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ compare32:
.flags: .byte 0


; multiply32
; multiply 4 byte values X*Y, producing in a 8 byte results

; multiply_uint32
; multiply unsigned 4 byte values X*Y, producing in a 8 byte results
;
; Arguments
; sp+3 - value X (multiplier) (4 bytes)
Expand All @@ -45,7 +46,7 @@ compare32:
; sp+3 - results (8 bytes)
;

multiply32:
multiply_uint32:
; set counter for 32 bits
ldi 32 sta .counter
; set up 8 byte results memory block
Expand Down Expand Up @@ -76,6 +77,7 @@ multiply32:
;
; local method for shifting .working_mem right 1 bit
.mult_lsr64:
; save MSB to do a arithmetic shift right
lda .working_mem+7 lsr sta .working_mem+7
lda .working_mem+6 ror sta .working_mem+6
lda .working_mem+5 ror sta .working_mem+5
Expand All @@ -85,6 +87,63 @@ multiply32:
lda .working_mem+1 ror sta .working_mem+1
lda .working_mem+0 ror sta .working_mem+0
rts
.sign_bit: .byte 0

; multiply_int32
; multiply signed 4 byte values X*Y, producing in a 8 byte results
;
; Arguments
; sp+3 - value X (multiplier) (4 bytes)
; sp+7 - value Y (multiplicand) (4 bytes)
;
; Return Value
; sp+3 - results (8 bytes)
;

multiply_int32:
; set counter for 32 bits
ldi 32 sta .counter
; set up 8 byte results memory block
cpy4ai .working_mem+4,0 ; high word inialized to 0
cpy4as .working_mem,3 ; multiplier in low word
.mult_loop:
; check to see if LSb of working memory is 1
lda .working_mem+0 lsr bcc .continue
; add high word of results to multiplicand
phs4s 7
phs4a .working_mem+4
jps add32
cpy4as .working_mem+4,1
pls4
pls4
.continue:
; shift results right one.
jps .mult_lsr64
; decrement counter and stop if 0
deb .counter ldi 0 cpa .counter bne .mult_loop
.end:
cpy4sa 3,.working_mem+4
cpy4sa 7,.working_mem+0
rts
.counter: .byte 0
.working_mem: .zero 8
; .mult_lsr64
;
; local method for shifting .working_mem right 1 bit
.mult_lsr64:
; save MSB to do a arithmetic shift right
lda .working_mem+7 ani %10000000 sta .sign_bit
lda .working_mem+7 lsr sta .working_mem+7
lda .working_mem+6 ror sta .working_mem+6
lda .working_mem+5 ror sta .working_mem+5
lda .working_mem+4 ror sta .working_mem+4
lda .working_mem+3 ror sta .working_mem+3
lda .working_mem+2 ror sta .working_mem+2
lda .working_mem+1 ror sta .working_mem+1
lda .working_mem+0 ror sta .working_mem+0
lda .sign_bit adb .working_mem+7 sta .working_mem+7
rts
.sign_bit: .byte 0


; divide32
Expand Down Expand Up @@ -259,13 +318,15 @@ subtract32:
.yval: .4byte 0


#if 0
;
; 8-bit Random Number Generator
;
; Based on algorithm listed here:
; https://www.electro-tech-online.com/threads/ultra-fast-pseudorandom-number-generator-for-8-bit.124249/
;


; init_random8:
;
; Arguments
Expand Down Expand Up @@ -321,3 +382,4 @@ _random_seed_a: .byte 0
_random_seed_b: .byte 0
_random_seed_c: .byte 0
_random_seed_x: .byte 0
#endif
4 changes: 2 additions & 2 deletions examples/slu4-minimal-64/software/primes.min64
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ is_prime32:
phsi 0 ; push placeholder on stack for later compare
phs4a .current_i_val
phs4a .current_i_val
jps multiply32
jps multiply_uint32
; high 4 bytes of result should be 0 since we are only doing 32 bit N
lds 1 cpi 0 bne .iteration_loop_done
lds 2 cpi 0 bne .iteration_loop_done
Expand Down Expand Up @@ -157,5 +157,5 @@ is_prime32:
.temp_val: .4byte 0


#include "mathlib.min64"
#include "math32lib.min64"
#include "stringlib.min64"
87 changes: 85 additions & 2 deletions examples/slu4-minimal-64/software/stringlib.min64
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ cstr_reverse:

; uint32_to_decimal_cstr
;
; converts the passed uint64 value to a decimal formatted cstr.
; converts the passed uint32 value to a decimal formatted cstr.
;
; Arguments
; sp+3 : the uint64 value (4 byte)
; sp+3 : the uint32 value (4 byte)
; sp+7 : buffer address (2 bytes)
; sp+9 : buffer size (1 byte)
;
Expand Down Expand Up @@ -147,3 +147,86 @@ uint32_to_decimal_cstr:
pls2
jpa _Prompt
.err_buffer_size_str: .cstr "\nERROR: buffer too small for decimal conversion\n\n"


; int32_to_decimal_cstr
;
; converts the passed signed int32 value to a decimal formatted cstr.
;
; Arguments
; sp+3 : the signed int32 value (4 byte)
; sp+7 : buffer address (2 bytes)
; sp+9 : buffer size (1 byte)
;
; Returns
; writes binary string to buffer. Will reset all other values in buffer to null (0)
; updates sp+9 top the character length of the decimal string
; resets sp+7 to the buffer address that the null char was written to
;

int32_to_decimal_cstr:
; now remove sign, and call uint32_to_decimal_cstr
phss 9+0 ; push buffer size onto stack
phs2s 7+1 ; push buffer address onto stack
phs4s 3+3 ; push passed value onto stack
; if value is signed, perform 2's complement on value
; first determine if value is signed, remember stack is in big endian
lds 1 ani %10000000 sta .is_signed
cpi 0 beq .cacluate_str
lds 1 not sts 1
lds 2 not sts 2
lds 3 not sts 3
lds 4 not sts 4
; now add 1, remember stack is in big endian
lds 4 adi 1 sts 4
lds 3 aci 0 sts 3
lds 2 aci 0 sts 2
lds 1 aci 0 sts 1
.cacluate_str:
jps uint32_to_decimal_cstr
pls4 ; remove pushed value
pls2 ; remove pushed address
pls ; remove pushed buffer size

; now prepend with negative sign if needed
lda .is_signed cpi 0
beq .finished
; need to make room for negative sign
; set up buffer pointer
lds 7+0 sta .buffer_ptr+1
lds 7+1 sta .buffer_ptr+0
ldi 1 sta .counter
; scann through to find the end
.buffer_scan:
ldr .buffer_ptr
cpi 0
beq .found_buffer_end
inw .buffer_ptr
inb .counter
jpa .buffer_scan
.found_buffer_end:
; TODO should check that buffer has enough room
; copy buffer pinter
lda .buffer_ptr+0 sta .destination_ptr+0
lda .buffer_ptr+1 sta .destination_ptr+1
; increment destination buffer pointer
inw .destination_ptr
; now copy characters backwords for .count
.copy_loop:
ldr .buffer_ptr
str .destination_ptr
dew .buffer_ptr
dew .destination_ptr
deb .counter
bne .copy_loop ; if .counter goes to zero, we are done. If not, loop.
; now add minus sign at begining of string. Conveniently, .destination_ptr points at it
ldi '-' str .destination_ptr
.finished:
rts

.is_signed:
.byte 0
.counter:
.byte 0
.buffer_ptr: .2byte 0
.destination_ptr: .2byte 0
30 changes: 14 additions & 16 deletions src/bespokeasm/assembler/line_object/data_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class DataLine(LineWithBytes):
PATTERN_DATA_DIRECTIVE = re.compile(
r'^(\.byte|\.2byte|\.4byte|\.8byte|\.cstr|\.asciiz)\b\s*(?:(?P<quote>[\"\'])((?:\\(?P=quote)|.)*)(?P=quote)'
r'|({0}(?:\s*\,{1})*))'.format(INSTRUCTION_EXPRESSION_PATTERN, INSTRUCTION_EXPRESSION_PATTERN),
r'|({}(?:\s*\,{})*))'.format(INSTRUCTION_EXPRESSION_PATTERN, INSTRUCTION_EXPRESSION_PATTERN),
flags=re.IGNORECASE | re.MULTILINE
)

Expand Down Expand Up @@ -109,22 +109,20 @@ def generate_bytes(self):
elif isinstance(arg_item, str):
e: ExpressionNode = parse_expression(self.line_id, arg_item)
arg_val = e.get_value(self.label_scope, self.line_id)
# if is_string_numeric(arg_item):
# arg_val = parse_numeric_string(arg_item)
# else:
# label_val = self.label_scope.get_label_value(arg_item, self.line_id)
# if label_val is not None:
# arg_val = label_val
# else:
# sys.exit(
# f'ERROR: line {self.line_id} - unknown label "{arg_item}" in current scope "{self.label_scope}"'
# )
else:
sys.exit(f'ERROR: line {self.line_id} - unknown data item "{arg_item}"')
value_bytes = (arg_val & DataLine.DIRECTIVE_VALUE_MASK[self._directive]).to_bytes(
DataLine.DIRECTIVE_VALUE_BYTE_SIZE[self._directive],
byteorder=self._endian,
signed=(arg_val < 0)
)
try:
value_bytes = (arg_val & DataLine.DIRECTIVE_VALUE_MASK[self._directive]).to_bytes(
DataLine.DIRECTIVE_VALUE_BYTE_SIZE[self._directive],
byteorder=self._endian,
# since we are masking the value to a specific byte size, the signed
# argument should always be False
signed=False,
)
except OverflowError as oe:
sys.exit(
f'ERROR - {self.line_id}: Overflow error when converting value ({arg_val}) to '
f'bytes on dataline. Error = {oe}'
)
for b in value_bytes:
self._append_byte(b)
58 changes: 40 additions & 18 deletions src/bespokeasm/expression/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,25 @@
from bespokeasm.utilities import is_valid_label

EXPRESSION_PARTS_PATTERN = \
r'(?:(?:\%|b)[01]+|{0}|\d+|[\+\-\*\/\&\|\^\(\)]|>>|<<|%|LSB\(|BYTE\d\(|(?:\.|_)?\w+|\'.\')'.format(
r'(?:(?:\%|b)[01]+|{}|\d+|[\+\-\*\/\&\|\^\(\)]|>>|<<|%|LSB\(|BYTE\d\(|(?:\.|_)?\w+|\'.\')'.format(
PATTERN_HEX
)


class TokenType(enum.Enum):
T_NUM = 0
T_LABEL = 1
T_RIGHT_SHIFT = 2
T_LEFT_SHIFT = 3
T_PLUS = 4
T_MINUS = 5
T_MULT = 6
T_DIV = 7
T_MOD = 8
T_AND = 9
T_OR = 10
T_XOR = 11
T_NEGATION = 2
T_RIGHT_SHIFT = 3
T_LEFT_SHIFT = 4
T_PLUS = 5
T_MINUS = 6
T_MULT = 7
T_DIV = 8
T_MOD = 9
T_AND = 10
T_OR = 11
T_XOR = 12
T_LSB = 13
T_BYTE = 14
T_LPAR = 15
Expand All @@ -55,13 +56,14 @@ class ExpressionNode:
TokenType.T_XOR: operator.xor,
TokenType.T_RIGHT_SHIFT: operator.rshift,
TokenType.T_LEFT_SHIFT: operator.lshift,
TokenType.T_NEGATION: operator.neg,
}

def __init__(self, token_type: TokenType, value=None):
self.token_type = token_type
self.value = value
self.left_child = None
self.right_child = None
self.left_child: ExpressionNode = None
self.right_child: ExpressionNode = None
self._is_unary = token_type in [TokenType.T_BYTE, TokenType.T_LSB]

def __repr__(self):
Expand All @@ -72,7 +74,11 @@ def __str__(self):

@property
def is_unary(self) -> bool:
return self._is_unary
return self.token_type in [
TokenType.T_BYTE,
TokenType.T_LSB,
TokenType.T_NEGATION,
]

def _numeric_value(self, label_scope: LabelScope, line_id: LineIdentifier) -> int:
if self.token_type == TokenType.T_NUM:
Expand All @@ -97,9 +103,19 @@ def _compute(self, label_scope: LabelScope, line_id: LineIdentifier) -> int:
if self.token_type == TokenType.T_BYTE:
byte_idx = int(self.value[4])
arg_value = int(self.left_child._compute(label_scope, line_id))
byte_count = max(((arg_value.bit_length() + 7) // 8), byte_idx+1)
arg_value_bytes = arg_value.to_bytes(byte_count, 'little')
byte_count = max(((abs(arg_value).bit_length() + 7) // 8), byte_idx+1)
masked_arg = arg_value & (2**(8 * byte_count) - 1)
try:
arg_value_bytes = masked_arg.to_bytes(byte_count, byteorder='little', signed=False)
except OverflowError as oe:
sys.exit(
f'ERROR - {line_id}: Cound not conver value "{arg_value}" to bytes. masked value = {masked_arg}, {oe}'
)
return arg_value_bytes[byte_idx]
elif self.token_type == TokenType.T_NEGATION:
arg_value = self.left_child._compute(label_scope, line_id)
operation = ExpressionNode._operations[self.token_type]
return operation(arg_value)
else:
left_result = self.left_child._compute(label_scope, line_id)
right_result = self.right_child._compute(label_scope, line_id)
Expand Down Expand Up @@ -128,7 +144,7 @@ def contains_register_labels(self, register_labels: set[str]) -> bool:

def contained_labels(self) -> set[str]:
if self.token_type == TokenType.T_LABEL:
return set([self.value])
return {self.value}
elif self.token_type in [TokenType.T_NUM]:
return set()
left_result: set[str] = self.left_child.contained_labels()
Expand Down Expand Up @@ -231,6 +247,12 @@ def _parse_e4(line_id: LineIdentifier, tokens: list[ExpressionNode]) -> Expressi
node.left_child = _parse_e(line_id, tokens)
_match(line_id, tokens, TokenType.T_RPAR)
return node
elif tokens[0].token_type == TokenType.T_MINUS:
# if we are here, this should be a negation
node = ExpressionNode(TokenType.T_NEGATION, value=tokens[0].value)
tokens.pop(0)
node.left_child = _parse_e(line_id, tokens)
return node
else:
_match(line_id, tokens, TokenType.T_LPAR)
expression = _parse_e(line_id, tokens)
Expand All @@ -242,4 +264,4 @@ def _match(line_id: LineIdentifier, tokens: list[ExpressionNode], token: TokenTy
if tokens[0].token_type == token:
return tokens.pop(0)
else:
raise SyntaxError(f'ERROR: {line_id} - Invalid syntax on token: {tokens}')
raise SyntaxError(f'ERROR: {line_id} - Invalid syntax on token: {tokens}. Expected {token}')
Loading

0 comments on commit 7fe201c

Please sign in to comment.