diff --git a/flake.nix b/flake.nix index 95becdc..7359d40 100644 --- a/flake.nix +++ b/flake.nix @@ -6,6 +6,9 @@ system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; in { - packages.${system}.default = pkgs.callPackage ./package.nix {}; + packages.${system} = { + default = pkgs.callPackage ./package.nix {}; + test = pkgs.callPackage ./test.nix {}; + }; }; } diff --git a/package.nix b/package.nix index fcdacf6..9969c3e 100644 --- a/package.nix +++ b/package.nix @@ -15,8 +15,8 @@ stdenv.mkDerivation { configurePhase = '' ''; buildPhase = '' - nasm -f elf main.asm - ld -m elf_i386 main.o -o main + nasm -f elf main_start.asm + ld -m elf_i386 main_start.o -o main ''; installPhase = '' mkdir -p $out/bin diff --git a/src/lib.asm b/src/lib.asm index d464f02..41061b2 100644 --- a/src/lib.asm +++ b/src/lib.asm @@ -7,16 +7,16 @@ slen: push ebx mov ebx, eax -slen_nextchar: - cmp byte [eax], 0 - jz slen_finished - inc eax - jmp slen_nextchar + .loop: + cmp byte [eax], 0 + jz .fin + inc eax + jmp .loop -slen_finished: - sub eax, ebx - pop ebx - ret + .fin: + sub eax, ebx + pop ebx + ret ; ********************* ; clear_bss : BSS -> IO @@ -25,13 +25,13 @@ slen_finished: clear_bss: cmp byte [eax], 0 - jz clear_bss_finished + jz .fin mov [eax], byte 0 inc eax jmp clear_bss -clear_bss_finished: - ret + .fin: + ret ; ******************************* ; scmp : String -> String -> Bool @@ -41,25 +41,50 @@ clear_bss_finished: scmp: push ecx -scmp_nextchar: - mov cl, byte [ebx] - cmp byte [eax], cl - jnz scmp_bad - cmp byte [eax], 0 - jz scmp_good - inc eax - inc ebx - jmp scmp_nextchar + .loop: + mov cl, byte [ebx] + cmp byte [eax], cl + jnz .bad + cmp byte [eax], 0 + jz .good + inc eax + inc ebx + jmp .loop -scmp_good: - mov eax, 0 - jmp scmp_finished + .good: + mov eax, 0 + jmp .fin -scmp_bad: - mov eax, 1 + .bad: + mov eax, 1 -scmp_finished: + .fin: + pop ecx + ret + +; ************************ +; sdecprint : String -> IO +; prints string 1 shorter +; ************************ + +sdecprint: + push edx + push ecx + push ebx + push eax + call slen + dec eax + + mov edx, eax + pop eax + mov ecx, eax + mov ebx, 1 + mov eax, 4 + int 80h + + pop ebx pop ecx + pop edx ret ; ********************* @@ -104,6 +129,45 @@ sprintln: pop eax ; initial ret +; ******************* +; bprint : Byte -> IO +; prints 1 byte +; ******************* + +bprint: + push edx + push ecx + push ebx + push eax + + mov edx, 1 + pop eax + mov ecx, eax + mov ebx, 1 + mov eax, 4 + int 80h + + pop ebx + pop ecx + pop edx + ret + +; ********************* +; bprintln : Byte -> IO +; prints 1 byte + 0Ah +; ********************* + +bprintln: + call bprint + push eax + mov eax, 0Ah + push eax + mov eax, esp + call bprint + pop eax + pop eax + ret + ; ************ ; exit : IO ; exit program diff --git a/src/main.asm b/src/main.asm index c947b6d..de23e10 100644 --- a/src/main.asm +++ b/src/main.asm @@ -1,9 +1,18 @@ %include 'lib.asm' SECTION .data -welcome_message db 'Welcome to this test program! It is experimental and very subject to change.', 0h -prompt db 'Enter a string to print: ', 0h -exit_str db 'exit', 0Ah, 0h +s: + .welcome_message db 'Welcome to this test program! It is experimental and very subject to change.', 0h + .prompt db 'repl>', 0h + .error db '[ERROR] ', 0h + .command db 'command ', 0h + .not_found db ' not found', 0h + +fn: + .exit_name db 'exit', 0Ah, 0h + + .help_name db 'help', 0Ah, 0h + .help_content db 'this program has just 2 commands right now: help and exit', 0Ah, ' help - prints this message', 0Ah, ' exit - quits the program', 0Ah, 'Have fun!', 0h SECTION .bss ; resb ; 0001 byte @@ -14,48 +23,65 @@ SECTION .bss sinput: resb 255 ; 255 bytes SECTION .text -global _start - -_start: +start: pop ecx ; number of arguments pop eax ; dispose of first value, the exe dec ecx - mov eax, welcome_message + mov eax, s.welcome_message + call sprintln + + .arg_loop: + cmp ecx, 0h + jz .repl + pop eax + call sprintln + dec ecx + jmp .arg_loop + + .repl: + mov eax, s.prompt + call sprint + + mov eax, sinput + call clear_bss + + mov edx, 255 ; max bytes to read + mov ecx, sinput ; buffer + mov ebx, 0 ; (STDIN) where to read from + mov eax, 3 ; (SYS_READ) + int 80h + + mov eax, sinput + mov ebx, fn.exit_name + call scmp + cmp byte eax, 0 + jz exit + + mov eax, sinput + mov ebx, fn.help_name + call scmp + cmp byte eax, 0 + jz fn_help + + mov eax, s.error + call sprint + mov eax, s.command + call sprint + mov eax, sinput + call sdecprint + mov eax, s.not_found + call sprintln + + jmp .repl + +fn_help: + push eax + + mov eax, fn.help_content call sprintln -next_arg: - cmp ecx, 0h - jz prompt_loop pop eax - call sprintln - dec ecx - jmp next_arg -prompt_loop: - mov eax, prompt - call sprint - - mov eax, sinput - call clear_bss - - mov edx, 255 ; max bytes to read - mov ecx, sinput ; buffer - mov ebx, 0 ; (STDIN) where to read from - mov eax, 3 ; (SYS_READ) - int 80h - - mov eax, sinput - mov ebx, exit_str - call scmp - cmp byte eax, 0 - jz done - - mov eax, sinput - call sprint - - jmp prompt_loop - -done: - call exit + jmp start.repl diff --git a/src/main_start.asm b/src/main_start.asm new file mode 100644 index 0000000..7d284a7 --- /dev/null +++ b/src/main_start.asm @@ -0,0 +1,7 @@ +%include 'main.asm' + +SECTION .text +global _start + +_start: + jmp start diff --git a/src/test_start.asm b/src/test_start.asm new file mode 100644 index 0000000..0a67a01 --- /dev/null +++ b/src/test_start.asm @@ -0,0 +1,242 @@ +%include 'lib.asm' + +SECTION .data +ts: + .test_welcome db "Welcome to the test suite!", 0h + + .running_test db "running test ", 0h + .ellipsis db "... ", 0h + .error db "[ERROR] ", 0h + .failed db " failed", 0h + .failed_code db " failed with exit code ", 0h + .all_good db "all tests passed", 0h + .divider db "--------------------------------", 0h + +slen_zero_length: + .name db "slen_zero_length", 0h + .should db "slen should be 0", 0h + .result db "slen result is ", 0h + +scmp_a_zero_length: + .name db "scmp_a_zero_length", 0h + .should db "scmp should be 1", 0h + .result db "scmp result is ", 0h + +scmp_b_zero_length: + .name db "scmp_b_zero_length", 0h + .should db "scmp should be 1", 0h + .result db "scmp result is ", 0h + +scmp_both_zero_length: + .name db "scmp_both_zero_length", 0h + .should db "scmp should be 0", 0h + .result db "scmp result is ", 0h + +scmp_both_short_s: + .name db "scmp_both_short_s", 0h + .should db "scmp should be 0", 0h + .result db "scmp result is ", 0h + +cases: + .zero_length db 0h + .short_s db "1 short Test String!", 0h + +numerals db "0123456789", 0h + +SECTION .text +global _start + +_start: + mov eax, ts.test_welcome + call sprintln + + mov eax, ts.divider + call sprintln + mov eax, slen_zero_length.name + call sprintln + call slen_zero_length_test + + mov eax, ts.divider + call sprintln + mov eax, scmp_a_zero_length.name + call sprintln + call scmp_a_zero_length_test + + mov eax, ts.divider + call sprintln + mov eax, scmp_b_zero_length.name + call sprintln + call scmp_b_zero_length_test + + mov eax, ts.divider + call sprintln + mov eax, scmp_both_zero_length.name + call sprintln + call scmp_both_zero_length_test + + mov eax, ts.divider + call sprintln + mov eax, scmp_both_short_s.name + call sprintln + call scmp_both_short_s_test + + mov eax, ts.divider + call sprintln + mov eax, ts.all_good + call sprintln + + call exit + +slen_zero_length_test: + push eax + + mov eax, cases.zero_length + call slen + push eax + mov eax, slen_zero_length.should + call sprintln + mov eax, slen_zero_length.result + call sprint + pop eax + cmp eax, 0 + jnz slen_zero_length_test.failed + add eax, numerals + call bprintln + + pop eax + ret + + .failed: + mov ebx, slen_zero_length.name + call failed_with_code + +scmp_a_zero_length_test: + push ebx + push eax + + mov eax, cases.zero_length + mov ebx, cases.short_s + call scmp + push eax + mov eax, scmp_a_zero_length.should + call sprintln + mov eax, scmp_a_zero_length.result + call sprint + pop eax + cmp eax, 1 + jnz scmp_a_zero_length_test.failed + add eax, numerals + call bprintln + + pop eax + pop ebx + ret + + .failed: + mov ebx, scmp_a_zero_length.name + call failed_with_code + +scmp_b_zero_length_test: + push ebx + push eax + + mov eax, cases.short_s + mov ebx, cases.zero_length + call scmp + push eax + mov eax, scmp_b_zero_length.should + call sprintln + mov eax, scmp_b_zero_length.result + call sprint + pop eax + cmp eax, 1 + jnz scmp_b_zero_length_test.failed + add eax, numerals + call bprintln + + pop eax + pop ebx + ret + + .failed: + mov ebx, scmp_b_zero_length.name + call failed_with_code + +scmp_both_zero_length_test: + push ebx + push eax + + mov eax, cases.zero_length + mov ebx, eax + call scmp + push eax + mov eax, scmp_both_zero_length.should + call sprintln + mov eax, scmp_both_zero_length.result + call sprint + pop eax + cmp eax, 0 + jnz scmp_both_zero_length_test.failed + add eax, numerals + call bprintln + + pop eax + pop ebx + ret + + .failed: + mov ebx, scmp_both_zero_length.name + call failed_with_code + +scmp_both_short_s_test: + push ebx + push eax + + mov eax, cases.short_s + mov ebx, eax + call scmp + push eax + mov eax, scmp_both_short_s.should + call sprintln + mov eax, scmp_both_short_s.result + call sprint + pop eax + cmp eax, 0 + jnz scmp_both_zero_length_test.failed + add eax, numerals + call bprintln + + pop eax + pop ebx + ret + + .failed: + mov ebx, scmp_both_zero_length.name + call failed_with_code + +failed: + mov eax, ts.ellipsis + call sprintln + mov eax, ts.error + call sprint + mov eax, ebx + call sprint + mov eax, ts.failed + call sprint + call exit + +failed_with_code: + push eax + + mov eax, ts.ellipsis + call sprintln + mov eax, ts.error + call sprint + mov eax, ebx + call sprint + mov eax, ts.failed_code + call sprint + pop eax + add eax, numerals + call bprintln + call exit diff --git a/test.nix b/test.nix new file mode 100644 index 0000000..573ecde --- /dev/null +++ b/test.nix @@ -0,0 +1,30 @@ +{ + lib, + stdenv, + nasm, + ... +}: +stdenv.mkDerivation { + name = "asm-test"; + src = ./src; + nativeBuildInputs = [ + nasm + ]; + buildInputs = [ + ]; + configurePhase = '' + ''; + buildPhase = '' + nasm -f elf test_start.asm + ld -m elf_i386 test_start.o -o test + ''; + installPhase = '' + mkdir -p $out/bin + cp test $out/bin/test + ''; + + meta = { + license = lib.licenses.wtfpl; + mainProgram = "test"; + }; +}