Tag: artificial-intelligence

  • 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
    *)
    
  • Assembly Arithmetic Algorithms RISC-V

    Assembly Arithmetic Algorithms

    RISC-V Edition

    Preface

    This book is the RISC-V edition of Assembly Arithmetic Algorithms. This is the third book in the series but it is a completely acceptable way to learn programming, even for beginners.

    The first book was for 16-bit DOS programming using Assembly. The second book was for 32-bit Linux programming using the same assembly language for Intel machines.

    But the book you are reading now, is fundamentally different in nature. It uses simulators that run in any operating system to teach a new type of Assembly language for the RISC-V (Reduced Instruction Set Computing-Generation 5) processor.

    RISC-V is important because it is a royalty free open standard. This means that as it catches on, more companies will be able to make cheaper computers because they will not have to pay royalties to the Intel or ARM companies. I guess you could say that it is Open Source Hardware, which in my opinion is the final piece we need to match the Open Source Software people like me already have been enjoying with GNU and Linux.

    Introduction

    First, let me introduce this book by telling you what I will teach you. By the end of this book, you will have enough information to write small programs that will run on your machine but also allow you to bypass complicated languages like C++, Java, or even the Bash Linux shell. Arguably, the beautiful thing about Assembly is its simplicity.

    RISC-V is great because it is a Reduced Instruction Set Computing device. It has fewer instructions to learn and remember, especially when compared to Intel. It also has more registers available for use. Each of them have special purposes and conventions for how they are used. However, as the programmer, you are allowed to break these conventions because right now, your focus should be on learning it for fun!

    Required Knowledge

    To get the most out of this book, some background on the Binary and Hexadecimal numeral systems is going to be helpful, but this is not strictly required because I will be providing functions you can use in your code that will convert between decimal (base ten), binary (base two), and hexadecimal (base sixteen).

    However, I would say that experience in at least one programming language is necessary for an understanding of terminology like “arrays”, “pointers”, “addresses”, “integers”, etc. I recommend the C Programming Language as a start. C++ is also a good starting language, but it tends to abstract details away that directly apply to Assembly Language, which is the lowest level a human can go for understanding a computer.

    But I think it is perfectly okay for this Assembly language to be your introductory language for the world of computer programming. The language is simple, beautiful, and in my opinion, was designed better than Intel x86. The makers of the RISC-V architecture saw what other processors were doing and in some ways tried to avoid making the same mistakes as previous generations did.

    RISC-V is a new processor type and philosophy that was created in 2010 at University of California, Berkeley. The V in its name is actually the Roman number 5. That means there were apparently those named I,II,III,VI in the past.

    Many people have high hopes about this new computer architecture that is also old enough to trust. Because it is 16 years old at this time and has been standardized, there are many simulators available. This book will present two of these simulators and perhaps a passing mention of others as I learn about them myself and can provide more links.

    But because RISC-V is still relatively young compared to Intel 8086 (year 1978) and ARM (year 1983), I expect that the timing of this book will be of use to people who have heard about RISC-V and wonder what it is about.

    Low Level

    Low level is a term that confuses people. People think something high-level is better than low-level. In simple terms, humans consider themselves superior to machines and therefore think themselves higher or more important because of their abstract thought.

    A computer thinks only in terms of numbers. A computer may not understand “high-level” abstractions such as love, religion, philosophy, etc, but that is not its job. A computer must add, subtract, multiply, and divide. These are the four arithmetic functions that many humans struggle with.

    Therefore, I ask you, between a human and a computer, who is really low level or high level? In the age of Artificial Intelligence taking over human jobs and beating humans at Chess, we would all do well to take this question seriously.

    I wrote this book because I think like a machine, and I hope to help others think this way because it is the best way to learn programming and control your computer by writing Assembly Language programs, or to go back to your favorite programming language with a greater understanding of why things work as they do.

    Why RISC-V

    After writing 3 programming books, I decided I wanted to learn a language that offered the things I love about C and Assembly but was also new and exciting at the time.

    I first learned RISC-V from Robert Winker’s book.

    https://leanpub.com/riscvassemblyprogramming

    It was also available on Leanpub and I thought his teaching style is a standard that I hope to live up to. Obviously he is more experienced and his book is probably better than mine. Even so, I wanted to do my own part to promote education about RISC-V because I want to see a world where programming is easier and computers are cheaper. As much as I love Intel machines because I grew up with them, RISC-V provided me a new learning experience that I hope others discover and enjoy as much as I have!

    Chapter 1: The First Program

    For this chapter, I will explain give the source code of an example program that works in both RARS and riscemu simulators. Take a good look at it and the comments I included. I will be explaining this in detail. I also recommend you type or copy and paste this code into a text editor and save the file as “main.s” in a place on your computer that you prefer.

    Hello World

    .data
    
    string0: .asciz "Hello World!\n"
    
    .text
    
    li a0, 1       # STDOUT file number
    la a1, string0 # address of string 
    li a2, 13      # length of string
    li a7, 64      # write call number
    ecall          # environment call
    
    li a0, 0       # status
    li a7, 93      # exit
    ecall          # environment call
    

    There are two sections named “.data” and “.text”. All variables used in the program should be defined in the .data section. The .text section is where your code starts executing. This organization scheme is required for most simulators and assemblers.

    This program prints “Hello World” followed by a newline. It uses only 3 kinds of instruction and is fairly easy to follow.

    • li = load integer (or immediate value)
    • la = load address (a special kind of integer)
    • ecall = enviroment call (or syscalls)

    The reason that sometimes li is used to load a register and other times la is used is because technically, there is a limit on the size of integers that can be part of an instruction. Instructions like “la” are technically macros for multiple instructions that load upper and lower bits of a register.

    But besides that, having la for memory addresses and li for regular numbers makes the code more readable in my opinion.

    ecall is the how you would call the system calls of your operating system. It is the same idea as “int 0x80” was in my Linux Intel Assembly book. However, in RISC-V, there are different registers and different system call numbers than you would expect on Intel CPUs.

    But more importantly, this program was written for simulators and not a real operating system. The call numbers, which are loaded into the a7 register, are specific to the RARS and riscemu simulators. In the next chapter, I will explain the differences between these two simulators and how to run the program.

    Chapter 2: Simulator Choices

    There are two simulators I personally use and test my code against. Both of them are Free Software and available to download from their Github repositories. I will offer some information about them so you can choose which to use when following along with this book.

    RARS

    RARS is a port of the MARS simulator for MIPS processors. It is written in Java and is downloadable as a

    https://github.com/rarsm/rars

    This book favors RARS because it is easy to use and is reliable enough to use from any operating system. It also supports a lot of system calls and is the simulator I learned to use first. Because it runs using Java, you must have the Java Runtime installed. However, since Java can be installed and run on any operating system, you can be sure that it will work no matter whether your host operating system is Windows, Linux, or Mac.

    On my system, I usually have the file named “rars.jar” in my home directory. This allows me to run my source file like this:

    java -jar ~/rars.jar main.s
    

    Because ~ is a shortcut for the home directory, this allows me to run it from whichever directory I happen to be in that contains my source file, which in this case is named “main.s”.

    riscemu

    Riscemu is a simulator written in Python. It does not have as many system calls built in but it is a good option for people who have Python installed but cannot, or don’t want to install Java.

    https://github.com/AntonLydike/riscemu

    Running a source file with riscemu is even easier that with RARS.

    riscemu main.s
    

    In my experience, riscemu also launches faster than RARS does, mostly just because RARS requires Java to load the whole virtual machine before it starts. Because riscemu is written in Python, and Python is written in C, it is only natural that it might be faster.

    However, riscemu is really picky about having a space after commas in your instructions. It will fail with cryptic error messages if you don’t have proper spacing between your arguments to an instruction. This is probably a bug but it allows for consistency in formatting if you just remember to add a space after each comma. If you don’t like to do this, just forget riscemu and stick with RARS.

    Why use a simulator?

    You might be wondering why I am recommending using these simulators rather than a real assembler for a real machine with an operating system. There are 3 reasons.

    • I don’t have a computer with a RISC-V processor.
    • Simulators can be used by more people because they don’t require buying new hardware.
    • No “risk” in trying out this new RISC based machine.

    In short, I use these simulators or emulators for the same reason I used DOSBox in the DOS version of Assembly Arithmetic Algorithms.

    An emulator allows people to learn the language a Central Processing Unit uses before they have invested time or money into learning it. This means no investment or sacrifice from you.

    In fact, you might decide to forget learning RISC-V Assembly and learn Java or Python instead. Even so, I wrote this book so that I can share what a beautiful language the RISC-V Assembly language is and why I enjoy coding in it.

    Chapter 3: System Calls

    There are many more system calls for RARS than I will be teaching in this book. riscemu has less than RARS but the table below shows the calls that these two simulators both have in common. The 3 calls are enough for most programs.

    System Calls for RARS and riscemu

    Name a7 a0 a1 a2
    exit 93 status
    read 63 fd buf count
    write 64 fd buf count

    For more advanced programs that open and close files, you will need to use different call numbers and also different mode numbers. Unlike Linux and the POSIX system calls, the simulators are meant for teaching the language but you can’t count on them being compatible with each other in the same way. It is pure luck that the system calls for the 3 calls above matched. It is precisely this reason that I chose RARS and riscemu as the simulators for this book.

    Registers on RISC-V

    There are a LOT more registers than there were on Intel machines. This makes using RISC-V very easy because you don’t have to use memory locations as often.

    Name meaning/usage
    zero always = zero
    ra return address
    a0->a7 arguments
    s0->s11 saved values
    t0->t6 temporary

    The zero register is just a register that always equals zero. It may seem weird but remember than RISC-V does not allow comparing a register directly with a number. Normally you need to load a number into another register. But because comparing with zero is a common operation, this zero register is available to be compared or copied any time!

    The ra register is important for function calls. It is the register that keeps where you will return to when the function is done. Because of this, you will run into trouble if you try to do something else with it.

    The other registers can technically be used any way you want except that those starting with ‘a’ are used for arguments to environment calls. For this reason, they are used more than others.

    The registers that begin with ‘s’ or ‘t’ are technically the same but it is a convention or tradition that t0 to t6 are used for temporary cases where you need to add, subtract, multiply, or divide numbers. After you are done with them, you use them for something else and forget what you did with them last time.

    The s0 to s11 registers are the most abundant and you are expected to keep things that endure most or all of the program in them. For example, you might keep a file descriptor in one of them so that you could copy it to the correct ‘a’ register when you need to.

    Because of the fact that you cannot compare or otherwise perform math on memory addresses directly, you have to always load everything into registers. However, because RISC-V has more registers, you can also write programs that run faster because you access memory less often.

    However, you will be loading and saving memory for variables stored in the data section. Usually these will be strings you are printing.

    In the next chapter I will be introducing a function that can print any string by automatically calculating its length. I will also be using the calling convention of the recommended register usage.

    To be continued

  • chastext for Windows

    I wrote a Windows Assembly version of my chastext program.

    #main.asm

    format PE console
    include 'win32ax.inc'
    include 'chastelibw32.asm'
    
    main:
    
    mov [radix],10 ; Choose radix for integer output.
    mov [int_width],1
    
    ;get command line argument string
    call [GetCommandLineA]
    
    mov [arg_string_index],eax ;back up eax to restore later
    
    call strlen ;get the length of the string
    
    mov ebx,[arg_string_index] ;mov the address of the string start into ebx
    add ebx,eax                ;add eax which contains the length
    mov [arg_string_end],ebx   ;move end of string address to permanent location
    
    ;optionally display the arg string to make sure it is working correctly
    ;mov eax,[arg_string_index]
    ;call putstring
    ;call putline
    
    ;set ebx back to the start of the arg string for the filter loop
    mov ebx,[arg_string_index]
    
    ;now ebx points to the first non space character in the arguments passed to the DOS program
    ;and we know that [arg_string_end] is where it ends
    
    ;the next step is to filter the arguments into separate zero terminated strings
    ;each space will be changed to a zero (normally)
    ;but we also need to account for spaces inside quotes that are considered part of the string
    ;Linux handles this normally but DOS needs me to write the code to mimic this behavior
    ;because the program needs to function identically for DOS or Linux
    
    mov cl,' ' ;set the default filter character (argument terminator) to a space
    mov ch,0   ;are we currently checking spaces 0 or quote characters 1 as terminators?
    
    ;this loop is the new and improved argument filter
    ;it keeps track of whether we are inside or outside a quote
    ;and also which type of quote started the quote
    ;the actual quote marks are not part of the string unless they
    ;are the opposite quote type than what started the string
    ;The important thing is that spaces can exist inside of quoted strings
    ;as one argument rather than each new word being a new argument
    ;could be important for filenames containing spaces, etc.
    
    argument_filter:
    
    cmp ebx,[arg_string_end] ;are we at the end of the arg string?
    jz argument_filter_end       ;if yes, stop the filter and terminate with zero
    
    cmp ch,1       ;are we inside a quoted string?
    jz quote_check ;if yes, don't do anything to the spaces
    
    cmp byte[ebx],cl ;compare the byte at address bx to the string terminator
    jnz ignore_char ;if it is not the same, we ignore it
    mov byte[ebx],0  ;but if it matches, change it to a zero
    ignore_char:
    
    cmp byte [ebx],0x22 ;is this a double quote -> "
    jz start_quote
    cmp byte [ebx],0x27 ;is this a single quote -> '
    jz start_quote
    jmp quote_no ;it was not a quote
    
    start_quote:
    
    mov ch,1    ;set ch to 1 to set that we are inside a quote now
    mov cl,[ebx] ;save this quote type as the new terminator
    mov byte[ebx],0 ;but delete the first quote with zero
    
    ;check for single or double quotes
    quote_check:
    
    cmp [ebx],cl ;is this character the same type of quote that started this sub string?
    jnz quote_no ;if it is not, then skip to quote_no section
    
    ;but if it was matching, change this byte to zero
    ;and change cl back to a space
    mov cl,' ' ;cl is now a space
    mov ch,0   ;ch is 0 because now we have ended the quoted string
    mov byte[ebx],0 ;delete the end quote with zero
    
    quote_no:
    
    inc ebx ;go to the next character
    jmp argument_filter   ;jump back to the beginning of argument filter
    
    argument_filter_end:
    mov byte [ebx],0 ;terminate the ending with a zero for safety
    
    ;check first argument which is name of program
    ;mov eax,[arg_string_index]
    ;call putstr_and_line
    
    call get_next_arg ;get address of next arg and return into eax register
    cmp eax,[arg_string_end] ;if there is no filename arg, we end
    jnz args_exist
    
    mov eax,help    ;if no arguments were given, show a help message
    call putstring
    jmp ending     ;and end the program because there is nothing to do
    
    args_exist:
    
    mov [filename],eax
    ;call putstr_and_line ;print filename before text output
    
    ;This is where the main part of the chastext program really begins.;
    
    ;now that the argument string is prepared, we will try to use the first argument as a filename to open
    
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
    ;https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
    
    ;open first file with the CreateFileA function
    
    push 0           ;NULL: We are not using a template file
    push 0x80        ;FILE_ATTRIBUTE_NORMAL
    push 3           ;OPEN_EXISTING
    push 0           ;NULL: No security attributes
    push 0           ;NULL: Share mode irrelevant. Only this program reads the file.
    push 0x80000000  ;GENERIC_READ access mode
    push [filename] ;
    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 [filedesc],eax
    
    ;before we proceed, we also check for more arguments.
    
    call get_next_arg ;get address of next arg and return into eax register
    cmp eax,[arg_string_end] ;if at end, no search string argument
    jz textdump ;jump to textdump section
    
    ;otherwise, we save the address at ax to our search string
    mov [string_search],eax
    ;call putstr_and_line
    
    
    call get_next_arg ;get address of next arg and return into ax register
    cmp eax,[arg_string_end] ;if at end, no replacement string argument
    jz textdump ;jump to hexdump section
    
    ;otherwise, we save the address at ax to our replacement string
    mov [string_replace],eax
    ;call putstr_and_line
    
    ;all other arguments that may exist after this are irrelevant
    
    textdump:
    
    ;this is the beginning of the textdump main loop of chastext
    
    ;first, check to see if there is a search string
    ;if there is a search string, skip the normal putchar
    
    cmp dword[string_search],0 ;do we have a search string?
    jnz putchar_skip
    
    ;but if there is not a search string
    ;we will read one character, then display it to stdout
    ;and then jump to the beginning of the textdump loop to print them until EOF
    ;we start the loop with a call to read exactly 1 byte
    
    ;read only 1 byte using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 1              ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [filedesc]     ;handle of the open file
    call [ReadFile]
    
    mov eax,[bytes_read]
    
    cmp eax,1        ;check to see if exactly 1 byte was read
    jz file_success ;if true, proceed to display
    ;mov ax,end_of_file
    ;call putstring
    jmp main_end ;otherwise close the file and end program after failure
    
    ; this point is reached if 1 byte was read from the file successfully
    file_success:
    
    mov al,[byte_array]
    call putchar
    jmp textdump
    
    ;if search string doesn't exist, just jump and repeat the loop
    ;otherwise we continue into the next section that compares the input with the search string
    
    putchar_skip:
    
    ;this is the beginning of search mode
    ;it handles the file by seeking and reading to search every position for the search string
    
    ;first, seek to the file_address we initialized to zero
    ;this variable will be added to depending on actions taken
    
    ;seek to 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 [file_address] ;where we are seeking to
    push [filedesc] ;seek within this file
    call [SetFilePointer]
    
    ;obtain the length of the search string using my strlen function
    mov eax,[string_search]
    call strlen ;get the length of the search string
    
    mov ecx,eax ;store this length in ecx
    mov [search_length],ecx
    
    ;call putint_and_line ;check length of search string
    
    ;use the length of the string we are searching for as the number of bytes to read at this location
    
    ;Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push ecx            ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [filedesc]     ;handle of the open file
    call [ReadFile]
    
    mov eax,[bytes_read]  ;get how many bytes were read with that last read operation
    
    mov ebx,byte_array    ;move the address of bytes read into bx
    add ebx,eax           ;add number of bytes read (return value of read function in eax)
    mov byte[ebx],0       ;terminate the string with zero
    
    cmp eax,[search_length] ;if the number of bytes is not what we expected to read, end this loop
    jnz textdump_end
    
    ;move our two strings into the esi and edi registers for comparison
    ;with my custom written strcmp function
    
    mov esi,[string_search]
    mov edi,byte_array
    call strcmp ;compare these two strings
    
    cmp eax,0 ;test if they are the same (if eax returned zero)
    jnz not_match ;if they are not a match go to that section for printing a character
    
    ;but if they are a match, then we either quote them
    ;or replace them if a replacement string is available
    
    ;but regardless of which action we do, since a match was found, let us add this count to the file address
    ;so that we read from beyond this point next time the textdump loop starts
    mov eax,[bytes_read]
    add [file_address],eax
    
    cmp dword[string_replace],0 ;check to see if a replacement string is available
    jz print_quotes ;if not, skip to the part where we just quote the strings that match
    
    ;otherwise, we will print the replacement string instead of the original!
    
    mov eax,[string_replace]
    call putstring ;print the string
    
    jmp textdump ;restart the main loop
    
    print_quotes:
    ;print quotes around matched string
    mov al,'"'
    call putchar
    
    mov eax,byte_array
    call putstring ;print the string
    
    mov al,'"'
    call putchar
    
    jmp textdump ;restart the main loop
    
    not_match: 
    
    mov al,[byte_array]
    call putchar
    add [file_address],1 ;add 1 to the file address so we don't read this same position again
    
    jmp textdump
    
    textdump_end:
    
    ;print the remaining bytes, if any, left after the main loop ended
    mov eax,byte_array
    call putstring
    
    main_end:
    
    ;this is the end of the program
    ;we close the open file and then use the exit call
    
    ;close the file
    push [filedesc]
    call [CloseHandle]
    
    
    ending:
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    arg_string_index  dd 0 ;start of arg string
    arg_string_end    dd 0 ;address of the end of the arg string
    
    ;function to move ahead to the next art
    ;only works after the filter has been applied to turn all spaces into zeroes
    get_next_arg:
    mov ebx,[arg_string_index]
    find_zero:
    cmp byte [ebx],0
    jz found_zero
    inc ebx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    find_non_zero:
    cmp ebx,[arg_string_end]
    jz arg_finish ;if ebx is already at end, nothing left to find
    cmp byte [ebx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc ebx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_string_index],ebx ; save this index to variable
    mov eax,ebx ;but also save it to ax register for use
    ret
    ;we can know that there are no more arguments when
    ;the either [arg_start] or eax are equal to [arg_end]
    
    ;the strlen and strcmp are named after the equivalent C functions
    ;but are written from scratch by me based on their expected behavior
    
    ;a function to get the length of string in eax and return the integer in eax
    
    strlen:
    
    mov ebx,eax ; copy eax to ebx. ebx will be used as index to the string
    
    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 strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp strlen_start
    
    strlen_end:
    sub ebx,eax ;subtract start pointer from current pointer to get length of string
    
    mov eax,ebx ;copy the string length back to eax
    
    ret
    
    ;strcmp compares the string at esi to the one at edi
    ;ax returns 0 if the strings are the same and 1 if different
    ;the algorithm is simple but I will explain it for those who are confused
    
    ;eax is initialized to zero
    ;a byte from each string is loaded into the al and bl 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
    ;for this purpose it doesn't matter whether we compare al or bl with zero
    ;because it is known that they are the same if the jnz did not take place
    ;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 bl will be subtracted from al
    ;this ensures that the function returns zero if the final characters are the same
    
    strcmp:
    
    mov eax,0
    
    strcmp_start:
    
    ;read a byte from each string
    mov al,[edi]
    mov bl,[esi]
    cmp al,bl
    jnz strcmp_end
    
    cmp al,0
    jz strcmp_end
    
    inc edi
    inc esi
    
    jmp strcmp_start
    
    strcmp_end:
    sub al,bl
    
    ret
    
    help db 'chastext by Chastity White Rose',0Dh,0Ah
    db '"cat" or "type" a file without changing it:',0Dh,0Ah,9,'chastext file',0Dh,0Ah
    db 'search for a string and quote it:',0Dh,0Ah,9,'chastext file search',0Dh,0Ah
    db 'replace string:',0Dh,0Ah,9,'chastext file search replace',0Dh,0Ah
    db 'Find or replace any string!',0Dh,0Ah,0
    
    file_error_message db 'Could not open the file! Error number: ',0
    filename dd 0
    filedesc dd 0
    file_address dd 0 ;file address defaults to zero AKA beginning of file
    end_of_file db 'EOF',0
    
    ;where we will store data from the file
    bytes_read dd 0
    
    search_length dd 0
    string_search dd 0 ; place to hold the search string pointer
    string_replace dd 0 ; place to hold the replacement string pointer
    
    byte_array db 0x73 dup 0