Skip To Main

Devlog 2: 2023-06-27

Devlog of completing the Make-A-Lisp guide

Page Details

Author: Suraj S. Singh

Word Count: 680 words

First Created: 2023-06-27

License: CC BY-NC-SA 4.0

Tags:

programming-language
rust

What was accomplished

Finished up all remaining steps from mal - Make a Lisp.

Step 6

In this step, I worked on loading strings from a file, command line arguments, evaluating from within the implementation, and working on implementing atoms. Much of the work on this step was simple enough.

I did start thinking about adding additional items to the program. For example, command line parsing with Clap and using Miette for error showing. However, initial refactoring took too much time, so I put that on hold for later work once I finished the guide.

Step 7

This step focused on implementing quoting functionality. I was very confused about how to implement the quasiquote() function, even after looking at the Rust example. The tests for quasiquote were not working. However, I learned I made a silly mistake where the code was not in the correct if-branch case. I also confused cons and concat in function, as well as unquote and splice, specifically forgetting to use them as special keywords rather than symbols.

Going through this process got me thinking that maybe making these special forms as separate variants wasn't a good idea. The mix of symbols and special forms lead to much confusion about whether I was evaluating the correct form.

I eventually got it all to work.

Step 8

For step 8, I worked on implementing Lisp macros. I had some confusion with the macroexpand() function, mainly with how the macro continues after applying the first run-through. Once I got my bearing with this and completed the function, the rest of the items in this step were simple to implement. I think I am beginning to feel comfortable programming in Rust.

Step 9

This step focused on implementing try-catch. This was fairly easy to accomplish using Rust's Result enum. I had the update the error case to add Exceptions and pass whatever value the user had. I did struggle a bit with the apply() function. This is mostly down to my incorrect mental model of how the function is applied to each item.

The rest of the functionality was simple enough, though there was much more than before. The hardest functionality to implement was the hash-map functions (namely assoc and dissoc), as I hadn't worked with them yet, and I implement the map as a vector of tuple pairs. Also had to fix equality for hashmaps since the ordering does not matter. I'll consider changing over when adding immutable data structures.

Step A

This final step was a catch-all for all other remaining items, such as reading a line from the user and more core functions. I spent way too much time trying to figure out why the step was failing when I added the *host-language* variable. Turns out, I missed quotes and ending parentheses. That just emphasizes how much I need an error diagnostic system added to my implementation.

On the other side, adding metadata for functions and collection was tedious more than hard. As well, I tried to get Rust evaluation working using the Evcxr crate but failed to get it working (the REPL would hang when evaluating Rust code).

What I learned

I got more comfortable working with Rust and learn about some external libraries that could help later on. Some of these include:

  • Evcxr REPL: Rust REPL, to allow running Rust code from my REPL.
  • Miette: Diagnostic library for more clearly showing where errors occur.
  • Criterion: Benchmarking library which can be a good way to check that I am making efficient changes.
  • Im: A library that provides immutable data structures just like Clojure.
  • Num: A generic numeric type library that can make interacting with different types of numbers a bit better.

What's next

Do an initial refactor of what is created and make sure new improvements can easily be added. Make sure to benchmark the changes.