So, just for funs and learning, I started following a build your own text editor tutorial using swi prolog… And I must say that I am really enjoying this !
So I was wondering if anyone has ever attempted to write a command line program in prolog ?
Here are some random notes about my experience:
the tutorial spends a lot of time putting the terminal in raw mode. Well, in swi-prolog, there is a predicate for that with_tty_raw/1. Same with getting the size of the terminal window tty_size/2.
almost all of my predicates are actually DCGs: a dcg to parse input keys from the user (things like escape sequences), a dcg to render the buffer to the screen
I use the new library macros to compile deterministic dcg goal. For example, I use macros to insert escape sequences by expanding the corresponding dcg goal.
I use the library settings to store my editor settings and … global state This has the interesting side effet that when restarting the editor, I get back to the exact same state as before ^^
As a life long vimmer, I think I had my own little Emacs moment when I realized I could modify the code of the editor in my new editor and then bind a key to the predicate prolog/0 to get back to a prolog top level. Then, I can call make/0 to compile the code of the editor. When quitting the top level, the changes of the editor code is automatically picked up by the running editor instance !
Lastly, I store the whole buffer in a list of lines, each line being a list of codes… Working with list means that I can easily use nth1/3 and nth1/4 to index and modify the buffer. However, I have no idea if this strategy will scale for anything more than very small files.
Does anyone know of a datastructure more suitable for this use case ?
If anyone wants to see the very ugly code I wrote: kilogic
I’d consider going for a an array of strings or maybe atoms. For an array you use a compound term, e.g.
functor(Lines, lines, 1000).
Now you have some options. For an updated line you can copy the term, which is rather expensive, or you can use setarg/3 or nb_setarg/3. If the array gets too small you create one that is twice as large and copy the lines to that. Of course, to work on a line you need to convert it to a list of characters/codes and back when done.
P.s. Settings are not really meant for this Possibly library(persistency) is more suitable. In that case you could consider representing your lines as clauses, although inserting/deleting lines is a bit complicated in that case.
Another simple data structure would be a list zipper, basically a pair ([Front],[Current|Tail]), where Current is the line with the cursor, Front is a reversed list of all lines preceding it and Tail a normal list of all the following lines. That deals gracefully with changes near the cursor, and should be alright for larger jumps as well. If you like, you can then represent the lines in Front and Tail compactly as strings and only “thaw” Current to a list of characters, “freezing” it to a string again as soon as the cursor leaves the line.
An array seems problematic because lines will be inserted or deleted somewhere in the middle extremely frequently.
Let me put in a plug for “extended DCGs” in pack(edcg) … these allow multiple accumulators and the accumulators can do custom things, such as symbol tables.
Speaking of symbol tables, I use rbtrees, which have O(log N) performance and they’re not a significant performance issue, even when the symbol tables are quite large (more than 10,000 entries).
So, for storing your list of lines, rbtrees might work well.