From b354036d7f60fe763ba9b96603b18aad63e1e43a Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Sun, 27 Sep 2026 20:17:30 +0300 Subject: [PATCH 1/2] Start implementing `fread` and `fwrite` in lisp This pull-request, once complete, will port the `fread` and `fwrite` primitives from assembly language to lisp. As part of that we first need to expose a memory allocation routine, `alloc`. This is needed to hold the data read from file-handles. Once complete this will close #261: * [ ] Expose `alloc`, and document it. * [ ] Port `fread`. * [ ] Port `fwrite`. --- PRIMITIVES.md | 4 ++++ compiler/template.tmpl | 30 ++++++++++++++++++++++++++++++ inception.lisp | 1 + stdlib.slisp | 9 +++++++++ 4 files changed, 44 insertions(+) diff --git a/PRIMITIVES.md b/PRIMITIVES.md index fece281..f7c5315 100644 --- a/PRIMITIVES.md +++ b/PRIMITIVES.md @@ -101,6 +101,10 @@ added to them too. * (Integer) comparison operations * `<`, `<=`, `>=`, `>`, and `=`. * Other functions implemented in assembly: + * `alloc` + * Allocate N bytes of string memory. + * This is only supposed to be used in conjunction with the `syscall` primitive. + * NOTE: There is no `free`, we rely upon our [garbage collector](GC.md) to free allocated memory. * `car` * Return the first item of a list. * `cdr` diff --git a/compiler/template.tmpl b/compiler/template.tmpl index 7c5cb8b..6c94b88 100644 --- a/compiler/template.tmpl +++ b/compiler/template.tmpl @@ -1306,6 +1306,36 @@ fn_printstr: ;; +;; Allocate (string) memory and return it. +FUNC fn_sys_alloc + mov rbx, rdi + GET_TAG_BITS rbx + cmp rbx, TAG_ID_INTEGER + jne type_error + UNTAG_REG rdi + cmp rdi, 0 ; N must be >= 1 + jle .nil + push rdi + mov rax, rdi + mov rbx, TAG_ID_STRING + call alloc + pop rbx ; restore requested size + push rax ; save new region + +.loop: + mov byte [rax], 42 ; * + inc rax + dec rbx + jnz .loop + + pop rax + TAG_STRING_REG rax + ret +.nil: + STORE_NIL_REG rax + ret + + ;; Get the first item in the cons cell. ;; Note: calling (car nil) will return nil. FUNC fn_sys_car diff --git a/inception.lisp b/inception.lisp index 2ee104f..5ae304c 100644 --- a/inception.lisp +++ b/inception.lisp @@ -299,6 +299,7 @@ (register-builtin "sys_=" (lambda (args) (sys_= (car args) (cadr args)))) (register-builtin "sys_>" (lambda (args) (sys_> (car args) (cadr args)))) (register-builtin "sys_>=" (lambda (args) (sys_>= (car args) (cadr args)))) + (register-builtin "sys_alloc" (lambda (args) (sys_alloc (car args)))) (register-builtin "sys_car" (lambda (args) (sys_car (car args)))) (register-builtin "sys_cdr" (lambda (args) (sys_cdr (car args)))) (register-builtin "sys_chr" (lambda (args) (sys_chr (car args)))) diff --git a/stdlib.slisp b/stdlib.slisp index fe9beac..e575c11 100644 --- a/stdlib.slisp +++ b/stdlib.slisp @@ -66,6 +66,15 @@ "Perform A modulus B" (sys_% a b)) +(defun alloc (n) + "Allocate N bytes of string memory, and fill it with ASCII. + +This is only supposed to be used alongside the `syscall` function. + +NOTE There is no corresponding free function, the garbage collector will +reap orphaned memory." + (sys_alloc n)) + (defun car (xs) "Return the first item from the list" (sys_car xs)) From 05ad34adbb7b21474696cc4d6f8f2478d860a536 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Sun, 27 Sep 2026 20:36:45 +0300 Subject: [PATCH 2/2] Port `fread` and `fwrite` from assembly to lisp. --- PRIMITIVES.md | 12 ++-- compiler/template.tmpl | 128 ----------------------------------------- inception.lisp | 2 - stdlib.slisp | 53 +++++++++++++---- 4 files changed, 47 insertions(+), 148 deletions(-) diff --git a/PRIMITIVES.md b/PRIMITIVES.md index f7c5315..c82f4e5 100644 --- a/PRIMITIVES.md +++ b/PRIMITIVES.md @@ -122,12 +122,6 @@ added to them too. * Terminate execution. * `explode` * Convert the supplied string to a list of characters. - * `fread` - * Read ALL available data from the given handle. - * To simplify usage `fread` will accept a nil-filehandle, and return nil. - * `fwrite` - * Write the given data, with length, to the open file handle. - * To simplify usage `fwrite` will accept a nil-filehandle, and return nil. * `implode` * Convert the given list of characters to a string. * `int` @@ -246,6 +240,12 @@ The implementation of these primitives can be found in the file [stdlib.slisp](s * To simplify usage `fclose` will accept a nil-filehandle. * `fopen` * Open the given filename, for read/write, and return a handle. +* `fread` + * Read ALL available data from the given handle. + * To simplify usage `fread` will accept a nil-filehandle, and return nil. +* `fwrite` + * Write the given data, with length, to the open file handle. + * To simplify usage `fwrite` will accept a nil-filehandle, and return nil. * `getc` * Read a single character from STDIN, return NIL on failure. * `getenv` diff --git a/compiler/template.tmpl b/compiler/template.tmpl index 6c94b88..f59deb5 100644 --- a/compiler/template.tmpl +++ b/compiler/template.tmpl @@ -1608,134 +1608,6 @@ FUNC fn_sys_explode -;; Read ALL data from the given file handle. -;; -;; As a special case reading from a nil-filehandle -;; will return nil. -FUNC fn_sys_fread - mov rbx, rdi ; type-check arg1 - GET_TAG_BITS rbx - cmp rbx, TAG_ID_NIL ; special case the nil-handle - je .fread_return_nil - cmp rbx, TAG_ID_INTEGER - jne type_error - - UNTAG_REG rdi - mov [random_buffer], rdi ; save file handle - - mov rax, SYS_lseek - xor rsi, rsi ; offset = 0 - mov rdx, 2 ; SEEK_END - syscall - - test rax, rax - js .fread_seek_end_fail - - mov rbx, rax ; file size - push rbx ; save size - - ; allocate size+1 bytes - mov rdi, rbx - inc rdi - - mov rax, rdi ; size to allocate - mov rbx, TAG_ID_STRING - call alloc ; Allocate memory, result in RAX - - pop rbx ; restore size - mov r12, rax ; data pointer in r12. - - mov rax, 8 ; seek to start of file - mov rdi,[random_buffer] ; saved file handle - xor rsi, rsi - xor rdx, rdx ; SEEK_SET - syscall - - test rax, rax - js .fread_seek_start_fail - - ; Read until we've consumed the entire file. - xor r13, r13 ; bytes_read_so_far = 0 - -.read_loop: - - cmp r13, rbx ; got all the file? Yay! - je .read_complete - - mov rdi, [random_buffer] ; file handle - lea rsi, [r12+r13] ; destination offset - - mov rdx, rbx - sub rdx, r13 ; remaining bytes - - mov rax, SYS_read - syscall - - test rax, rax ; failed to read - js .fread_read_fail - - test rax, rax - je .fread_short_read ; unexpected EOF - - add r13, rax - jmp .read_loop - -.read_complete: - mov byte [r12+r13], 0 ; null-terminate - mov rax, r12 - TAG_STRING_REG rax - ret - -.fread_short_read: -.fread_read_fail: -.fread_seek_start_fail: -.fread_seek_end_fail: -.fread_return_nil: - STORE_NIL_REG rax - ret - - - - - -;; Write data, with a size, to the given file handle -;; e.g. (fwrite handle "Steve" 5) -;; -;; As a special case writing to a nil-filehandle -;; will return nil. -FUNC fn_sys_fwrite - mov rbx, rdi ; type-check arg1: int - GET_TAG_BITS rbx - cmp rbx, TAG_ID_NIL ; special case the nil-handle - je .fwrite_return_nil - cmp rbx, TAG_ID_INTEGER - jne type_error - - mov rbx, rsi ; type-check arg2: str - GET_TAG_BITS rbx - cmp rbx, TAG_ID_STRING - jne type_error - - mov rbx, rdx ; type-check arg3: int - GET_TAG_BITS rbx - cmp rbx, TAG_ID_INTEGER - jne type_error - - UNTAG_REG rsi - UNTAG_REG rdi - UNTAG_REG rdx - - mov rax, SYS_write - syscall - TAG_INTEGER_REG rax - ret - -.fwrite_return_nil: - STORE_NIL_REG rax - ret - - - ;; join a character list to a string. FUNC fn_sys_implode mov rbx,rdi diff --git a/inception.lisp b/inception.lisp index 5ae304c..631554a 100644 --- a/inception.lisp +++ b/inception.lisp @@ -310,8 +310,6 @@ (register-builtin "sys_environment" (lambda (args) (sys_environment))) (register-builtin "sys_exit" (lambda (args) (sys_exit (car args)))) (register-builtin "sys_explode" (lambda (args) (sys_explode (car args)))) - (register-builtin "sys_fread" (lambda (args) (sys_fread (car args)))) - (register-builtin "sys_fwrite" (lambda (args) (sys_fwrite (car args) (cadr args) (caddr args)))) (register-builtin "sys_implode" (lambda (args) (sys_implode (car args)))) (register-builtin "sys_int" (lambda (args) (sys_int (car args)))) (register-builtin "sys_isqrt" (lambda (args) (sys_isqrt (car args)))) diff --git a/stdlib.slisp b/stdlib.slisp index e575c11..5568dc0 100644 --- a/stdlib.slisp +++ b/stdlib.slisp @@ -107,18 +107,6 @@ reap orphaned memory." "Convert a string to a list of characters." (sys_explode str)) -(defun fread (handle) - "Read all available data from the given file-handle. - -It is assumed the filehandle came from fopen, but nil is also accepted." - (sys_fread handle)) - -(defun fwrite (handle data len) - "Write the given data, of length LEN, to the specified file-handle. - -It is assumed the filehandle came from fopen, but nil is also accepted." - (sys_fwrite handle data len)) - (defun implode (xs) "Convert a list of characters to a string." (sys_implode xs)) @@ -440,6 +428,47 @@ Mode is \"r\" for read, \"w\" for write, and \"a\" for append." handle nil))) +(defconst SYS_lseek 8) +(defconst SEEK_END 2) + +(defconst SEEK_START 0) + +(defun fread (handle) + "Read all available data from the given file-handle. + +It is assumed the filehandle came from fopen, but nil is also accepted." + (if (nil? handle) + nil + (let ((size 0)) + ; seek to end of handle to get size + (set! size (syscall SYS_lseek handle 0 SEEK_END)) + (if (< size 0) + nil + (do + ; seek back to the start + (syscall SYS_lseek handle 0 SEEK_START) + + ; allocate memory and read into it + (let ((mem (alloc size)) + (res (syscall SYS_read handle mem size))) + (if (< res 0) + nil + mem))))))) + +(defconst SYS_write 1) + +(defun fwrite (handle data len) + "Write the given data, of length LEN, to the specified file-handle. + +It is assumed the filehandle came from fopen, but nil is also accepted." + (if (nil? handle) + nil + (let ((res (syscall SYS_write handle data len))) + (if (> res 0) + res + nil)))) + + (defun file? (path) "Is the given path a file?" (let ((res (stat path)))