Tag: Programming

  • chastelib Java Edition

    I translated my C library of functions into a Java class. This will help make future programs easier to port. I am very rusty on the Java language but it is a fairly old and reliable language. There are some cool things about Java, even though my favorite language will always be C.

    main.java

    /*
     Java Port of Chastity's library of standard functions
    
     This Java class is a library of functions written by Chastity White Rose.
     The functions are for converting strings into integers and integers into strings.
    
     Core Four Functions
    
     intstr: Convert an integer to a string in bases 2 to 36
     strint: Convert a string in bases 2 to 36 to an integer
     putstr: Print a string to standard output
     putint: Print an integer using intstr and then putstr
    
    */
    public class main
    {
    
     public static void main(String[] args)
     {
      String s="Official test suite for the Java version of chastelib.\n";
      int a=0,b=0x10;
    
      radix=16;
      int_width=1;
    
      putstr(s);
    
      b=strint("100"); /*convert string to an integer*/
      if(strint_errors!=0) /*if there are errors, print some messages and exit the program*/
      {
       putstr("Input string passed to strint function contains errors.\n");
       putstr("Fix the invalid characters or radix and try again.\n");
       return;
      }
    
      while(a<b)
      {
       radix=2;
       int_width=8;
       putint(a);
       putstr(" ");
       radix=16;
       int_width=2;
       putint(a);
       putstr(" ");
       radix=10;
       int_width=3;
       putint(a);
    
       if(a>=0x20 && a<=0x7E)
       {
        putstr(" ");
        putchar(a);
       }
    
       putstr("\n");
       a++;
      }
    
      putstr(s);
     }
    
     static void putstr(String s)
     {
      System.out.print(s);
     }
    
     static void putchar(int i)
     {
      String s=""; /*create empty string*/
      s=(char)i+s; /*add char of i to string*/
      putstr(s);   /*print the string*/
     }
    
     /*radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal*/
     static int radix=2;
     /*default minimum digits for printing integers*/
     static int int_width=1;
    
     static String intstr(int i)     /*Chastity's supreme integer to string conversion function*/
     {
      int width=0;                   /*the width or how many digits including prefixed zeros are printed*/
      int c=0;                       /*byte for the current character*/
      String s="";
      while(i!=0 || width<int_width) /*loop to fill the string with every required digit plus prefixed zeros*/
      {
       c=i%radix;                    /*get the remainder of division by the radix or base*/
       i/=radix;                     /*divide the input by radix*/
       if(c<10){c+='0';}             /*convert digits 0 to 9 to the ASCII character for that digit*/
       else{c=c+'A'-10;}             /*for digits higher than 9, convert to letters starting at A*/
       s=(char)c+s;                  /*prefix return string with this digit's character*/
       width++;                      /*increment the width so we know when enough digits are saved*/
      }
     return s;                      /*return this string to be used by putstr,printf,std::cout or whatever*/
    }
    
    
     static void putint(int i)
     {
      putstr(intstr(i));
     }
    
     static int strint_errors = 0; 
    
    
     static int strint(String s)
     {
      int i=0;
      int c;
      strint_errors = 0; /*set zero errors before we parse the string*/
      if( radix<2 || radix>36 ){ strint_errors++; putstr("Error: radix is out of range!\n");}
      int x=0;
      while(x<s.length())
      {
       c=s.charAt(x);
       if( c >= '0' && c <= '9' ){c-='0';}
       else if( c >= 'A' && c <= 'Z' ){c-='A';c+=10;}
       else if( c >= 'a' && c <= 'z' ){c-='a';c+=10;}
       else if( c == ' ' || c == '\n' || c == '\t' ){break;}
       else
       {
        strint_errors++;
        putstr("Error: character is not alphanumeric -> ");
        putchar(s.charAt(x));
        putstr("\n");
        break;
       }
       if(c>=radix)
       {
        strint_errors++;
        putstr("Error: character is not a valid for current radix -> ");
        putchar(s.charAt(x));
        putstr("\n");
        break;
       }
       i*=radix;
       i+=c;
       x++;
      }
      return i;
     }
    
    }
    
    
  • 32-bit Windows chastehex update

    I made an update to the 32-bit version of chastehex for windows. Variable names have been changed to match the Linux version and the getarg function is now used instead of my old badly maintained code. Having all arguments handled by a separate header file helps keep things very organized.

    Writing this program with the Windows API in Assembly has taking a lot more time than the Linux version did, but this experience has made me appreciate the Linux system call interface a lot more. I also have working Windows versions of my Assembly programs now because I have made the effort. The chastehex program is only a small taste of what will be included in the Windows edition of Assembly Arithmetic Algorithms for Windows. So far, I have 10 chapters out of 15 to 20 planned.

    https://leanpub.com/assemblyarithmeticalgorithms-windows

    main.asm

    format PE console
    entry main
    
    include 'win32a.inc'        ;includes standard Windows 32-bit definitions and macros
    include 'chastelib-w32.asm' ;include standard library functions by Chastity
    include 'getarg-w32.asm'    ;include Chastity's getarg function for command line arguments
    
    main:
    
    mov [radix],16 ; Choose radix for integer output.
    mov [int_width],1
    
    call getarg ;this first call will get the command string
    cmp eax,0   ;did the getarg function return 0?
    jz help     ;if eax was zero, there are no args so we end the program safely after help message
    
    ;optionally display the command string
    ;call putstring
    ;call putline
    
    call getarg ;get next arg as file name
    cmp eax,0   ;did the getarg function return 0?
    jz help     ;if eax was zero, there are no args so we end the program safely after help message
    
    ;print the filename
    call putstring
    call putline
    
    mov [filename], eax ;save the filename to a permanent address
    
    jmp open_sesame
    
    help:
    
    mov eax,help_message
    call putstring
    
    jmp main_end
    
    open_sesame:
    
    ;open a file with the CreateFileA function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
    
    ;call CreateFileA with Windows 32-bit calling convention
    
    push 0                  ;hTemplateFile = NULL: We are not using a template file
    push 0x80               ;dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL
    push 3                  ;dwCreationDisposition = OPEN_EXISTING
    push 0                  ;lpSecurityAttributes = NULL: No security attributes
    push 1                  ;dwShareMode = FILE_SHARE_READ (other programs can read this file)
    push 0x10000000         ;dwDesiredAccess = GENERIC_ALL access mode (Read+Write+Execute)
    push [filename]         ;lpFileName
    call [CreateFileA]
    
    
    ;check eax for file handle or error code
    ;call putint
    cmp eax,-1
    jnz file_ok
    
    mov eax,file_error_message
    call putstring
    call [GetLastError]
    call putint
    jmp main_end ;end program if the file was not opened
    
    ;this label is jumped to when the file is opened correctly
    file_ok:
    
    mov [fd],eax
    
    ;before we proceed, we also check for more arguments.
    
    ;get next arg (first one after name of program)
    call getarg
    cmp eax,0  ;did the getarg function return 0?
    jz hexdump ;proceed to normal hex dump if no more args
    
    ;otherwise interpret the arg as a hex address to seek to
    
    call strint
    mov [offset],eax
    
    ;seek to 32-bit address of file with SetFilePointer function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointer
    
    
    push 0           ;seek from beginning of file (SEEK_SET)
    push 0           ;NULL: We are not using a 64 bit address
    push [offset]    ;where we are seeking to
    push [fd]        ;seek within this file
    call [SetFilePointer]
    
    
    ;check for more args after the address argument
    call getarg
    cmp eax,0
    jz read_one_byte ;proceed to read one byte mode
    
    ;otherwise, write the rest of the arguments as bytes to the file!
    write_bytes:
    call strint
    mov [buf],al
    
    ;Windows 32-bit WriteFile system call
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
    
    
    push 0               ;lpOverlapped = NULL
    push 0               ;lpNumberOfBytesWritten = NULL
    push 1               ;nNumberOfBytesToWrite = 1
    push buf             ;lpBuffer = address of data to write
    push [fd]            ;hFile = [fd] (copied there earlier)
    call [WriteFile]
    
    
    mov eax,[offset]
    inc [offset]
    mov [int_width],8
    call putint_and_space
    
    mov eax,0
    mov al,[buf]
    mov [int_width],2
    call putint_and_line
    
    ;check for more args
    call getarg ;get next arg as potential bytes to write
    cmp eax,0   ;did the getarg function return 0?
    jnz write_bytes
    ;continue write if the args still exist
    ;otherwise end program
    jmp main_end
    
    read_one_byte:
    
    ;read only 1 byte using Win32 ReadFile system call.
    
    
    push 0              ;Optional Overlapped Structure 
    push count     ;Store Number of Bytes Read from this call
    push 1              ;Number of bytes to read
    push buf     ;address to store bytes
    push [fd]  ;handle of the open file
    call [ReadFile]
    
    cmp [count],1 
    jz print_byte ;if less than one bytes read, there is an error
    
    mov eax,[offset]
    mov [int_width],8
    call putint_and_space
    mov eax,end_of_file
    call putstr_and_line
    
    jmp main_end
    
    print_byte:
    mov eax,[offset]
    mov [int_width],8
    call putint_and_space
    
    mov eax,0
    mov al,[buf]
    mov [int_width],2
    call putint_and_line
    
    jmp main_end
    
    hexdump:
    
    ;read bytes using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push count     ;Store Number of Bytes Read from this call
    push 16             ;Number of bytes to read
    push buf     ;address to store bytes
    push [fd]  ;handle of the open file
    call [ReadFile]     ;all the data is in place, do the write thing!
    
    mov eax,[count]
    ;call putint
    ;mov eax,buf
    ;call putstring
    
    cmp eax,0
    jnz read_ok ;if more than zero bytes read, proceed to display
    
    jmp eof_end
    
    read_ok:
    call print_bytes_row
    
    jmp hexdump
    
    print_EOF:
    
    mov eax,[offset]
    mov [int_width],8
    call putint_and_space
    
    mov eax,end_of_file
    call putstr_and_line
    
    jmp main_end
    
    
    eof_end:
    ;before we end the program, let the user know End Of File was reached
    mov eax,end_of_file
    call putstr_and_line
    
    main_end:
    
    ;close the file
    push [fd]
    call [CloseHandle]
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    
    
    ;variables for displaying messages
    file_error_message db 'error: ',0
    end_of_file db 'EOF',0
    read_error_message db 'Failure during reading of file. Error number: ',0
    
    help_message db 'chastehex by Chastity White Rose',0Ah,0Ah
    db 'hexdump a file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'read a byte:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'write a byte:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah
    db 'The file must exist',0Ah,0
    
    ;this function prints a row of hex bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov eax,[offset]
    mov [int_width],8
    call putint_and_space
    
    mov ebx,buf
    mov ecx,[count]
    add [offset],ecx
    next_byte:
    mov eax,0
    mov al,[ebx]
    mov [int_width],2
    call putint_and_space
    
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_byte
    
    mov ecx,[count]
    pad_spaces:
    cmp ecx,0x10
    jz pad_spaces_end
    mov eax,space_three
    call putstring
    inc ecx
    jmp pad_spaces
    pad_spaces_end:
    
    ;optionally, print chars after hex bytes
    call print_bytes_row_text
    call putline
    
    ret
    
    space_three db '   ',0
    
    print_bytes_row_text:
    mov ebx,buf
    mov ecx,[count]
    next_char:
    mov eax,0
    mov al,[ebx]
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp al,0x20
    jb not_printable
    cmp al,0x7E
    ja not_printable
    
    printable:
    ;if char is in printable range,copy as is and proceed to next index
    jmp next_index
    
    not_printable:
    mov al,'.' ;otherwise replace with placeholder value
    
    next_index:
    mov [ebx],al
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_char
    mov [ebx],byte 0 ;make sure string is zero terminated
    
    mov eax,buf
    call putstring
    
    ret
    
    ;variables for managing file IO.
    filename dd ?
    count dd ? ;how many bytes are read with ReadFile operation
    buf db 16 dup ?,0
    fd dd ?
    offset dd ?
    
    ;FASM builds the Import Address Table (IAT) directly in the source file
    section '.idata' import data readable writeable
    
    library kernel32, 'KERNEL32.DLL'
    
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA',\
     CreateFileA, 'CreateFileA',\
     GetLastError, 'GetLastError',\
     SetFilePointer, 'SetFilePointer',\
     ReadFile, 'ReadFile',\
     CloseHandle, 'CloseHandle'
    
    

    chastelib-w32.asm

    ; chastelib assembly header file for 32 bit Windows
    ; This file is where I keep the source of my most important Assembly functions
    ; These are my string and integer output and conversion routines.
    
    ; To simplify documentation. The Accumulator/Arithmetic register
    ; (ax,eax,rax) depending on bit size shall be referred to as register A
    ; for the description of these core functions because the A register
    ; is treated special both by the Intel company and my code;
    
    ; putstring; Prints a zero terminated string from the address pointer to by A register.
    ; intstr;    Converts the number in A into a zero terminated string and points A to that address
    ; putint;    Prints the integer in A by calling intstr and then putstring.
    ; strint;    Converts the zero terminated string into an integer and sets A to that value
       
    ; Now, the source of the functions begins, with comments included for parts that I felt needed explanation.
    
    putstring:         ;print string pointed to by eax register
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax             ;copy eax to ebx to be used as index to the string
    
    putstring_strlen_start: ;this loop finds the length of the string as part of the putstring function
    
    cmp [ebx],byte 0        ;compare byte at address ebx with 0
    jz putstring_strlen_end ;if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub ebx,eax ;subtract start pointer from current pointer to get length of string
    
    ;Windows 32-bit WriteFile system call
    
    push 0               ;lpOverlapped = NULL
    push 0               ;lpNumberOfBytesWritten = NULL
    push ebx             ;nNumberOfBytesToWrite = ebx
    push eax             ;lpBuffer = address of string to write
    push -11             ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle]  ;Get Standard Handle for -11
    push eax             ;hFile = eax (returned from GetStdHandle)
    call [WriteFile]
    
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ; This is the location in memory where digits are written to by the intstr function
    ; The string of bytes and settings such as the radix and width are global variables defined below.
    
    int_string db 32 dup '?' ;reserve bytes for characters string for 32-bit binary integer
    
    int_string_end db 0 ;zero byte terminator for the integer string
    
    radix dd 2     ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dd 8 ;default width of integers. Extra zeros prefixed if more than 1
    
    ;this function creates a string of the integer in eax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads eax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov ebx,int_string_end-1 ;find address of lowest digit
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    jnb hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add edx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub edx,10
    add edx,'A'
    
    save_digit:
    
    mov [ebx],dl
    cmp eax,0
    jz intstr_end
    dec ebx
    inc ecx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp ecx,[int_width]
    jnb end_zeros
    dec ebx
    mov [ebx],byte '0'
    inc ecx
    jmp prefix_zeros
    end_zeros:
    
    mov eax,ebx ;point eax register to this string for putstring
    
    ret
    
    ;function to print string form of whatever integer is in eax
    ;The radix determines which number base the string form takes.
    ;Anything from 2 to 36 is a valid radix
    ;in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ;this function does not process anything by itself but calls the combination of my other
    ;functions in the order I intended them to be used.
    
    putint: 
    
    push eax
    push ebx
    push ecx
    push edx
    
    call intstr
    call putstring
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ;this function converts a string pointed to by eax into an integer returned in eax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    ;This function is improved with error checking and uses the new strint_error variable
    ;The program can check this value after the call and see how many errors happened.
    
    strint_error db 0 ;declare a byte variable that keeps track of errors
    
    strint:
    
    mov ebx,eax ;copy string address from eax to ebx because eax will be replaced soon!
    mov eax,0
    mov byte[strint_error],0 ;set errors to 0 at the start of this function
    
    read_strint:
    mov ecx,0   ;zero ecx so only lower 8 bits are used
    mov cl,[ebx]
    inc ebx
    cmp cl,0    ;compare this byte with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a decimal digit, but it could be perhaps an alphabet character
    ;which could be a digit in a higher base like hexadecimal
    ;we will check for that possibility next
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function with error
    jmp strint_end_error
    
    process_char:
    
    cmp ecx,[radix] ;compare char with radix
    jnb strint_end_error ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov edx,0 ;zero edx because it is used in mul sometimes
    mul dword [radix] ;mul eax with radix
    add eax,ecx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end_error:  ;we jump here if there was an error with one of the chars
    inc byte[strint_error] ;increment error counter because char invalid
    
    strint_end: ;we jump here when no errors happened
    
    ret
    
    ;The utility functions below simply print a space or a newline.
    ;these help me save code when printing lots of strings and integers.
    
    space db ' ',0 ;a string containing only a space
    
    putspace:
    push eax
    mov eax,space
    call putstring
    pop eax
    ret
    
    line db 0x0D,0x0A,0 ;a string containing only a newline
    
    ;the next function which pushes eax to the stack
    ;moves the address of the line string and prints it with putstring
    ;then it pops the original value of eax back from the stack before the function returns
    ;this allows me to print a newline anywhere in the code without a single register changing
    
    putline:
    push eax
    mov eax,line
    call putstring
    pop eax
    ret
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push eax
    mov [char],al
    mov eax,char
    call putstring
    pop eax
    ret
    
    ;a small function just for the common operation of
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    
    putint_and_space:
    call putint
    call putspace
    ret
    
    ;a small function just for the common operation of
    ;printing an integer followed by a line feed
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    
    putint_and_line:
    call putint
    call putline
    ret
    
    ;a small function just for the common operation of
    ;printing a string followed by a line feed
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    ;it also means we don't need to include a newline in every string!
    
    putstr_and_line:
    call putstring
    call putline
    ret
    
    

    getarg-w32.asm

    ;This is the Windows 32-bit source of the getarg function 
    
    ;The getarg function makes my assembly code for DOS and Windows easier to read
    ;by writing one function to handle all possible cases I will need for my tools.
    ;
    ;The first time it is run, it returns the whole command string or zero if no args are given
    ;Each time after that, it will give you the next argument, which is a substring of the original.
    ;When no more arguments are available, it will always return zero
    ;The program calling this is expected to check for this error and then terminate
    ;or print a message depending on the goals of that program
    
    ;A word of warning, though, this function has multiple return statements and is long
    ;However, it is fully featured in that it can recognize quoted strings as being the same argument
    ;This brings full compatibility for my DOS and Windows programs that use command line arguments.
    
    ;Because all of my programs are ported to DOS and Windows from Linux
    ;I found there to be conflicts in the way these operating systems did not
    ;handle the quoted strings.
    ;
    ;This same function is available in a separate file for DOS Assembly.
    
    getarg:
    
    mov ebx,[arguments_start] ;get the address of start of arguments
    cmp ebx,0 ;is this address zero? (meaning this function was not called before)
    jz get_arg_data ;if it was zero, then get the argument data for the first execution of this function
    
    ;if the start was not zero, then clearly arguments exist and addresses have been saved
    cmp ebx,[arguments_end]  ;is the address of the start and end the same?
    jnz find_next_string  ;if they are not the same, find the next sub string
    mov eax,0 ;otherwise, return eax as zero and check this in the main program
    
    ret
    
    find_next_string:
    
    mov ebx,[arguments_start] ;get address of current arg
    
    skip_spaces:
    
    cmp byte[ebx],' '   ;is this byte a space?
    jnz skip_spaces_end ;if it is not a space, we can end this loop
    inc ebx ;otherwise, go to next byte
    jmp skip_spaces ;and keep looping till we find non-space
    skip_spaces_end:
    mov eax,ebx ;copy this non-space address to eax register
    
    ;we have found a non-space which is the start of a printable string
    ;but we still have to find the next space and terminate it with a zero!
    
    ;however, there is a special case where we want a string to contain spaces. In this case, I have another routine!
    
    ;check for quoted strings
    cmp byte[ebx],0x22 ;is this a double quote -> "
    jz scan_quoted_string
    cmp byte[ebx],0x27 ;is this a single quote -> '
    jz scan_quoted_string
    
    find_space:
    cmp byte [ebx],' ' ;is this a space?
    jz found_space ;if this was a space, end the loop and terminate with zero
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    cmp byte[ebx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc ebx
    jmp find_space ; this char was not space, go to the next char
    found_space:
    mov byte[ebx],0 ;terminate this string
    
    inc ebx ;but go to the next byte
    mov [arguments_start],ebx ;and set the new start address for the next call
    
    ret ;We can return eax safely knowing the string ends in a zero
    
    scan_quoted_string:
    
    mov cl,byte[ebx] ;mov this quote type to cl
    inc ebx ;go to next byte
    mov eax,ebx ;set eax to this address which is assumed to be the start of a quoted string
    
    find_end_quote:
    cmp byte[ebx],cl ;is this the same quote we started with?
    jz found_end_quote ;if it is, end this loop
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    ;this avoids a crash if I forgot to add the second quotation mark in the arguments
    cmp byte[ebx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc ebx
    jmp find_end_quote
    found_end_quote:
    mov byte[ebx],0 ;terminate this string
    
    inc ebx ;but go to the next byte
    mov [arguments_start],ebx ;and set the new start address for the next call
    
    ret
    
    no_more_args:
    
    mov [arguments_start],ebx ;mov the start to where the string ended
    
    ;now that the start and end addresses are the same
    ;this function will always return zero
    ret
    
    ;this will happen first time this function is called to get the argument data
    get_arg_data:
    
    ;get command line argument string with this Windows API call
    ;it returns a pointer in the eax register
    
    call [GetCommandLineA]
    mov [arguments_start],eax ;but copy it to memory too
    
    
    ;the following loop is a modified strlen loop except that we don't
    ;care about how long the string is. We only need the pointer for where it ends
    
    mov ebx,eax  ;copy eax to ebx to be used in loop to find ending zero
    argend_find: ; this loop finds the zero at the end of the argument string
    cmp [ebx],byte 0 ; compare byte at address ebx with 0
    jz argend_found ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp argend_find
    argend_found:
    mov [arguments_end],ebx ;save ebx address containing the zero byte
    
    mov ebx,eax ;mov the start of the arg string again before the next loop
    
    skip_prog_name:
    cmp byte[ebx],0   ;check for zero first (very important)
    jz no_args        ;if we found a zero before a space, there are no arguments after program name!
    cmp byte[ebx],' ' ;is this byte a space?
    jz skip_prog_name_end ;if it is a space, we can end this loop
    mov byte[ebx],' ' ;turn this byte into a space and therefore delete it
    inc ebx ; go to next byte
    jmp skip_prog_name ;and keep looping till we find space
    skip_prog_name_end:
    mov eax,ebx ;copy this non-space address to eax register
    ;return eax pointing to an address of string with arguments after program name
    ret
    
    no_args:
    mov eax,0
    ;in this case, we return zero because there are no args
    
    ret
    
    ;start and end default to address of zero, which means we have not tested the arguments yet
    arguments_start dd 0
    arguments_end dd 0
    
    
  • chastdin calculator for RISC-V Assembly

    I really did it this time. I translated all of my Intel Assembly functions into RISC-V Assembly and rebuilt my calculator. It works flawlessly running under the rars simulator.

    To run this example, you need the RARS java archive and a java runtime environment installed on your machine.

    You can get RARS here:

    https://github.com/rarsm/rars

    However, once you do, you can run my program with a command like the following.

    java -jar ~/rars.jar main.s
    
    # chastelib test suite for RISC-V Assembly in RARS simulator
    
    # this program tests the stdin extension of chastelib
    
    # The same library of functions I commonly use in my Intel Assembly code
    # have now been translated to RISC-V.
    # All assembly code seen here is for the RARS simulator written in Java.
    
    .data
    
    ##################################################################
    # chastelib core specific variables                              #
    #                                                                #
    # These variables are used by the intstr function to convert an  #
    # integer to a string and what radix and widthshould be used     #
    # width means how many minimum digits including leading zeros    #
    ##################################################################
    
    int_string: .space 32 #reserve space for 32 bytes for up to 32 bits if printed in binary
    int_end: .byte 0 #the terminating zero of the integer string
    radix: .byte 2   #the radix the number will be shown in
    int_width: .byte 1 #by default
    
    # These variables are for outputting special strings
    # such as a newline, space, or a single character based on s0
    
    space: .byte 0x20, 0
    line:  .byte 0x0A, 0
    char:  .byte 0, 0 
    
    ##################################################################
    # chastdin specific variables                                    #
    #                                                                #
    # these variables are used as the default controllers            #
    # for the getstr and getline functions                           #
    # buf stores keyboard input during those functions               #
    # count stores how many bytes were read during system read calls #
    # last_char stores the last character read                       #
    # usually this will be a space, tab, or newline                  #
    ##################################################################
    
    buf: .space 0x100
    count: .word 0
    last_char: .byte 0
    
    # program specific variables
    # These variables are for outputting specific messages
    # or to simulate user input as integers in the strint function
    
    string0: .ascii "calculator for RISC-V Assembly\n"
    string1: .asciz "chastdin (Chastity's STanDard INput) extension\n\n"
    
    string_add: .asciz "add"
    string_sub: .asciz "sub"
    string_mul: .asciz "mul"
    string_div: .asciz "div"
    string_rem: .asciz "rem"
    string_setradix: .asciz "setradix"
    
    string_help: .asciz "help"
    string_exit: .asciz "exit"
    string_putstack: .asciz "?"
    string_clear: .asciz "clear"
    
    string_prompt: .asciz "->"
    
    string_err: .asciz "Error: invalid number or command: "
    string_err1: .asciz "Error: need one number on stack for command: "
    string_err2: .asciz "Error: need two numbers on stack for command: "
    
    chastdin_help: .ascii "chastdin is a stack based interactive calculator\n"
                  .ascii "that reads stdin for numbers and commands.\n"
                  .ascii "Numbers are pushed on the stack for all math.\n"
                  .ascii "Each line can contain multiple numbers or commands.\n\n"
                  .ascii "Arithmetic commands are add,sub,mul,div,rem\n"
                  .ascii "The exit command ends the program\n"
                  .ascii "The ? command prints the entire stack\n"
                  .asciz "The setradix command changes the radix for input and output\n"
    
    .align 2  # Aligns the next item to a 4-byte (2^2) word boundary
    chastack: .space 0x400 #reserve space for RPN calculator stack
    
    .text
    
    la s0, string0
    jal putstr
    
    # change radix for this program
    li t0, 10    #load t0 register with the new radix
    la t1, radix #load t1 register with the address the radix will go to
    sb t0, 0(t1) #save t0 register (byte) to address t1
    
    la s11, chastack #s11 will be used as the virtual stack pointer for this program
    
    #print the help message at the beginning of the program
    la s0, chastdin_help
    jal putstr
    
    #print the initial arrow prompt
    la s0, string_prompt
    jal putstr
    
    main_loop:
    
    la t1, last_char #load address of last_char
    lb t0, 0(t1)     #get the last character
    
    #show the arrow indicating we wait for the user to enter something
    #but only show it when the last character is a newline
    #otherwise it will print too many if multiple commands were entered on the same line
    li t1, 0xA
    bne t0, t1, skip_prompt
    la s0, string_prompt
    jal putstr
    skip_prompt:
    
    jal getstr  # read the string from standard input
    
    #load the length of string just entered from (count)
    la t1, count            #load address of count into t1
    lw t0, 0(t1)            #load number of chars read at (count) address
    beq t0, zero, main_loop #restart main_loop on empty string
    
    #jal putline # print extra line for readability
    #jal putstr # echo it to standard output
    #jal putline
    
    #s0 already contains string that was input
    #s1 will be loaded with address of exit string
    la s1, string_exit
    jal strcmp
    # end program if the string entered is equal to string_exit
    beq t0, zero, exit
    
    la s1, string_putstack
    jal strcmp
    beq t0, zero, command_putstack
    
    la s1, string_clear
    jal strcmp
    beq t0, zero, command_clear
    
    la s1, string_help
    jal strcmp
    beq t0, zero, command_help
    
    #next we begin checking for actual math commands of arithmetic
    
    la s1, string_add
    jal strcmp
    beq t0, zero, command_add
    
    la s1, string_sub
    jal strcmp
    beq t0, zero, command_sub
    
    la s1, string_mul
    jal strcmp
    beq t0, zero, command_mul
    
    la s1, string_div
    jal strcmp
    beq t0, zero, command_div
    
    la s1, string_rem
    jal strcmp
    beq t0, zero, command_rem
    
    la s1, string_setradix
    jal strcmp
    beq t0, zero, command_setradix
    
    
    #if the last string entered was not exit or a math command then
    #The default command is to turn the argument into a number and push to stack
    command_num:
    
    mv s1, s0              #back up this string address to s1 register
    jal strint             #try to get a number from the string pointed to by s0 register
    beq a0, zero, num_push #branch to number push if zero errors in integer string
    
    la s0, string_err    #load error message
    jal putstr           #print error message
    mv s0, s1            #load original command string
    jal putstr           #print which command failed
    jal putline
    j num_push_end       #skip the push because this can't be used
    
    num_push:            #push the number to the fake stack
    addi s11, s11, 4     #increment the pointer by the size of the native int for this mode
    sw s0, 0(s11)        #store the value we converted from the string with strint to this stack space
    num_push_end:
    j main_loop          #once value is pushed, continue the program
    
    exit:
    li a0, 0  #status
    li a7, 93 #exit
    ecall     #environment call
    
    #################################################################################
    # The following functions are used in the calculator program                    #
    # The all jump back to the main_loop after they are done                        #
    #                                                                               #
    #################################################################################
    
    #check if the stack has enough space for the last command
    #this will print an error if less than two numbers were on the stack
    #when using one of the math commands above
    
    memory_check:
    
    la s10, chastack        #load s10 with chastack address for branch comparison
    blt s10, s11, memory_ok # if s10 is less than s11, no errors
    
    print_stack_error:   #otherwise we print error message
    la s0, string_err2   #get error message for less than 2 numbers on stack
    jal putstr           #print error message
    mv s0, s1            #get name of the command used
    jal putstr           #print which command failed
    jal putline
    addi s11, s11, 4     #increment the pointer to what it was before the failed command
    j main_loop          #now go back to main loop after error was printed
    
    memory_ok:
    sw zero, 4(sp)       #if no error, erase the old top of stack by storing zero
    j main_loop          #and continue main_loop as normal
    
    
    command_putstack: #print all numbers on the stack
    la s9, chastack #load s9 with address of chastack
    mv s10, s11     #copy value of s11 to s10
    command_putstack_loop:
    
    #is s10 equal to the address of stack start?
    #if so, end the putstack loop
    beq s9, s10 command_putstack_end
    lw s0, 0(s10) #load the word at s10 into s0 for printing integer 
    addi s10, s10, -4 #subtract the word size from this temp stack index
    jal putint
    jal putline
    j command_putstack_loop
    command_putstack_end:
    j main_loop
    
    
    
    
    command_clear: #erase all numbers on the stack
    la s9, chastack #load s9 with address of chastack
    command_clear_loop:
    
    #is s11 equal to the address of stack start?
    #if so, end the clear loop
    beq s9, s11 command_clear_end
    sw zero, 0(s11) #store zero into the word at 0(s11) to erase it
    addi s11, s11, -4 #subtract the word size from this temp stack index
    j command_clear_loop
    command_clear_end:
    j main_loop
    
    
    command_help:
    la s0, chastdin_help
    jal putstr
    j main_loop
    
    #add number on top of stack to the one below it
    command_add:
    lw t1, 0(s11)     #load the word at this chastack address
    addi s11, s11, -4 #subtract the word size from s11
    lw t0, 0(s11)     #load the word at this chastack address
    add t0, t0, t1    #t0 = t0 + t1
    sw t0, 0(s11)     #save the word at this chastack address
    j memory_check    #check stack for errors after this command
    
    #add number on top of stack to the one below it
    command_sub:
    lw t1, 0(s11)     #load the word at this chastack address
    addi s11, s11, -4 #subtract the word size from s11
    lw t0, 0(s11)     #load the word at this chastack address
    sub t0, t0, t1    #t0 = t0 - t1
    sw t0, 0(s11)     #save the word at this chastack address
    j memory_check    #check stack for errors after this command
    
    #mul number on top of stack to the one below it
    command_mul:
    lw t1, 0(s11)     #load the word at this chastack address
    addi s11, s11, -4 #subtract the word size from s11
    lw t0, 0(s11)     #load the word at this chastack address
    mul t0, t0, t1    #t0 = t0 * t1
    sw t0, 0(s11)     #save the word at this chastack address
    j memory_check    #check stack for errors after this command
    
    #divide and store quotient on stack
    command_div:
    lw t1, 0(s11)     #load the word at this chastack address
    addi s11, s11, -4 #subtract the word size from s11
    lw t0, 0(s11)     #load the word at this chastack address
    divu t0, t0, t1    #t0 = t0 / t1
    sw t0, 0(s11)     #save the word at this chastack address
    j memory_check    #check stack for errors after this command
    
    #divide and store remainder on stack
    command_rem:
    lw t1, 0(s11)     #load the word at this chastack address
    addi s11, s11, -4 #subtract the word size from s11
    lw t0, 0(s11)     #load the word at this chastack address
    remu t0, t0, t1   #t0 = t0 % t1
    sw t0, 0(s11)     #save the word at this chastack address
    j memory_check    #check stack for errors after this command
    
    
    
    #pop top of stack and set the current radix to it
    #it has error checking and leaves the radix as is
    #unless at least one number is on the stack
    command_setradix:
    
    
    la s10, chastack     #load s10 with chastack address for branch comparison
    ble s11, s10, change_radix_no # if s11 is less than or equal to chastack address, branch to radix error
    change_radix_yes:
    lw t0, 0(s11)        #load t0 register with the new radix
    la t1, radix         #load t1 register with the address the radix will go to
    sb t0, 0(t1)         #save t0 register (byte) to address t1
    sw zero, 0(s11)      #erase the old top of stack by storing zero
    addi s11, s11, -4
    j main_loop          #and continue main_loop as normal
    change_radix_no:
    la s0,string_err1    #get error message for less than 1 numbers on stack
    jal putstr           #print error message
    mv s0, s1            #get name of the command used
    jal putstr           #print which command failed
    jal putline
    
    addi s11, s11, 4     #increment the pointer to what it was before the failed command
    j main_loop          #now go back to main loop after error was printed
    
    #################################################################################
    # The following functions are independent of a specific RISC-V Operating System #
    #                                                                               #
    # intstr = convert integer into a string ready for printing                     #
    # putint = prints integer using intstr and the OS specific putstr function      #
    # strint = convert string into an integer                                       #
    #                                                                               #
    # The s0 register is used for pass data in or out of these functions            #
    # See comments above those specific functions for full details                  #
    #################################################################################
    
    # The intstr function does several things at once and is the foundation for all integer output.
    # It uses the global radix variable to know which radix or number base to use when turning the integer to a string
    # It also uses the global int_width variable to determine how many leading zeros should be used for the string
    # The purpose of this is to make numbers look good when lined up when they are printed in a list.
    # radices 2 to 36 are supported. Digits higher than 9 will be capital letters
    
    intstr:
    
    la t1, radix     #load address of radix into t1
    lb t2, 0(t1)     #load value of radix into t2
    la t1, int_width #load address of width into t1
    lb t4, 0(t1)     #load value of int_width into t4
    li t3, 1         #load current number of digits, always 1
    
    la t1, int_end   #t1=address of terminating zero in string
    addi t1, t1, -1  #t1-- to go to lowest digit
    
    digits_start:
    
    remu t0, s0, t2  #t0=remainder of the previous division
    divu s0, s0, t2  #s0=s0/t2 (divide s0 by the radix value in t2)
    
    li t5, 10        #load t5 with 10 because RISC-V does not allow constants for branches
    
    blt t0, t5, decimal_digit
    bge t0, t5, hexadecimal_digit
    
    decimal_digit:   #we go here if it is only a digit 0 to 9
    
    addi t0, t0, 0x30
    
    j save_digit
    
    hexadecimal_digit:
    addi t0, t0, -10
    addi t0, t0, 0x41
    
    save_digit:
    sb t0, 0(t1)     #store byte from t0 at address t1
    beq s0, zero, intstr_end
    addi t1, t1, -1
    addi t3, t3, 1
    j digits_start
    
    intstr_end:
    
    li t0, 0x30
    prefix_zeros:
    bge t3, t4, end_zeros
    addi t1, t1, -1
    sb t0, 0(t1) # store byte from t0 at address t1
    addi t3, t3, 1
    j prefix_zeros
    end_zeros:
    
    mv s0, t1
    
    ret
    
    # this function calls intstr to convert the s0 register into a string
    # then it uses the system specific putstr call to print the string
    # it also uses the stack to save the value of s0 and ra (return address)
    # this way, s0 is restored to the value it had before this function
    # restoring ra is required because it is modified during calls to other functions
    
    putint:
    
    addi sp, sp, -8
    sw ra, 0(sp)
    sw s0, 4(sp)
    
    jal intstr
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    addi sp, sp, 8
    
    ret
    
    # strint takes the string at address pointed to by s0 register
    # and then loads the s0 register with an integer equivalent value
    # the a0 register is returned with the number of errors that happened
    # programs can use this to find if a user entered a valid number
    # number is intepreted according to the current radix
    
    strint:
    
    li a0, 0         #load zero into register for error counting
    
    la t1, radix     #load address of radix into t1
    lb t2, 0(t1)     #load value of radix into t2
    
    mv t1, s0        #copy string address from s0 to t1
    li s0, 0
    
    read_strint:
    lb t0, 0(t1)
    addi t1, t1, 1
    beq t0, zero, strint_end
    
    #if char is below '0' or above '9', it is outside the range of these and is not a digit
    li t5, 0x30
    blt t0, t5, not_digit
    li t5, 0x39
    blt t5, t0, not_digit
    
    #but if it is a digit, then correct and process the character
    is_digit:
    andi t0, t0, 0xF
    j process_char
    
    not_digit:
    #it isn't a digit, but it could be an alphabet character
    #which counts as a digit in a higher base
    
    # if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    li t5, 0x41
    blt t0, t5, not_upper
    li t5, 0x5A
    blt t5, t0, not_upper
    
    is_upper:
    li t5, 0x41
    sub t0, t0, t5
    addi t0, t0, 10
    j process_char
    
    not_upper:
    
    # if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    li t5, 0x61
    blt t0, t5, not_lower
    li t5, 0x7A
    blt t5, t0, not_lower
    
    is_lower:
    li t5, 0x61
    sub t0, t0, t5
    addi t0, t0, 10
    j process_char
    
    not_lower:
    
    # if we have reached this point, result invalid and end function
    # this is only reached if the byte was not a valid digit or alphabet character
    j strint_end_error
    
    process_char:
    
    blt t2, t0 strint_end_error #if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mul s0, s0, t2 # multiply s0 by the radix
    add s0, s0, t0 # add the correct value of this digit
    
    j read_strint # jump back and continue the loop if nothing has exited it
    
    strint_end_error:  #we jump here if there was an error with one of the chars
    addi a0, a0, 1 #add 1 to the a0 register indicating an error occurred
    
    strint_end: #we jump here when no errors happened
    ret
    
    ###############################################################################
    # This putstr function is my most portable function for RISC-V simulators     #
    # It calculates the length of a zero terminated string before printing it     #
    # This is the same way used in my Intel Assembly programs for DOS and Linux   #
    # This function was written to operate the same in both RARS and riscemu      #
    ###############################################################################
    
    putstr:
    
    mv t1, s0                       # t1 will be used as an index register
    
    putstr_strlen_start:
    lb t0, 0(t1)                    # load byte into t0 from address of t1
    beq t0, zero, putstr_strlen_end # if t0==0, then we jump to the end of the loop.
    addi t1, t1, 1                  # go to next byte
    j putstr_strlen_start           # jump to start of the loop
    putstr_strlen_end:              
    
    li a0, 1                        # STDOUT file number
    mv a1, s0                       # address of string 
    sub a2, t1, s0                  # length of string
    li a7, 64                       # write call number
    ecall                           # environment call
    
    ret
    
    #############################################################################
    # The next four 3 functions print things to standard output                 #
    # All of them use the putstr function above to achieve the output           #
    # They use the stack to preserve the values of the s0 and t1 registers used #
    # They also use global variables in the data section                        #
    #############################################################################
    
    #the putchar function, which is named after the C language function of the same name
    #prints the lowest byte of the s0 register as a byte or character to standard output
    
    putchar:
    
    addi sp, sp, -12
    sw ra, 0(sp)
    sw s0, 4(sp)
    sw t1, 8(sp)
    
    la t1, char
    sb s0, 0(t1)
    la s0, char
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    lw t1, 8(sp)
    addi sp, sp, 12
    
    ret
    
    # the putspace function prints a space to standard output
    
    putspace:
    
    addi sp, sp, -8
    sw ra, 0(sp)
    sw s0, 4(sp)
    
    la s0, space
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    addi sp, sp, 8
    
    ret
    
    # the putline function prints a newline to standard output
    
    putline:
    
    addi sp, sp, -8
    sw ra, 0(sp)
    sw s0, 4(sp)
    
    la s0, line
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    addi sp, sp, 8
    
    ret
    
    ##########################################################################
    # chastdin extension functions                                           #
    #                                                                        #
    # all functions that deal with getting strings and characters from stdin #
    ##########################################################################
    
    # the getstr function will read a string into a buffer from stdin
    # and return it in the s0 register for printing with the putstr function
    # the (count) variable will also return the number of characters
    
    getstr:
    
    li t0, 0                        # use t0 register to track chars read
    la a1, buf                      # load address of buffer for read string
    li a2, 1                        # read only 1 byte for each env call
    
    getstr_chars:
    
    li a0, 0                        # STDIN file number
    li a7, 63                       # read call number
    ecall                           # environment call
    
    # Branch to label getstr_end if a0 is less than a2
    # a0 is the return value of this environment read call
    # as will be -1 on error or 1 if successful
    # because we read 1 character at a time
    
    blt a0, a2, getstr_end
    
    # if no error, test range of the last byte
    
    lb t1, 0(a1)      #load byte at address (a1) into t1 register
    
    # if t1 is less than 0x21
    # or t1 is more than 0x7E
    # branch to function end because it is outside of print range
    
    li t2, 0x21
    blt t1, t2, getstr_end
    li t2, 0x7E
    blt t2, t1, getstr_end
    
    # otherwise, proceed to read more characters
    add t0, t0, a0    # add to read counter
    addi a1, a1, 1    # add 1 to buffer pointer register a1
    j getstr_chars # unconditional jump to getstr_chars
    
    getstr_end:
    
    la t2, count       #load address of count into t2
    sw t0, 0(t2)       #store number of chars read at (count) address
    la t2, last_char   #load address of last_char into t2
    sb t1, 0(t2)       #store last byte at (last_char) address
    sb zero, 0(a1)     #store byte zero to terminate string
    la s0, buf         #return address of buf in s0 register
    
    ret
    
    
    
    
    # the getline function will read a string into a buffer from stdin
    # and return it in the s0 register for printing with the putstr function
    # the (count) variable will also return the number of characters
    # this function will get the whole line including spaces
    
    getline:
    
    li t0, 0                        # use t0 register to track chars read
    la a1, buf                      # load address of buffer for read string
    li a2, 1                        # read only 1 byte for each env call
    
    getline_chars:
    
    li a0, 0                        # STDIN file number
    li a7, 63                       # read call number
    ecall                           # environment call
    
    # Branch to label getline_end if a0 is less than a2
    # a0 is the return value of this environment read call
    # as will be -1 on error or 1 if successful
    # because we read 1 character at a time
    
    blt a0, a2, getline_end
    
    # if no error, test range of the last byte
    
    lb t1, 0(a1)      #load byte at address (a1) into t1 register
    
    # if t1 is less than 0x20
    # or t1 is more than 0x7E
    # branch to function end because it is outside of print range
    
    li t2, 0x20
    blt t1, t2, getline_end
    li t2, 0x7E
    blt t2, t1, getline_end
    
    # otherwise, proceed to read more characters
    add t0, t0, a0    # add to read counter
    addi a1, a1, 1    # add 1 to buffer pointer register a1
    j getline_chars # unconditional jump to getline_chars
    
    getline_end:
    
    la t2, count       #load address of count into t2
    sw t0, 0(t2)       #store number of chars read at (count) address
    la t2, last_char   #load address of last_char into t2
    sb t1, 0(t2)       #store last byte at (last_char) address
    sb zero, 0(a1)     #store byte zero to terminate string
    la s0, buf         #return address of buf in s0 register
    
    ret
    
    
    
    # Short Description of strlen:
    # The strlen function gets the length of string in s0 and returns it in s0
    # This is the same algorithm used in my putstr function but is independent of an operating system.
    
    strlen:
    
    mv t1, s0                       # t1 will be used as an index register
    
    strlen_start:
    lb t0, 0(t1)                    # load byte into t0 from address of t1
    beq t0, zero, strlen_end        # if t0==0, then we jump to the end of the loop.
    addi t1, t1, 1                  # go to next byte
    j strlen_start                  # jump to start of the loop
    strlen_end:              
    
    sub s0, t1, s0                  # return length of string in s0
    
    ret
    
    
    # Short Description of strcmp:
    # strcmp compares the string at s0 to the one at s1
    # t0 returns 0 if the strings are the same and non zero if different
    # the algorithm is simple but I will explain it for those who are confused
    
    # Long Description of strcmp:
    # each byte from each string is loaded into the t0 and t1 registers
    # the bytes are compared. if they are different, then we jump to the end
    # However, if they are the same, then we check if one of them is zero
    # if it is zero, this also jumps to the end of the function
    # If neither jump took place, then we jump to the start of the loop
    # but when the function finally ends t1 will be subtracted from t0
    # this ensures that the t0 register returns zero if the final characters are the same
    # a zero result in t0 also guarantees that both strings are equal
    
    strcmp:
    
    mv a0, s0 # move pointer s0 to a0
    mv a1, s1 # move pointer s1 to a1
    
    strcmp_start:
    
    #read a byte from each string
    lb t0, 0(a0) 
    lb t1, 0(a1) 
    #if the two bytes are not equal end comparison
    bne t0, t1, strcmp_end
    
    #but if they are equal, test for zero
    #if one of them is zero, also end the loop
    beq t0, zero, strcmp_end
    
    addi a0, a0, 1                  # go to next byte
    addi a1, a1, 1                  # go to next byte
    
    j strcmp_start
    
    strcmp_end:
    
    #subtract t1 from t0
    #if t0 is still zero after the function returns
    #it means that the strings are equal
    sub t0, t0, t1
    
    ret
    
    
  • chastdin for FreeBASIC

    I have rewritten my chastdin program (the stack based calculator that reads keyboard input from standard input) into the FreeBASIC programming language. I did it as an exercise to prepare myself for a future book on the BASIC programming language which was my first programming language. FreeBASIC is compatible with QBASIC which is what I first started on. Luckily, BASIC is not so different from C but I had to spend a lot of time on the documentation to refresh my memory in how I used to do things with it.

    Eventually I would like to make a GUI version of this command line calculator. I am trying to take baby steps in working my way into programs that the average person would use. However, command line utilities are still the easiest to build and that is an acceptable place to start.

    main.bas

    #include "chastelib.bi"
    #include "chastdin.bi"
    
    dim shared as integer chastack(256)
    dim shared as integer csi=0 'Chastity's Stack Index
    
    radix=10
    
    dim as integer a,b
    dim shared as string s
    
    sub help()
    ?  "chastdin is a stack based interactive calculator"
    ?  "Numbers are pushed on the stack and commands can do math."
    ?  "It is a fork of chastack that reads from stdin instead of arguments."
    ?  "Each line can contain multiple numbers or commands."
    ?
    ?  "Math commands are add,sub,mul,div,rem"
    ?  "And use the top two stack numbers for their operations"
    ?
    ?  "The setradix command uses the top of stack as the new radix"
    ?  "The exit command ends the program"
    ?  "The ? command prints the entire stack"
    ?
    end sub
    
    sub stack_check()
     if csi>0 then
      chastack(csi+1)=0 /'erase old top of stack because command was successful'/
     else
      print "Error: two numbers required for command: ";s
      csi+=1 /'increment the pointer to what it was before the failed command'/
     end if
    end sub
    
    help():
    
    while s<>"exit"
    
    s=""
    
     s=getstr() 'read and ignore empty strings
    
    'print entire stack
    if s="?" or s="print" then
     b=csi
     while csi>0
      print intstr(chastack(csi))
      csi-=1
     wend
     csi=b
    
    elseif s="exit" then
    exit while
    
    elseif s="help" then
    help()
    
    elseif s="setradix" then
     if csi>0 then
     radix=chastack(csi)
     chastack(csi)=0
     csi-=1
     else
      print "Error: need one number on stack for command: ";s
     end if
    
    elseif s="add" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a+=b
    chastack(csi)=a
    stack_check()
    
    elseif s="sub" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a-=b
    chastack(csi)=a
    stack_check()
    
    elseif s="mul" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a*=b
    chastack(csi)=a
    stack_check()
    
    elseif s="div" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a\=b
    chastack(csi)=a
    stack_check()
    
    elseif s="rem" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a=a mod b
    chastack(csi)=a
    stack_check()
    
    else
    
    'try to interpret string as a number if not empty
     a=strint(s)
     if strint_errors<>0 or len(s)=0 then
     'print s;" cannot be added to the stack because it is not a valid number"
     else
     csi+=1
     chastack(csi)=a
     print intstr(a);" was added to the stack"
     end if
    
    end if
    
    wend
    
    /'
     This is a FreeBASIC program.
    
     compile and run as:
    
     fbc main.bas && ./main
    '/
    
    

    chastelib.bi

    /'
     global variables to define radix and formatting
     for the intstr function
    '/
    dim shared as integer radix=2
    dim shared as integer int_width=1
    
    /'
     translation of intstr function for FreeBASIC
     by original C programmer Chastity White Rose
    '/
    function intstr(i as uinteger) as string
     dim as string s=""
     dim as integer w=0
     dim as byte c
    
     while i<>0 or w<int_width 
    
      c=i mod radix                  
      i\=radix                     
    
      if c<10 then 
      c+=48
      else
      c+=55
      end if
    
      s=chr(c)+s
    
      w+=1                     
     wend
    
    return s
    end function
    
    /'
     global variable for error detection in strint function
     this variable will be zero if last string was a number
    '/
    dim shared as integer strint_errors=0
    
    /'
     translation of strint function for FreeBASIC
     by original C programmer Chastity White Rose
    '/
    function strint(s as string) as uinteger
    dim as uinteger i=0
    dim as integer x=0,y=len(s)
    dim as byte c
    
    strint_errors = 0 /' clear errors '/
    
    while x<y
    
     /' read digit from string '/
     c=s[x]
    
     /' 0 to 9 '/
     if c >= 48 and c <= 57 then
     c-=48
     /' A to Z '/
     elseif c >= 65 and c <= 90 then
     c-=65
     c+=10
     /' a to z '/
     elseif c >= 97 and c <= 122 then
     c-=97
     c+=10
     /' whitespace '/
     elseif c >= 0 and c <= 32 then
      exit while /' exit correctly at string end '/
     else
      strint_errors+=1
      print "Error: ";chr(s[x]);" is not an alphanumeric character!"
      exit while /' exit at invalid character '/
     end if
    
     if c>=radix then
      strint_errors+=1
      print "Error: ";chr(s[x]);" is not a valid character for radix ";radix
      exit while /' exit at digit wrong for radix '/
     end if
    
     /'multiply by radix then add digit'/
     i*=radix
     i+=c
    
    x+=1
    wend
    
    return i
    end function
    

    chastdin.bi

    dim shared as string stdin_buf
    dim shared as integer stdin_buf_index
    dim shared as integer stdin_buf_length=0
    
    function getstr() as string
    dim as string s=""         'create empty string
    dim as byte c              'temporary byte/char variable
    
    /'
    this section gets a line of text
    if the length of the string/buffer is 0
    '/
    
    if stdin_buf_length=0 then      'check if there are characters in the buf
    input "-> ",stdin_buf           'if not, read a line of text
    stdin_buf_index=0               'set index to zero
    stdin_buf_length=len(stdin_buf) 'set the length
    end if
    
    /'
    regardless of whether input was added above
    or if it still had bytes from the last input
    we then extract characters one at a time into the
    substring s to be returned from the function
    '/
    
    while stdin_buf_index<stdin_buf_length
    c=stdin_buf[stdin_buf_index]
    stdin_buf_index+=1
    if(c>=33) and (c<=126) then
    s=s+chr(c)
    else
    exit while
    endif
    wend
    
    /'
    if the index matches the length of buffer
    set length to zero so that more will be read
    next time this function is called
    '/
    
    if stdin_buf_index=stdin_buf_length then
    stdin_buf_length=0
    end if
    
    return s
    end function
    
    /'
    the getline function always gets an entire line of text
    I don't really need it but it is here as a reminder of
    how to use the input statement in FreeBASIC
    '/
    
    function getline() as string
    dim as string s=""
    input "-> ",stdin_buf
    s=stdin_buf
    return s
    end function
    

  • chastelib for Pascal Programming Language

    I managed to hack my four functions from chastelib into the Pascal programming language. This program includes the functions and the test suite which works just like the C version. The code is a bit more complex than the C version because strings and characters are handled very differently than they are in the C programming language. The strint function was the hardest to write but it seems to be working according to the standards I require.

    I am doing this for education and possibly a future book on old programming languages. Pascal is nice but I will also be studying BASIC again.

    program chastelib;
    
    const
     string0='Official test suite for the Pascal version of chastelib.'#10;
    
    var //this is the global variable section
     a:integer;
     b:integer;
     
     radix:integer;       //current radix being used
     int_width:integer=1; //global integer width
     strint_errors:integer=0; //error result for strint function
    
    (*
    A function to print a string using Pascal's write function.
    *)
    procedure putstr(s:string);
    begin
     write(s);
    end;
    
    (*
     a function to return a string form of an integer
     using the global radix variable
    *)
    function intstr(i:integer):string;
    var
     s:string=''; //string that will be built and returned from this function
     width:integer=0; //the current width
     c:integer;
     ch:char;
    begin
     while (i>0) or (width<int_width) do
     begin
    
      c:=i mod radix; //get integer division modulus or remainder
      i:=i div radix; //get integer division quotient
    
      (*turn remainder c into character ch for digit in this radix*)
      if c<10 then
      begin
       ch:=chr(c+48);
      end
      else
      begin
       ch:=chr(c+55);
      end;
    
       s:=ch+s; //prefix the string with this character
       width+=1;
    
     end;
    
     intstr:=s; //return this string from the function
    
    end;
    
    (*use both putstr and intstr to print an integer*)
    procedure putint(i:integer);
    begin
     putstr(intstr(i));
    end;
    
    (*
    Because characters and integers are separate types in Pascal,
    it is required to get the ASCII value of characters in the string
    for the strint function so I can do the math the same way
    as I did in the C version of the function.
    *)
    
    function strint(s:string):integer;
    var
     i:integer=0; //integer that will be built and returned from this function
     x:integer=1; //index used to scan forward through the string
     c:integer=0;
    begin
     strint_errors := 0; (*set zero errors before we parse the string*)
     if (radix<2) or (radix>36 ) then
     begin
      strint_errors+=1;
      writeln('Error: radix ',radix,' is out of range!');
     end;
     while(x<=length(s)) do
     begin
      c:=ord(s[x]);
      if (c>=ord('0')) and (c<=ord('9')) then 
      begin
       c-=ord('0')
      end
      else if (c>=ord('A')) and (c<=ord('Z')) then
      begin
       c-=ord('A');
       c+=10;
      end
      else if (c>=ord('a')) and (c<=ord('z')) then
      begin
       c-=ord('a');
       c+=10;
      end
      
      else if (c < $21 ) then
      begin
       break; (*end loop because we have found whitespace*)
      end
      
      else
      begin
       strint_errors+=1;
       writeln('Error: ',s[x],' is not an alphanumeric character!');break;
      end;
      
      if(c>=radix) then
      begin
       strint_errors+=1;
       writeln('Error: ',s[x],' is not a valid character for radix ',radix);
       break;
      end;
      
      i*=radix; //multiply by the radix
      i+=c;     //add the digit from the character processed
    
      x:=x+1;
     end;
     strint:=i;
    end;
    
    
    
    
    begin
     radix:=16; //set the radix used by both intstr and strint functions
    
     a:=0;
     b:=strint('100');
    
     putstr(string0);
    
     while a<b do
     begin
      radix:=2;
      int_width:=8;
      putint(a);
      putstr(' ');
      radix:=16;
      int_width:=2;
      putint(a);
      putstr(' ');
      radix:=10;
      int_width:=3;
      putint(a);
    
      if (a>=$20) and (a<=$7E) then
      begin
       putstr(' ');
       putstr(chr(a));
      end;
    
      putstr(#10);
      a+=1;
     end;
     
     putstr(string0);
    
    end.
    
    (*
     fpc main.pas && ./main
    *)