Programming thread

Garbage collection is a fundamental feature of LISP present even in Mccarthy's initial paper from 1960. I'm not sure you can even create a LISP dialect without it.
not really you just need to be able to allocate memory. Since infinite-memory computers aren't readily available on this plane of existence, most lisp-like languages scan the heap for unreachable objects and returning them to the available memory, but you could mix and match many other techniques like memory regions, borrow checker bullshit (buzzword ready!), reference counting, an unholy procedure to free your conses manually (cnile's choice!), or the noble technique called "bump allocate until you run out of memory or the program finishes and you can release the entire heap as garbage" (which i think would actually be a pretty good idea in certain niche cases)
there are probably 7 more of them that i missed because my knowledge of memory allocation techniques is rather limited

i think for lispy languages there are three main ways to go:
  1. have the programmer insert some extra information about how that memory is being used (slightly tedious)
  2. "memory is infinite as long as i don't cons the last byte of it" (toy compilers and certain extremely niche cases)
  3. hypothetical super-advanced sufficiently smart compiler that can infer all the possible memory lifetimes in your program and insert deallocation commands in the perfect places (i will be excited to use this compiler to compile gnu hurd 1.0 in 45 years when they are both finally done)
personally i think option 1 is probably the most sane as far as having a working implementation before everybody here dies of old age, and is also much more practical than option 2 letting you only write programs that last for a few fractions of a second of cpu time
 
The other day i had the bright idea to make a terminal text editor bcus i like the idea of Vim but hate the modality of it. Because i am at the end of the day a man who would rather bash my head against a wall than learn anything new no matter how useful i decided that Vim but with keyboard shortcuts instead of modes was a way better idea, i sought to find a tutorial and stumbled upon a video made by some flavour of Slav doing a coding challenge., he wanted to make a proof of concept terminal editor in 50 lines of code so i followed along making changes to the best of my ability and this is the fruit of my labour.

and now im stuck, not sure what to do because the only parts i know too intimately are the save and load functions since thats what i spent time "optimizing" (i basically just encapsulated them), i know the basics of how the system works, its an array labeled buffer that stores the text data in the form of ascii code that when its time to save gets converted back to readable text and written to a file. the thing is that im not sure of how any of the rest of it really works, i cant stand python documentation (none of it is good, not even community made libraries have good docs, or at least not in my opinion) and most other resources are entirely unhelpful. so how would you fix the rest of this? while i was following along with the video i made general comments about what each section does and now i want to clean it up and add more keybinds, it should be simple enough in theory right?
Python:
import curses
import sys
import os


def load_file(filepath):
    """
    This function opens a file provided by the user,
    converts the lines from standard strings to a series of characetrs which are easier for our buffer array to keep track of and modify.
    After converting the lines they're loaded into the buffer for modification.
    """
    buffer = [] #this is our bufer array

    #[1}basic error catching system which "tries" to open a provided file
    try:
        with open(filepath, "r", encoding="utf-8")as file: #this line handles opening the file specified.
         
            #This line nandles the conversion of the file to a list of strings where each string is a line
            #E.g.: Hello\n World\n would become ["Hello", "World"]
            lines = file.read().splitlines()

            #this is a nested for that loops through the lines array creating an ascii line array to store the asci data
            #It then loops through the individual lines and converts them to their ascii numbers
            #After which the ascii line array is stored in the buffer
            for line in lines:
                ascii_line = []
                for char in line:
                    ascii_line.append(ord(char))
                buffer.append(ascii_line)


    #{2] if it cannot open the file it will print an error message and then create a blank line instead of crashing the program.
    except (FileNotFoundError, PermissionError) as e:
        print(f"[!] Could not open file '{filepath}': {e}")

        #Here we append an empty list to the buffer
        #This clears the memory so to speak giving us a cleared line to work with
        buffer.append([])           #sets an empty buffer if the file read fails

    return buffer # we return the buffer so that other functions can use it.
         

def save_file(buffer, filepath):
    """This function opens the file provided by the user,
    Converts the lines found in the buffer back to standard strings,
    after which it writes these lines to the file and closes it"""
    content = ""
    for line in buffer:
        for column in line:
          content += chr(column)
        content += "\n"
     
    with open(filepath, "w") as file:
        file.write(content)



def main(stdscr):
    #initialize the screen
    screen = stdscr             #because of the curses.wrapper(main) line at the end curses.initscr is redundant
    screen.nodelay(1)           # makes getch(get character) non blocking
    curses.noecho()             # makes it so that non letter inputs arent displayed on the screen when typing
    curses.raw()                # disables line buffering
    screen.keypad(1)            # enables the special keys like arrows and whatnot

 
    src='noname.txt'                                        #sets the default file name
    row,column=screen.getmaxyx()                            #sets the terminal dimensions
    r_offset, c_offset, current_r, current_c = [0]*4        #sets the viewport(terminal camera) amd cursor positions

    #handles file loading R
    if len(sys.argv) ==2:
        src = sys.argv[1]
    buffer = load_file(src)      


 
    while True:
        screen.move(0,0)            #positions the cursor to top left

        #TODO encapsulate these processes into their own functions and call them

        #VERTICAL scrolling
        if current_r < r_offset:
            r_offset = current_r
        if current_r >= r_offset + row:
            r_offset = current_r - row +1
     
        #HORIZONTAL scrolling
        if current_c < c_offset:
            c_offset =current_c
        if current_c >= c_offset + column:
            c_offset = current_c - column +1


        #renders buffer to the terminal
        for rw in range(row):
            buffer_row = rw + r_offset
            for cl in range (column):
                buffer_column = cl + c_offset
                try:
                    screen.addch(rw, cl,buffer[buffer_row][buffer_column])
                except:
                    pass
            screen.clrtoeol()
            try:
                screen.addch("\n")
            except:
                pass
     
        #handles curser position
        curses.curs_set(0)
        screen.move(current_r - r_offset, current_c - c_offset)
        curses.curs_set(1)
        screen.refresh()

        #input manager
        ch = -1 # character
        while (ch == -1):
            ch =screen.getch()

        #Text insert
        if ch != ((ch) & 0x1f) and ch < 128:
            buffer[current_r].insert(current_c, ch)
            current_c +=1

        #Text insertion  
        elif chr(ch) in "\n\r":
            line = buffer[current_r][current_c:]
            buffer[current_r] = buffer[current_r][:current_c]
            current_r+=1
            current_c = 0
            buffer.insert(current_r, [] + line)
     
        #Backspace handling
        elif ch in [8,263]:
            if current_c:
                current_c -=1
                del buffer[current_r][current_c]
            elif current_r>0:
                line = buffer[current_r][current_c:]
                del buffer[current_r]
                current_r -=1
                current_c = len(buffer[current_r])
                buffer[current_r] += line

        #Cursor actions    
        elif ch == curses.KEY_LEFT:
            if current_c !=0:
                current_c -=1
            elif current_r > 0:
                current_r -=1
                current_c = len(buffer[current_r])
        elif ch == curses.KEY_RIGHT:
            if current_c < len(buffer[current_r]):
                current_c+=1
            elif current_r<len(buffer)-1:
                current_r +=1
                current_c = 0
        elif ch == curses.KEY_UP:
            if current_r >0:
                current_r -=1
                #current_c = len(buffer[current_r])
        elif ch ==curses.KEY_DOWN:
            if current_r < len(buffer)-1:
                current_r +=1
                #current_c = len(buffer[current_r])


        if current_r < len(buffer):
            rw = buffer[current_r]
        else:
            None
        rwlen = len(rw) if rw is not None else 0
        if current_c >rwlen:
            current_c = rwlen

        #save and quit
        elif ch == (ord("s") & 0x1f):  # Ctrl+S
            save_file(buffer=buffer, filepath=src)

        elif ch == (ord("q") & 0x1f):  # Ctrl+Q
            sys.exit()

curses.wrapper(main) #this protects the terminal
 
i like the idea of Vim but hate the modality of it
Vim but with keyboard shortcuts instead of modes
sir have you heard of the extensible self-documenting editor called gnu emacs
it's a very good editor that works in gtk and curses modes and even though it has a very steep learning curve you will never accidentally type shit in command mode and apply several kinds of state-of-the-art encryption to your file
have a link: GNU Emacs
how would you fix the rest of this?
first priority is actually understanding what all this is doing. if you don't know what's going on, you're not going to be able to fix all the broken shit and lacking features.

here's some broken shit:
Python:
#input manager
ch = -1 # character
while (ch == -1):
    ch =screen.getch()
im going to guess that this is a busywait and your text editor is constantly using 100% of one of the cores on your system at all times when it is running
you should check in your favorite process activity monitor; if you see /usr/bin/python3 /home/chud/Desktop/epicTextEditor/editor.py using lots of cpu, you'll know it's fucked
as a rule, well-behaved programs will generally sleep until they wake to an event from the operating system, process it, and immediately go back to sleep until the next event comes
in this case i might try removing the screen.nodelay(1) # makes getch(get character) non blocking and the busywait if i know a blocking getch will always return a valid character
keep in mind, i don't have much experience with curses (and especially not in python) so there might be something preventing this, but i doubt it

other than the huge performance issue that will make your console text editor quickly drain laptop batteries, this code doesn't look horrible. great job!
if you keep it up for long enough, maybe you'll eventually end up with a decent little python equivalent of gnu nano (another curses-based text editor that is like vim with keyboard shortcuts instead of modes) or whatever

here's an idea: you might want to set one of the terminal rows aside for a little line where you can display messages and ask questions. emacs calls it the "echo area". one of the uses of it could be that instead of saving to noname.txt by default when you save a new file, it can ask you what you want to call it. this is only the beginning of what you could do with such a line, you can probably imagine how much easier a search and replace hotkey would be if you had this. you could also display stuff like the current filename and how far along you are in the file, the sky's the limit!
 
  • Informative
Reactions: ChudJack009
sir have you heard of the extensible self-documenting editor called gnu emacs
gnu nano (another curses-based text editor that is like vim with keyboard shortcuts instead of modes) or whatever
while i have very limited knowledge of Emacs i am vaguely aware of nano since i briefly used it when i was messing about with debian on an old laptop and remembering it was the basis of this project. i will say that ive considered all that youve said in terms of features but most of it seems to be beyond the scope of my current abilities.
this code doesn't look horrible. great job!
Ill assume youre speaking of the encapsulation i did and not the slapchopped slav jank below and to that i say thank you
im going to guess that this is a busywait and your text editor is constantly using 100% of one of the cores on your system at all times when it is running
you should check in your favorite process activity monitor; if you see /usr/bin/python3 /home/chud/Desktop/epicTextEditor/editor.py using lots of cpu, you'll know it's fucked
as a rule, well-behaved programs will generally sleep until they wake to an event from the operating system, process it, and immediately go back to sleep until the next event comes
not sure what all these phrases like busywait mean but i'll look into it.

thank you for the feedback
 
Will second this wholeheartedly. Although it's a royal pain in the ass specifically going through the motions for C's string and arrays gives you a much better appreciation of low-level memory management and data manipulation than any more recent language ever could. The moment you learn how the latest languages abstracted and improved upon the presentation of stuff like strings is the moment you start appreciating just what the tools have given you, and more importantly, just what the limitations are.
I'm gonna be real, C isn't a problem, as that is the language I use primarily, the problem is shitty fucking documentation.
the official datasheet doesn't say squat what exact GPIO pin does (which tbh makes sense), and alternate functions are described as <internal> whatever the fuck that means, and afaik don't say what their primary (non-alternate) functions are
the errata linked to on raspberry pi website says the GPIO pinout table "may be wrong idk"
this table is wrong, it says pin 47 is SD card whatever, and pin 16 is activity LED, while on my shit the LED is on pin 47 (they use different schematics idk)
and the official schematics don't say anything about which pin controls the activity led for example

so im mostly going off this repo and this repo
I've decided to buy a serial to usb, so I can at least have something to print to
 
Ill assume youre speaking of the encapsulation i did and not the slapchopped slav jank below and to that i say thank you
the slavjank isn't that terrible as a piece of code except the fact that it looks like it hogs a whole cpu core
also what you call "encapsulation" is more often called "refactoring into a function"

your file helper functions are fairly ok, but i have one nitpick with them: they have too much commenting. that's not actually a bad problem to have, but often beginners will do stuff like x = x + 4 # add 4 to x and store it in x
when making comments. you always have to ask yourself: is this comment just verbosely explaining what the code is already explaining? for example, when you say for line in file.read().splitlines(): do you really need to explain what a for loop is and how it reads the file and splits the lines into a list? is # we return the buffer so that other functions can use it. really helpful?

you should always comment your code to explain why you're doing things instead of what you're doing. here's an example: you should replace the #this is our bufer array comment. (seriously... what else could this line possibly be, other than our buffer array? somebody else's? this is not a very helpful comment!) i recommend replacing it with something like # array containing arrays of numbers corresponding to character codes so you actually know what your buffer is, instead of just saying "dis my bufer array it is very nice". but that's not even the best place to put that; you could instead remove the comment entirely and put that information in the function's docstring, where the function is supposed to be documented. certain really smart text editors can do stuff like pop up the docstring on hover, but they can't do that for random comments unless they are told to open the source code of the function

also you could probably replace the buffer.append([]) with return [[]] # return a single blank line and move that return statement at the bottom up into the try block. makes it less tangly.

otherwise, your plan to refactor everything into functions is admirable. if you feel really brave, you should try turning the cursor and the logical window for scrolling into classes. classes allow you to make objects, which you already use a lot of. classes and objects can make your code a lot cleaner and easier to understand, if you use them right. i assume cursor.getrow() looks better to you than some random variable called current_r? it will probably make your handle_keypress(buffer, screen.getch()) function a lot better. and if you make enough stuff into classes and separate the state enough, you could probably do epic things like multiple files being edited in little windows in your terminal window, since you can make as many objects you want from a class you make. you could end up with 3 EditorWindow objects and they all get their own buffer, and when you switch around between them you switch between their own cursor objects! then your main function is just a skeleton that reads keypresses for the sole purpose of sending them off to the appropriate windows

a good first class candidate might be to make it so your buffer is attached to the file name it's supposed to save to. one variable to deal with is a lot better than two. you can then change the opening and saving functions into functions like buffer = Buffer() buffer.load_from_file(...)

word to the wise: don't feel bad if you end up rewriting the whole thing a few times
I've decided to buy a serial to usb, so I can at least have something to print to
all hail printf(), the universal debugger
 
ez just use sprintf and then flash the light in morse code (adapted for ascii by using dot as 0 and dash as 1) and then decode it through your computer's webcam using a shitty python script
I don't have sprintf either lmao. I only have what's available in -ffreestanding and, shockingly, that doesn't include <string.h>.
I do have <stdint.h> and <tgmath.h>, though. :)
 
I don't have sprintf either lmao. I only have what's available in -ffreestanding and, shockingly, that doesn't include <string.h>.
I do have <stdint.h> and <tgmath.h>, though. :)
ow fuck that sucks
at least you have <stdint.h> and <tgmath.h> though
 
ow fuck that sucks
at least you have <stdint.h> and <tgmath.h> though
1748850056751.webp
all the libs available to me, I especially like <stdatomic.h>, since raspi zero uses a one core cpu
 
i think for lispy languages there are three main ways to go:
  1. have the programmer insert some extra information about how that memory is being used (slightly tedious)
  2. "memory is infinite as long as i don't cons the last byte of it" (toy compilers and certain extremely niche cases)
  3. hypothetical super-advanced sufficiently smart compiler that can infer all the possible memory lifetimes in your program and insert deallocation commands in the perfect places (i will be excited to use this compiler to compile gnu hurd 1.0 in 45 years when they are both finally done)
personally i think option 1 is probably the most sane as far as having a working implementation before everybody here dies of old age, and is also much more practical than option 2 letting you only write programs that last for a few fractions of a second of cpu time
Reminder: Macros can expand at runtime, so anything the programmer can write before compile time can appear at runtime. Since these expansions can occur based on unpredictable external values (e.g. the response of a server), even solving the Halting Problem would not make it possible to statically predict what code is getting interpreted. Accordingly, you'd need to neuter or entirely remove the macro system for a "compiler" to be a relevant abstraction at all.

Option 1:
This might be feasible, but I'd be interested to know what kind of metadata could be added, and where (since, if the metadata has a non-atomic syntax, the lists containing it would themselves need memory metadata, which would itself need memory metadata, which would itself...). Moreover, by the time you made a metadata system as expressive as freely allocating and freeing memory, it would be difficult to have a language that isn't a total mess.

Option 2:
What was called for was a "C-like language" with LISP macros/syntax. To me, "C-like" implies the ability to free memory.

Option 3:
That super smart compiler would have to be working at runtime, so unless the magic compiler also somehow has no performance cost or memory usage, it would have an enormous overhead, and that would not be very "C-like" in my opinion.


Don't get me wrong: I'd love to see a systems language with LISP-like syntax, but to be a proper LISP with macro functionality requires a language to be evaluated in itself at runtime, and I just don't think that's possible without some form of runtime overhead.
 
Last edited:
in this case i might try removing the screen.nodelay(1) # makes getch(get character) non blocking and the busywait if i know a blocking getch will always return a valid character keep in mind, i don't have much experience with curses (and especially not in python) so there might be something preventing this, but i doubt it
Absolutely this. If you don't have anything to do but wait for a character, using a nonblocking getch is wrong.

Code:
        ch = -1 # character
        while (ch == -1):
            ch =screen.getch()

This is what's called a "busywait": your program is doing nothing but checking for input over and over and over and over. It waits for input, and while it waits, it sits there polling endlessly. This can be replaced with a one-line ch=screen.getch() if you disable non-blocking read.
 
So i took the advice here to heart and created dummy classes for the screen and cursor (ill populate them as i figure it out). Removed the nodelay/non blocking feature as it was causing a busywait and cleaned up comments. I also added try catches for curses.curs_set due to weird interactions with Msys2 (When installed to system path the default terminal is overridden. I found this out as i set up my home pc for C development with MSYS2, shortly after which my text editor refused to run a quick google search followed by a prompting lead to the conclusion that MSYS2 was the problem and after implementing the try catches fixed everythinge how that works because i really dont know, the try catch fixes it though)
Python:
import curses
import sys
import os


def load_file(filepath):
    """
    This function opens a file provided by the user,
    converts the lines from standard strings to a series of characetrs which are easier,
    for our buffer array to keep track of and modify.
    After converting the lines they're loaded into the buffer for modification.
    (this uses a nested for that loops through the lines array creating an ascii line array to store the asci data
    It then loops through the individual lines and converts them to their ascii numbers
    After which the ascii line array is stored in the buffer)
    """
    buffer = []

    #[1}basic error catching system which "tries" to open a provided file
    try:
        with open(filepath, "r", encoding="utf-8")as file:
           
           
            lines = file.read().splitlines()

           
            for line in lines:
                ascii_line = []
                for char in line:
                    ascii_line.append(ord(char))
                buffer.append(ascii_line)


    #{2] if it cannot open the file it will print an error message and then create a blank line instead of crashing the program.
    except (FileNotFoundError, PermissionError) as e:
        print(f"[!] Could not open file '{filepath}': {e}")

        #Here we append an empty list to the buffer
        #This clears the memory so to speak giving us a cleared line to work with
        buffer.append([])           #sets an empty buffer if the file read fails

    return buffer
           

def save_file(buffer, filepath):
    """This function opens the file provided by the user,
    Converts the lines found in the buffer back to standard strings,
    after which it writes these lines to the file and closes it"""
    content = ""
    for line in buffer:
        for column in line:
          content += chr(column)
        content += "\n"
       
    with open(filepath, "w") as file:
        file.write(content)

class Screen:
    pass

class Cursor:
    pass

def main(stdscr):
    #initialize the screen
    screen = stdscr             #because of the curses.wrapper(main) line at the end curses.initscr is redundant
    curses.noecho()             # makes it so that non letter inputs arent displayed on the screen when typing
    curses.raw()                # disables line buffering
    screen.keypad(1)            # enables the special keys like arrows and whatnot

   
    src='noname.txt'                                        #sets the default file name
    row,column=screen.getmaxyx()                            #sets the terminal dimensions
    r_offset, c_offset, current_r, current_c = [0]*4        #sets the viewport(terminal camera) amd cursor positions

    #handles file loading R
    if len(sys.argv) ==2:
        src = sys.argv[1]
    buffer = load_file(src)        


   
    while True:
        screen.move(0,0)            #positions the cursor to top left

        #TODO encapsulate these processes into their own functions and call them

        #VERTICAL scrolling
        if current_r < r_offset:
            r_offset = current_r
        if current_r >= r_offset + row:
            r_offset = current_r - row +1
       
        #HORIZONTAL scrolling
        if current_c < c_offset:
            c_offset =current_c
        if current_c >= c_offset + column:
            c_offset = current_c - column +1


        #renders buffer to the terminal
        for rw in range(row):
            buffer_row = rw + r_offset
            for cl in range (column):
                buffer_column = cl + c_offset
                try:
                    screen.addch(rw, cl,buffer[buffer_row][buffer_column])
                except:
                    pass
            screen.clrtoeol()
            try:
                screen.addch("\n")
            except:
                pass
       
        #thandles curser position
        try:
            curses.curs_set(0)
        except curses.error:
            pass
        screen.move(current_r - r_offset, current_c - c_offset)
        try:
            curses.curs_set(1)
        except curses.error:
            pass
       
        screen.refresh()

        #input manager
       
        ch =screen.getch()

        #Text insert  
        if ch != ((ch) & 0x1f) and ch < 128:
            buffer[current_r].insert(current_c, ch)
            current_c +=1

        #Text insertion    
        elif chr(ch) in "\n\r":
            line = buffer[current_r][current_c:]
            buffer[current_r] = buffer[current_r][:current_c]
            current_r+=1
            current_c = 0
            buffer.insert(current_r, [] + line)
       
        #Backspace handling
        elif ch in [8,263]:
            if current_c:
                current_c -=1
                del buffer[current_r][current_c]
            elif current_r>0:
                line = buffer[current_r][current_c:]
                del buffer[current_r]
                current_r -=1
                current_c = len(buffer[current_r])
                buffer[current_r] += line

        #Cursor actions      
        elif ch == curses.KEY_LEFT:
            if current_c !=0:
                current_c -=1
            elif current_r > 0:
                current_r -=1
                current_c = len(buffer[current_r])
        elif ch == curses.KEY_RIGHT:
            if current_c < len(buffer[current_r]):
                current_c+=1
            elif current_r<len(buffer)-1:
                current_r +=1
                current_c = 0
        elif ch == curses.KEY_UP:
            if current_r >0:
                current_r -=1
                #current_c = len(buffer[current_r])
        elif ch ==curses.KEY_DOWN:
            if current_r < len(buffer)-1:
                current_r +=1
                #current_c = len(buffer[current_r])


        if current_r < len(buffer):
            rw = buffer[current_r]
        else:
            None
        rwlen = len(rw) if rw is not None else 0
        if current_c >rwlen:
            current_c = rwlen

        #save and quit
        elif ch == (ord("s") & 0x1f):  # Ctrl+S
            save_file(buffer=buffer, filepath=src)

        elif ch == (ord("q") & 0x1f):  # Ctrl+Q
            sys.exit()

curses.wrapper(main) #this protects the terminal
i suppose the next thing to figure out is how to format the classes.
 
  • Like
Reactions: y a t s
Reminder: Macros can expand at runtime, so anything the programmer can write before compile time can appear at runtime. Since these expansions can occur based on unpredictable external values (e.g. the response of a server), even solving the Halting Problem would not make it possible to statically predict what code is getting interpreted. Accordingly, you'd need to neuter or entirely remove the macro system for a "compiler" to be a relevant abstraction at all.
Most macros run on the compiler and then get turned into a macro-free subset of the language when the compiler is done expanding them. I'd say removing any runtime macro support would be a good compromise to make this feasible, since a language with LISP-style macros at compile time is better than having to use some shitty language that doesn't have good macros at all. Same goes for eval and its friends.
This might be feasible, but I'd be interested to know what kind of metadata could be added, and where (since, if the metadata has a non-atomic syntax, the lists containing it would themselves need memory metadata, which would itself need memory metadata, which would itself...). Moreover, by the time you made a metadata system as expressive as freely allocating and freeing memory, it would be difficult to have a language that isn't a total mess.
Things like Rust's annotations for borrow checker. Also, adding extra syntax to a form that only the compiler and the macro expander will see does not directly influence the size of the program.
What was called for was a "C-like language" with LISP macros/syntax. To me, "C-like" implies the ability to free memory.
malloc() and co. actually aren't a fundamental part of C. C programmers are used to having it, though, since it's usually the first function implemented in most C libraries. But yeah, this option is very limited.
That super smart compiler would have to be working at runtime, so unless the magic compiler also somehow has no performance cost or memory usage, it would have an enormous overhead, and that would not be very "C-like" in my opinion.

Don't get me wrong: I'd love to see a systems language with LISP-like syntax, but to be a proper LISP with macro functionality requires a language to be evaluated in itself at runtime, and I just don't think that's possible without some form of runtime overhead.
this only happens if you use eval at runtime and if you don't use eval at runtime the compiler will never have to run (and can be removed from the final program)
does every c program have to include a c compiler simply because you can write a c file and use posix apis to spawn gcc to make a shared object, then load it using dlopen()? (the unholy c eval)

LISP isn't very static at all, but I just want to argue that it can be made a lot more static without making it too dumbed down. This might suck for the people who really need esoteric forms of runtime expansion and runtime evaluation, but it would be great for people who want to use a reasonably lispy language in the place of C or Rust or some other non-parenthetical heretic language.
i suppose the next thing to figure out is how to format the classes.
nice work improving your program performance by infinity% but to figure out the basics of classes you might want to make a new script and make some animals that inherit from each other and then come back to the editor when you feel like you know enough to not cause a huge disaster
 
not sure what the etiquette is about posting the same program over and over but i dont fancy exposing myself on accident with a github repo. i successfully added the cursor class (essentially everything works as it did before i implemented it) and now i should ideally be moving on to sorting out the other loose functions in main and consolidating them as well as working out the screen/editor class. I also want to look into adding this line number feature suggested.


Code:
import curses
import sys
import os


def load_file(buffer, filepath):
    """
    This function opens a file provided by the user,
    converts the lines from standard strings to a series of characetrs which are easier,
    for our buffer array to keep track of and modify.
    After converting the lines they're loaded into the buffer for modification.
    (this uses a nested for that loops through the lines array creating an ascii line array to store the asci data
    It then loops through the individual lines and converts them to their ascii numbers
    After which the ascii line array is stored in the buffer)
    """
   

    #[1}basic error catching system which "tries" to open a provided file
    try:
        with open(filepath, "r", encoding="utf-8")as file:
           
           
            lines = file.read().splitlines()

           
            for line in lines:
                ascii_line = []
                for char in line:
                    ascii_line.append(ord(char))
                buffer.append(ascii_line)


    #{2] if it cannot open the file it will print an error message and then create a blank line instead of crashing the program.
    except (FileNotFoundError, PermissionError) as e:
        print(f"[!] Could not open file '{filepath}': {e}")

        #Here we append an empty list to the buffer
        #This clears the memory so to speak giving us a cleared line to work with
        buffer.append([])           #sets an empty buffer if the file read fails

    return buffer
           

def save_file(buffer, filepath):
    """This function opens the file provided by the user,
    Converts the lines found in the buffer back to standard strings,
    after which it writes these lines to the file and closes it"""
    content = ""
    for line in buffer:
        for column in line:
          content += chr(column)
        content += "\n"
       
    with open(filepath, "w", encoding="utf-8") as file:
        file.write(content)



   


def buffer_renderer(screen, buffer: list, row: int, column: int, r_offset: int, c_offset: int):
    """This function handles rendering whats in the buffer to the terminal screen."""
    for rw in range(row):
            buffer_row = rw + r_offset
            for cl in range (column):
                buffer_column = cl + c_offset
                try:
                    screen.addch(rw, cl,buffer[buffer_row][buffer_column])
                except:
                    pass
            screen.clrtoeol()

    pass

class Screen:
    """"""
    def __init__(self):
        pass

class Cursor:
    """This class handles Cursor initialization and application.
    It needs to be aware of the cursors logical position, meaning its position within the editors buffer
    as well as its visual position, meaning the actual position of the cursor is rendered to in the terminal"""
    def __init__(self):
        self.row = 0
        self.column = 0
        self.row_offset = 0
        self.column_offset = 0

    def visual_cursor_renderer(self, screen):
        """This function handles rendering the visual cursor
        The visual cursor is the cursor found inside the terminal"""
        try:
            curses.curs_set(0)
        except curses.error:
            pass
       
        # Tell curses where to draw the visual cursor, based on scroll offset and logical cursor
        screen.move(self.row - self.row_offset, self.column - self.column_offset)

        try:
            curses.curs_set(1)
        except curses.error:
            pass

        #NOTE: This refresh might be unnecessary, will remove later
        #screen.refresh()
   


def main(stdscr):
    #initialize the screen
    screen = stdscr            
    curses.noecho()             # makes it so that non letter inputs arent displayed on the screen when typing
    curses.raw()                # disables line buffering
    screen.keypad(1)            # enables the special keys like arrows and whatnot

    buffer = []

   
    src='noname.txt'                                        #sets the default file name
    row,column=screen.getmaxyx()                            #sets the terminal dimensions

    cursor = Cursor()

    #NOTE: i Think current_r and _c are the cursor positions and r_offset and c_offset are the viewport variables
    # this means that the offsets would likely go into the editor/screen class and the current r and c would be in the cursor class.
    #r_offset, c_offset, current_r, current_c = [0]*4        #sets the viewport(terminal camera) amd cursor positions to the first index... i think

    #handles file loading
    if len(sys.argv) ==2:
        src = sys.argv[1]
        load_file(buffer, src)        


   
    while True:
        screen.move(0,0)            #positions the cursor to top left

        #TODO encapsulate these processes into their own functions and call them

        #VERTICAL scrolling
        if cursor.row < cursor.row_offset:
            cursor.row_offset = cursor.row
        if cursor.row >= cursor.row_offset + row:
            cursor.row_offset = cursor.row - row +1
       
        #HORIZONTAL scrolling
        if cursor.column < cursor.column_offset:
            cursor.column_offset =cursor.column
        if cursor.column >= cursor.column_offset + column:
            cursor.column_offset = cursor.column - column +1


        #renders buffer to the terminal
        buffer_renderer(screen= screen, buffer=buffer, row=row, column=column, r_offset=cursor.row_offset, c_offset= cursor.column_offset )

            #TODO:REMOVE
            #try:
            #    screen.addch("\n")
            #except:
            #   pass
       

        #handles the visual cursor position
        cursor.visual_cursor_renderer(screen=screen)
        #screen.refresh()
 
       

        #input manager
        ch =screen.getch()

        #Text insert  
        if ch != ((ch) & 0x1f) and ch < 128:
            buffer[cursor.row].insert(cursor.column, ch)
            cursor.column +=1

        #Text insertion    
        elif chr(ch) in "\n\r":
            line = buffer[cursor.row][cursor.column:]
            buffer[cursor.row] = buffer[cursor.row][:cursor.column]
            cursor.row+=1
            cursor.column = 0
            buffer.insert(cursor.row, [] + line)
       
        #Backspace handling
        elif ch in [8,263]:
            if cursor.column:
                cursor.column -=1
                del buffer[cursor.row][cursor.column]
            elif cursor.row>0:
                line = buffer[cursor.row][cursor.column:]
                del buffer[cursor.row]
                cursor.row -=1
                cursor.column = len(buffer[cursor.row])
                buffer[cursor.row] += line

        #Cursor actions      
        elif ch == curses.KEY_LEFT:
            if cursor.column !=0:
                cursor.column -=1
            elif cursor.row > 0:
                cursor.row -=1
                cursor.column = len(buffer[cursor.row])
        elif ch == curses.KEY_RIGHT:
            if cursor.column < len(buffer[cursor.row]):
                cursor.column+=1
            elif cursor.row<len(buffer)-1:
                cursor.row +=1
                cursor.column = 0
        elif ch == curses.KEY_UP:
            if cursor.row >0:
                cursor.row -=1
                #cursor.column = len(buffer[cursor.row])
        elif ch ==curses.KEY_DOWN:
            if cursor.row < len(buffer)-1:
                cursor.row +=1
                #cursor.column = len(buffer[cursor.row])


        if cursor.row < len(buffer):
            rw = buffer[cursor.row]
        else:
            None
        rwlen = len(rw) if rw is not None else 0
        if cursor.column >rwlen:
            cursor.column = rwlen

        #save and quit
        elif ch == (ord("s") & 0x1f):  # Ctrl+S
            save_file(buffer=buffer, filepath=src)

        elif ch == (ord("q") & 0x1f):  # Ctrl+Q
            sys.exit()

curses.wrapper(main) #this protects the terminal
Definetly want to look into this line number feature since it'll be the newest visual change to the project and i like visual changes. if you see anything or have any suggestions please do share them.

use winlibs, specifically gcc 14.2.0 cuz it adds llvms stuff
ive never had a problem with it
Ill look into this but im keeping my fix since it will work if someone does use MSYS2 and or any other program that messes with the terminal
 
not sure what the etiquette is about posting the same program over and over
it's fine if you keep it in a spoiler like you've been doing
i dont fancy exposing myself on accident with a github repo.
github is fucking gay use codeberg or notabug or literally anything else, or just use git standalone on your own computer because you can do that
(back in the day, kf had its own git host but it was lost after the keffals bullshit)
I also want to look into adding this line number feature suggested.
i suggested turning the bottom row into a little area for prompts and messages, but line numbers would be neat too
the message/prompt area would be very nice for hotkeys to do things like searching. you could hit ctrl+f and it would call editorsession.ask_user_for_string("Find:") and then it would ask you to type a string to find

anyway your program is slowly starting to become more capable of being extended to do more stuff. when you make your editor class you should try changing the screen rendering algorithm so it draws to a rectangle on the screen instead of just drawing the whole screen. then you can start adding extra ui, including line numbers

also: curses can color text, you should look up the function that does that and do something awesome with it like making the line numbers a bit gray
 
Back