diff --git a/.dir-locals.el b/.dir-locals.el index 506354d..94e3cb0 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,6 +1,6 @@ ;;; Directory Local Variables -*- no-byte-compile: t -*- ;;; For more information see (info "(emacs) Directory Variables") -((nil . ((compile-command . "make MODE=debug -B -k") +((nil . ((compile-command . "make MODE=debug -k") (+license/license-choice . "MIT License"))) (c-mode . ((mode . clang-format)))) diff --git a/.gitignore b/.gitignore index d163863..ab0b029 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build/ \ No newline at end of file +build/ + +# Development +compile_commands.json +.cache/ +TAGS \ No newline at end of file diff --git a/Makefile b/Makefile index dd198e2..62f95b9 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ CC=cc DIST=build -OUT=$(DIST)/main.out +OUT=$(DIST)/arl.out LDFLAGS= -GFLAGS=-Wall -Wextra -Wpedantic -std=c23 +GFLAGS=-Wall -Wextra -Wpedantic -std=c23 -I./src/ DFLAGS=-ggdb -fsanitize=address -fsanitize=undefined RFLAGS=-O3 @@ -15,14 +15,26 @@ else CFLAGS=$(GFLAGS) $(DFLAGS) endif -$(OUT): $(DIST)/main.o | $(DIST) +HEADERS=$(shell find "src" -type 'f' -name '*.h') +MODULES=main lib/vec lib/sv parser/ast parser/parser +OBJECTS=$(patsubst %,$(DIST)/%.o, $(MODULES)) + +$(OUT): $(OBJECTS) | $(DIST) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -$(DIST)/main.o: main.c | $(DIST) - $(CC) $(CFLAGS) -c -o $@ $^ +$(DIST)/%.o: src/arl/%.c $(HEADERS) | $(DIST) + $(CC) $(CFLAGS) -c -o $@ $< + +$(DIST)/%.o: src/arl/parser/%.c $(HEADERS) | $(DIST) + $(CC) $(CFLAGS) -c -o $@ $< + +$(DIST)/%.o: src/arl/lib/%.c $(HEADERS) | $(DIST) + $(CC) $(CFLAGS) -c -o $@ $< $(DIST): mkdir -p $(DIST) + mkdir -p $(DIST)/lib + mkdir -p $(DIST)/parser .PHONY: run clean ARGS= diff --git a/examples/hello-world.arl b/examples/hello-world.arl index 22d3740..fd36f5a 100644 --- a/examples/hello-world.arl +++ b/examples/hello-world.arl @@ -1 +1 @@ -"Hello, world! println \ No newline at end of file +"Hello, world!" println \ No newline at end of file diff --git a/src/arl/lib/base.h b/src/arl/lib/base.h new file mode 100644 index 0000000..346e719 --- /dev/null +++ b/src/arl/lib/base.h @@ -0,0 +1,56 @@ +/* base.h: Basic definitions + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + + Taken from prick_aliases.h: see https://github.com/oreodave/prick. + */ + +#ifndef BASE_H +#define BASE_H + +#include +#include +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +static_assert(sizeof(float) == 4, "f32 requires 4 byte floats"); +static_assert(sizeof(double) == 8, "f64 requires 8 byte doubles"); +typedef float f32; +typedef double f64; + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) > (B) ? (B) : (A)) +#define ARRSIZE(A) ((sizeof(A)) / sizeof((A)[0])) + +#define FAIL(...) \ + do \ + { \ + fprintf(stderr, "FAIL: "); \ + fprintf(stderr, __VA_ARGS__); \ + assert(0); \ + } while (0) + +#endif + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/src/arl/lib/sv.c b/src/arl/lib/sv.c new file mode 100644 index 0000000..d9a8497 --- /dev/null +++ b/src/arl/lib/sv.c @@ -0,0 +1,63 @@ +/* sv.c: String View implementations + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: See /include/sv.h + */ + +#include + +#include + +sv_t sv_chop_left(sv_t sv, u64 len) +{ + if (sv.size < len) + { + return SV(NULL, 0); + } + else + { + return SV(sv.data + len, sv.size - len); + } +} + +sv_t sv_chop_right(sv_t sv, u64 len) +{ + if (sv.size < len) + { + return SV(NULL, 0); + } + else + { + return SV(sv.data, sv.size - len); + } +} + +u64 sv_while(const sv_t sv, const char *expected) +{ + u64 i; + for (i = 0; i < sv.size && strchr(expected, sv.data[i]) != NULL; ++i) + continue; + return i; +} + +u64 sv_till(const sv_t sv, const char *expected) +{ + u64 i; + for (i = 0; i < sv.size && strchr(expected, sv.data[i]) == NULL; ++i) + continue; + return i; +} + +/* Copyright (C) 2026 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 MIT License + * for details. + + * You may distribute and modify this code under the terms of the + * MIT License, which you should have received a copy of along with this + * program. If not, please go to . + + */ diff --git a/src/arl/lib/sv.h b/src/arl/lib/sv.h new file mode 100644 index 0000000..8574abe --- /dev/null +++ b/src/arl/lib/sv.h @@ -0,0 +1,39 @@ +/* sv.h: String Views + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: Absolute bajoding + */ + +#ifndef SV_H +#define SV_H + +#include + +typedef struct +{ + char *data; + u64 size; +} sv_t; + +#define SV(PTR, SIZE) ((sv_t){.data = (PTR), .size = (SIZE)}) +#define PR_SV "%.*s" +#define SV_FMT(SV) (int)((SV).size), (SV).data +sv_t sv_chop_left(sv_t sv, u64 len); +sv_t sv_chop_right(sv_t sv, u64 len); +u64 sv_while(const sv_t sv, const char *expected); +u64 sv_till(const sv_t sv, const char *expected); + +#endif + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/src/arl/lib/vec.c b/src/arl/lib/vec.c new file mode 100644 index 0000000..084e2a6 --- /dev/null +++ b/src/arl/lib/vec.c @@ -0,0 +1,111 @@ +/* vec.c: Vector implementation + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + + Taken from prick_vec.h: see https://github.com/oreodave/prick. + */ + +#include +#include + +#include +#include + +void vec_append(vec_t *vec, const void *const ptr, u64 size) +{ + if (!vec || !ptr || !size) + return; + vec_ensure_free(vec, size); + memcpy(&VEC_GET(vec, vec->size, u8), ptr, size); + vec->size += size; +} + +void vec_append_byte(vec_t *vec, u8 byte) +{ + if (!vec) + return; + vec_ensure_free(vec, 1); + VEC_GET(vec, vec->size, u8) = byte; + ++vec->size; +} + +void *vec_data(vec_t *vec) +{ + if (!vec) + return NULL; + + if (vec->not_inlined) + { + return vec->ptr; + } + else + { + return vec->inlined; + } +} + +void vec_ensure_capacity(vec_t *vec, u64 capacity) +{ + if (!vec) + return; + if (vec->capacity == 0) + vec->capacity = VEC_INLINE_CAPACITY; + if (vec->capacity < capacity) + { + vec->capacity = MAX(vec->capacity * VEC_MULT, capacity); + if (!vec->not_inlined) + { + // We were a small buffer, and now we cannot be i.e. we need to allocate + // on the heap. + vec->not_inlined = 1; + void *buffer = calloc(1, vec->capacity); + memcpy(buffer, vec->inlined, vec->size); + memset(vec->inlined, 0, sizeof(vec->inlined)); + vec->ptr = buffer; + } + else + { + // We're already on the heap, just reallocate. + vec->ptr = realloc(vec->ptr, vec->capacity); + } + } +} + +void vec_ensure_free(vec_t *vec, u64 size) +{ + if (!vec) + return; + vec_ensure_capacity(vec, vec->size + size); +} + +void vec_free(vec_t *vec) +{ + if (!vec) + return; + if (vec->not_inlined) + free(vec->ptr); + memset(vec, 1, sizeof(*vec)); +} + +void vec_clone(vec_t *v2, vec_t *v1) +{ + if (!v1 || !v2) + return; + vec_append(v2, vec_data(v1), v1->size); +} + +#undef MAX + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/src/arl/lib/vec.h b/src/arl/lib/vec.h new file mode 100644 index 0000000..9dcfc79 --- /dev/null +++ b/src/arl/lib/vec.h @@ -0,0 +1,55 @@ +/* vec.h: A dynamically sized array with SBO. + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + + Taken from prick_vec.h: see https://github.com/oreodave/prick. + */ + +#ifndef VEC_H +#define VEC_H + +#include +#include + +#include + +#define VEC_INLINE_CAPACITY 32 +#define VEC_MULT 2 + +typedef struct +{ + u64 size, capacity; + u8 not_inlined; + union + { + void *ptr; + alignas(max_align_t) u8 inlined[VEC_INLINE_CAPACITY]; + }; +} vec_t; +static_assert(sizeof(vec_t) == 64, "Expected sizeof(vec_t) to be 64"); + +void vec_append(vec_t *vec, const void *const ptr, u64 size); +void vec_append_byte(vec_t *vec, u8 byte); +void *vec_data(vec_t *vec); +void vec_ensure_capacity(vec_t *vec, u64 capacity); +void vec_ensure_free(vec_t *vec, u64 size); +void vec_free(vec_t *vec); +void vec_clone(vec_t *v2, vec_t *v1); + +#define VEC_GET(VEC, INDEX, TYPE) (((TYPE *)vec_data(VEC))[INDEX]) + +#endif + +/* Copyright (C) 2026 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 Unlicense for details. + + * You may distribute and modify this code under the terms of the Unlicense, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/main.c b/src/arl/main.c similarity index 55% rename from main.c rename to src/arl/main.c index f0d00aa..448efff 100644 --- a/main.c +++ b/src/arl/main.c @@ -6,18 +6,19 @@ */ #include +#include +#include #include #include +#include -#define FAIL(...) \ - do \ - { \ - fprintf(stderr, "FAIL: "); \ - fprintf(stderr, __VA_ARGS__); \ - abort(); \ - } while (0) +#include +#include +#include +#include -char *read_file(const char *filename) +/// Parser +sv_t read_file(const char *filename) { FILE *fp = fopen(filename, "rb"); if (!fp) @@ -31,16 +32,34 @@ char *read_file(const char *filename) fclose(fp); buffer[size] = '\0'; - return buffer; + return SV(buffer, size); } int main(void) { const char *filename = "./examples/hello-world.arl"; - char *buffer = read_file(filename); - printf("%s => %s\n", filename, buffer); - free(buffer); + sv_t contents = read_file(filename); + printf("%s => " PR_SV "\n", filename, SV_FMT(contents)); + + parse_stream_t stream = {.line = 1, .column = 0, .contents = contents}; + ast_t ast = {0}; + parse_err_t perr = parse(&ast, &stream); + if (perr) + { + fprintf(stderr, "%s:%lu:%lu: %s\n", filename, stream.line, stream.column, + parse_err_to_string(perr)); + goto fail; + } + + free(contents.data); + ast_free(&ast); return 0; +fail: + if (contents.data) + free(contents.data); + if (ast.objects.capacity > 0) + ast_free(&ast); + return 1; } /* Copyright (C) 2026 Aryadev Chavali diff --git a/src/arl/parser/ast.c b/src/arl/parser/ast.c new file mode 100644 index 0000000..55f42f5 --- /dev/null +++ b/src/arl/parser/ast.c @@ -0,0 +1,46 @@ +/* ast.c: Implementation of AST constructor/destructor functions + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: See ast.h. + */ + +#include + +obj_t obj_string(u64 line, u64 col, sv_t string) +{ + return (obj_t){ + .line = line, + .column = col, + .type = TYPE_STRING, + .value = {string}, + }; +} + +obj_t obj_symbol(u64 line, u64 col, sv_t symbol) +{ + return (obj_t){ + .line = line, + .column = col, + .type = TYPE_SYMBOL, + .value = {symbol}, + }; +} + +void ast_free(ast_t *ast) +{ + // we can free the vector itself and we're done + vec_free(&ast->objects); +} + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/src/arl/parser/ast.h b/src/arl/parser/ast.h new file mode 100644 index 0000000..ed77fff --- /dev/null +++ b/src/arl/parser/ast.h @@ -0,0 +1,57 @@ +/* ast.h: General definition of the AST and objects within it. + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#ifndef AST_H +#define AST_H + +#include +#include +#include + +typedef enum +{ + TYPE_SYMBOL, + TYPE_STRING, +} type_t; + +typedef struct +{ + u64 line, column; + + type_t type; + union + { + sv_t as_string; + sv_t as_symbol; + } value; +} obj_t; + +obj_t obj_string(u64 line, u64 col, sv_t string); +obj_t obj_symbol(u64 line, u64 col, sv_t symbol); + +// Our AST is simply a vector of objects. Nesting and tree like structure is +// imposed by individual objects. +typedef struct +{ + vec_t objects; +} ast_t; + +void ast_free(ast_t *ast); + +#endif + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/src/arl/parser/parser.c b/src/arl/parser/parser.c new file mode 100644 index 0000000..2ee5d52 --- /dev/null +++ b/src/arl/parser/parser.c @@ -0,0 +1,153 @@ +/* parser.c: Implementation of parser. + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: See parser.h + */ + +#include "arl/lib/sv.h" +#include +#include + +#include + +/// Expected characters in a symbol +static const char *SYMBOL_CHARS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&'()*+,-./" + ":;<=>?@\\^_`{|}~0123456789"; + +const char *parse_err_to_string(parse_err_t err) +{ + switch (err) + { + case PARSE_ERR_OK: + return "OK"; + case PARSE_ERR_UNEXPECTED_EOF: + return "UNEXPECTED_EOF"; + case PARSE_ERR_EXPECTED_SPEECH_MARKS: + return "EXPECTED_SPEECH_MARKS"; + case PARSE_ERR_UNKNOWN_CHAR: + return "UNKNOWN_CHAR"; + default: + FAIL("Unexpected value for parse_err_t: %d\n", err); + } +} + +bool stream_eos(parse_stream_t *stream) +{ + return stream->cursor >= stream->contents.size; +} + +char stream_peek(parse_stream_t *stream) +{ + if (stream_eos(stream)) + return '\0'; + else + return stream->contents.data[stream->cursor]; +} + +void stream_advance(parse_stream_t *stream, u64 size) +{ + if (stream->cursor + size >= stream->contents.size) + stream->cursor = stream->contents.size; + else + { + for (u64 i = 0; i < size; ++i) + { + ++stream->cursor; + if (stream_peek(stream) == '\n') + { + stream->line++; + stream->column = 0; + } + else + { + stream->column++; + } + } + } +} + +u64 stream_size(parse_stream_t *stream) +{ + return stream->contents.size; +} + +parse_err_t parse_string(parse_stream_t *stream, obj_t *ret) +{ + // Increment the cursor just past the first speechmark + stream_advance(stream, 1); + sv_t current_contents = sv_chop_left(stream->contents, stream->cursor); + u64 string_size = sv_till(current_contents, "\""); + if (string_size + stream->cursor == stream_size(stream)) + return PARSE_ERR_EXPECTED_SPEECH_MARKS; + // Bounds of string are well defined, generate an object and advance the + // stream + *ret = obj_string(stream->line, stream->column - 1, + SV(current_contents.data, string_size)); + stream_advance(stream, string_size + 1); + return PARSE_ERR_OK; +} + +parse_err_t parse_symbol(parse_stream_t *stream, obj_t *ret) +{ + sv_t current_contents = sv_chop_left(stream->contents, stream->cursor); + u64 symbol_size = sv_while(current_contents, SYMBOL_CHARS); + // Generate symbol + *ret = obj_symbol(stream->line, stream->column, + SV(current_contents.data, symbol_size)); + stream_advance(stream, symbol_size); + return PARSE_ERR_OK; +} + +parse_err_t parse(ast_t *out, parse_stream_t *stream) +{ + while (!stream_eos(stream)) + { + char cur = stream_peek(stream); + if (isspace(cur)) + { + while (isspace(cur) && !stream_eos(stream)) + { + stream_advance(stream, 1); + cur = stream_peek(stream); + } + } + else if (cur == '"') + { + // we make a copy for parse_string to mess with + obj_t ret = {0}; + parse_err_t perr = parse_string(stream, &ret); + if (perr) + return perr; + vec_append(&out->objects, &ret, sizeof(ret)); + } + else if (strchr(SYMBOL_CHARS, cur) && !isdigit(cur)) + { + // we make a copy for parse_symbol to mess with + obj_t ret = {0}; + parse_err_t perr = parse_symbol(stream, &ret); + if (perr) + return perr; + + vec_append(&out->objects, &ret, sizeof(ret)); + } + else + { + return PARSE_ERR_UNKNOWN_CHAR; + } + } + return PARSE_ERR_OK; +} + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */ diff --git a/src/arl/parser/parser.h b/src/arl/parser/parser.h new file mode 100644 index 0000000..40b8c53 --- /dev/null +++ b/src/arl/parser/parser.h @@ -0,0 +1,42 @@ +/* parser.h: Parser which takes character buffers and yields an AST + * Created: 2026-01-22 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#ifndef PARSER_H +#define PARSER_H + +#include + +typedef enum +{ + PARSE_ERR_OK = 0, + PARSE_ERR_UNEXPECTED_EOF, + PARSE_ERR_EXPECTED_SPEECH_MARKS, + PARSE_ERR_UNKNOWN_CHAR, +} parse_err_t; +const char *parse_err_to_string(parse_err_t err); + +typedef struct +{ + u64 line, column, cursor; + sv_t contents; +} parse_stream_t; + +parse_err_t parse(ast_t *out, parse_stream_t *stream); + +#endif + +/* Copyright (C) 2026 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 MIT License for details. + + * You may distribute and modify this code under the terms of the MIT License, + * which you should have received a copy of along with this program. If not, + * please go to . + + */