Compare commits

..

31 Commits

Author SHA1 Message Date
Aryadev Chavali
4aad62fec9 alisp.org: update 2026-02-11 10:29:57 +00:00
Aryadev Chavali
a4fb48a863 reader: implement read_vec and setup errors for random closed brackets 2026-02-11 10:29:57 +00:00
Aryadev Chavali
5f05a6a108 lisp: implement TAG_VEC lisp_print method 2026-02-11 10:29:57 +00:00
Aryadev Chavali
e60a7459e0 reader: fix issue with read_list of infinite loop 2026-02-11 10:29:57 +00:00
Aryadev Chavali
4936460b39 reader: factor out read_negative 2026-02-11 10:29:57 +00:00
Aryadev Chavali
04be0ecad0 alisp.org: update todos 2026-02-11 10:29:57 +00:00
Aryadev Chavali
f09e0d33a4 reader: deal with negative prefix numbers in read 2026-02-11 10:29:57 +00:00
Aryadev Chavali
d1d0783fc3 reader: implement read_int 2026-02-11 10:29:57 +00:00
Aryadev Chavali
21254e77bf tag: INT_MAX/INT_MIN are now i64 by default 2026-02-11 10:29:57 +00:00
Aryadev Chavali
e772b06ae5 main: rearrange code and setup prototypes 2026-02-11 10:29:57 +00:00
Aryadev Chavali
eca46069f8 main: print out expressions from reading 2026-02-11 10:29:57 +00:00
Aryadev Chavali
c2f1835b4c main: factor out the stream init code into its own function 2026-02-11 10:29:57 +00:00
Aryadev Chavali
93a52db7cc alisp.org: add TODO about reader macros 2026-02-11 10:29:57 +00:00
Aryadev Chavali
d88bc2baeb alisp.org: mark some TODOs 2026-02-11 10:29:57 +00:00
Aryadev Chavali
2dc0c6080e reader: deal with quotes
This is currently implemented as a parse-time primitive.  Scheme
doesn't seem to have reader macros (which is INSANE) but Common Lisp
does.  We'll need to add a TODO about this.
2026-02-11 10:29:57 +00:00
Aryadev Chavali
8e81e6f762 reader: at every iteration of read_all, skip comments and whitespace 2026-02-11 10:29:57 +00:00
Aryadev Chavali
a49492b27f stream: clean up 2026-02-11 10:29:57 +00:00
Aryadev Chavali
8b2fe97fc2 lisp: sys_cost for memory footprint 2026-02-11 10:29:57 +00:00
Aryadev Chavali
ff512986f8 symtable: sym_table_cost
Useful for figuring out the rough memory footprint (actually utilised)
by the symbol table.
2026-02-11 10:29:57 +00:00
Aryadev Chavali
47f33cb969 tag: make function inputs constant 2026-02-11 10:29:57 +00:00
Aryadev Chavali
39e5b76f8b lisp: lisp_print 2026-02-11 10:29:57 +00:00
Aryadev Chavali
b91e79933b symtable: refactor for SV changes and propagate 2026-02-11 10:29:57 +00:00
Aryadev Chavali
3e7734037c sv: rearrange sv_substr and sv_truncate 2026-02-11 10:29:57 +00:00
Aryadev Chavali
9f3bb57972 sv: major refactor
- Internal data pointer is read only by default => any uses that
  require use of the pointer itself (freeing, mutating) must perform a
  cast.

- refactor tests to use some new sv macros and functions, and clean
  them up.

- slight cleanup of sv.c
2026-02-11 10:29:57 +00:00
Aryadev Chavali
b646ae3f7e sv: fix sv_substr
Issue I came up with when looking at the code, based on
sv_chop_right's impl.  Why do we keep this function around again?
2026-02-11 10:29:57 +00:00
Aryadev Chavali
818d4da850 sv: SV_AUTO macro (for literal strings/literal byte arrays really). 2026-02-11 10:29:57 +00:00
Aryadev Chavali
daa1d3d565 lisp: add and implement lisp_free_rec
May be useful for testing.
2026-02-11 10:29:57 +00:00
Aryadev Chavali
b7fc5170b0 reader: implement read_sym and read_list
To be tested properly.
2026-02-11 10:29:57 +00:00
Aryadev Chavali
d02174ea8b Makefile: add reader to units to compile 2026-02-11 10:29:57 +00:00
Aryadev Chavali
7ef6905d7a main: fit reader into main. 2026-02-11 10:29:57 +00:00
Aryadev Chavali
c12f4b2d2c reader: some work done on basic API 2026-02-11 10:29:57 +00:00
18 changed files with 523 additions and 85 deletions

View File

@@ -19,7 +19,7 @@ CFLAGS=$(GFLAGS) $(DFLAGS) -DTEST_VERBOSE=1
endif
# Units to compile
UNITS=src/sv.c src/vec.c src/stream.c src/symtable.c src/tag.c src/lisp.c
UNITS=src/sv.c src/vec.c src/stream.c src/symtable.c src/tag.c src/lisp.c src/reader.c
OBJECTS:=$(patsubst src/%.c, $(DIST)/%.o, $(UNITS))
TEST_UNITS=test/main.c

View File

@@ -4,7 +4,7 @@
#+filetags: :alisp:
* Tasks
** WIP Reader system
** Reader system :reader:
We need to design a reader system. The big idea: given a "stream" of
data, we can break out expressions from it. An expression could be
either an atomic value or a container.
@@ -45,13 +45,15 @@ i.e. no parsing.
*** DONE Design what a "parser function" would look like
The general function is something like ~stream -> T | Err~. What
other state do we need to encode?
*** TODO Write a parser for integers
*** TODO Write a parser for symbols
*** TODO Write a parser for lists
*** TODO Write a parser for vectors
*** TODO Write the general parser
*** DONE Write a parser for integers
*** DONE Write a parser for symbols
*** DONE Write a parser for lists
*** DONE Write a parser for vectors
*** TODO Write a parser for strings
Requires [[*Design Strings for the Lisp]] to be complete first.
*** WIP Write the general parser
** Unit tests :tests:
*** TODO Test streams
*** TODO Test streams :streams:
**** DONE Test file init
[[file:test/test_stream.c::void stream_test_file(void)]]
***** DONE Test successful init from real files
@@ -97,10 +99,11 @@ Also ensure stream_eoc is false.
- line_col on bad stream (no effect on args)
- line_col on eoc stream (should go right to the end)
- line_col on random points in a stream
*** TODO Test reader :reader:
*** DONE Test system registration of allocated units
In particular, does clean up work as we expect? Do we have situations
where we may double free or not clean up something we should've?
** String views :sv_t:
** String views :strings:
[[file:include/alisp/sv.h::/// String Views]]
*** DONE sv_substr
Takes an index and a size, returns a string view to that substring.
@@ -109,12 +112,12 @@ Super obvious.
*** TODO Design Strings for the Lisp :api:
We have ~sv_t~ so our basic C API is done. We just need pluggable
functions to construct and deconstruct strings as lisps.
** Backlog
** Design :design:
*** TODO Design Big Integers :api:
We currently have 62 bit integers implemented via immediate values
embedded in a pointer. We need to be able to support even _bigger_
integers. How do we do this?
*** TODO Design garbage collection scheme :design:gc:
*** TODO Design garbage collection scheme :gc:
Really, regardless of what I do, we need to have some kind of garbage
collection header on whatever managed objects we allocate.
@@ -198,10 +201,16 @@ Latter approach time complexity:
Former approach is better time complexity wise, but latter is way
better in terms of simplicity of code. Must deliberate.
*** TODO Capitalise symbols (TBD) :optimisation:design:
*** TODO Capitalise symbols (TBD) :optimisation:
Should we capitalise symbols? This way, we limit the symbol table's
possible options a bit (potentially we could design a better hashing
algorithm?) and it would be kinda like an actual Lisp.
*** TODO Consider reader macros :reader:
Common Lisp has so-called "reader macros" which allows users to write
Lisp code that affects further Lisp code reading. It's quite
powerful.
Scheme doesn't have it. Should we implement this?
** Completed
*** DONE Test value constructors and destructors :test:
Test if ~make_int~ works with ~as_int,~ ~intern~ with ~as_sym~.

View File

@@ -8,6 +8,8 @@
#ifndef LISP_H
#define LISP_H
#include <stdio.h>
#include <alisp/symtable.h>
#include <alisp/vec.h>
@@ -32,6 +34,9 @@ void sys_init(sys_t *);
void sys_register(sys_t *, lisp_t *);
void sys_free(sys_t *);
// Debugging function: provides total memory usage from system.
u64 sys_cost(sys_t *);
/// Constructors and destructors
lisp_t *make_int(i64);
lisp_t *make_vec(sys_t *, u64);
@@ -50,6 +55,9 @@ lisp_t *car(lisp_t *);
lisp_t *cdr(lisp_t *);
void lisp_free(lisp_t *);
void lisp_free_rec(lisp_t *);
void lisp_print(FILE *, lisp_t *);
#endif

View File

@@ -15,6 +15,10 @@ typedef enum
{
READ_ERR_OK = 0,
READ_ERR_EOF,
READ_ERR_EXPECTED_CLOSED_BRACE,
READ_ERR_EXPECTED_CLOSED_SQUARE_BRACKET,
READ_ERR_UNEXPECTED_CLOSED_BRACE,
READ_ERR_UNEXPECTED_CLOSED_SQUARE_BRACKET,
READ_ERR_UNKNOWN_CHAR,
} read_err_t;

View File

@@ -14,11 +14,12 @@
typedef struct
{
u64 size;
char *data;
const char *data;
} sv_t;
// String view macro constructor
#define SV(DATA, SIZE) ((sv_t){.data = (DATA), .size = (SIZE)})
#define SV_AUTO(DATA) ((sv_t){.data = (void *)(DATA), .size = sizeof(DATA) - 1})
// Pretty printers
#define SV_FMT(SV) (int)(SV).size, (SV).data
#define PR_SV "%.*s"
@@ -28,8 +29,8 @@ typedef struct
sv_t sv_copy(sv_t);
sv_t sv_chop_left(sv_t, u64 size);
sv_t sv_chop_right(sv_t, u64 size);
sv_t sv_substr(sv_t, u64 position, u64 size);
sv_t sv_truncate(sv_t, u64 newsize);
sv_t sv_substr(sv_t, u64 position, u64 size);
sv_t sv_till(sv_t, const char *reject);
sv_t sv_while(sv_t, const char *accept);

View File

@@ -21,9 +21,12 @@ typedef struct
#define SYM_TABLE_INIT_SIZE (1 << 10)
void sym_table_init(sym_table_t *);
char *sym_table_find(sym_table_t *, sv_t);
const char *sym_table_find(sym_table_t *, sv_t);
void sym_table_free(sym_table_t *);
// Debugging function: total memory used by symbol table.
u64 sym_table_cost(sym_table_t *);
#endif
/* Copyright (C) 2026 Aryadev Chavali

View File

@@ -43,14 +43,14 @@ enum Mask
#define IS_TAG(PTR, TYPE) (((u64)(PTR) & MASK_##TYPE) == TAG_##TYPE)
#define UNTAG(PTR, TYPE) (((u64)PTR) >> SHIFT_##TYPE)
#define INT_MAX ((1L << 62) - 1)
#define INT_MIN (-(1L << 62))
#define INT_MAX ((((i64)1) << 62) - 1)
#define INT_MIN (-(((i64)1) << 62))
tag_t get_tag(lisp_t *);
lisp_t *tag_int(i64);
lisp_t *tag_sym(char *);
lisp_t *tag_cons(cons_t *);
lisp_t *tag_vec(vec_t *);
tag_t get_tag(const lisp_t *);
lisp_t *tag_int(const i64);
lisp_t *tag_sym(const char *);
lisp_t *tag_cons(const cons_t *);
lisp_t *tag_vec(const vec_t *);
#endif

View File

@@ -22,6 +22,11 @@ void sys_register(sys_t *sys, lisp_t *ptr)
vec_append(&sys->memory, &ptr, sizeof(&ptr));
}
u64 sys_cost(sys_t *sys)
{
return sym_table_cost(&sys->symtable) + sys->memory.capacity;
}
void sys_free(sys_t *sys)
{
static_assert(NUM_TAGS == 5);
@@ -71,7 +76,7 @@ lisp_t *make_vec(sys_t *sys, u64 capacity)
lisp_t *intern(sys_t *sys, sv_t sv)
{
char *str = sym_table_find(&sys->symtable, sv);
const char *str = sym_table_find(&sys->symtable, sv);
return tag_sym(str);
}
@@ -115,6 +120,135 @@ void lisp_free(lisp_t *item)
}
}
void lisp_free_rec(lisp_t *item)
{
switch (get_tag(item))
{
case TAG_CONS:
{
lisp_free_rec(car(item));
lisp_free_rec(cdr(item));
free(as_cons(item));
break;
}
case TAG_VEC:
{
vec_t *vec = as_vec(item);
for (size_t i = 0; i < VEC_SIZE(vec, lisp_t **); ++i)
{
lisp_t *allocated = VEC_GET(vec, i, lisp_t *);
lisp_free_rec(allocated);
}
vec_free(vec);
free(vec);
break;
}
case TAG_NIL:
case TAG_INT:
case TAG_SYM:
case NUM_TAGS:
// shouldn't be dealt with (either constant or dealt with elsewhere)
break;
}
}
void lisp_print(FILE *fp, lisp_t *lisp)
{
if (!fp)
return;
switch (get_tag(lisp))
{
case TAG_NIL:
fprintf(fp, "NIL");
break;
case TAG_INT:
#if VERBOSE_LOGS
fprintf(fp, "INT[");
#endif
fprintf(fp, "%ld", as_int(lisp));
#if VERBOSE_LOGS
fprintf(fp, "]");
#endif
break;
case TAG_SYM:
#if VERBOSE_LOGS
fprintf(fp, "SYM[");
#endif
fprintf(fp, "%s", as_sym(lisp));
#if VERBOSE_LOGS
fprintf(fp, "]");
#endif
break;
case TAG_CONS:
{
#if VERBOSE_LOGS
fprintf(fp, "LIST[");
#else
fprintf(fp, "(");
#endif
for (; lisp; lisp = CDR(lisp))
{
if (IS_TAG(lisp, CONS))
{
lisp_t *car = CAR(lisp);
lisp_t *cdr = CDR(lisp);
lisp_print(fp, car);
if (cdr && !IS_TAG(cdr, CONS))
{
fprintf(fp, " . ");
}
else if (cdr)
{
fprintf(fp, " ");
}
}
else
{
lisp_print(fp, lisp);
break;
}
}
#if VERBOSE_LOGS
fprintf(fp, "]");
#else
fprintf(fp, ")");
#endif
break;
}
case TAG_VEC:
{
#if VERBOSE_LOGS
fprintf(fp, "VEC[");
#else
fprintf(fp, "[");
#endif
vec_t *vec = as_vec(lisp);
for (u64 i = 1; i <= VEC_SIZE(vec, lisp_t *); ++i)
{
lisp_t *item = VEC_GET(vec, i - 1, lisp_t *);
lisp_print(fp, item);
if (i != VEC_SIZE(vec, lisp_t *))
{
fprintf(fp, " ");
}
}
#if VERBOSE_LOGS
fprintf(fp, "]");
#else
fprintf(fp, "]");
#endif
break;
}
case NUM_TAGS:
default:
FAIL("Unreachable");
break;
}
}
/* Copyright (C) 2025, 2026 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but WITHOUT

View File

@@ -11,27 +11,66 @@
#include <alisp/alisp.h>
void usage(FILE *fp)
{
fprintf(fp, "Usage: alisp [OPTIONS...] FILE\n"
"Options:\n"
"\t--help Print this usage and exit.\n"
"File:\n"
"\t<filename> Read and interpret this file from filesystem.\n"
"\t-- Read and interpret from stdin using an EOF.\n");
}
void usage(FILE *fp);
int init_stream_on_args(int argc, char *argv[], FILE **pipe, stream_t *stream);
int main(int argc, char *argv[])
{
int ret = 0;
FILE *pipe = NULL;
stream_t stream = {0};
vec_t ast = {0};
sys_t sys = {0};
ret = init_stream_on_args(argc, argv, &pipe, &stream);
if (ret)
goto end;
LOG("[INFO]: Initialised stream for `%s`\n", stream.name);
{
read_err_t err = read_all(&sys, &stream, &ast);
if (err)
{
u64 line = 0, col = 0;
stream_line_col(&stream, &line, &col);
fprintf(stderr, "%s:%lu:%lu: ERROR: %s\n", stream.name, line, col,
read_err_to_cstr(err));
ret = 1;
goto end;
}
}
LOG("[INFO]: Utilised %lu bytes in parsing\n", sys_cost(&sys));
LOG("[INFO]: Parsed %lu %s\n", VEC_SIZE(&ast, lisp_t *),
VEC_SIZE(&ast, lisp_t *) == 1 ? "expr" : "exprs");
{
for (u64 i = 0; i < VEC_SIZE(&ast, lisp_t *); ++i)
{
lisp_t *expr = VEC_GET(&ast, i, lisp_t *);
#if VERBOSE_LOGS
printf("\t[%lu]: ", i);
lisp_print(stdout, expr);
printf("\n");
#endif
}
}
end:
sys_free(&sys);
vec_free(&ast);
stream_free(&stream);
if (pipe)
fclose(pipe);
return ret;
}
int init_stream_on_args(int argc, char *argv[], FILE **pipe, stream_t *stream)
{
if (argc == 1)
{
usage(stderr);
ret = 1;
goto end;
return 1;
}
else if (argc != 2)
{
@@ -40,39 +79,42 @@ int main(int argc, char *argv[])
if (strncmp(argv[1], "--", 2) == 0)
{
stream_err_t err = stream_init_pipe(&stream, "stdin", stdin);
stream_err_t err = stream_init_pipe(stream, "stdin", stdin);
if (err)
{
fprintf(stderr, "ERROR: %s from `%s`\n", stream_err_to_cstr(err),
argv[1]);
ret = 1;
goto end;
return 1;
}
}
else if (strncmp(argv[1], "--help", 6) == 0)
{
usage(stdout);
goto end;
return 0;
}
else
{
pipe = fopen(argv[1], "rb");
stream_err_t err = stream_init_file(&stream, argv[1], pipe);
*pipe = fopen(argv[1], "rb");
stream_err_t err = stream_init_file(stream, argv[1], *pipe);
if (err)
{
fprintf(stderr, "ERROR: %s from `%s`\n", stream_err_to_cstr(err),
argv[1]);
ret = 1;
goto end;
return 1;
}
}
LOG("[INFO]: Initialised stream for `%s`\n", stream.name);
end:
stream_stop(&stream);
if (pipe)
fclose(pipe);
return ret;
return 0;
}
void usage(FILE *fp)
{
fprintf(fp, "Usage: alisp [OPTIONS...] FILE\n"
"Options:\n"
"\t--help Print this usage and exit.\n"
"File:\n"
"\t<filename> Read and interpret this file from filesystem.\n"
"\t-- Read and interpret from stdin using an EOF.\n");
}
/* Copyright (C) 2025, 2026 Aryadev Chavali

View File

@@ -8,8 +8,8 @@
#include <ctype.h>
#include <string.h>
#include <alisp/base.h>
#include <alisp/reader.h>
#include <alisp/tag.h>
const char *read_err_to_cstr(read_err_t err)
{
@@ -21,11 +21,233 @@ const char *read_err_to_cstr(read_err_t err)
return "EOF";
case READ_ERR_UNKNOWN_CHAR:
return "UNKNOWN_CHAR";
break;
case READ_ERR_EXPECTED_CLOSED_BRACE:
return "EXPECTED_CLOSED_BRACE";
case READ_ERR_EXPECTED_CLOSED_SQUARE_BRACKET:
return "EXPECTED_CLOSED_SQUARE_BRACKET";
case READ_ERR_UNEXPECTED_CLOSED_BRACE:
return "UNEXPECTED_CLOSED_BRACE";
case READ_ERR_UNEXPECTED_CLOSED_SQUARE_BRACKET:
return "UNEXPECTED_CLOSED_SQUARE_BRACKET";
default:
FAIL("Unreachable");
}
}
// Accepted characters for symbols.
static const char *SYMBOL_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!$%&*+,-./"
":<=>?@\\^_`{|}~0123456789";
// Little predicate using SYMBOL_CHARS
bool is_sym(char c)
{
return strchr(SYMBOL_CHARS, c) != NULL;
}
void skip_comments_and_whitespace(stream_t *stream)
{
for (char c = stream_peek(stream); c != '\0' && (isspace(c) || c == ';');
c = stream_peek(stream))
{
stream_while(stream, " \t\n\0");
if (stream_peek(stream) == ';')
{
// Skip till newline
stream_till(stream, "\n");
}
}
}
read_err_t read_sym(sys_t *sys, stream_t *stream, lisp_t **ret)
{
sv_t sym_sv = stream_while(stream, SYMBOL_CHARS);
*ret = intern(sys, sym_sv);
return READ_ERR_OK;
}
read_err_t read_int(sys_t *sys, stream_t *stream, lisp_t **ret)
{
sv_t digits_sv = stream_while(stream, "0123456789");
if (is_sym(stream_peek(stream)))
{
// This is actually a symbol
stream_seek_backward(stream, digits_sv.size);
return read_sym(sys, stream, ret);
}
if (digits_sv.size > 19)
{
TODO("alisp doesn't support big integers (bigger than 63 bits) yet");
}
i64 n = 0;
for (u64 i = 0; i < digits_sv.size; ++i)
{
char c = digits_sv.data[i];
u8 digit = c - '0';
// NOTE: 10i + digit > INT_MAX
// => 10i > INT_MAX - digit
// => i > (INT_MAX - digit) / 10
if (n > (INT_MAX - digit) / 10)
{
TODO("alisp doesn't support big integers (bigger than 63 bits) yet");
}
n *= 10;
n += digit;
}
*ret = make_int(n);
return READ_ERR_OK;
}
read_err_t read_negative(sys_t *sys, stream_t *stream, lisp_t **ret)
{
char c = stream_next(stream);
if (isdigit(c))
{
read_err_t err = read_int(sys, stream, ret);
if (err)
return err;
i64 n = as_int(*ret);
n *= -1;
*ret = make_int(n);
return READ_ERR_OK;
}
else if (is_sym(c) || isspace(c))
{
stream_seek_backward(stream, 1);
return read_sym(sys, stream, ret);
}
else
return READ_ERR_UNKNOWN_CHAR;
}
read_err_t read_list(sys_t *sys, stream_t *stream, lisp_t **ret)
{
// skip past the open parentheses '('
(void)stream_next(stream);
lisp_t *top = NIL;
lisp_t *cur = NIL;
while (!stream_eoc(stream) && stream_peek(stream) != ')')
{
lisp_t *item = NIL;
read_err_t err = read(sys, stream, &item);
if (err == READ_ERR_EOF)
{
return READ_ERR_EXPECTED_CLOSED_BRACE;
}
else if (err)
{
return err;
}
else if (!top)
{
top = cons(sys, item, NIL);
cur = top;
}
else
{
as_cons(cur)->cdr = cons(sys, item, NIL);
cur = cdr(cur);
}
}
if (stream_peek(stream) != ')')
return READ_ERR_EXPECTED_CLOSED_BRACE;
stream_next(stream);
*ret = top;
return READ_ERR_OK;
}
read_err_t read_vec(sys_t *sys, stream_t *stream, lisp_t **ret)
{
(void)stream_next(stream);
lisp_t *container = make_vec(sys, 0);
while (!stream_eoc(stream) && stream_peek(stream) != ']')
{
lisp_t *item = NIL;
read_err_t err = read(sys, stream, &item);
if (err == READ_ERR_EOF)
{
return READ_ERR_EXPECTED_CLOSED_BRACE;
}
else if (err)
{
return err;
}
else
{
vec_append(as_vec(container), &item, sizeof(item));
}
}
if (stream_peek(stream) != ']')
return READ_ERR_EXPECTED_CLOSED_SQUARE_BRACKET;
stream_next(stream);
*ret = container;
return READ_ERR_OK;
}
read_err_t read_quote(sys_t *sys, stream_t *stream, lisp_t **ret)
{
lisp_t *to_quote = NIL;
stream_next(stream);
read_err_t err = read(sys, stream, &to_quote);
if (err)
return err;
*ret = cons(sys, to_quote, NIL);
*ret = cons(sys, intern(sys, SV_AUTO("quote")), *ret);
return READ_ERR_OK;
}
read_err_t read_all(sys_t *sys, stream_t *stream, vec_t *out)
{
while (!stream_eoc(stream))
{
lisp_t *item = NIL;
read_err_t err = read(sys, stream, &item);
if (err)
return err;
else
vec_append(out, &item, sizeof(item));
skip_comments_and_whitespace(stream);
}
return READ_ERR_OK;
}
read_err_t read(sys_t *sys, stream_t *stream, lisp_t **ret)
{
skip_comments_and_whitespace(stream);
if (stream_eoc(stream))
return READ_ERR_EOF;
char c = stream_peek(stream);
if (isdigit(c))
return read_int(sys, stream, ret);
else if (c == '-')
return read_negative(sys, stream, ret);
else if (is_sym(c))
return read_sym(sys, stream, ret);
else if (c == '\'')
return read_quote(sys, stream, ret);
else if (c == '(')
return read_list(sys, stream, ret);
else if (c == ')')
return READ_ERR_UNEXPECTED_CLOSED_BRACE;
else if (c == '[')
return read_vec(sys, stream, ret);
else if (c == ']')
return READ_ERR_UNEXPECTED_CLOSED_SQUARE_BRACKET;
return READ_ERR_UNKNOWN_CHAR;
}
/* Copyright (C) 2026 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but WITHOUT

View File

@@ -107,7 +107,7 @@ void stream_free(stream_t *stream)
switch (stream->type)
{
case STREAM_TYPE_STRING:
free(stream->string.data);
free((char *)stream->string.data);
break;
case STREAM_TYPE_FILE:
case STREAM_TYPE_PIPE:
@@ -303,7 +303,8 @@ u64 stream_seek_backward(stream_t *stream, u64 offset)
sv_t stream_sv(stream_t *stream)
{
sv_t sv = stream_sv_abs(stream);
return sv_chop_left(sv, stream->position);
sv = sv_chop_left(sv, stream->position);
return sv;
}
sv_t stream_sv_abs(stream_t *stream)

View File

@@ -36,18 +36,19 @@ sv_t sv_chop_right(sv_t sv, u64 size)
return SV(sv.data, sv.size - size);
}
sv_t sv_substr(sv_t sv, u64 position, u64 size)
{
return sv_chop_right(sv_chop_left(sv, position), size);
}
sv_t sv_truncate(sv_t sv, u64 newsize)
{
if (newsize >= sv.size)
return sv;
if (newsize > sv.size)
return SV(NULL, 0);
return SV(sv.data, newsize);
}
sv_t sv_substr(sv_t sv, u64 position, u64 size)
{
sv_t result = sv_truncate(sv_chop_left(sv, position), size);
return result;
}
sv_t sv_till(sv_t sv, const char *reject)
{
if (sv.size == 0 || !sv.data)
@@ -58,8 +59,6 @@ sv_t sv_till(sv_t sv, const char *reject)
++offset)
continue;
if (offset == sv.size)
return sv;
return sv_truncate(sv, offset);
}
@@ -73,8 +72,6 @@ sv_t sv_while(sv_t sv, const char *accept)
++offset)
continue;
if (offset == sv.size)
return sv;
return sv_truncate(sv, offset);
}

View File

@@ -29,7 +29,7 @@ void sym_table_init(sym_table_t *table)
vec_init(&table->entries, table->capacity * sizeof(sv_t));
}
char *sym_table_find(sym_table_t *table, sv_t sv)
const char *sym_table_find(sym_table_t *table, sv_t sv)
{
// Initialise the table if it's not done already
if (table->entries.capacity == 0)
@@ -62,13 +62,29 @@ void sym_table_free(sym_table_t *table)
{
current = ENTRY_GET(table, i);
if (current.data)
free(current.data);
{
// NOTE: We clone all data here, so it's okay to free by hand.
free((void *)current.data);
}
}
// Free the underlying container
vec_free(&table->entries);
memset(table, 0, sizeof(*table));
}
u64 sym_table_cost(sym_table_t *table)
{
if (!table || !table->count)
return 0;
else
{
u64 total_size = 0;
for (u64 i = 0; i < table->capacity; ++i)
total_size += ENTRY_GET(table, i).size;
return total_size;
}
}
/* Copyright (C) 2025, 2026 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but WITHOUT

View File

@@ -15,22 +15,22 @@ lisp_t *tag_int(i64 i)
return TAG((u64)i, INT);
}
lisp_t *tag_sym(char *str)
lisp_t *tag_sym(const char *str)
{
return TAG((u64)str, SYM);
}
lisp_t *tag_vec(vec_t *vec)
lisp_t *tag_vec(const vec_t *vec)
{
return TAG((u64)vec, VEC);
}
lisp_t *tag_cons(cons_t *cons)
lisp_t *tag_cons(const cons_t *cons)
{
return TAG((u64)cons, CONS);
}
tag_t get_tag(lisp_t *lisp)
tag_t get_tag(const lisp_t *lisp)
{
static_assert(NUM_TAGS == 5);
if (!lisp)

View File

@@ -84,10 +84,10 @@ void sym_unique_test(void)
sys_init(&system);
sv_t symbols[] = {
SV("hello", 5),
SV("goodbye", 7),
SV("display", 7),
SV("@xs'a_sh;d::a-h]", 16),
SV_AUTO("hello"),
SV_AUTO("goodbye"),
SV_AUTO("display"),
SV_AUTO("@xs'a_sh;d::a-h]"),
};
lisp_t *ptrs[ARRSIZE(symbols)];
@@ -159,7 +159,7 @@ void sys_test(void)
"Making integers doesn't affect system memory size");
// Creating symbols won't affect memory size, but does affect the symbol table
(void)intern(&sys, SV("hello world!", 12));
(void)intern(&sys, SV_AUTO("hello world!"));
TEST(sys.memory.size == old_memory_size,
"Interning doesn't affect system memory size");
TEST(sys.symtable.count > 0, "Interning affects symbol table");
@@ -169,7 +169,7 @@ void sys_test(void)
TEST(sys.memory.size > 0, "Creating conses affects memory size");
old_memory_size = sys.memory.size;
(void)cons(&sys, intern(&sys, SV("test", 4)), NIL);
(void)cons(&sys, intern(&sys, SV_AUTO("test")), NIL);
TEST(sys.memory.size > old_memory_size,
"Creating conses back to back affects memory size");
old_memory_size = sys.memory.size;

View File

@@ -64,9 +64,9 @@ void stream_test_string(void)
{
TEST_START();
sv_t test_strings[] = {
SV("hello, world!", 13),
SV("another string", 14),
SV((char *)text, ARRSIZE(text) / 2),
SV_AUTO("hello, world!"),
SV_AUTO("another string"),
sv_truncate(SV_AUTO(text), ARRSIZE(text) / 2),
};
for (u64 i = 0; i < ARRSIZE(test_strings); ++i)
@@ -87,7 +87,8 @@ void stream_test_string(void)
"Freeing a stream does not free the underlying memory it was derived "
"from");
free(copy.data);
// NOTE: Okay to free since we own copy.
free((void *)copy.data);
}
stream_t stream = {0};
@@ -319,7 +320,7 @@ void stream_test_substr(void)
TEST(result.size == size, "Substring has right size (%lu)", result.size);
sv_t expected = SV((char *)words_text + position, size);
sv_t expected = sv_substr(SV_AUTO(words_text), position, size);
TEST(strncmp(result.data, expected.data, result.size) == 0,
"Expect the substring to be the same as the data we put in");
}
@@ -332,7 +333,7 @@ void stream_test_substr(void)
TEST(result.size == size, "Substring has right size (%lu)", result.size);
sv_t expected = SV((char *)words_text, size);
sv_t expected = sv_truncate(SV_AUTO(words_text), size);
TEST(strncmp(result.data, expected.data, result.size) == 0,
"Expect the substring to be the same as the data we put in");
}
@@ -349,7 +350,7 @@ void stream_test_substr(void)
TEST(result.size == size, "Substring has right size (%lu)", result.size);
sv_t expected = SV((char *)words_text + position, size);
sv_t expected = sv_substr(SV_AUTO(words_text), position, size);
TEST(strncmp(result.data, expected.data, result.size) == 0,
"Expect the substring to be the same as the data we put in");

View File

@@ -24,8 +24,8 @@ void sv_copy_test(void)
TEST(strncmp(word.data, copy.data, copy.size) == 0, "`%s` == `%s`",
word.data, copy.data);
// Obviously we can't just have this lying around.
free(copy.data);
// NOTE: Okay to free since we own copy.
free((void *)copy.data);
}
}

View File

@@ -50,13 +50,13 @@ void vec_test_gen_substr(void)
{0, 16},
{0, 32},
{32, 64},
{0, ARRSIZE(text)},
{0, ARRSIZE(text) - 1},
};
for (u64 i = 0; i < ARRSIZE(tests); ++i)
{
struct Test test = tests[i];
const sv_t substr = SV((char *)text + test.start, test.size);
const sv_t substr = sv_substr(SV_AUTO(text), test.start, test.size);
const u64 size = test.size / 2;
lisp_t *lvec = make_vec(&system, size);