.option norelax .option rvc .global init_flags .global parse_flags .data flags: .dword 0 # value / default .byte 1 # type: , 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