Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Lisp with Advent of Code 2023 🎄 #556

Merged
merged 32 commits into from
Dec 17, 2023
Merged

Conversation

vinc
Copy link
Owner

@vinc vinc commented Dec 1, 2023

This year again I'll do the easiest parts of the Advent of Code in Lisp to improve the language with practical examples.

  • Add \b and \e escape sequences
  • Add dict type (implemented with BTreeMap)
  • Add empty? function to test empty strings
  • Add reject function as the inverse of filter
  • Add put function to insert an element into a dict, list, and string
  • Add push function to insert an element at the end of a list or string
  • Add host function to resolve hostnames into IP addresses
  • Rename system function to shell (aliased to sh)
  • Rename nth function to get and make it work with dict
  • Load (load "/lib/lisp/core.lsp") in REPL automatically
  • Test for truthiness in conditions of if and while
  • Update doc

@vinc
Copy link
Owner Author

vinc commented Dec 1, 2023

Day 1 of AoC 2023

Part 1:

(load "/lib/lisp/core.lsp")

(def (contains? x lst)
  (if (nil? lst) false
    (if (eq? x (head lst)) true
      (contains? c (tail lst)))))

(def (two-digits line) (do
  (var digits
    (filter
      (fun (c)
        (contains? c (chars "0123456789")))
      (chars line)))
  (str->num (str.join (list (first digits) (last digits)) ""))))

(print (reduce +
  (map two-digits (lines (read (first args))))))

The contains? function will be included in the core library. Most lisp dialects call it member but modern languages tend to use contains so I went with this name.

Edit: I forgot about it but we already had it, it was just missing from the doc

@vinc vinc changed the title Advent of Code 2023 in Lisp Advent of Code 2023 in Lisp 🎄 Dec 1, 2023
@vinc vinc changed the title Advent of Code 2023 in Lisp 🎄 Improve Lisp with Advent of Code 2023 🎄 Dec 1, 2023
@vinc
Copy link
Owner Author

vinc commented Dec 1, 2023

We can boot directly into the Lisp interpreter by removing the login and the shell in /ini/boot.sh and adding lisp at the end:

vga set palette "/ini/palettes/gruvbox-dark.csv"
vga set font "/ini/fonts/zap-light-8x16.psf"
# read "/ini/banner.txt"
# user login
# shell
lisp

For the AoC we can also increase the memory from 32MB to 256MB:

$ make image output=serial keyboard=dvorak memory=256 && make qemu output=serial nic=rtl8139 kvm=true memory=256
touch src/lib.rs
env | grep MOROS
MOROS_MEMORY=256
MOROS_VERSION=0.10.1-24-g67e890d
MOROS_KEYBOARD=dvorak
cargo bootimage --no-default-features --features serial --bin moros --release
WARNING: `CARGO_MANIFEST_DIR` env variable not set
Building kernel
   Compiling moros v0.10.1 (/home/v/src/vinc/moros)
    Finished release [optimized] target(s) in 9.69s
Building bootloader
   Compiling bootloader v0.9.23 (/home/v/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bootloader-0.9.23)
    Finished release [optimized + debuginfo] target(s) in 1.39s
Created bootimage for `moros` at `/home/v/src/vinc/moros/target/x86_64-moros/release/bootimage-moros.bin`
dd conv=notrunc if=target/x86_64-moros/release/bootimage-moros.bin of=disk.img
3847+0 records in
3847+0 records out
1969664 bytes (2.0 MB, 1.9 MiB) copied, 0.0189267 s, 104 MB/s
qemu-system-x86_64 -m 256 -drive file=disk.img,format=raw -audiodev sdl,id=a0 -machine pcspk-audiodev=a0 -netdev user,id=e0,hostfwd=tcp::8080-:80 -device rtl8139,netdev=e0 -cpu host -accel kvm -display none -chardev stdio,id=s0,signal=off -serial chardev:s0
[0.250962] MOROS v0.10.1-24-g67e890d
[0.252961] MEM [0x00000000000000-0x00000000000FFF] FrameZero
[0.252961] MEM [0x00000000001000-0x00000000004FFF] PageTable
[0.252961] MEM [0x00000000005000-0x00000000016FFF] Bootloader
[0.252961] MEM [0x00000000017000-0x00000000017FFF] BootInfo
[0.252961] MEM [0x00000000018000-0x0000000002EFFF] Kernel
[0.252961] MEM [0x0000000002F000-0x0000000009EFFF] KernelStack
[0.252961] MEM [0x0000000009F000-0x0000000009FFFF] Reserved
[0.252961] MEM [0x000000000F0000-0x000000000FFFFF] Reserved
[0.252961] MEM [0x00000000100000-0x0000000028FFFF] KernelStack
[0.252961] MEM [0x00000000290000-0x000000003FFFFF] Usable
[0.252961] MEM [0x00000000400000-0x000000005D2FFF] Kernel
[0.252961] MEM [0x000000005D3000-0x000000005E2FFF] PageTable
[0.252961] MEM [0x000000005E3000-0x0000000FFDFFFF] Usable
[0.252961] MEM [0x0000000FFE0000-0x0000000FFFFFFF] Reserved
[0.252961] MEM [0x000000FEFFC000-0x000000FEFFFFFF] Reserved
[0.252961] MEM [0x000000FFFC0000-0x000000FFFFFFFF] Reserved
[0.252961] MEM 262096 KB
[0.254961] CPU GenuineIntel
[0.257961] CPU Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
[0.259960] CPU 0 MHz
[0.286956] PCI 0000:00:00 [8086:1237]
[0.314952] PCI 0000:01:00 [8086:7000]
[0.343948] PCI 0000:01:01 [8086:7010]
[0.371943] PCI 0000:01:03 [8086:7113]
[0.405938] PCI 0000:02:00 [1234:1111]
[0.434934] PCI 0000:03:00 [10EC:8139]
[3.764427] NET RTL8139 MAC 52-54-00-12-34-56
[3.770426] ATA 0:0 QEMU HARDDISK QM00001 (32 MB)
[3.773425] MFS Superblock found in ATA 0:0
[3.777425] RTC 2023-12-01 20:44:06 +0000
MOROS Lisp v0.6.0

> (load "/lib/lisp/core.lsp")
true

> (print "Hello, World")
Hello, World
()

>

@vinc
Copy link
Owner Author

vinc commented Dec 1, 2023

Screenshot of the interpreter that we are about to leave for the editor:

lisp-boot

@vinc
Copy link
Owner Author

vinc commented Dec 2, 2023

I used a dictionary to solve both parts of the second day challenge in Ruby (https://github.com/vinc/advent-of-code/tree/master/ruby/2023/02) but we don't have that in MOROS Lisp yet so I will not complete today's challenge:

(var line "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green")
(var games (str.split (second (str.split line ": ")) "; "))
(map (fun (game) (map words (str.split game ", "))) games)

@vinc
Copy link
Owner Author

vinc commented Dec 3, 2023

Typing (load "/lib/lisp/core.lsp") every time we open the interpreter gets old after a while so before going into REPL mode we'll execute /ini/lisp.lsp if it exists. By default this file will load the core library.

One could also use it to load /lib/lisp/legacy.lsp automatically to have a syntax more compatible with other Lisp dialects.

@vinc
Copy link
Owner Author

vinc commented Dec 4, 2023

Day 4 of AoC 2023

Part 1:

(load "/lib/lisp/core.lsp")

(def (aoc-count line) (do
  (var nums (str.split (second (str.split line ": ")) " | "))
  (var win (reject empty? (words (nth nums 0))))
  (var own (reject empty? (words (nth nums 1))))
  (var n (len (intersection win own)))
  (if (> n 0) (^ 2 (- n 1)) 0)))

(print (reduce + (map aoc-count (lines (read (first args))))))

I added empty? and reject to the language to be able to write (reject empty? (words text)) instead of (filter (fun (x) (not (eq? x ""))) (words text).

@vinc
Copy link
Owner Author

vinc commented Dec 5, 2023

Day 2 of AoC 2023

Part 1:

(load "/lib/lisp/core.lsp")

(var puzzle (dict "red" 12 "green" 13 "blue" 14))

(def (aoc-cube s)
  (<= (str->num (first (words s))) (get puzzle (second (words s)))))

(def (aoc-cubes s)
  (map aoc-cube (str.split s ", ")))

(def (aoc-game s) (do
  (var game (str.split s ": "))
  (var id (str->num (second (words (first game)))))
  (var res (map aoc-cubes (str.split (second game) "; ")))
  (if (contains? (reduce concat res) false) 0 id)))

(print (reduce +
  (map aoc-game (lines (read (first args))))))

I added a dict type implemented with a BTreeMap in Rust to have a dictionnary with two new functions (get d k) and (put d k v).

Those functions can also work with lists and strings where get is replacing nth.

I'll change the signature of put with them. Right now it is (put l v) or (put s v) to put a value at the end of a list or a string, but this should be push and put would become (put l i v) or (put s i v) to put the value at the given index in the list or the string.

@vinc
Copy link
Owner Author

vinc commented Dec 5, 2023

A few examples:

(get "Hello" 0)                     # => "H"
(get "Hello" 6)                     # => ()
(get (list 1 2 3) 0)                # => 1
(get (list 1 2 3) 3)                # => ()
(get (dict "a" 1 "b" 2 "c" 3) "a")  # => 1
(get (dict "a" 1 "b" 2 "c" 3) "d")  # => ()

(put "Heo" 2 "ll")                  # => "Hello"
(put (list 1 3) 1 2)                # => (1 2 3)
(put (dict "a" 1 "b" 2) "c" 3)      # => (dict "a" 1 "b" 2 "c" 3)

(push "Hell" "o")                   # => "Hello"
(push (list 1 2) 3)                 # => (1 2 3)

@vinc
Copy link
Owner Author

vinc commented Dec 9, 2023

Add \e and \b escape sequences to the parser:

lisp-escape

@vinc
Copy link
Owner Author

vinc commented Dec 9, 2023

Add host function to resolve hostnames into IP addresses:

lisp-host

@vinc
Copy link
Owner Author

vinc commented Dec 9, 2023

Previously we were comparing the condition of if and while with true so (if 42 1 2) would return 2. But most Lisp dialects and dynamic languages like Ruby would return 1. Now we are checking for the truthiness of the condition, that is we are checking if it neither false nor nil.

> (if true 1 2)
1

> (if false 1 2)
2

> (if nil 1 2)
2

> (if 0 1 2)
1

> (if 42 1 2)
1

The macro or also changed to return the first expression that is truthy instead of true, which is often very useful:

> (or nil 42)
42

@vinc
Copy link
Owner Author

vinc commented Dec 9, 2023

The previous commits are now allowing us to do this:

~
> lisp /tmp/lisp/ntp.lsp time.cloudflare.com
1702165530

Instead of this:

~
> host time.cloudflare.com
162.159.200.123

~
> lisp /tmp/lisp/ntp.lsp 162.159.200.123
1702165661

@vinc vinc marked this pull request as ready for review December 13, 2023 18:41
@vinc
Copy link
Owner Author

vinc commented Dec 15, 2023

With the latest commit we get this:

> "a\bcd\e"
"a\bcd\e"

Instead of that:

> "a\bcd\e"
"a\u{8}cd\u{1b}"

Which is the default because \b and \e are not supported by Rust.

@vinc
Copy link
Owner Author

vinc commented Dec 15, 2023

Functions in a namespace like open in file will now use a slash instead of a dot as separator, so instead of file.open we will now use file/open. This is less common but more in line with the file hierarchy in the system.

@vinc
Copy link
Owner Author

vinc commented Dec 15, 2023

A new script will document the functions available:

~
> lisp /tmp/lisp/doc.lsp
(% args)
(* args)
(+ args)
(- args)
(/ args)
(< args)
(<< args)
(<= args)
(= args)
(> args)
(>= args)
(>> args)
(^ args)
(abs x)
(acos args)
(and x y)
(append path text) # Append text to file
(append-binary path data) # Append binary to file
(asin args)
(atan args)
(bin->num args)
(bin->str args)
(binary->number args)
(binary->string args)
(bool? x)
(boolean? x)
(chars text) # Split text into a list of chars
(chunks args)
(concat args)
(contains? args)
(cos args)
(def args)
(def-fun args)
(def-mac args)
(dict args)
(empty? x)
(eq? args)
(file/close args)
(file/open args)
(file/read args)
(file/size args)
(file/write args)
(filter f ls) # Filter the elements of the list with the function
(first ls)
(fun args)
(fun? x)
(function? x)
(get args)
(help args)
(host args)
(intersection a b) # Return elements found in both lists
(last ls)
(len args)
(length args)
(let params values body)
(lines text) # Split text into a list of lines
(list args)
(list? x)
(mac args)
(mac? x)
(macro? x)
(map f ls) # Apply the function to the elements of the list
(max ls) # Return the maximum element of the list
(min ls) # Return the minimum element of the list
(mod a b)
(nil? x)
(not x)
(num->bin args)
(num/type args)
(num? x)
(number->binary args)
(number/type args)
(number? x)
(or x y)
(p exp) # Print expression to the console
(parse args)
(print exp) # Print expression to the console with a newline
(print-doc f)
(push ls x) # Push element to the end of the list
(put args)
(range start stop) # Return a list of integers from start to stop excluded
(read path) # Read text file
(read-binary path) # Read binary file
(read-char) # Read char from the console
(read-line) # Read line from the console
(realtime)
(reduce f ls) # Reduce the elements of the list with the function
(regex/find args)
(regex/match? r s) # Return true if the string match the pattern
(reject f ls) # Reject the elements of the list with the function
(rem args)
(rest args)
(rev ls) # Reverse list
(reverse ls) # Reverse list
(second ls)
(sh args)
(shell args)
(sin args)
(slice args)
(socket/accept args)
(socket/connect args)
(socket/listen args)
(sort args)
(str args)
(str->bin args)
(str->num args)
(str/join ls s) # Join the elements of the list with the string
(str/split args)
(str/trim args)
(str? x)
(string args)
(string->binary args)
(string->number args)
(string/join ls s) # Join the elements of the list with the string
(string/split args)
(string/trim args)
(string? x)
(sym? x)
(symbol? x)
(tan args)
(third ls)
(trunc args)
(type args)
(uniq args)
(unique args)
(uptime)
(var args)
(words text) # Split text into a list of words
(write path text) # Write text to file
(write-binary path data) # Write binary to file

@vinc
Copy link
Owner Author

vinc commented Dec 15, 2023

The script has a colored output:

lisp-doc

@vinc
Copy link
Owner Author

vinc commented Dec 16, 2023

The PR is getting ready to be merged

lisp-examples

@vinc vinc merged commit 9f52599 into trunk Dec 17, 2023
1 check passed
@vinc vinc deleted the feature/adventofcode-2023 branch December 17, 2023 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant