From 574cf9219ed04f3e8099ac131134eb64b443759d Mon Sep 17 00:00:00 2001 From: r4 Date: Thu, 1 Jul 2021 17:04:13 +0200 Subject: [PATCH] Initial upload --- Makefile | 40 +++++++++++ ast.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++ ast.h | 24 +++++++ error.c | 30 +++++++++ error.h | 23 +++++++ leakcheck.sh | 4 ++ lex.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++ lex.h | 55 +++++++++++++++ main.c | 72 ++++++++++++++++++++ ptr_stack.c | 33 +++++++++ ptr_stack.h | 31 +++++++++ token.c | 71 ++++++++++++++++++++ token.h | 52 ++++++++++++++ token_list.c | 130 +++++++++++++++++++++++++++++++++++ token_list.h | 36 ++++++++++ 15 files changed, 958 insertions(+) create mode 100644 Makefile create mode 100644 ast.c create mode 100644 ast.h create mode 100644 error.c create mode 100644 error.h create mode 100755 leakcheck.sh create mode 100644 lex.c create mode 100644 lex.h create mode 100644 main.c create mode 100644 ptr_stack.c create mode 100644 ptr_stack.h create mode 100644 token.c create mode 100644 token.h create mode 100644 token_list.c create mode 100644 token_list.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa86cb4 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +CC=gcc +CFLAGS=-Wall -O3 -march=native -std=c99 +LDFLAGS= + +EXE=str_eval + +ODIR=obj + +LIBS=-lm + +DEPS=lex.h ast.h token.h token_list.h ptr_stack.h error.h + +_OBJ=lex.c ast.c token.c token_list.c ptr_stack.c error.c main.c +OBJ=$(patsubst %.c,$(ODIR)/%.o,$(_OBJ)) + +all: makedepend odir $(EXE) + +run: all + ./$(EXE) + +$(ODIR)/%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) $(LIBS) + +$(EXE): $(OBJ) + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) $(LDFLAGS) + +makedepend: $(_OBJ) $(DEPS) + echo "# Automatically generated by makedepend" > $@ + makedepend -Y -f $@ -p $(ODIR)/ $(_OBJ) 2>/dev/null + rm $@.bak + +odir: + mkdir -p $(ODIR) + +.PHONY: clean makedepend + +clean: + rm -rf $(ODIR) $(EXE) makedepend + +include makedepend diff --git a/ast.c b/ast.c new file mode 100644 index 0000000..640294e --- /dev/null +++ b/ast.c @@ -0,0 +1,171 @@ +/* vim: set filetype=c: */ + +#include "ast.h" + +#include "ptr_stack.h" + +#include +#include +#include + +void AST_init(AST* obj) { + obj->root = NULL; +} + +static Token* new_default_ExprToken_() { + Token* tkn = malloc(sizeof(Token)); + tkn->type = TokenTypeExpr; + tkn->data.expr.lhs = NULL; /* Left operand */ + tkn->data.expr.op = NULL; /* Operator */ + tkn->data.expr.rhs = NULL; /* Right operand */ + return tkn; +} + +static Token* new_NumToken_with_num_(NumToken val) { + Token* tkn = malloc(sizeof(Token)); + tkn->type = TokenTypeNum; + tkn->data.num = val; + return tkn; +} + +static Token* new_OpToken_with_sym_(OpToken val) { + Token* tkn = malloc(sizeof(Token)); + tkn->type = TokenTypeOp; + tkn->data.op = val; + return tkn; +} + +Result AST_parse_from_TokenList(AST* obj, const TokenList* tokens) { + /* If root weren't NULL, the AST was either not initialized, or + * it was already populated */ + assert(obj->root == NULL); + + obj->root = new_default_ExprToken_(); + + PtrStack node_stack; /* Always has curr_node at the top, followed by curr_node's parent, etc.. */ + PtrStack_init(&node_stack); + Token* curr_node = obj->root; /* Node means ExprToken in this case */ + PtrStack_push(&node_stack, curr_node); + /* Linearly iterate through every token the lexer generated */ + const TokenListItem* curr; + for(curr = tokens->front; curr != NULL; curr = curr->next) { + switch(curr->val.type) { + default: + return Result_err("Found invalid token type while building AST"); + break; + case TokenTypeSep: { + if(curr->val.data.sep.sym == '(') { + /* Create a new sub expression as a child of the current expression */ + Token* new_tkn = new_default_ExprToken_(); + + /* The left and right hand operands get filled from left to right; + * insert the new sub expression into the next free operand slot, + * free meaning unassigned or NULL */ + if(curr_node->data.expr.lhs == NULL) + curr_node->data.expr.lhs = new_tkn; + else if (curr_node->data.expr.rhs == NULL) + curr_node->data.expr.rhs = new_tkn; + else { + free(new_tkn); /* Free new_tkn, as it is unused due to an error */ + return Result_err("Found more than 2 operands for 1 operator while building AST"); + break; + } + + /* Push the new curr_node onto the pointer stack to allow to + * go a layer back, as needed in case of an rparen */ + curr_node = new_tkn; + PtrStack_push(&node_stack, curr_node); + + } else /* if(curr->val.data.sep.sym == ')') */ { + /* Go back a layer, effectively changing curr_node to its parent */ + PtrStack_pop(&node_stack); + curr_node = node_stack.top->ptr; + } + break; + } + case TokenTypeNum: { + assert(curr_node->type == TokenTypeExpr); + Token* num_tkn = new_NumToken_with_num_(curr->val.data.num); + + /* Fill the curr_node expression operands from left to right */ + if(curr_node->data.expr.lhs == NULL) + curr_node->data.expr.lhs = num_tkn; + else if (curr_node->data.expr.rhs == NULL) + curr_node->data.expr.rhs = num_tkn; + else { + free(num_tkn); /* Free num_tkn, as it is unused due to an error */ + return Result_err("Found more than 2 operands for 1 operator while building AST"); + break; + } + + break; + } + case TokenTypeOp: { + if(curr_node->data.expr.op == NULL) { + /* Fill the expression token's operator field with exactly the same + * data as the current token */ + Token* op_tkn = new_OpToken_with_sym_(curr->val.data.op); + curr_node->data.expr.op = op_tkn; + } else { + return Result_err("Found more than 1 operator in a single expression while building the AST"); + break; + } + break; + } + } + } + PtrStack_uninit(&node_stack); + return Result_noerr(); +} + +static long double AST_eval_node_(Token* node) { + assert(node->type == TokenTypeExpr); + long double a, b; + if(node->data.expr.lhs == NULL) + /* TODO: Error handling */ + return 0; + if(node->data.expr.lhs->type == TokenTypeExpr) + a = AST_eval_node_(node->data.expr.lhs); + else /* if(node->data.expr.lhs->type == TokenTypeNum) */ + a = node->data.expr.lhs->data.num; + + if(node->data.expr.rhs == NULL) + /* If there is no right hand side expression, just return the left hand one */ + return a; + if(node->data.expr.rhs->type == TokenTypeExpr) + b = AST_eval_node_(node->data.expr.rhs); + else /* if(node->data.expr.rhs->type == TokenTypeNum) */ + b = node->data.expr.rhs->data.num; + + switch(node->data.expr.op->data.op) { + case '+': + return a + b; + break; + case '-': + return a - b; + break; + case '*': + return a * b; + break; + case '/': + return a / b; + break; + case '^': + return pow(a, b); + break; + default: + /* TODO: Error handling */ + break; + } + return 0; +} + +long double AST_evaluate(AST* obj) { + return AST_eval_node_(obj->root); +} + +void AST_uninit(AST* obj) { + if(obj->root) + /* uninit and deallocate the root token and its children */ + ExprToken_uninit_recursive(obj->root); +} diff --git a/ast.h b/ast.h new file mode 100644 index 0000000..ea6963e --- /dev/null +++ b/ast.h @@ -0,0 +1,24 @@ +/* vim: set filetype=c: */ + +#ifndef _AST_H_ +#define _AST_H_ + +#include "token.h" +#include "token_list.h" +#include "error.h" + +typedef struct AST AST; +struct AST { + Token* root; +}; + +extern void AST_init(AST* obj); + +/* Can be called exactly once after AST_init */ +extern Result AST_parse_from_TokenList(AST* obj, const TokenList* tokens); + +extern long double AST_evaluate(AST* obj); + +extern void AST_uninit(AST* obj); + +#endif /* _AST_H_ */ diff --git a/error.c b/error.c new file mode 100644 index 0000000..2dd8c31 --- /dev/null +++ b/error.c @@ -0,0 +1,30 @@ +/* vim: set filetype=c: */ + +#include "error.h" + +#include + +Result Result_err(char* err_str) { + Result res; + res.has_err = true; + res.err_str = err_str; + return res; +} + +Result Result_noerr() { + Result res; + res.has_err = false; + res.err_str = NULL; + return res; +} + +extern void Result_print_err_or(const Result obj, const char* err_prefix, const char* no_err, FILE* err_file, FILE* no_err_file) { + if(obj.has_err) { + if(err_prefix != NULL) + fprintf(err_file, "%s", err_prefix); + assert(obj.err_str != NULL); + fprintf(err_file, "%s\n", obj.err_str); + } + else if(no_err != NULL) + fprintf(no_err_file, "%s\n", no_err); +} diff --git a/error.h b/error.h new file mode 100644 index 0000000..d884f27 --- /dev/null +++ b/error.h @@ -0,0 +1,23 @@ +/* vim: set filetype=c: */ + +#ifndef _ERROR_H_ +#define _ERROR_H_ + +#include +#include + +typedef struct Result Result; +struct Result { + bool has_err; + char* err_str; +}; + +extern Result Result_err(char* err_str); +extern Result Result_noerr(); +/* err_prefix: string to print before the error, if on occurred; prints nothing if it has value NULL + * no_err: string to print if no error occured; prints nothing if it has value NULL + * err_file: where to output the string, if an error has occurred + * no_err_file: where to output, if no error has occurred */ +extern void Result_print_err_or(const Result obj, const char* err_prefix, const char* no_err, FILE* err_file, FILE* no_err_file); + +#endif /* _ERROR_H_ */ diff --git a/leakcheck.sh b/leakcheck.sh new file mode 100755 index 0000000..4823a3a --- /dev/null +++ b/leakcheck.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +make +EXPR="2.5/(5+3)-2*4/3.3^4.1^5" +echo "$EXPR" | valgrind --leak-check=yes ./str_eval diff --git a/lex.c b/lex.c new file mode 100644 index 0000000..862bf9e --- /dev/null +++ b/lex.c @@ -0,0 +1,186 @@ +/* vim: set filetype=c: */ + +#include "lex.h" + +#include + +void Lex_init(Lex* obj) { + TokenList_init(&obj->tokens); + PtrStack_init(&obj->ptr_stack_); + + obj->out_read_total = 0; + obj->out_written_total = 0; + + obj->context_size_ = 0; +} + +Result Lex_lex_char(Lex* obj, char in) { + /* If the current char is part of a Num token */ + if((in >= '0' && in <= '9') || in == '.') { + /* Don't let contexts become as large as the predefined size, as + * we still need space for the null string terminator char \0 */ + if(obj->context_size_ < LEX_MAX_CONTEXT_SIZE - 1) { + obj->context_[obj->context_size_] = in; + obj->context_size_++; + } + /* If the current char is not part of a number, but the + * previous one was, write the number as a token */ + } else { + if(obj->context_size_) { + /* Add the context string terminator */ + obj->context_[obj->context_size_] = '\0'; + obj->context_size_++; + /* Write the token */ + TokenListItem* itm = malloc(sizeof(TokenListItem)); + itm->val.type = TokenTypeNum; + itm->val.data.num = strtold(obj->context_, NULL); + TokenList_push_back(&obj->tokens, itm); + obj->context_size_ = 0; + } + /* If the char is not a whitespace, newline, string terminator, etc.. */ + if(!(in == ' ' || in == '\t' || in == '\n' || in == '\0')) { + switch(in) { + case '(': + case ')': { + TokenListItem* itm = malloc(sizeof(TokenListItem)); + itm->val.type = TokenTypeSep; + itm->val.data.sep.sym = in; + + if(in == '(') + PtrStack_push(&obj->ptr_stack_, (void*)itm); + else /* if in == ')' */ { + /* Throw an error, if the pointer stack has no top element */ + if(obj->ptr_stack_.size == 0) + return Result_err("Found ')' without matching '('"); + /* Matching LParen */ + TokenListItem* match = obj->ptr_stack_.top->ptr; + /* Define the RParen's matching LParen */ + itm->val.data.sep.matching = match; + /* Define the LParen's matching RParen */ + match->val.data.sep.matching = itm; + /* Pop the top element */ + PtrStack_pop(&obj->ptr_stack_); + } + + TokenList_push_back(&obj->tokens, itm); + return Result_noerr(); + break; + } + case '+': + case '-': + case '*': + case '/': + case '^': { + TokenListItem* itm = malloc(sizeof(TokenListItem)); + itm->val.type = TokenTypeOp; + itm->val.data.op = in; + TokenList_push_back(&obj->tokens, itm); + return Result_noerr(); + break; + } + default: + return Result_err("Unrecognized character"); + break; + } + } + } + return Result_noerr(); +} + +Result Lex_finish_lex(Lex* obj) { + Result res = Lex_lex_char(obj, '\n'); + if(res.has_err) + return res; + if(obj->ptr_stack_.size) { + return Result_err("Found '(' without matching ')'"); + } + return Result_noerr(); +} + +static bool contains_char_(char c, const char* charset) { + size_t i; + for(i = 0; charset[i] != '\0'; i++) { + if(charset[i] == c) + return true; + } + return false; +} + +Result Lex_apply_op_precedence(Lex* obj, const char* opsyms, bool right_to_left) { + /* Iterate through tokens, where curr is the current token */ + TokenListItem* const begin = right_to_left ? obj->tokens.back : obj->tokens.front; + TokenListItem* curr; + for(curr = begin; curr != NULL; curr = right_to_left ? curr->prev : curr->next) { + if(curr->val.type != TokenTypeOp) + continue; + if(curr->prev == NULL || curr->next == NULL) { + return Result_err("Found an operator with at least one missing operand"); + continue; + } + if(!contains_char_(curr->val.data.op, opsyms)) + continue; + /* Items right and left, takes into consideration potential parentheses: + * For example, if we have (4+3)*5, lofop will be the position of the left paren, + * and rofop will be the position of the 5. This is possible due to each paren + * having a pointer to its matching paren (for details on how it works, refer to + * Lex_lex_char()). */ + TokenListItem* lofop = NULL; /* Item left of operator */ + TokenListItem* rofop = NULL; /* Item right of operator */ + /* lofop */ + if(curr->prev->val.type == TokenTypeNum) + lofop = curr->prev; + else if(curr->prev->val.type == TokenTypeSep) + /* Find the matching paren and position lofop there */ + lofop = curr->prev->val.data.sep.matching; + else { + return Result_err("Invalid operand type left of operator"); + } + + /* rofop: essentially the same as lofop */ + if(curr->next->val.type == TokenTypeNum) + rofop = curr->next; + else if(curr->next->val.type == TokenTypeSep) + rofop = curr->next->val.data.sep.matching; + else { + return Result_err("Invalid operand type right of operator"); + } + + assert(lofop != NULL); + assert(rofop != NULL); + + /* Don't add any parens, if they would be redundant. + * We know that they are redundant, if lofop has an + * LParen left of it and rofop has an RParen right of it*/ + if( + /* Check for LParen left of lofop */ + lofop->prev != NULL && + lofop->prev->val.type == TokenTypeSep && + lofop->prev->val.data.sep.sym == '(' && + /* Check for RParen right of rofop */ + rofop->next != NULL && + rofop->next->val.type == TokenTypeSep && + rofop->next->val.data.sep.sym == ')' + ) continue; + + + /* Insert parens to make the AST builder handle precedence */ + TokenListItem* lp = malloc(sizeof(TokenListItem)); + TokenListItem* rp = malloc(sizeof(TokenListItem)); + + lp->val.type = TokenTypeSep; + lp->val.data.sep.sym = '('; + lp->val.data.sep.matching = rp; + TokenList_insert_before(&obj->tokens, lofop, lp); + + rp->val.type = TokenTypeSep; + rp->val.data.sep.sym = ')'; + rp->val.data.sep.matching = lp; + TokenList_insert_after(&obj->tokens, rofop, rp); + } + return Result_noerr(); +} + +void Lex_uninit(Lex* obj) { + TokenList_uninit(&obj->tokens); + PtrStack_uninit(&obj->ptr_stack_); +} diff --git a/lex.h b/lex.h new file mode 100644 index 0000000..8487a9d --- /dev/null +++ b/lex.h @@ -0,0 +1,55 @@ +/* vim: set filetype=c: */ + +#ifndef _LEX_H_ +#define _LEX_H_ + +#include +#include +#include "token.h" +#include "token_list.h" +#include "ptr_stack.h" +#include "error.h" + +#define LEX_MAX_CONTEXT_SIZE 256 + +typedef struct Lex Lex; +struct Lex { + /** Output **/ + TokenList tokens; + /* Stats */ + size_t out_written_total; /* Total number of tokens put out */ + size_t out_read_total; /* Total number of read chars */ + + /** Internal **/ + /* Stores a stack of pointers to LParen TokenListItems. Using this, + * we can always know where each paren's matching paren is located, + * which allows for a fast operator precedence implementation. */ + PtrStack ptr_stack_; + /* Holds chars of text that haven't completely been lexed yet */ + char context_[LEX_MAX_CONTEXT_SIZE]; + size_t context_size_; +}; + +extern void Lex_init(Lex* obj); + +extern Result Lex_lex_char(Lex* obj, char in); + +extern Result Lex_finish_lex(Lex* obj); + +/* This function MUST be called exactly once for every operator , as it transforms the + * expression into a representation which allows for an easy creation of a tertiary tree. + * The order of calls to this function is what dictates the actual precedence. + * Usage: Call this function once for each operator precedence: + * opsym is a string with the operator characters to apply precedence to, for example "+-"; + * right_to_left specifies the direction. For most math operators, this should be false, but + * for pow for example, it should be true. + * You should call this function for the operators with highest priority, i.e. pow first. + * An example setup would be: + * Lex_apply_op_precedence(&lex, "^", true); + * Lex_apply_op_precedence(&lex, "*:", false); + * Lex_apply_op_precedence(&lex, "+-", false); */ +extern Result Lex_apply_op_precedence(Lex* obj, const char* opsyms, bool right_to_left); + +extern void Lex_uninit(Lex* obj); + +#endif /* _LEX_H_ */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..4629a10 --- /dev/null +++ b/main.c @@ -0,0 +1,72 @@ +/* vim: set filetype=c: */ + +#include "lex.h" +#include "ast.h" + +#include +#include + +#define INPUT_SIZE 512 + +static void print_tokens(const Lex* lex) { + const TokenListItem* curr; + for(curr = lex->tokens.front; curr != NULL; curr = curr->next) { + Token_print(&curr->val, stdout); + fputc('\n', stdout); + } +} + +static void handle_normal_err(const Result res, const char* domain) { + Result_print_err_or(res, domain, NULL, stderr, stdout); + if(res.has_err) + exit(EXIT_FAILURE); +} + +int main() { + size_t i; + Result res; + char in[INPUT_SIZE]; + + Lex lex; + Lex_init(&lex); + + printf("Enter an expression:\n"); + for(i = 0;;i++) { + const char c = fgetc(stdin); + if(i >= INPUT_SIZE - 1 || c == '\n' || c == EOF) { + in[i] = '\0'; /* String terminator */ + break; + } + in[i] = c; + } + for(i = 0; in[i] != '\0'; i++) { + res = Lex_lex_char(&lex, in[i]); + handle_normal_err(res, "Lexer error: "); + } + res = Lex_finish_lex(&lex); + handle_normal_err(res, "Lexer error: "); + printf("\n**Tokens**\n"); + print_tokens(&lex); + res = Lex_apply_op_precedence(&lex, "^", true); + handle_normal_err(res, "Lexer error: "); + res = Lex_apply_op_precedence(&lex, "*/", false); + handle_normal_err(res, "Lexer error: "); + res = Lex_apply_op_precedence(&lex, "+-", false); + handle_normal_err(res, "Lexer error: "); + printf("\n**Tokens (with operator precedence)**\n"); + print_tokens(&lex); + + AST ast; + AST_init(&ast); + res = AST_parse_from_TokenList(&ast, &lex.tokens); + handle_normal_err(res, "Parser error: "); + printf("\n**AST**\n"); + Token_print_as_tree(ast.root, stdout); + + long double result = AST_evaluate(&ast); + printf("Result: %Lf\n", result); + + AST_uninit(&ast); + Lex_uninit(&lex); + return EXIT_SUCCESS; +} diff --git a/ptr_stack.c b/ptr_stack.c new file mode 100644 index 0000000..6b04231 --- /dev/null +++ b/ptr_stack.c @@ -0,0 +1,33 @@ +/* vim: set filetype=c: */ + +#include "ptr_stack.h" + +#include + +void PtrStack_init(PtrStack* obj) { + obj->top = NULL; + obj->size = 0; +} + +void PtrStack_push(PtrStack* obj, void* ptr) { + PtrStackItem* itm = malloc(sizeof(PtrStackItem)); + itm->ptr = ptr; + itm->prev = obj->top; + obj->top = itm; + obj->size++; +} +void PtrStack_pop(PtrStack* obj) { + PtrStackItem* itm = obj->top; + obj->top = itm->prev; + free(itm); + obj->size--; +} + +void PtrStack_uninit(PtrStack* obj) { + PtrStackItem* curr = obj->top; + while(curr != NULL) { + PtrStackItem* prev = curr->prev; + free(curr); + curr = prev; + } +} diff --git a/ptr_stack.h b/ptr_stack.h new file mode 100644 index 0000000..7580704 --- /dev/null +++ b/ptr_stack.h @@ -0,0 +1,31 @@ +/* vim: set filetype=c: */ + +#ifndef _PTR_STACK_H_ +#define _PTR_STACK_H_ + +#include + +/* THIS STRUCT IS ONLY FOR HOLDING POINTERS AS REFERENCES, NOT + * HEAP ALLOCATED OBJECTS. IT DOES NOT AUTOMATICALLY ALLOC OR FREE + * ANYTHING BUT INTERNAL OBJECTS. */ + +typedef struct PtrStackItem PtrStackItem; +struct PtrStackItem { + PtrStackItem* prev; + void* ptr; +}; + +typedef struct PtrStack PtrStack; +struct PtrStack { + PtrStackItem* top; + size_t size; +}; + +extern void PtrStack_init(PtrStack* obj); + +extern void PtrStack_push(PtrStack* obj, void* ptr); +extern void PtrStack_pop(PtrStack* obj); + +extern void PtrStack_uninit(PtrStack* obj); + +#endif /* _PTR_STACK_H_ */ diff --git a/token.c b/token.c new file mode 100644 index 0000000..7e64cdc --- /dev/null +++ b/token.c @@ -0,0 +1,71 @@ +/* vim: set filetype=c: */ + +#include "token.h" + +#include +#include + +void ExprToken_uninit_recursive(Token* obj) { + if(obj->type == TokenTypeExpr) { + /* Uninit lhs recursively */ + if(obj->data.expr.lhs) + ExprToken_uninit_recursive(obj->data.expr.lhs); + /* Uninit op */ + if(obj->data.expr.op) { + assert(obj->data.expr.op->type == TokenTypeOp); + free(obj->data.expr.op); + } + /* Uninit rhs recursively */ + if(obj->data.expr.rhs) + ExprToken_uninit_recursive(obj->data.expr.rhs); + } else { + /* If it's not an ExprToken, the type must be NumToken */ + assert(obj->type == TokenTypeNum); + } + free(obj); /* Free the token itself */ +} + +void Token_print(const Token* obj, FILE* file) { + switch(obj->type) { + default: + fprintf(file, "(Invalid)"); + break; + case TokenTypeNull: + fprintf(file, "(Null)"); + break; + case TokenTypeNum: + fprintf(file, "(Num, %Lf)", obj->data.num); + break; + case TokenTypeSep: + fprintf(file, "(Sep, '%c')", obj->data.sep.sym); + break; + case TokenTypeOp: + fprintf(file, "(Op, '%c')", obj->data.op); + break; + case TokenTypeExpr: + fprintf(file, "(Expr)"); + break; + } +} + +static void Token_print_as_tree_(const Token* obj, FILE* file, size_t depth) { + /* Put spaces in front for hierarchical view */ + size_t i; + for(i = 0; i < depth * 4; i++) + fputc(' ', file); + Token_print(obj, file); /* Print the token itself */ + fputc('\n', file); /* Print newline */ + /* Print the children one layer deeper */ + if(obj->type == TokenTypeExpr) { + if(obj->data.expr.lhs != NULL) + Token_print_as_tree_(obj->data.expr.lhs, file, depth + 1); + if(obj->data.expr.op != NULL) + Token_print_as_tree_(obj->data.expr.op, file, depth + 1); + if(obj->data.expr.rhs != NULL) + Token_print_as_tree_(obj->data.expr.rhs, file, depth + 1); + } +} + +void Token_print_as_tree(const Token* obj, FILE* file) { + Token_print_as_tree_(obj, file, 0); +} diff --git a/token.h b/token.h new file mode 100644 index 0000000..228425f --- /dev/null +++ b/token.h @@ -0,0 +1,52 @@ +/* vim: set filetype=c: */ + +#ifndef _TOKEN_H_ +#define _TOKEN_H_ + +#include + +typedef struct Token Token; +struct Token; +typedef struct TokenListItem TokenListItem; +struct TokenListItem; + +typedef long double NumToken; +typedef struct SepToken SepToken; +struct SepToken { + char sym; + TokenListItem* matching; +}; +typedef char OpToken; +typedef struct ExprToken ExprToken; +struct ExprToken { + Token* lhs; + Token* op; + Token* rhs; +}; + +typedef struct Token Token; +struct Token { + enum { + TokenTypeNull, /* Invalid type */ + TokenTypeNum, + TokenTypeSep, + TokenTypeOp, + TokenTypeExpr, + } type; + + union { + NumToken num; + SepToken sep; + OpToken op; + ExprToken expr; + } data; +}; + +/* Recursively frees Token of type ExprToken and its children */ +extern void ExprToken_uninit_recursive(Token* obj); + +extern void Token_print(const Token* obj, FILE* file); + +extern void Token_print_as_tree(const Token* obj, FILE* file); + +#endif /* _TOKEN_H_ */ diff --git a/token_list.c b/token_list.c new file mode 100644 index 0000000..dba7569 --- /dev/null +++ b/token_list.c @@ -0,0 +1,130 @@ +/* vim: set filetype=c: */ + +#include "token_list.h" + +#include + +void TokenList_init(TokenList* obj) { + obj->front = NULL; + obj->back = NULL; + obj->size = 0; +} + +static void TokenList_add_initial_item_(TokenList* obj, TokenListItem* data) { + obj->front = obj->back = data; + data->prev = data->next = NULL; + obj->size++; +} + +void TokenList_push_back(TokenList* obj, TokenListItem* data) { + if(obj->size == 0) { + TokenList_add_initial_item_(obj, data); + return; + } + data->prev = obj->back; + obj->back->next = data; + obj->back = data; + data->next = NULL; + obj->size++; +} + + +void TokenList_push_front(TokenList* obj, TokenListItem* data) { + if(obj->size == 0) { + TokenList_add_initial_item_(obj, data); + return; + } + data->next = obj->front; + obj->front->prev = data; + obj->front = data; + data->prev = NULL; + obj->size++; +} + +void TokenList_insert_before(TokenList* obj, TokenListItem* itm, TokenListItem* data) { + if(itm == obj->front) { + TokenList_push_front(obj, data); + return; + } + /* See insert_after for details */ + data->prev = itm->prev; + data->next = itm; + itm->prev->next = data; + itm->prev = data; + obj->size++; +} + +void TokenList_insert_after(TokenList* obj, TokenListItem* itm, TokenListItem* data) { + if(itm == obj->back) { + TokenList_push_back(obj, data); + return; + } + /* Initial state + * +---+ -> +---+ -> +---+ + * |itm| | | | | + * +---+ <- +---+ <- +---+ + * +----+ + * |data| + * +----+ */ + + /* Make data point to its new neighbours + * +---+ -> +---+ -> +---+ + * |itm| | | | | + * +---+ <- +---+ <- +---+ + * ^ ^ + * | | + * | +----+ -+ + * | |data| + * +- +----+ */ + data->next = itm->next; + data->prev = itm; + + /* Make new datas' neighbours point to it + * +---+ -+ +---+ -> +---+ + * |itm| | | | | | + * +---+ | +---+ <- +---+ + * ^ | | ^ + * | +---+ | | + * | | +---+--+ + * | | | | + * | +-> +----+ -+ | + * | |data| | + * +---- +----+ <---+ */ + + /* +---+ -> +----+ -> +---+ -> +---+ + * |itm| |data| | | | | + * +---+ <- +----+ <- +---+ <- +---+ */ + itm->next->prev = data; + itm->next = data; + + obj->size++; +} + +void TokenList_remove(TokenList* obj, TokenListItem* itm) { + if(obj->size == 1) { + obj->front = NULL; + obj->back = NULL; + } + else if(itm == obj->front) { + itm->next->prev = itm->prev; + obj->front = itm->next; + } + else if(itm == obj->back) { + itm->prev->next = itm->next; + obj->back = itm->prev; + } else { + itm->prev->next = itm->next; + itm->next->prev = itm->prev; + } + free(itm); + obj->size--; +} + +void TokenList_uninit(TokenList* obj) { + TokenListItem* curr = obj->front; + while(curr != NULL) { + TokenListItem* next = curr->next; + free(curr); + curr = next; + } +} diff --git a/token_list.h b/token_list.h new file mode 100644 index 0000000..5d44da4 --- /dev/null +++ b/token_list.h @@ -0,0 +1,36 @@ +/* vim: set filetype=c: */ + +#ifndef _TOKEN_LIST_H_ +#define _TOKEN_LIST_H_ + +#include "token.h" + +typedef struct TokenListItem TokenListItem; +struct TokenListItem { + TokenListItem* next; + TokenListItem* prev; + Token val; +}; + +typedef struct TokenList TokenList; +struct TokenList { + TokenListItem* front; + TokenListItem* back; + size_t size; +}; + +extern void TokenList_init(TokenList* obj); + +/* All inserted / appended elements must be heap allocated in advance */ +extern void TokenList_push_back(TokenList* obj, TokenListItem* data); +extern void TokenList_push_front(TokenList* obj, TokenListItem* data); + +extern void TokenList_insert_before(TokenList* obj, TokenListItem* itm, TokenListItem* data); +extern void TokenList_insert_after(TokenList* obj, TokenListItem* itm, TokenListItem* data); + +extern void TokenList_remove(TokenList* obj, TokenListItem* itm); + +/* Frees all TokenStreamItems */ +extern void TokenList_uninit(TokenList* obj); + +#endif /* _TOKEN_LIST_H_ */