325 lines
6.1 KiB
ArmAsm
325 lines
6.1 KiB
ArmAsm
|
|
.option norelax
|
|
.option rvc
|
|
.global init_flags
|
|
.global parse_flags
|
|
|
|
.data
|
|
|
|
flags:
|
|
.dword 0 # value / default
|
|
.byte 1 # type: <end>, bool, string, int
|
|
.byte 5 # flag length
|
|
#.short 16 # help length
|
|
.ascii "-help"
|
|
#.ascii "prints help text"
|
|
|
|
# last flag
|
|
.dword 0
|
|
.dword 0
|
|
|
|
.bss
|
|
|
|
entry_sp: .dword 0
|
|
saved_argc: .dword 0
|
|
saved_argv: .dword 0
|
|
|
|
.section .rodata
|
|
|
|
flag_failure_msg: .ascii "flag parsing failed\n"
|
|
.equ flag_failure_msg_len, (.) - flag_failure_msg
|
|
unknown_flag_msg: .ascii "unknown flag\n"
|
|
.equ unknown_flag_msg_len, (.) - unknown_flag_msg
|
|
|
|
.text
|
|
|
|
# when a program starts, it should stash the initial value of the sp register somewhere
|
|
# e.g.
|
|
#
|
|
# .data
|
|
# entry_sp:
|
|
# .dword 0
|
|
# _start:
|
|
# la t0, entry_sp
|
|
# sd sp, (t0)
|
|
#
|
|
# then at some opportune moment, it can call init_flags.
|
|
# a0 should be used to pass in the initial sp value.
|
|
#
|
|
# la t0, entry_sp
|
|
# ld a0, (t0)
|
|
# call init_flags
|
|
|
|
# in order to store argc and argv
|
|
# a0 - initial stack pointer
|
|
init_flags:
|
|
la t0, saved_argc
|
|
ld t1, (a0)
|
|
sd t1, (t0)
|
|
|
|
la t0, saved_argv
|
|
add t2, a0, 8
|
|
sd t2, (t0)
|
|
ret
|
|
|
|
# a0 - flag definition struct
|
|
parse_flags:
|
|
la a1, saved_argc
|
|
la a2, saved_argv
|
|
ld a1, (a1)
|
|
ld a2, (a2)
|
|
|
|
tail parse_flags_args
|
|
# n.b. tail clobbers t1
|
|
|
|
.macro subi dst, src, imm
|
|
addi \dst, \src, -\imm
|
|
.endm
|
|
|
|
# a0 - flag definition struct
|
|
# a1 - argc
|
|
# a2 - argv
|
|
parse_flags_args:
|
|
mv a7, ra
|
|
# TODO: positional arguments
|
|
|
|
# syntax:
|
|
# -flag (only boolean flags)
|
|
# -flag value (only non-boolean flags)
|
|
# -flag=value
|
|
|
|
# if argc <= 1, return
|
|
li t0, 1
|
|
bleu a1, t0, .Ldone
|
|
|
|
# main loop:
|
|
# for each argument, if it starts with a '-' then see if it matches a flag
|
|
|
|
# skip arg 0
|
|
addi s2, a2, 8
|
|
subi a1, a1, 1
|
|
.L_arg_loop:
|
|
ld s1, (s2) # load string pointer
|
|
beqz s1, .L_next_arg # skip null pointer
|
|
# check if first char is a '-'
|
|
lbu t0, 0(s1)
|
|
li t1, '-'
|
|
beq t0, t1, find_flag
|
|
|
|
.L_next_arg:
|
|
addi s2, s2, 8
|
|
subi a1, a1, 1
|
|
beqz a1, .Ldone
|
|
j .L_arg_loop
|
|
|
|
.Ldone:
|
|
mv ra, a7
|
|
mv a0, x0
|
|
ret
|
|
|
|
.Lunknown_flag:
|
|
mv ra, a7
|
|
li a7, 64 # sys_write
|
|
li a0, 2
|
|
la a1, unknown_flag_msg
|
|
li a2, unknown_flag_msg_len
|
|
ecall
|
|
li a0, -1
|
|
ret
|
|
|
|
# TODO: allow --flag
|
|
# TODO: -- should end flag parsing
|
|
|
|
find_flag:
|
|
mv s0, a0
|
|
.L_flag_loop:
|
|
lbu t3, 8(s0) # type
|
|
lbu s3, 9(s0) # flag length
|
|
beqz s3, .Lunknown_flag # len=0 signifies the end of the flag definitions
|
|
call flagcmp
|
|
# return value:
|
|
# 0: no match
|
|
# 1: match
|
|
# 2: match w/ equal sign
|
|
bnez t0, .Lfound
|
|
|
|
.L_next_flag:
|
|
addi s0, s0, 10
|
|
add s0, s0, s3
|
|
j .L_flag_loop
|
|
|
|
|
|
.Lfound:
|
|
subi t3, t3, 1
|
|
add s1, s1, s3 # arg+len
|
|
lbu t0, (s1)
|
|
beqz t0, 1f # is it a \0 or an =?
|
|
# =
|
|
# move s1 past the =
|
|
addi s1, s1, 1
|
|
j handle_flag
|
|
|
|
1:# \0
|
|
# is this a boolean flag? then there's no value
|
|
beqz t3, handle_flag
|
|
# if not, advance to the next arg
|
|
# a missing arg is an error
|
|
addi s2, s2, 8
|
|
subi a1, a1, 1
|
|
beqz a1, .Lbadarg
|
|
# load arg into s1
|
|
# a null arg is an error
|
|
ld s1, 0(s2)
|
|
beqz s1, .Lbadarg
|
|
# fallthrough
|
|
|
|
# s1 now points to the value of the flag
|
|
|
|
handle_flag:
|
|
# ok now parse the flag
|
|
#lbu t3, 8(s0) # load type
|
|
beqz t3, boolflag
|
|
li t0, 2-1
|
|
beq t0, t3, strflag
|
|
li t0, 3-1
|
|
beq t0, t3, intflag
|
|
j .Lbad_flag_def
|
|
|
|
strflag:
|
|
# easy, just store the string pointer
|
|
# TODO: strlen
|
|
sd s1, 0(s0)
|
|
j .L_next_arg
|
|
|
|
boolflag:
|
|
lbu t0, 0(s1)
|
|
# if arg is empty, set value to true
|
|
# TODO: should -bool and -bool= be treated differently?
|
|
beqz t0, 1f
|
|
# check for "true" and "false"
|
|
li t1, 'f'
|
|
beq t0, t1, .Lboolcmpfalse
|
|
li t1, 't'
|
|
beq t0, t1, .Lboolcmptrue
|
|
# anything else is an error
|
|
j .Lbadbool
|
|
|
|
.Lboolcmpfalse:
|
|
lbu t0, 1(s1)
|
|
#beqz t0, .Lbadbool
|
|
li t1, 'a'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 2(s1)
|
|
#beqz t0, .Lbadbool
|
|
li t1, 'l'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 3(s1)
|
|
#beqz t0, .Lbadbool
|
|
li t1, 's'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 4(s1)
|
|
#beqz t0, .Lbadbool
|
|
li t1, 'e'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 5(s1)
|
|
bnez t0, .Lbadbool
|
|
|
|
0:sd x0, 0(s0)
|
|
j .L_next_arg
|
|
|
|
.Lboolcmptrue:
|
|
lbu t0, 1(s1)
|
|
beqz t0, .Lbadbool
|
|
li t1, 'r'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 2(s1)
|
|
beqz t0, .Lbadbool
|
|
li t1, 'u'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 3(s1)
|
|
beqz t0, .Lbadbool
|
|
li t1, 'e'
|
|
bne t1, t0, .Lbadbool
|
|
|
|
lbu t0, 4(s1)
|
|
bnez t0, .Lbadbool
|
|
|
|
1:li t0, 1
|
|
sd t0, 0(s0)
|
|
j .L_next_arg
|
|
|
|
intflag:
|
|
# gotta convert string to int
|
|
# TODO: negative numbers
|
|
# TODO: detect overflow
|
|
# TODO: hex
|
|
mv t0, x0
|
|
li t2, 10
|
|
lbu t1, 0(s1)
|
|
beqz t1, .Lbadint
|
|
0:subi t1, t1, 0x30 # '0'
|
|
bltz t1, .Lbadint
|
|
bge t1, t2, .Lbadint
|
|
mul t0, t0, t2
|
|
add t0, t0, t1
|
|
addi s1, s1, 1
|
|
lbu t1, 0(s1)
|
|
bnez t1, 0b
|
|
1:sd t0, 0(s0)
|
|
j .L_next_arg
|
|
|
|
.Lbad_flag_def:
|
|
.Lbadarg:
|
|
.Lbadint:
|
|
.Lbadbool:
|
|
mv ra, a7
|
|
li a7, 64 # sys_write
|
|
li a0, 2
|
|
la a1, flag_failure_msg
|
|
li a2, flag_failure_msg_len
|
|
ecall
|
|
li a0, -1
|
|
ret
|
|
|
|
# in: s1 - string to compare, length unknown (null terminated)
|
|
# in: s0 - flag definition
|
|
# in: s3 - flag length
|
|
# out: t0 - match=1 no match=0
|
|
# clobbers t0, t1, t4, t5, t6, ra
|
|
flagcmp:
|
|
add t1, s0, 10 # flag
|
|
add t0, t1, s3 # end of flag
|
|
mv t4, s1 # str
|
|
3:lbu t5, (t1)
|
|
lbu t6, (t4)
|
|
bne t5, t6, 0f
|
|
# we don't _need_ to specifically check if t6==\0
|
|
# since that should be caught by the comparison above
|
|
# unless the flag contains an embedded \0 (it shouldn't)
|
|
# but do the check anyway just to be extra safe
|
|
beqz t6, 0f
|
|
addi t1, t1, 1
|
|
addi t4, t4, 1
|
|
# once we reach the end of the flag, we just need to check
|
|
# that the argument string ends in the same place
|
|
bge t1, t0, .Ltail
|
|
j 3b # loop
|
|
.Ltail:
|
|
lbu t1, (t4)
|
|
li t0, '='
|
|
beq t0, t1, 2f
|
|
beqz t1, 1f
|
|
0:mv t0, zero
|
|
ret
|
|
2:li t0, 2
|
|
ret
|
|
1:li t0, 1
|
|
ret
|
|
|