From 85a7ee18759b41bdb9ab3c77b34822a2b4685c1c Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Mon, 28 Sep 2026 06:21:07 +0300 Subject: [PATCH] Ported `entries` from assembly to lisp. Adding the `strat!` method was required to allow easily copying the result of the getdents64 syscall into a new string so we could add to the list we build/return. This closes #266. --- PRIMITIVES.md | 6 ++-- compiler/template.tmpl | 37 ++++++++++++++++++++ inception.lisp | 1 - stdlib.slisp | 78 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/PRIMITIVES.md b/PRIMITIVES.md index c82f4e5..e94efe3 100644 --- a/PRIMITIVES.md +++ b/PRIMITIVES.md @@ -113,9 +113,6 @@ added to them too. * Return the ASCII character corresponding to the given integer. * `cons` * Add the element to the start of the given (potentially empty) list. - * `entries` - * Return the names of all files in the given directory. - * See [test/entries.lisp](test/entries.lisp) for an example * `environment` * Return a list of all environmental variables. * `exit` @@ -223,6 +220,9 @@ The implementation of these primitives can be found in the file [stdlib.slisp](s * Does the given path exist as a directory? * `even?` * Return 1 if the given number is even, nil otherwise. +* `entries` + * Return the names of all files in the given directory. + * See [test/entries.lisp](test/entries.lisp) for an example * `every` * Does every list item return non-nil when the given function is called? * `exists?` diff --git a/compiler/template.tmpl b/compiler/template.tmpl index cb35bee..e924d1f 100644 --- a/compiler/template.tmpl +++ b/compiler/template.tmpl @@ -2441,6 +2441,43 @@ FUNC fn_sys_strat TAG_INTEGER_REG rax ret +;; Allow updating the Nth byte of the given string. +;; +;; This function is dangerous as it has no bound checking, we could add +;; it since we know the size of all allocations made with alloc.. +;; +;; For the moment it is a workaround for the fact our strings can't have +;; embedded null-characters - if they do "explode" and "substr" don't work +;; any longer. +;; +;; TODO: Add bound-checking. +;; +FUNC fn_sys_stratBANG + mov rbx, rdi ; type-check arg1 + GET_TAG_BITS rbx + cmp rbx, TAG_ID_STRING + jne type_error + mov rbx, rsi ; type-check arg2 + GET_TAG_BITS rbx + cmp rbx, TAG_ID_INTEGER + jne type_error + + UNTAG_REG rdi + UNTAG_REG rsi + UNTAG_REG rdx + + ; RDI points to the string, add the RSI/offset to it + add rdi, rsi + + ; Load and return the character/byte as an integer. + mov byte [rdi], dl + mov rax, rdx + and rax, 0xFF + TAG_INTEGER_REG rax + ret + + + ;; join the two given strings and return a new one. FUNC fn_sys_strcat mov rbx, rdi diff --git a/inception.lisp b/inception.lisp index 631554a..7ad3b6e 100644 --- a/inception.lisp +++ b/inception.lisp @@ -305,7 +305,6 @@ (register-builtin "sys_chr" (lambda (args) (sys_chr (car args)))) (register-builtin "sys_cons" (lambda (args) (sys_cons (car args) (cadr args)))) (register-builtin "sys_divide" (lambda (args) (sys_divide (car args) (cadr args)))) - (register-builtin "sys_entries" (lambda (args) (sys_entries (car args)))) (register-builtin "sys_and" (lambda (args) (sys_and (car args) (cadr args)))) (register-builtin "sys_environment" (lambda (args) (sys_environment))) (register-builtin "sys_exit" (lambda (args) (sys_exit (car args)))) diff --git a/stdlib.slisp b/stdlib.slisp index 5568dc0..5cc1380 100644 --- a/stdlib.slisp +++ b/stdlib.slisp @@ -91,10 +91,6 @@ reap orphaned memory." "Join A and B into a list" (sys_cons a b)) -(defun entries (str) - "Return the names of all filesystem entries beneath the given directory." - (sys_entries str)) - (defun environment () "Return a list of all known environmental variables, and their values." (sys_environment)) @@ -390,6 +386,80 @@ do the right thing for each entry in the given list." (if (= 1 (car res)) t)))) +(defconst SYS_getdents64 217) + +(defconst O_RDONLY 0) +(defconst O_DIRECTORY 65536) + +(defun entries (path) + "Return a list of directory entries beneath PATH. +Return nil on failure. + +The implementation here is either horrid, or awesome. You decide." + + (let ((fd 0) + (size 8192) ; size of the pool we read entries into. + (buf (alloc size)) ; Allocate that pool for the results. + (result nil) + (n 0) + (pos 0)) + + (set! fd (syscall SYS_open path (+ O_RDONLY O_DIRECTORY) 0)) + (if (< fd 0) + nil + + (do + ;; Read a batch of directory entries. + (set! n (syscall SYS_getdents64 fd buf size)) + + (while (> n 0) + + (set! pos 0) + + ;; Walk the linux_dirent64 records. + (while (< pos n) + + ;; offset 16: uint16 d_reclen + ;; offset 18: uint8 d_type + ;; offset 19: char[] d_name + + (let ((reclen (u16 buf (+ pos 16))) + (name-pos (+ pos 19)) + (name-len 0)) + + ;; Find the NUL terminator in d_name. + (while (and + (< name-len (- reclen 19)) + (not (= (u8 buf (+ name-pos name-len)) 0))) + (set! name-len (+ name-len 1))) + + ;; Allocate name + terminating NUL to copy the entry's filename into. + (let ((name (alloc (+ name-len 1))) + (i 0)) + + ;; Copy the name byte-by-byte. + (while (< i name-len) + (sys_strat! name i + (u8 buf (+ name-pos i))) + (set! i (+ i 1))) + + ;; Add the terminating NUL. + (sys_strat! name name-len 0) + + ;; Add name to the result list. + (set! result (cons name result))) + + (set! pos (+ pos reclen)))) + (set! n (syscall SYS_getdents64 fd buf size))) + + (if (< n 0) + (do + (syscall SYS_close fd) + nil) + (do + (syscall SYS_close fd) + result)))))) + (defun exists? (path) "Does the named path exist?" (cons? (stat path)))