#include "types.h" #include #include #include #include #include #define Array(t) Array_Named(t, t) #define Array_Named(t, name) \ typedef struct name { \ isize length; \ t elements[]; \ } name##_t Array_Named(i32, line_offsets); // Array_Named(i8, bytes); // Location in the source file typedef struct Location { // Offset in file of the first character u32 offset; // Line number (zero indexed) u32 line; // Offset on the line of the first character u32 column; // Length in bytes u32 size; } Location_t; typedef struct Token { // identifying tag enum TokenType { t_error, t_size, t_multiply, t_divide, t_add, t_subtract, t_space, t_tab, t_newline, t_number, } type; Location_t pos; } Token_t; Array_Named(Token_t, TokenList); typedef struct AExp { enum AExpType { e_error, e_mul, e_div, e_add, e_sub, e_number, } type; union { i32 number; struct AExp* e_unary; struct { struct AExp* a; struct AExp* b; } e_binary; } data; // Token_t* t; } AExp_t; static usize f_read(FILE* restrict stream, char** restrict dst); static usize f_size(FILE* restrict stream); static line_offsets_t* l_read(char const* restrict text, usize text_length, TokenList_t** restrict dst); static AExp_t* p_arith(char const* restrict text, TokenList_t* restrict tokens); static AExp_t p_number(char const* restrict text, Token_t* restrict tnum); static void print_aexp(AExp_t* t); int main(int argc, char* argv[]) { if (argc == 2) { FILE* srcfile = NULL; // pointer to a buffer holding the raw bytes of the file char* contents = NULL; // length of contents usize content_length = 0; // tokens, parsed by l_read TokenList_t* tokens = NULL; // auxilliary structure to keep track of the lines, used for debugging // -- maybe turn off with a flag? line_offsets_t* lines = NULL; srcfile = fopen(argv[1], "re"); if (srcfile == NULL) { fprintf(stdout, "! Failed to open file: %s\n", argv[1]); } if (srcfile != NULL) { content_length = f_read(srcfile, &contents); if (content_length > 0) { printf("%s:%ld\n%s\n", argv[1], content_length, contents); } if (fclose(srcfile)) { fprintf(stdout, "! Failed to close file %s: %s\n", argv[1], strerror(errno)); } } if (content_length > 0) { lines = l_read(contents, strlen(contents), &tokens); } if (lines != NULL) { const isize num_tokens = tokens->length; printf("%ld tokens\n", num_tokens); AExp_t* expression = p_arith(contents, tokens); printf("ast:\n"); print_aexp(expression); printf("\n"); } if (tokens != NULL) { free(tokens); } if (lines != NULL) { free((void*)lines); } if (contents != NULL) { free(contents); } } return 0; } /* Copies the contents of the file stream to *dst. * @param stream An open file stream. * @param dst** Destination buffer. If *dst is NULL allocates a buffer the size * of stream using malloc. * @return The number of bytes copied into dst. * @error returns -1 on error. */ static usize f_read(FILE* restrict stream, char** restrict dst) { usize file_sz = f_size(stream); if (dst == NULL) { fprintf(stderr, "! Got NULL ptr\n"); } if (dst != NULL && *dst == NULL && file_sz > 0) { *dst = (char*)malloc(file_sz * sizeof(char)); } if (dst != NULL && *dst == NULL && file_sz > 0) { fprintf(stderr, "! Failed to allocate %ld bytes: %s\n", file_sz * sizeof(char), strerror(errno)); } usize bytes_read = 0; if (dst != NULL && *dst != NULL && file_sz > 0) { bytes_read = fread(*dst, sizeof(char), file_sz, stream); } if (bytes_read < file_sz && feof(stream)) { fprintf(stderr, "! Failed to read file, read %ld out of %ld bytes\n", bytes_read, file_sz); } return bytes_read; } /* Returns the size in bytes and rewinds the stream. * @param stream An open file stream. * @return the size of the file in bytes. * @error Returns -1 on error. */ static usize f_size(FILE* restrict stream) { // Could ftell and rewind to previous position, but scratch that i64 size = -1; i32 error = fseek(stream, 0, SEEK_END); if (error) { // TODO: handle pipes (check errno == ESPIPE) fprintf(stderr, "! Failed to seek file: %s\n", strerror(errno)); } else { size = ftell(stream); error = fseek(stream, 0, SEEK_SET); if (error) { fprintf(stderr, "! Failed to rewind file: %s\n", strerror(errno)); } } if (size == -1) { fprintf(stderr, "! Failed to ftell file: %s\n", strerror(errno)); size = 0; } return (usize)size; } /* Reads text and parses it into an array of tokens * @param text A buffer holding the input text * @return a pointer to dst * @error Returns NULL on error. */ static line_offsets_t* l_read(char const* restrict text, usize text_length, TokenList_t** restrict dst) { line_offsets_t* lines = NULL; if (dst == NULL) { fprintf(stderr, "dst is NULL"); } if (text_length > 0 && dst != NULL && *dst == NULL) { // upper bound of tokens must be equal to text_length *dst = (TokenList_t*)malloc(sizeof(isize) + text_length * sizeof(Token_t)); lines = (line_offsets_t*)malloc(sizeof(isize) + text_length * sizeof(i32)); } if (text_length > 0 && dst != NULL && *dst == NULL) { fprintf(stderr, "! Failed to allocate %ld bytes for `dst`\n", sizeof(isize) + text_length * sizeof(Token_t)); } if (text_length > 0 && dst != NULL && lines == NULL) { fprintf(stderr, "! Failed to allocate %ld bytes for `lines`\n", sizeof(isize) + text_length * sizeof(i32)); } if (text_length > 0 && dst != NULL && *dst != NULL && lines != NULL) { // Token counter isize tokenno = 0; i32 error = 0; enum TokenType token_previous = t_error; enum TokenType token = t_error; Location_t cursor; cursor.line = 0; cursor.column = 1; cursor.size = 0; do { switch (text[cursor.offset]) { // case t_size: // break; case '*': token = t_multiply; break; case '/': token = t_divide; break; case t_add: token = t_add; break; case '-': token = t_subtract; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': token = t_number; break; case '+': token = t_add; break; case ' ': token = t_space; break; case '\t': token = t_tab; break; case '\n': token = t_newline; break; default: token = t_error; error = 1; break; } if (token == token_previous) { (*dst)->elements[tokenno - 1].pos.size = ++cursor.size; } if (token != token_previous) { cursor.size = 1; (*dst)->elements[tokenno].type = token; (*dst)->elements[tokenno].pos = cursor; tokenno++; token_previous = token; } if (token == t_newline) { lines->elements[cursor.line] = cursor.offset; cursor.line++; cursor.column = 0; } cursor.offset++; cursor.column++; } while (!error && cursor.offset < text_length && cursor.offset < INT_MAX); (*dst)->length = tokenno; lines->length = cursor.line; } return lines; } static AExp_t* p_arith(char const* restrict text, TokenList_t* restrict tokens) { i64 exp_idx = 0; // "groups" or parenthesized expressions // i64 groups = 0; const i64 num_tokens = tokens->length; // upper bounded by tokens AExp_t* expression = calloc(num_tokens, sizeof(AExp_t)); // Root is the "current working root" AExp_t* root = NULL; // Exp is the return expression, or the "root" of the expression that has // parsed. AExp_t* exp = NULL; for (int tokenidx = 0; tokenidx < num_tokens; tokenidx++) { // ignore whitespace if (tokens->elements[tokenidx].type == t_space || tokens->elements[tokenidx].type == t_tab || tokens->elements[tokenidx].type == t_newline) { continue; } if (tokens->elements[tokenidx].type == t_number && root == NULL) { expression[exp_idx] = p_number(text, &tokens->elements[tokenidx]); root = &expression[exp_idx]; exp = &expression[exp_idx]; exp_idx++; } else if (tokens->elements[tokenidx].type == t_number && root != NULL && root->type == e_number) { fprintf(stderr, "Unexpected number at %d:%d\n", tokens->elements[tokenidx].pos.line, tokens->elements[tokenidx].pos.column); return exp; } else if (tokens->elements[tokenidx].type == t_number && root != NULL && root->type != e_number) { if (exp_idx > 0 && expression[exp_idx - 1].type == e_number) { fprintf(stderr, "Unexpected number at %d:%d\n", tokens->elements[tokenidx].pos.line, tokens->elements[tokenidx].pos.column); return exp; } expression[exp_idx] = p_number(text, &tokens->elements[tokenidx]); root->data.e_binary.b = &expression[exp_idx]; // "pop" "root" stack here? root = exp; exp_idx++; } else if (tokens->elements[tokenidx].type != t_number && root == NULL) { fprintf(stderr, "Unexpected operator at %d:%d\n", tokens->elements[tokenidx].pos.line, tokens->elements[tokenidx].pos.column); return exp; } else if (tokens->elements[tokenidx].type != t_number && root != NULL) { if (tokens->elements[tokenidx].type == t_multiply) { expression[exp_idx] = (AExp_t){.type = e_mul}; } else if (tokens->elements[tokenidx].type == t_divide) { expression[exp_idx] = (AExp_t){.type = e_div}; } else if (tokens->elements[tokenidx].type == t_add) { expression[exp_idx] = (AExp_t){.type = e_add}; } else if (tokens->elements[tokenidx].type == t_subtract) { expression[exp_idx] = (AExp_t){.type = e_sub}; } // Currently best way to test if "leaf" or binary operator, also only // binary are handled if (root->type == e_number) { expression[exp_idx].data.e_binary.a = root; root = &expression[exp_idx]; exp = root; } else { // Currently best way to test precendence (root < expression) :( if ((expression[exp_idx].type == e_mul || expression[exp_idx].type == e_div) && (root->type == e_add || root->type == e_sub)) { expression[exp_idx].data.e_binary.a = root->data.e_binary.b; // TODO: check precedence of exp vs root exp = root; root->data.e_binary.b = &expression[exp_idx]; root = &expression[exp_idx]; } else { expression[exp_idx].data.e_binary.a = root; root = &expression[exp_idx]; exp = root; } } exp_idx++; } } return exp; } static AExp_t p_number(char const* restrict text, Token_t* restrict tnum) { i32 number = 0; char* start = (char*)&text[tnum->pos.offset]; for (u32 i = 0; i < tnum->pos.size; i++) { number *= 10; number += (*start - '0'); start++; } return (AExp_t){ .type = e_number, .data.number = number, }; } static void print_aexp(AExp_t* t) { if (t == NULL) { printf("nil"); return; } switch (t->type) { case e_error: fprintf(stderr, "error"); break; case e_mul: printf("( * "); print_aexp(t->data.e_binary.a); printf(" "); print_aexp(t->data.e_binary.b); printf(" )"); break; case e_div: printf("( / "); print_aexp(t->data.e_binary.a); printf(" "); print_aexp(t->data.e_binary.b); printf(" )"); break; case e_add: printf("( + "); print_aexp(t->data.e_binary.a); printf(" "); print_aexp(t->data.e_binary.b); printf(" )"); break; case e_sub: printf("( - "); print_aexp(t->data.e_binary.a); printf(" "); print_aexp(t->data.e_binary.b); printf(" )"); break; case e_number: printf("%d", t->data.number); break; } }