Initial implementation
This commit is contained in:
419
reader.c
Normal file
419
reader.c
Normal file
@@ -0,0 +1,419 @@
|
||||
/* Copyright (C) 2025 Aryadev Chavali
|
||||
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
|
||||
* details.
|
||||
|
||||
* You may distribute and modify this code under the terms of the GNU General
|
||||
* Public License Version 2, which you should have received a copy of along with
|
||||
* this program. If not, please go to <https://www.gnu.org/licenses/>.
|
||||
|
||||
* Created: 2025-04-16
|
||||
* Description: Implementation of parser
|
||||
*/
|
||||
|
||||
#include "./reader.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
bool is_digit(char c)
|
||||
{
|
||||
return isdigit(c);
|
||||
}
|
||||
|
||||
bool is_alpha(char c)
|
||||
{
|
||||
return isalpha(c);
|
||||
}
|
||||
|
||||
bool is_space(char c)
|
||||
{
|
||||
return isspace(c);
|
||||
}
|
||||
|
||||
bool is_skip(char c)
|
||||
{
|
||||
return is_space(c) || c == ';';
|
||||
}
|
||||
|
||||
bool is_sym(char c)
|
||||
{
|
||||
return strchr(SYM_CHARS, c) != NULL;
|
||||
}
|
||||
|
||||
void input_from_sv(context_t *ctx, input_t *inp, const char *name, sv_t sv)
|
||||
{
|
||||
inp->name = name;
|
||||
inp->str = sv_copy(&ctx->read, sv);
|
||||
}
|
||||
|
||||
void input_from_fp(context_t *ctx, input_t *input, const char *name, FILE *fp)
|
||||
{
|
||||
input->name = name;
|
||||
// TODO: Choose a best fit (i.e. maximal capacity, unused) page
|
||||
page_t *page = page_create(INPUT_CHUNK_SIZE);
|
||||
// chunk should be in scratch space so we can reset it later.
|
||||
char *chunk = context_salloc(ctx, INPUT_CHUNK_SIZE);
|
||||
|
||||
u64 total_size = 0, size_read = 0;
|
||||
while (!feof(fp))
|
||||
{
|
||||
size_read = fread(chunk, 1, INPUT_CHUNK_SIZE, fp);
|
||||
if (size_read > 0)
|
||||
{
|
||||
page_rappend(&page, chunk, size_read);
|
||||
total_size += size_read;
|
||||
}
|
||||
}
|
||||
|
||||
input->str = SV((char *)page->data, total_size);
|
||||
|
||||
// Memory cleanup
|
||||
context_reset_scratch(ctx);
|
||||
arena_attach(&ctx->read, page);
|
||||
}
|
||||
|
||||
bool input_eof(input_t *input)
|
||||
{
|
||||
return !input || (input->offset >= input->str.size) ||
|
||||
(input->str.data[input->offset] == '\0');
|
||||
}
|
||||
|
||||
char input_peek(input_t *input, u64 offset)
|
||||
{
|
||||
if (input_eof(input) || input->offset + offset >= input->str.size)
|
||||
return '\0';
|
||||
return input->str.data[input->offset + offset];
|
||||
}
|
||||
|
||||
char input_next(input_t *input, u64 offset)
|
||||
{
|
||||
if (input_eof(input) || input->offset + offset >= input->str.size)
|
||||
return '\0';
|
||||
input->offset += offset;
|
||||
return input->str.data[input->offset];
|
||||
}
|
||||
|
||||
void input_skip(input_t *inp)
|
||||
{
|
||||
// n + 2 lookup
|
||||
sv_t current = sv_cut(inp->str, inp->offset);
|
||||
sv_t lookup = sv_chop(current, 2);
|
||||
while ((!input_eof(inp) && is_space(lookup.data[0])) ||
|
||||
lookup.data[0] == ';' || strncmp(lookup.data, "#|", 2) == 0)
|
||||
{
|
||||
if (lookup.data[0] == ';')
|
||||
{
|
||||
i64 newline = sv_find_subcstr(current, "\n", 1);
|
||||
if (newline < 0)
|
||||
inp->offset = inp->str.size;
|
||||
else
|
||||
inp->offset += newline + 1;
|
||||
}
|
||||
else if (strncmp(lookup.data, "#|", 2) == 0)
|
||||
{
|
||||
i64 offset = sv_find_subcstr(current, "|#", 2);
|
||||
if (offset < 0)
|
||||
inp->offset = inp->str.size;
|
||||
else
|
||||
inp->offset += offset + 2;
|
||||
}
|
||||
|
||||
inp->offset += sv_while(sv_cut(inp->str, inp->offset), is_space);
|
||||
current = sv_cut(inp->str, inp->offset);
|
||||
lookup = sv_chop(current, 2);
|
||||
}
|
||||
}
|
||||
|
||||
perr_t parse_int(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
debug("parse_int[%lu] => ", inp->offset);
|
||||
|
||||
// TODO: Parse arbitrary sized integers
|
||||
(void)ctx;
|
||||
|
||||
bool negative = (input_peek(inp, 0) == '-');
|
||||
sv_t current = sv_cut(inp->str, inp->offset + (negative ? 1 : 0));
|
||||
sv_t digits = sv_chop(current, sv_while(current, is_digit));
|
||||
|
||||
debug("`" PR_SV "` => ", SV_FMT(digits));
|
||||
i64 x = (negative ? -1L : 1L) * strtol(digits.data, NULL, 10);
|
||||
debug("%ld\n", x);
|
||||
|
||||
input_next(inp, digits.size + (negative ? 1 : 0));
|
||||
|
||||
*ret = make_int(x);
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
perr_t parse_sym(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
debug("parse_sym[%lu] => ", inp->offset);
|
||||
|
||||
sv_t current = sv_cut(inp->str, inp->offset);
|
||||
sv_t sym = sv_chop(current, sv_while(current, is_sym));
|
||||
debug("`" PR_SV "`\n", SV_FMT(sym));
|
||||
|
||||
if (sym.size == 3)
|
||||
{
|
||||
// NOTE: We can't mutate sym directly because it's on `read` space.
|
||||
|
||||
// TODO: Make this beautiful please.
|
||||
char buf[3];
|
||||
for (u64 i = 0; i < 3; ++i)
|
||||
buf[i] = toupper(sym.data[i]);
|
||||
|
||||
// NOTE: NIL symbol to actual NIL
|
||||
if (strncmp(buf, "NIL", 3) == 0)
|
||||
{
|
||||
input_next(inp, 3);
|
||||
return NIL;
|
||||
}
|
||||
}
|
||||
|
||||
lisp_t *lsym = make_sym(ctx, sym.data, sym.size);
|
||||
input_next(inp, sym.size);
|
||||
*ret = lsym;
|
||||
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
perr_t parse_bool(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
(void)ctx;
|
||||
debug("parse_bool[%lu] => ", inp->offset);
|
||||
char c = input_peek(inp, 1);
|
||||
bool b = -1;
|
||||
if (c == 't')
|
||||
b = true;
|
||||
else if (c == 'f')
|
||||
b = false;
|
||||
else
|
||||
return PERR_EXPECTED_BOOLEAN;
|
||||
*ret = tag_bool(b);
|
||||
input_next(inp, 2);
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
perr_t parse_cons(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
// TODO: Put this in a symbol table
|
||||
lisp_t *lisp_dot = make_sym(ctx, ".", 1);
|
||||
debug("parse_cons[%lu] => (\n", inp->offset);
|
||||
inp->offset += 1;
|
||||
|
||||
lisp_t *root = NIL;
|
||||
lisp_t **cur = NIL;
|
||||
bool dotted = false;
|
||||
|
||||
while (!input_eof(inp) && input_peek(inp, 0) != ')')
|
||||
{
|
||||
lisp_t *lisp = NIL;
|
||||
perr_t res = parse(ctx, inp, &lisp);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
// This is cheap to do
|
||||
if (lisp == lisp_dot)
|
||||
{
|
||||
dotted = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!root)
|
||||
{
|
||||
root = make_cons(ctx, lisp, NIL);
|
||||
cur = &root;
|
||||
}
|
||||
else if (!dotted)
|
||||
*cur = make_cons(ctx, lisp, NIL);
|
||||
else
|
||||
*cur = lisp;
|
||||
|
||||
if (cur && !dotted)
|
||||
cur = &as_cons(*cur)->cdr;
|
||||
|
||||
input_skip(inp);
|
||||
}
|
||||
|
||||
if (input_peek(inp, 0) != ')')
|
||||
return PERR_EXPECTED_CLOSE_BRACKET;
|
||||
|
||||
input_next(inp, 1);
|
||||
|
||||
debug(")\n");
|
||||
*ret = root;
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
perr_t parse_vec(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
debug("parse_vec[%lu] => [\n", inp->offset);
|
||||
input_next(inp, 2);
|
||||
|
||||
lisp_t *lvec = make_vec(ctx, 0);
|
||||
vec_t *vec = as_vec(lvec);
|
||||
|
||||
while (!input_eof(inp) && input_peek(inp, 0) != ')')
|
||||
{
|
||||
lisp_t *lisp = NIL;
|
||||
perr_t res = parse(ctx, inp, &lisp);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
vec_append(&ctx->memory, vec, &lisp, sizeof(lisp));
|
||||
input_skip(inp);
|
||||
}
|
||||
|
||||
if (input_peek(inp, 0) != ')')
|
||||
return PERR_EXPECTED_CLOSE_BRACKET;
|
||||
|
||||
input_next(inp, 1);
|
||||
|
||||
debug("]\n");
|
||||
*ret = lvec;
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
perr_t parse_str(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
debug("parse_str[%lu] => ", inp->offset);
|
||||
input_next(inp, 1); // 1 for the first speechmark
|
||||
sv_t sv = sv_cut(inp->str, inp->offset);
|
||||
i64 size = sv_find_subcstr(sv, "\"", 1);
|
||||
if (size < 0)
|
||||
return PERR_EXPECTED_SPEECH_MARK;
|
||||
|
||||
input_next(inp, size + 1); // 1 for that last speechmark
|
||||
sv_t str_content = sv_chop(sv, size);
|
||||
debug("\"" PR_SV "\"\n", SV_FMT(str_content));
|
||||
*ret = make_str(ctx, str_content.data, str_content.size);
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
perr_t parse_quote(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
char c = input_peek(inp, 0);
|
||||
if (!(c == '\'' || c == '`'))
|
||||
return PERR_UNEXPECTED_CHAR;
|
||||
input_next(inp, 1);
|
||||
sv_t prefix = {0};
|
||||
if (c == '\'')
|
||||
prefix = SV("quote", 5);
|
||||
else if (c == '`')
|
||||
prefix = SV("quasiquote", 10);
|
||||
lisp_t *root = make_cons(ctx, make_sym(ctx, prefix.data, prefix.size), NIL);
|
||||
lisp_t *rest = NIL;
|
||||
perr_t perr = parse(ctx, inp, &rest);
|
||||
if (perr)
|
||||
return perr;
|
||||
CDR(root) = make_cons(ctx, rest, NIL);
|
||||
*ret = root;
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
// TODO: Make this interactable with user once we have evaluation
|
||||
perr_t parse_reader_macro(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
char c = input_peek(inp, 1);
|
||||
if (c == '\\')
|
||||
{
|
||||
// character or weird base integer
|
||||
TODO("Not implemented reader macro for characters or weird bases");
|
||||
}
|
||||
else if (c == '(')
|
||||
{
|
||||
return parse_vec(ctx, inp, ret);
|
||||
}
|
||||
else if (c == 't' || c == 'f')
|
||||
return parse_bool(ctx, inp, ret);
|
||||
return PERR_UNEXPECTED_READER_MACRO_SYMBOL;
|
||||
}
|
||||
|
||||
static_assert(NUM_TAGS == 9);
|
||||
perr_t parse(context_t *ctx, input_t *inp, lisp_t **ret)
|
||||
{
|
||||
debug("parse => ");
|
||||
input_skip(inp);
|
||||
if (input_eof(inp))
|
||||
return PERR_EOF;
|
||||
|
||||
char c = input_peek(inp, 0);
|
||||
|
||||
if (is_digit(c) || (c == '-' && is_digit(input_peek(inp, 1))))
|
||||
return parse_int(ctx, inp, ret);
|
||||
else if (c == '#')
|
||||
return parse_reader_macro(ctx, inp, ret);
|
||||
else if (is_sym(c))
|
||||
return parse_sym(ctx, inp, ret);
|
||||
else if (c == '(')
|
||||
return parse_cons(ctx, inp, ret);
|
||||
else if (c == '\'' || c == '`')
|
||||
return parse_quote(ctx, inp, ret);
|
||||
else if (c == '\"')
|
||||
return parse_str(ctx, inp, ret);
|
||||
else
|
||||
return PERR_UNEXPECTED_CHAR;
|
||||
}
|
||||
|
||||
perr_t parse_all(context_t *ctx, input_t *inp, vec_t *vec)
|
||||
{
|
||||
while (!input_eof(inp))
|
||||
{
|
||||
lisp_t *member = NIL;
|
||||
perr_t err = parse(ctx, inp, &member);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
else
|
||||
vec_append(&ctx->scratch, vec, &member, sizeof(member));
|
||||
|
||||
input_skip(inp);
|
||||
}
|
||||
return PERR_OK;
|
||||
}
|
||||
|
||||
int print_perror(FILE *fp, input_t *inp, perr_t error)
|
||||
{
|
||||
pos_t pos = input_offset_to_pos(inp);
|
||||
fprintf(fp, "%s:%lu:%lu: %s", inp->name, pos.line, pos.col,
|
||||
perr_to_cstr(error));
|
||||
switch (error)
|
||||
{
|
||||
case PERR_UNEXPECTED_CHAR:
|
||||
fprintf(fp, "(`%c`)", input_peek(inp, 0));
|
||||
break;
|
||||
case PERR_OK:
|
||||
case PERR_EOF:
|
||||
case PERR_EXPECTED_BOOLEAN:
|
||||
case PERR_UNEXPECTED_READER_MACRO_SYMBOL:
|
||||
case PERR_EXPECTED_CLOSE_BRACKET:
|
||||
case PERR_EXPECTED_SPEECH_MARK:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
pos_t input_offset_to_pos(input_t *inp)
|
||||
{
|
||||
pos_t pos = {.col = 1, .line = 1};
|
||||
for (u64 i = 0; i < inp->offset && i < inp->str.size; ++i)
|
||||
{
|
||||
char c = (inp->str.data[i]);
|
||||
if (c == '\n')
|
||||
{
|
||||
++pos.line;
|
||||
pos.col = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
++pos.col;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
Reference in New Issue
Block a user