#include #include #include #include #include #include "shorthand.h" // FIXME: We are not worried right now about freeing memory, but we should in // the future. // TODO: Better error messages. typedef struct StringView { char *start; size_t n; } StringView; void sv_write(StringView sv) { for (size_t i = 0; i < sv.n; i++) { putchar(sv.start[i]); } } bool sv_equal(StringView a, StringView b) { if (a.n == b.n) { for (size_t i = 0; i < a.n; i++) { if (a.start[i] != b.start[i]) { return false; } } return true; } return false; } #define READLINE_VALID_CHAR(C) (((u8)(C) >= 0x20 && (u8)(C) < 0x7F) || (C) == '\n') StringView read_line(FILE *fd, char delimiter) { #define RL_BUF_SIZE 1024 static char readline_buf[RL_BUF_SIZE]; // Clear buffer. for (size_t i = 0; i < RL_BUF_SIZE; i++) { readline_buf[i] = 0; } // Barebones readline implementation. size_t n = 0; char c; while ((c = getc(fd)) != delimiter) { if (c == '\b') { readline_buf[n] = '\0'; n--; } else if (READLINE_VALID_CHAR(c) && n < RL_BUF_SIZE) { readline_buf[n] = c; n++; } } return (StringView){.start = (char *)&readline_buf, .n = n}; } typedef enum TokenType { TOKEN_UNKNOWN = 0, TOKEN_LPAREN, TOKEN_RPAREN, TOKEN_FIXNUM, TOKEN_SYMBOL, TOKEN_BOOL, TOKEN_STRING, } TokenType; typedef struct Token { TokenType type; StringView value; } Token; typedef struct Tokens { Token *start; size_t n; } Tokens; #define TRUE_TOKEN (StringView){"true", 4} #define FALSE_TOKEN (StringView){"false", 5} #define LPAREN_TOKEN (StringView){"(", 1} #define RPAREN_TOKEN (StringView){")", 1} TokenType find_token_type(StringView value) { bool is_fixnum = true; for (size_t i = 0; i < value.n; i++) { char c = value.start[i]; if (i == 0 && c == '-' && value.n > 1) { continue; } if (!isdigit(c)) { is_fixnum = false; break; } } if (is_fixnum) { return TOKEN_FIXNUM; } if (sv_equal(value, TRUE_TOKEN) || sv_equal(value, FALSE_TOKEN)) { return TOKEN_BOOL; } return TOKEN_SYMBOL; } Tokens tokenize(StringView sv) { // NOTE: Not allocating any memory for now, but we are limited by a maximum // number of tokens we can process. #define TOKENS_BUF_SIZE 1024 static Token tokens_buf[TOKENS_BUF_SIZE]; // Clear buffer. for (size_t i = 0; i < TOKENS_BUF_SIZE; i++) { tokens_buf[i] = (Token){0}; } size_t n = 0; size_t token_n = 0; for (size_t i = 0; i < sv.n; i++) { switch (sv.start[i]) { case ' ': case '\f': case '\n': case '\r': case '\t': case '\v': { if (token_n != 0) { Token token = (Token){ .type = TOKEN_UNKNOWN, .value = (StringView){ .start = &sv.start[i - token_n], .n = token_n, } }; token.type = find_token_type(token.value); tokens_buf[n++] = token; token_n = 0; } } break; case ';': { if (token_n != 0) { Token token = (Token){ .type = TOKEN_UNKNOWN, .value = (StringView){ .start = &sv.start[i - token_n], .n = token_n, } }; token.type = find_token_type(token.value); tokens_buf[n++] = token; token_n = 0; } // Advance until the next newline. do { i++; } while (i < sv.n && sv.start[(i + 1)] != '\n'); } break; case '"': { if (token_n != 0) { fprintf(stderr, "error: string started inside symbol\n"); return (Tokens){0}; } // Find end delimiter. size_t string_start = i + 1; size_t string_end = i + 1; while (true) { if (sv.start[string_end] == '"' && sv.start[string_end - 1] != '\\') { break; } if (string_end >= sv.n) { fprintf(stderr, "error: string delimiter not found\n"); return (Tokens){0}; } string_end++; } Token token = (Token){ .type = TOKEN_STRING, .value = (StringView){ .start = &sv.start[string_start], .n = string_end - string_start, } }; tokens_buf[n++] = token; token_n = 0; i += string_end - string_start + 1; } break; case '(': { if ((i + 1) < sv.n) { char next_c = sv.start[i + 1]; if (isspace(next_c)) { fprintf(stderr, "error: lparen delimiter followed by space\n"); return (Tokens){0}; } } if (token_n != 0) { fprintf(stderr, "error: lparen delimiter within symbol name\n"); return (Tokens){0}; } Token token = (Token){ .type = TOKEN_LPAREN, .value = LPAREN_TOKEN, }; tokens_buf[n++] = token; } break; case ')': { if ((i + 1) < sv.n) { char next_c = sv.start[i + 1]; if ((next_c != ')' && !isspace(next_c))) { fprintf(stderr, "error: rparen delimiter within symbol name\n"); return (Tokens){0}; } } if (token_n != 0) { // Push previous token. Token token = (Token){ .type = TOKEN_UNKNOWN, .value = (StringView){ .start = &sv.start[i - token_n], .n = token_n, } }; token.type = find_token_type(token.value); tokens_buf[n++] = token; token_n = 0; } Token token = (Token){ .type = TOKEN_RPAREN, .value = RPAREN_TOKEN, }; tokens_buf[n++] = token; } break; case EOF: { break; } break; default: { token_n++; } break; } } if (token_n != 0) { // End of line encountered. Token token = (Token){ .type = TOKEN_UNKNOWN, .value = (StringView){ .start = &sv.start[sv.n - token_n], .n = token_n, } }; token.type = find_token_type(token.value); tokens_buf[n++] = token; } return (Tokens){.start = (Token *)&tokens_buf, .n = n}; } Token * consume_token(Tokens *tokens) { if (tokens->n == 0) { return NULL; } Token *ret = tokens->start; tokens->start = &tokens->start[1]; tokens->n--; return ret; } typedef enum ObjectType { OBJ_TYPE_FIXNUM, OBJ_TYPE_BOOL, OBJ_TYPE_NIL, OBJ_TYPE_SYMBOL, OBJ_TYPE_STRING, OBJ_TYPE_PAIR, OBJ_TYPE_PROCEDURE, } ObjectType; typedef struct Object { ObjectType type; union { // OBJ_TYPE_FIXNUM ssize_t fixnum; // OBJ_TYPE_BOOL bool boolean; // OBJ_TYPE_STRING struct { char *string; size_t string_n; }; // OBJ_TYPE_PAIR struct { struct Object *car; struct Object *cdr; }; // OBJ_TYPE_SYMBOL struct { char *symbol; size_t symbol_n; }; // OBJ_TYPE_PROCEDURE struct Object *(*proc)(struct Object *args); }; } Object; // // Singletons. // Object *obj_nil; Object *obj_true; Object *obj_false; // // Environment. // typedef struct EnvSymbol { Object *symbol; Object *value; } EnvSymbol; #define ENV_SIZE 256 static EnvSymbol environment[ENV_SIZE]; static size_t env_n = 0; Object * make_fixnum(ssize_t num) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_FIXNUM; obj->fixnum = num; return obj; } Object * make_boolean(bool b) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_BOOL; obj->boolean = b; return obj; } Object * make_empty_string(void) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_STRING; obj->string = NULL; obj->string_n = 0; return obj; } void append_string(Object *string, StringView sv) { assert(string != NULL); assert(string->type == OBJ_TYPE_STRING); if (sv.n == 0) { return; } string->string = realloc(string->string, (string->string_n + sv.n) * sizeof(char)); memcpy(string->string + string->string_n, sv.start, sv.n); string->string_n += sv.n; } Object * make_symbol(const char *str, size_t n) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_SYMBOL; obj->string = malloc(sizeof(char) * n); memcpy(obj->string, str, n); obj->string_n = n; return obj; } Object * make_empty_list(void) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_NIL; return obj; } Object * make_procedure(Object *(*proc)(struct Object *args)) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_PROCEDURE; obj->proc = proc; return obj; } Object * make_pair(Object *car, Object *cdr) { Object *obj = malloc(sizeof(Object)); obj->type = OBJ_TYPE_PAIR; obj->car = car; obj->cdr = cdr; return obj; } Object * parse(Tokens *tokens) { while (tokens->n > 0) { Token *token = consume_token(tokens); if (token == NULL) { return NULL; } switch (token->type) { case TOKEN_FIXNUM: { ssize_t num = 0; int sign = 1; for (size_t i = 0; i < token->value.n; i++) { char c = token->value.start[i]; if (c == '-') { sign = -1; continue; } num = num * 10 + (c - '0'); } return make_fixnum(num * sign); } break; case TOKEN_BOOL: { if (sv_equal(token->value, TRUE_TOKEN)) { return obj_true; } if (sv_equal(token->value, FALSE_TOKEN)) { return obj_false; } } break; case TOKEN_RPAREN: { return NULL; } break; case TOKEN_LPAREN: { if (tokens->n > 0 && tokens->start[0].type == TOKEN_RPAREN) { return obj_nil; } Object *next_obj = parse(tokens); if (next_obj == NULL) { return NULL; } Object *root = make_pair(next_obj, obj_nil); Object *list = root; while (tokens->n > 0 && (next_obj = parse(tokens)) != NULL) { list->cdr = make_pair(next_obj, obj_nil); list = list->cdr; } return root; } break; case TOKEN_STRING: { Object *obj = make_empty_string(); append_string(obj, token->value); return obj; } break; case TOKEN_SYMBOL: { return make_symbol(token->value.start, token->value.n); } break; default: { fprintf(stderr, "error: unknown token\n"); } break; } } return NULL; } void display(Object *root); void display_pair(Object *root) { display(root->car); if (root->cdr->type == OBJ_TYPE_PAIR) { printf(" "); display_pair(root->cdr); } else if (root->cdr->type == OBJ_TYPE_NIL) { return; } else { printf(" . "); display(root->cdr); } } void display(Object *root) { if (root == NULL) { return; } switch (root->type) { case OBJ_TYPE_FIXNUM: { printf("%zd", root->fixnum); } break; case OBJ_TYPE_BOOL: { if (root->boolean) { printf("true"); } else { printf("false"); } } break; case OBJ_TYPE_NIL: { printf("()"); } break; case OBJ_TYPE_STRING: { printf("\"%.*s\"", (int)root->string_n, root->string); } break; case OBJ_TYPE_SYMBOL: { printf(":%.*s", (int)root->symbol_n, root->symbol); } break; case OBJ_TYPE_PAIR: { printf("("); display_pair(root); printf(")"); } break; default: { printf("TYPE NOT IMPLEMENTED FOR DISPLAY."); } break; } } #define REPL_PROMPT "bdl> " Object * eval(Object *root); Object * proc_add(Object *args) { ssize_t tot = 0; while (args->type == OBJ_TYPE_PAIR) { Object *car = eval(args->car); if (car->type != OBJ_TYPE_FIXNUM) { fprintf(stderr, "addition not supported for type %d\n", car->type); return NULL; } tot += car->fixnum; args = args->cdr; } return make_fixnum(tot); } Object * proc_sub(Object *args) { if (args->type != OBJ_TYPE_PAIR) { fprintf(stderr, "substraction not supported for type %d\n", args->type); return NULL; } // Extract first parameter. Object *car = eval(args->car); args = args->cdr; ssize_t tot = car->fixnum; while (args->type == OBJ_TYPE_PAIR) { Object *car = eval(args->car); if (car->type != OBJ_TYPE_FIXNUM) { fprintf(stderr, "substraction not supported for type %d\n", car->type); return NULL; } tot -= car->fixnum; args = args->cdr; } return make_fixnum(tot); } Object * proc_mul(Object *args) { ssize_t tot = 1; while (args->type == OBJ_TYPE_PAIR) { Object *car = eval(args->car); if (car->type != OBJ_TYPE_FIXNUM) { fprintf(stderr, "multiply not supported for type %d\n", car->type); return NULL; } tot *= car->fixnum; args = args->cdr; } return make_fixnum(tot); } Object * proc_div(Object *args) { if (args->type != OBJ_TYPE_PAIR) { fprintf(stderr, "substraction not supported for type %d\n", args->type); return NULL; } // Extract first parameter. Object *car = eval(args->car); args = args->cdr; ssize_t tot = car->fixnum; while (args->type == OBJ_TYPE_PAIR) { Object *car = eval(args->car); if (car->type != OBJ_TYPE_FIXNUM) { fprintf(stderr, "div not supported for type %d\n", car->type); return NULL; } tot /= car->fixnum; args = args->cdr; } return make_fixnum(tot); } bool symbol_eq(Object *a, Object *b) { if (a->type != b->type || a->type != OBJ_TYPE_SYMBOL || a->symbol_n != b->symbol_n) { return false; } for (size_t i = 0; i < a->symbol_n; i++) { if (a->symbol[i] != b->symbol[i]) { return false; } } return true; } Object * find_environment_symbol(Object *symbol) { for (size_t i = 0; i < env_n; i++) { if (symbol_eq(environment[i].symbol, symbol)) { return environment[i].value; } } return NULL; } void init(void) { // Clear env. for (size_t i = 0; i < ENV_SIZE; i++) { environment[i] = (EnvSymbol){0}; } // Initialize singletons. obj_nil = make_empty_list(); obj_true = make_boolean(true); obj_false = make_boolean(false); // Add primitive functions. environment[env_n++] = (EnvSymbol){make_symbol("+", 1), make_procedure(proc_add)}; environment[env_n++] = (EnvSymbol){make_symbol("-", 1), make_procedure(proc_sub)}; environment[env_n++] = (EnvSymbol){make_symbol("*", 1), make_procedure(proc_mul)}; environment[env_n++] = (EnvSymbol){make_symbol("/", 1), make_procedure(proc_div)}; } Object * eval(Object *root) { if (root == NULL) { return NULL; } switch (root->type) { case OBJ_TYPE_FIXNUM: case OBJ_TYPE_BOOL: case OBJ_TYPE_NIL: case OBJ_TYPE_STRING: case OBJ_TYPE_SYMBOL: { return root; } break; case OBJ_TYPE_PAIR: { if (root->car->type == OBJ_TYPE_SYMBOL) { Object *value = find_environment_symbol(root->car); if (value == NULL) { printf("error: symbol not found: `"); display(root->car); printf("`\n"); return NULL; } if (value->type == OBJ_TYPE_PROCEDURE) { return value->proc(root->cdr); } } } break; default: { printf("error: can't eval type %d.\n", root->type); } break; } return NULL; } void eval_line(FILE *fd, char delimiter) { StringView line = read_line(fd, delimiter); Tokens tokens = tokenize(line); #if DEBUG printf("N_TOKENS: %ld\n", tokens.n); for (size_t i = 0; i < tokens.n; i++) { printf("\tTYPE: %3d ", tokens.start[i].type); printf("N: %3ld ", tokens.start[i].value.n); printf("VALUE: "); sv_write(tokens.start[i].value); printf("\n"); } #endif while (tokens.n > 0) { Object *ast = parse(&tokens); if (ast) { #if DEBUG printf("AST: "); display(ast); printf("\n"); printf("EVAL: "); #endif display(eval(ast)); printf("\n"); } } } void run_repl(void) { printf("BDL REPL (Press Ctrl-C to exit)\n"); while (true) { printf(REPL_PROMPT); eval_line(stdin, '\n'); } } void run_file(char *file_name) { #if DEBUG printf("Executing file: %s\n", file_name); #endif FILE *fd = fopen(file_name, "r"); if (!fd) { fprintf(stderr, "couldn't open file: %s\n", file_name); exit(EXIT_FAILURE); } eval_line(fd, EOF); fclose(fd); } void print_usage(void) { printf("Usage: %s [options] \n", BIN_NAME); printf("\n"); printf("\t-i\tInteractive mode (REPL).\n"); printf("\t-x\tExecute expression from stdin.\n"); printf("\n"); } int main(int argc, char *argv[]) { init(); int option; while ((option = getopt(argc, argv, "ix")) != -1) { switch (option) { case 'i': { // Interactive mode. run_repl(); return EXIT_SUCCESS; } break; case 'x': { // Execute expression from stdin. eval_line(stdin, EOF); return EXIT_SUCCESS; } break; default: { print_usage(); return EXIT_FAILURE; } break; } } // Run from file. if (optind != argc - 1) { fprintf(stderr, "%s: No input file given.\n", BIN_NAME); print_usage(); return EXIT_FAILURE; } char *file_name = argv[optind]; run_file(file_name); return EXIT_SUCCESS; }