Compare commits
31 Commits
b8d0ee2c7f
...
4aad62fec9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aad62fec9 | ||
|
|
a4fb48a863 | ||
|
|
5f05a6a108 | ||
|
|
e60a7459e0 | ||
|
|
4936460b39 | ||
|
|
04be0ecad0 | ||
|
|
f09e0d33a4 | ||
|
|
d1d0783fc3 | ||
|
|
21254e77bf | ||
|
|
e772b06ae5 | ||
|
|
eca46069f8 | ||
|
|
c2f1835b4c | ||
|
|
93a52db7cc | ||
|
|
d88bc2baeb | ||
|
|
2dc0c6080e | ||
|
|
8e81e6f762 | ||
|
|
a49492b27f | ||
|
|
8b2fe97fc2 | ||
|
|
ff512986f8 | ||
|
|
47f33cb969 | ||
|
|
39e5b76f8b | ||
|
|
b91e79933b | ||
|
|
3e7734037c | ||
|
|
9f3bb57972 | ||
|
|
b646ae3f7e | ||
|
|
818d4da850 | ||
|
|
daa1d3d565 | ||
|
|
b7fc5170b0 | ||
|
|
d02174ea8b | ||
|
|
7ef6905d7a | ||
|
|
c12f4b2d2c |
2
Makefile
2
Makefile
@@ -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
|
||||
|
||||
31
alisp.org
31
alisp.org
@@ -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~.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
136
src/lisp.c
136
src/lisp.c
@@ -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
|
||||
|
||||
92
src/main.c
92
src/main.c
@@ -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
|
||||
|
||||
224
src/reader.c
224
src/reader.c
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
19
src/sv.c
19
src/sv.c
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user