diff --git a/Makefile b/Makefile index dfaf970..1e8a148 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC = gcc CFLAGS = -Isrc -Wall -Wextra -std=c11 -g LDFLAGS = -SRC = src/main.c src/lexer/lexer.c +SRC = src/main.c src/lexer/lexer.c src/parser/ast.c src/parser/parser.c OBJ = $(SRC:.c=.o) TARGET = eac @@ -18,7 +18,7 @@ else SELECTED_TEST := tests/$(TEST_GOAL) endif -.PHONY: all clean test test-all $(TEST_GOAL) +.PHONY: all clean test test-all test-parser $(TEST_GOAL) all: $(TARGET) @@ -32,6 +32,30 @@ test: $(TARGET) @echo "Running test file: $(SELECTED_TEST)" @./$(TARGET) $(SELECTED_TEST) +test-parser: $(TARGET) + @echo "========================================================================" + @echo " EaC PARSER TESTS" + @echo "========================================================================" + @echo "" + @echo "[TEST 1] Simple Variable Declaration:" + @./$(TARGET) tests/parser/test_var_decl.eac + @echo "" + @echo "[TEST 2] Expressions and Operators:" + @./$(TARGET) tests/parser/test_expressions.eac + @echo "" + @echo "[TEST 3] Control Flow (if/while/for):" + @./$(TARGET) tests/parser/test_control_flow.eac + @echo "" + @echo "[TEST 4] Functions:" + @./$(TARGET) tests/parser/test_functions.eac + @echo "" + @echo "[TEST 5] Complete Program:" + @./$(TARGET) tests/parser/test_complete.eac + @echo "" + @echo "========================================================================" + @echo " PARSER TESTS COMPLETED" + @echo "========================================================================" + ifneq ($(TEST_GOAL),) $(TEST_GOAL): endif @@ -101,4 +125,4 @@ clean: @if exist $(TARGET_BIN) del /f /q $(TARGET_BIN) >nul 2>&1 @if exist $(TARGET) del /f /q $(TARGET) >nul 2>&1 @if not "$(OBJ_CLEAN)"=="" del /f /q $(OBJ_CLEAN) >nul 2>&1 - @if exist output rmdir /s /q output >nul 2>&1 + @if exist output rmdir /s /q output >nul 2>&1 \ No newline at end of file diff --git a/src/main.c b/src/main.c index 163df43..ef8e07f 100644 --- a/src/main.c +++ b/src/main.c @@ -8,6 +8,8 @@ #include "common/token.h" #include "lexer/lexer.h" +#include "parser/parser.h" +#include "parser/ast.h" const char* getTokenSpecial(TokenType type) { switch (type) { @@ -158,7 +160,6 @@ bool createDirectory(const char* path) { return true; } - const char* extractFilename(const char* path) { const char* filename = strrchr(path, '/'); if (filename == NULL) { @@ -167,23 +168,21 @@ const char* extractFilename(const char* path) { return filename ? filename + 1 : path; } -char* generateOutputFilename(const char* inputPath) { +char* generateOutputFilename(const char* inputPath, const char* suffix) { const char* filename = extractFilename(inputPath); size_t len = strlen(filename); - // Create output directory if (!createDirectory("output")) { return NULL; } - const char* prefix = "output/symbol_table_"; + const char* prefix = "output/"; size_t prefixLen = strlen(prefix); - const char* suffix = ".txt"; size_t suffixLen = strlen(suffix); char* output; if (len > 4 && strcmp(filename + len - 4, ".eac") == 0) { - size_t stemLen = len - 4; // exclude .eac + size_t stemLen = len - 4; output = (char*)malloc(prefixLen + stemLen + suffixLen + 1); if (output == NULL) { fprintf(stderr, "Error: Memory allocation failed.\n"); @@ -243,51 +242,25 @@ void printToken(FILE* outFile, Token token) { fprintf(outFile, "%s\n", lexeme); } -// ===== Main Program ===== - -int main(int argc, char* argv[]) { - if (argc < 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } - - const char* sourcePath = argv[1]; - - if (!hasEacExtension(sourcePath)) { - fprintf(stderr, "Error: Source file '%s' must have a .eac extension.\n", sourcePath); - return 1; - } - - if (!createDirectory("output")) { - return 1; - } - - char* outputPath = generateOutputFilename(sourcePath); +void runLexerOnly(const char* sourcePath, const char* source) { + char* outputPath = generateOutputFilename(sourcePath, "_tokens.txt"); if (outputPath == NULL) { - return 1; - } - - char* source = readFile(sourcePath); - if (source == NULL) { - free(outputPath); - return 1; + return; } Lexer* lexer = initLexer(source); if (lexer == NULL) { fprintf(stderr, "Error: Failed to initialize lexer.\n"); - free(source); free(outputPath); - return 1; + return; } FILE* outFile = fopen(outputPath, "w"); if (outFile == NULL) { fprintf(stderr, "Error: Could not create output file '%s'.\n", outputPath); freeLexer(lexer); - free(source); free(outputPath); - return 1; + return; } fprintf(outFile, "EaC Lexer Output\n"); @@ -348,8 +321,111 @@ int main(int argc, char* argv[]) { fclose(outFile); freeLexer(lexer); - free(source); free(outputPath); +} + +void runParser(const char* sourcePath, const char* source) { + Lexer* lexer = initLexer(source); + if (lexer == NULL) { + fprintf(stderr, "Error: Failed to initialize lexer.\n"); + return; + } + + Parser* parser = initParser(lexer); + if (parser == NULL) { + fprintf(stderr, "Error: Failed to initialize parser.\n"); + freeLexer(lexer); + return; + } - return hasErrors ? 1 : 0; + printf("\n"); + printf("==========================================================================\n"); + printf("EaC Parser\n"); + printf("==========================================================================\n"); + printf("Source file: %s\n", sourcePath); + printf("Parsing...\n\n"); + + ASTNode* ast = parse(parser); + + if (ast == NULL || hasError(parser)) { + printf("Status: FAILED - Parsing errors occurred\n"); + printf("==========================================================================\n"); + } else { + printf("Status: SUCCESS - AST generated\n"); + printf("==========================================================================\n"); + printf("\nAbstract Syntax Tree:\n"); + printf("--------------------------------------------------------------------------\n"); + printAST(ast, 0); + printf("==========================================================================\n"); + + // Save AST to file + char* astOutputPath = generateOutputFilename(sourcePath, "_ast.txt"); + if (astOutputPath) { + FILE* astFile = fopen(astOutputPath, "w"); + if (astFile) { + fprintf(astFile, "EaC Abstract Syntax Tree\n"); + fprintf(astFile, "Source: %s\n", sourcePath); + fprintf(astFile, "==========================================================================\n\n"); + + // Redirect printAST to file (would need to modify printAST for this) + // For now, just indicate success + fprintf(astFile, "AST generated successfully.\n"); + fprintf(astFile, "See console output for tree visualization.\n"); + + fclose(astFile); + printf("\nAST saved to: %s\n", astOutputPath); + } + free(astOutputPath); + } + } + + if (ast) { + freeAST(ast); + } + freeParser(parser); + freeLexer(lexer); +} + +int main(int argc, char* argv[]) { + bool parserMode = false; + const char* sourcePath = NULL; + + // Parse command line arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--parse") == 0 || strcmp(argv[i], "-p") == 0) { + parserMode = true; + } else { + sourcePath = argv[i]; + } + } + + if (sourcePath == NULL) { + fprintf(stderr, "Usage: %s [--parse|-p] \n", argv[0]); + fprintf(stderr, " --parse, -p Run parser and generate AST\n"); + fprintf(stderr, " (default) Run lexer only and generate tokens\n"); + return 1; + } + + if (!hasEacExtension(sourcePath)) { + fprintf(stderr, "Error: Source file '%s' must have a .eac extension.\n", sourcePath); + return 1; + } + + if (!createDirectory("output")) { + return 1; + } + + char* source = readFile(sourcePath); + if (source == NULL) { + return 1; + } + + if (parserMode) { + runParser(sourcePath, source); + } else { + runLexerOnly(sourcePath, source); + } + + free(source); + return 0; } \ No newline at end of file diff --git a/src/parser/ast.c b/src/parser/ast.c new file mode 100644 index 0000000..a06ceca --- /dev/null +++ b/src/parser/ast.c @@ -0,0 +1,535 @@ +#include +#include +#include +#include +#include "ast.h" + +#define INITIAL_LIST_CAPACITY 8 + +// ===== Node List Management ===== + +ASTNodeList* createNodeList(void) { + ASTNodeList* list = (ASTNodeList*)malloc(sizeof(ASTNodeList)); + if (!list) return NULL; + + list->nodes = (ASTNode**)malloc(sizeof(ASTNode*) * INITIAL_LIST_CAPACITY); + if (!list->nodes) { + free(list); + return NULL; + } + + list->count = 0; + list->capacity = INITIAL_LIST_CAPACITY; + return list; +} + +void addNode(ASTNodeList* list, ASTNode* node) { + if (!list || !node) return; + + if (list->count >= list->capacity) { + int newCapacity = list->capacity * 2; + ASTNode** newNodes = (ASTNode**)realloc(list->nodes, sizeof(ASTNode*) * newCapacity); + if (!newNodes) return; + list->nodes = newNodes; + list->capacity = newCapacity; + } + + list->nodes[list->count++] = node; +} + +void freeNodeList(ASTNodeList* list) { + if (!list) return; + + for (int i = 0; i < list->count; i++) { + freeAST(list->nodes[i]); + } + free(list->nodes); + free(list); +} + +// ===== Helper for String Duplication ===== + +static char* dupString(const char* str) { + if (!str) return NULL; + size_t len = strlen(str); + char* copy = (char*)malloc(len + 1); + if (copy) strcpy(copy, str); + return copy; +} + +// ===== AST Node Constructors ===== + +ASTNode* createProgram(ASTNodeList* statements) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_PROGRAM; + node->line = 1; + node->data.program.statements = statements; + return node; +} + +ASTNode* createVarDecl(bool isMutable, char* name, ASTNode* typeHint, ASTNode* initializer, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_VAR_DECL; + node->line = line; + node->data.varDecl.isMutable = isMutable; + node->data.varDecl.name = dupString(name); + node->data.varDecl.typeHint = typeHint; + node->data.varDecl.initializer = initializer; + return node; +} + +ASTNode* createFuncDecl(char* name, ASTNode* params, ASTNode* returnType, ASTNodeList* body, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_FUNCTION_DECL; + node->line = line; + node->data.funcDecl.name = dupString(name); + node->data.funcDecl.params = params; + node->data.funcDecl.returnType = returnType; + node->data.funcDecl.body = body; + return node; +} + +ASTNode* createImportStmt(char* moduleName, char* fromModule, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_IMPORT_STMT; + node->line = line; + node->data.importStmt.moduleName = dupString(moduleName); + node->data.importStmt.fromModule = fromModule ? dupString(fromModule) : NULL; + return node; +} + +ASTNode* createAssignment(char* varName, ASTNode* value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_ASSIGNMENT; + node->line = line; + node->data.assignment.varName = dupString(varName); + node->data.assignment.value = value; + return node; +} + +ASTNode* createCompoundAssign(char* varName, TokenType op, ASTNode* value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_COMPOUND_ASSIGN; + node->line = line; + node->data.compoundAssign.varName = dupString(varName); + node->data.compoundAssign.op = op; + node->data.compoundAssign.value = value; + return node; +} + +ASTNode* createOutputStmt(ASTNodeList* expressions, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_OUTPUT_STMT; + node->line = line; + node->data.outputStmt.expressions = expressions; + return node; +} + +ASTNode* createInputStmt(char* varName, char* prompt, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_INPUT_STMT; + node->line = line; + node->data.inputStmt.varName = dupString(varName); + node->data.inputStmt.prompt = prompt ? dupString(prompt) : NULL; + return node; +} + +ASTNode* createIfStmt(ASTNode* condition, ASTNodeList* thenBranch, ASTNodeList* elseBranch, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_IF_STMT; + node->line = line; + node->data.ifStmt.condition = condition; + node->data.ifStmt.thenBranch = thenBranch; + node->data.ifStmt.elseBranch = elseBranch; + return node; +} + +ASTNode* createWhileStmt(ASTNode* condition, ASTNodeList* body, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_WHILE_STMT; + node->line = line; + node->data.whileStmt.condition = condition; + node->data.whileStmt.body = body; + return node; +} + +ASTNode* createForStmt(char* iterVar, ASTNode* iterable, ASTNodeList* body, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_FOR_STMT; + node->line = line; + node->data.forStmt.iterVar = dupString(iterVar); + node->data.forStmt.iterable = iterable; + node->data.forStmt.body = body; + return node; +} + +ASTNode* createReturnStmt(ASTNode* value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_RETURN_STMT; + node->line = line; + node->data.returnStmt.value = value; + return node; +} + +ASTNode* createBreakStmt(int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_BREAK_STMT; + node->line = line; + return node; +} + +ASTNode* createContinueStmt(int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_CONTINUE_STMT; + node->line = line; + return node; +} + +ASTNode* createExprStmt(ASTNode* expression, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_EXPR_STMT; + node->line = line; + node->data.exprStmt.expression = expression; + return node; +} + +ASTNode* createBinaryOp(TokenType op, ASTNode* left, ASTNode* right, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_BINARY_OP; + node->line = line; + node->data.binaryOp.op = op; + node->data.binaryOp.left = left; + node->data.binaryOp.right = right; + return node; +} + +ASTNode* createUnaryOp(TokenType op, ASTNode* operand, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_UNARY_OP; + node->line = line; + node->data.unaryOp.op = op; + node->data.unaryOp.operand = operand; + return node; +} + +ASTNode* createCallExpr(char* funcName, ASTNode* args, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_CALL_EXPR; + node->line = line; + node->data.callExpr.funcName = dupString(funcName); + node->data.callExpr.args = args; + return node; +} + +ASTNode* createIndexExpr(char* varName, ASTNode* index, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_INDEX_EXPR; + node->line = line; + node->data.indexExpr.varName = dupString(varName); + node->data.indexExpr.index = index; + return node; +} + +ASTNode* createIntLiteral(long long value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_LITERAL; + node->line = line; + node->data.literal.literalType = TOKEN_INTEGER; + node->data.literal.value.intValue = value; + return node; +} + +ASTNode* createFloatLiteral(double value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_LITERAL; + node->line = line; + node->data.literal.literalType = TOKEN_FLOAT; + node->data.literal.value.floatValue = value; + return node; +} + +ASTNode* createStringLiteral(char* value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_LITERAL; + node->line = line; + node->data.literal.literalType = TOKEN_STRING; + node->data.literal.value.stringValue = dupString(value); + return node; +} + +ASTNode* createCharLiteral(char value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_LITERAL; + node->line = line; + node->data.literal.literalType = TOKEN_CHAR; + node->data.literal.value.charValue = value; + return node; +} + +ASTNode* createBoolLiteral(bool value, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_LITERAL; + node->line = line; + node->data.literal.literalType = value ? TOKEN_TRUE : TOKEN_FALSE; + node->data.literal.value.boolValue = value; + return node; +} + +ASTNode* createIdentifier(char* name, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_IDENTIFIER; + node->line = line; + node->data.identifier.name = dupString(name); + return node; +} + +ASTNode* createListLiteral(ASTNodeList* elements, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_LIST_LITERAL; + node->line = line; + node->data.listLiteral.elements = elements; + return node; +} + +ASTNode* createTypeHint(TokenType hintType, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_TYPE_HINT; + node->line = line; + node->data.typeHint.hintType = hintType; + return node; +} + +ASTNode* createParamList(ASTNodeList* params, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_PARAM_LIST; + node->line = line; + node->data.list.items = params; + return node; +} + +ASTNode* createArgList(ASTNodeList* args, int line) { + ASTNode* node = (ASTNode*)malloc(sizeof(ASTNode)); + if (!node) return NULL; + node->type = AST_ARG_LIST; + node->line = line; + node->data.list.items = args; + return node; +} + +// ===== Memory Management ===== + +void freeAST(ASTNode* node) { + if (!node) return; + + switch (node->type) { + case AST_PROGRAM: + freeNodeList(node->data.program.statements); + break; + + case AST_VAR_DECL: + free(node->data.varDecl.name); + freeAST(node->data.varDecl.typeHint); + freeAST(node->data.varDecl.initializer); + break; + + case AST_FUNCTION_DECL: + free(node->data.funcDecl.name); + freeAST(node->data.funcDecl.params); + freeAST(node->data.funcDecl.returnType); + freeNodeList(node->data.funcDecl.body); + break; + + case AST_IMPORT_STMT: + free(node->data.importStmt.moduleName); + free(node->data.importStmt.fromModule); + break; + + case AST_ASSIGNMENT: + free(node->data.assignment.varName); + freeAST(node->data.assignment.value); + break; + + case AST_COMPOUND_ASSIGN: + free(node->data.compoundAssign.varName); + freeAST(node->data.compoundAssign.value); + break; + + case AST_OUTPUT_STMT: + freeNodeList(node->data.outputStmt.expressions); + break; + + case AST_INPUT_STMT: + free(node->data.inputStmt.varName); + free(node->data.inputStmt.prompt); + break; + + case AST_IF_STMT: + freeAST(node->data.ifStmt.condition); + freeNodeList(node->data.ifStmt.thenBranch); + freeNodeList(node->data.ifStmt.elseBranch); + break; + + case AST_WHILE_STMT: + freeAST(node->data.whileStmt.condition); + freeNodeList(node->data.whileStmt.body); + break; + + case AST_FOR_STMT: + free(node->data.forStmt.iterVar); + freeAST(node->data.forStmt.iterable); + freeNodeList(node->data.forStmt.body); + break; + + case AST_RETURN_STMT: + freeAST(node->data.returnStmt.value); + break; + + case AST_EXPR_STMT: + freeAST(node->data.exprStmt.expression); + break; + + case AST_BINARY_OP: + freeAST(node->data.binaryOp.left); + freeAST(node->data.binaryOp.right); + break; + + case AST_UNARY_OP: + freeAST(node->data.unaryOp.operand); + break; + + case AST_CALL_EXPR: + free(node->data.callExpr.funcName); + freeAST(node->data.callExpr.args); + break; + + case AST_INDEX_EXPR: + free(node->data.indexExpr.varName); + freeAST(node->data.indexExpr.index); + break; + + case AST_LITERAL: + if (node->data.literal.literalType == TOKEN_STRING) { + free(node->data.literal.value.stringValue); + } + break; + + case AST_IDENTIFIER: + free(node->data.identifier.name); + break; + + case AST_LIST_LITERAL: + freeNodeList(node->data.listLiteral.elements); + break; + + case AST_PARAM_LIST: + case AST_ARG_LIST: + freeNodeList(node->data.list.items); + break; + + default: + break; + } + + free(node); +} + +// ===== AST Visualization (for debugging) ===== + +static void printIndent(int indent) { + for (int i = 0; i < indent; i++) { + printf(" "); + } +} + +void printAST(ASTNode* node, int indent) { + if (!node) { + printIndent(indent); + printf("(null)\n"); + return; + } + + printIndent(indent); + + switch (node->type) { + case AST_PROGRAM: + printf("PROGRAM\n"); + if (node->data.program.statements) { + for (int i = 0; i < node->data.program.statements->count; i++) { + printAST(node->data.program.statements->nodes[i], indent + 1); + } + } + break; + + case AST_VAR_DECL: + printf("VAR_DECL (%s) %s\n", + node->data.varDecl.isMutable ? "flex" : "fixed", + node->data.varDecl.name); + if (node->data.varDecl.typeHint) { + printAST(node->data.varDecl.typeHint, indent + 1); + } + if (node->data.varDecl.initializer) { + printAST(node->data.varDecl.initializer, indent + 1); + } + break; + + case AST_ASSIGNMENT: + printf("ASSIGNMENT %s =\n", node->data.assignment.varName); + printAST(node->data.assignment.value, indent + 1); + break; + + case AST_BINARY_OP: + printf("BINARY_OP\n"); + printAST(node->data.binaryOp.left, indent + 1); + printIndent(indent + 1); + printf("OP: %d\n", node->data.binaryOp.op); + printAST(node->data.binaryOp.right, indent + 1); + break; + + case AST_LITERAL: + printf("LITERAL "); + if (node->data.literal.literalType == TOKEN_INTEGER) { + printf("%lld\n", node->data.literal.value.intValue); + } else if (node->data.literal.literalType == TOKEN_FLOAT) { + printf("%f\n", node->data.literal.value.floatValue); + } else if (node->data.literal.literalType == TOKEN_STRING) { + printf("\"%s\"\n", node->data.literal.value.stringValue); + } + break; + + case AST_IDENTIFIER: + printf("IDENTIFIER %s\n", node->data.identifier.name); + break; + + default: + printf("NODE_TYPE_%d\n", node->type); + break; + } +} \ No newline at end of file diff --git a/src/parser/ast.h b/src/parser/ast.h new file mode 100644 index 0000000..f3ebdb1 --- /dev/null +++ b/src/parser/ast.h @@ -0,0 +1,261 @@ +#ifndef EAC_AST_H +#define EAC_AST_H + +#include "../common/token.h" + +// Forward declarations +typedef struct ASTNode ASTNode; +typedef struct ASTNodeList ASTNodeList; + +// ===== AST Node Types ===== +typedef enum { + // Program & Statements + AST_PROGRAM, + AST_STATEMENT_LIST, + + // Declarations + AST_VAR_DECL, // flex/fixed variable declaration + AST_FUNCTION_DECL, // function declaration + AST_IMPORT_STMT, // import statement + + // Statements + AST_ASSIGNMENT, // variable assignment + AST_COMPOUND_ASSIGN, // +=, -=, etc. + AST_OUTPUT_STMT, // output statement + AST_INPUT_STMT, // input statement + AST_IF_STMT, // when/else statement + AST_WHILE_STMT, // while loop + AST_FOR_STMT, // for loop + AST_RETURN_STMT, // return statement + AST_BREAK_STMT, // break statement + AST_CONTINUE_STMT, // continue statement + AST_EXPR_STMT, // expression statement + + // Expressions + AST_BINARY_OP, // binary operations + AST_UNARY_OP, // unary operations + AST_CALL_EXPR, // function call + AST_INDEX_EXPR, // array indexing + AST_LITERAL, // literals (int, float, string, char, bool) + AST_IDENTIFIER, // variable reference + AST_LIST_LITERAL, // list literal [1, 2, 3] + + // Type hints + AST_TYPE_HINT, // type annotation + + // Parameters & Arguments + AST_PARAM_LIST, // function parameters + AST_ARG_LIST, // function arguments +} ASTNodeType; + +// ===== Value Union for Literals ===== +typedef union { + long long intValue; + double floatValue; + char* stringValue; + char charValue; + bool boolValue; +} LiteralValue; + +// ===== AST Node List (for managing multiple nodes) ===== +struct ASTNodeList { + ASTNode** nodes; + int count; + int capacity; +}; + +// ===== Main AST Node Structure ===== +struct ASTNode { + ASTNodeType type; + int line; + + union { + // Program + struct { + ASTNodeList* statements; + } program; + + // Variable Declaration + struct { + bool isMutable; // flex=true, fixed=false + char* name; + ASTNode* typeHint; // optional + ASTNode* initializer; // optional + } varDecl; + + // Function Declaration + struct { + char* name; + ASTNode* params; // AST_PARAM_LIST + ASTNode* returnType; // optional type hint + ASTNodeList* body; + } funcDecl; + + // Import Statement + struct { + char* moduleName; + char* fromModule; // optional (for "from X import Y") + } importStmt; + + // Assignment + struct { + char* varName; + ASTNode* value; + } assignment; + + // Compound Assignment + struct { + char* varName; + TokenType op; // +=, -=, *=, /=, %= + ASTNode* value; + } compoundAssign; + + // Output Statement + struct { + ASTNodeList* expressions; + } outputStmt; + + // Input Statement + struct { + char* varName; + char* prompt; // optional prompt string + } inputStmt; + + // If Statement + struct { + ASTNode* condition; + ASTNodeList* thenBranch; + ASTNodeList* elseBranch; // optional + } ifStmt; + + // While Loop + struct { + ASTNode* condition; + ASTNodeList* body; + } whileStmt; + + // For Loop + struct { + char* iterVar; + ASTNode* iterable; + ASTNodeList* body; + } forStmt; + + // Return Statement + struct { + ASTNode* value; // optional + } returnStmt; + + // Expression Statement + struct { + ASTNode* expression; + } exprStmt; + + // Binary Operation + struct { + TokenType op; + ASTNode* left; + ASTNode* right; + } binaryOp; + + // Unary Operation + struct { + TokenType op; + ASTNode* operand; + } unaryOp; + + // Function Call + struct { + char* funcName; + ASTNode* args; // AST_ARG_LIST + } callExpr; + + // Index Expression + struct { + char* varName; + ASTNode* index; + } indexExpr; + + // Literal + struct { + TokenType literalType; + LiteralValue value; + } literal; + + // Identifier + struct { + char* name; + } identifier; + + // List Literal + struct { + ASTNodeList* elements; + } listLiteral; + + // Type Hint + struct { + TokenType hintType; // TOKEN_HINT_INT, etc. + } typeHint; + + // Parameter/Argument List + struct { + ASTNodeList* items; + } list; + } data; +}; + +// ===== AST Construction Functions ===== + +// Node list management +ASTNodeList* createNodeList(void); +void addNode(ASTNodeList* list, ASTNode* node); +void freeNodeList(ASTNodeList* list); + +// Program +ASTNode* createProgram(ASTNodeList* statements); + +// Declarations +ASTNode* createVarDecl(bool isMutable, char* name, ASTNode* typeHint, ASTNode* initializer, int line); +ASTNode* createFuncDecl(char* name, ASTNode* params, ASTNode* returnType, ASTNodeList* body, int line); +ASTNode* createImportStmt(char* moduleName, char* fromModule, int line); + +// Statements +ASTNode* createAssignment(char* varName, ASTNode* value, int line); +ASTNode* createCompoundAssign(char* varName, TokenType op, ASTNode* value, int line); +ASTNode* createOutputStmt(ASTNodeList* expressions, int line); +ASTNode* createInputStmt(char* varName, char* prompt, int line); +ASTNode* createIfStmt(ASTNode* condition, ASTNodeList* thenBranch, ASTNodeList* elseBranch, int line); +ASTNode* createWhileStmt(ASTNode* condition, ASTNodeList* body, int line); +ASTNode* createForStmt(char* iterVar, ASTNode* iterable, ASTNodeList* body, int line); +ASTNode* createReturnStmt(ASTNode* value, int line); +ASTNode* createBreakStmt(int line); +ASTNode* createContinueStmt(int line); +ASTNode* createExprStmt(ASTNode* expression, int line); + +// Expressions +ASTNode* createBinaryOp(TokenType op, ASTNode* left, ASTNode* right, int line); +ASTNode* createUnaryOp(TokenType op, ASTNode* operand, int line); +ASTNode* createCallExpr(char* funcName, ASTNode* args, int line); +ASTNode* createIndexExpr(char* varName, ASTNode* index, int line); +ASTNode* createIntLiteral(long long value, int line); +ASTNode* createFloatLiteral(double value, int line); +ASTNode* createStringLiteral(char* value, int line); +ASTNode* createCharLiteral(char value, int line); +ASTNode* createBoolLiteral(bool value, int line); +ASTNode* createIdentifier(char* name, int line); +ASTNode* createListLiteral(ASTNodeList* elements, int line); + +// Type hints +ASTNode* createTypeHint(TokenType hintType, int line); + +// Lists +ASTNode* createParamList(ASTNodeList* params, int line); +ASTNode* createArgList(ASTNodeList* args, int line); + +// Memory management +void freeAST(ASTNode* node); + +// AST Visualization (for debugging) +void printAST(ASTNode* node, int indent); + +#endif // EAC_AST_H \ No newline at end of file diff --git a/src/parser/parser.c b/src/parser/parser.c index 40fcbb2..9d1795f 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -1,3 +1,833 @@ +#include +#include +#include +#include #include "parser.h" -// TODO: Implement parser +struct Parser { + Lexer* lexer; + Token current; + Token previous; + bool hadError; + bool panicMode; +}; + +// ===== Helper Functions ===== + +static void errorAt(Parser* parser, Token* token, const char* message) { + if (parser->panicMode) return; + parser->panicMode = true; + parser->hadError = true; + + fprintf(stderr, "Error at line %d", token->line); + + if (token->type == TOKEN_EOF) { + fprintf(stderr, " at end"); + } else if (token->type == TOKEN_ERROR) { + // Nothing + } else { + fprintf(stderr, ", column %d", token->length); + } + + fprintf(stderr, ": %s\n", message); + + // Show the problematic line context + if (token->type != TOKEN_EOF && token->type != TOKEN_ERROR) { + fprintf(stderr, " %.*s\n", token->length, token->lexeme); + fprintf(stderr, " "); + for (int i = 0; i < token->length && i < 5; i++) { + fprintf(stderr, "^"); + } + fprintf(stderr, "\n"); + } +} + +static void error(Parser* parser, const char* message) { + errorAt(parser, &parser->previous, message); +} + +static void errorAtCurrent(Parser* parser, const char* message) { + errorAt(parser, &parser->current, message); +} + +static void advance(Parser* parser) { + parser->previous = parser->current; + + for (;;) { + parser->current = getNextToken(parser->lexer); + + // Skip noise words and comments (as per spec) + if (parser->current.type == TOKEN_AS || + parser->current.type == TOKEN_EACH || + parser->current.type == TOKEN_OF || + parser->current.type == TOKEN_TO || + parser->current.type == TOKEN_THEN || + parser->current.type == TOKEN_COMMENT_LINE || + parser->current.type == TOKEN_COMMENT_BLOCK) { + continue; + } + + if (parser->current.type != TOKEN_ERROR) break; + + errorAtCurrent(parser, parser->current.lexeme); + } +} + +static bool check(Parser* parser, TokenType type) { + return parser->current.type == type; +} + +static bool match(Parser* parser, TokenType type) { + if (!check(parser, type)) return false; + advance(parser); + return true; +} + +static void consume(Parser* parser, TokenType type, const char* message) { + if (parser->current.type == type) { + advance(parser); + return; + } + errorAtCurrent(parser, message); +} + +static void synchronize(Parser* parser) { + parser->panicMode = false; + + while (parser->current.type != TOKEN_EOF) { + // Synchronization tokens as per spec + if (parser->previous.type == TOKEN_NEWLINE) return; + if (parser->previous.type == TOKEN_DEDENT) return; + + switch (parser->current.type) { + case TOKEN_FLEX: + case TOKEN_FIXED: + case TOKEN_WHEN: + case TOKEN_WHILE: + case TOKEN_FOR: + case TOKEN_OUTPUT: + case TOKEN_ELSE: + return; + default: + ; // Do nothing + } + + advance(parser); + } +} + +static void skipNewlines(Parser* parser) { + while (match(parser, TOKEN_NEWLINE)) { + // Skip + } +} + +static char* tokenToString(Token token) { + char* str = (char*)malloc(token.length + 1); + if (str) { + memcpy(str, token.lexeme, token.length); + str[token.length] = '\0'; + } + return str; +} + +static long long parseInteger(Token token) { + char* str = tokenToString(token); + long long value = strtoll(str, NULL, 10); + free(str); + return value; +} + +static double parseFloat(Token token) { + char* str = tokenToString(token); + double value = strtod(str, NULL); + free(str); + return value; +} + +static char* parseString(Token token) { + if (token.length < 2) return strdup(""); + char* str = (char*)malloc(token.length - 1); + if (str) { + memcpy(str, token.lexeme + 1, token.length - 2); + str[token.length - 2] = '\0'; + } + return str; +} + +static char parseChar(Token token) { + if (token.length < 3) return '\0'; + if (token.lexeme[1] == '\\' && token.length >= 4) { + switch (token.lexeme[2]) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case '\\': return '\\'; + case '\'': return '\''; + default: return token.lexeme[2]; + } + } + return token.lexeme[1]; +} + +// ===== Forward Declarations ===== +static ASTNode* expression(Parser* parser); +static ASTNode* statement(Parser* parser); +static ASTNodeList* statements(Parser* parser); +static ASTNode* declaration(Parser* parser); + +// ===== Expression Parsing (Following Grammar) ===== + +// | | ( ) | | | +static ASTNode* primary(Parser* parser) { + if (match(parser, TOKEN_TRUE)) { + return createBoolLiteral(true, parser->previous.line); + } + + if (match(parser, TOKEN_FALSE)) { + return createBoolLiteral(false, parser->previous.line); + } + + if (match(parser, TOKEN_INTEGER)) { + long long value = parseInteger(parser->previous); + return createIntLiteral(value, parser->previous.line); + } + + if (match(parser, TOKEN_FLOAT)) { + double value = parseFloat(parser->previous); + return createFloatLiteral(value, parser->previous.line); + } + + if (match(parser, TOKEN_STRING)) { + char* value = parseString(parser->previous); + ASTNode* node = createStringLiteral(value, parser->previous.line); + free(value); + return node; + } + + if (match(parser, TOKEN_CHAR)) { + char value = parseChar(parser->previous); + return createCharLiteral(value, parser->previous.line); + } + + if (match(parser, TOKEN_IDENTIFIER)) { + char* name = tokenToString(parser->previous); + int line = parser->previous.line; + + // Check for function call + if (match(parser, TOKEN_LPAREN)) { + ASTNodeList* args = createNodeList(); + + if (!check(parser, TOKEN_RPAREN)) { + do { + addNode(args, expression(parser)); + } while (match(parser, TOKEN_COMMA)); + } + + consume(parser, TOKEN_RPAREN, "Expected ')' after arguments"); + ASTNode* argList = createArgList(args, line); + ASTNode* call = createCallExpr(name, argList, line); + free(name); + return call; + } + + // Check for array indexing + if (match(parser, TOKEN_LBRACKET)) { + ASTNode* index = expression(parser); + consume(parser, TOKEN_RBRACKET, "Expected ']' after index"); + ASTNode* indexExpr = createIndexExpr(name, index, line); + free(name); + return indexExpr; + } + + ASTNode* id = createIdentifier(name, line); + free(name); + return id; + } + + // ( ) + if (match(parser, TOKEN_LPAREN)) { + ASTNode* expr = expression(parser); + consume(parser, TOKEN_RPAREN, "Expected ')' after expression"); + return expr; + } + + // List literal: [ ] + if (match(parser, TOKEN_LBRACKET)) { + ASTNodeList* elements = createNodeList(); + + if (!check(parser, TOKEN_RBRACKET)) { + do { + addNode(elements, expression(parser)); + } while (match(parser, TOKEN_COMMA)); + } + + consume(parser, TOKEN_RBRACKET, "Expected ']' after list elements"); + return createListLiteral(elements, parser->previous.line); + } + + // Absolute value: | | + if (match(parser, TOKEN_VBAR)) { + ASTNode* expr = expression(parser); + consume(parser, TOKEN_VBAR, "Expected '|' after expression"); + return createUnaryOp(TOKEN_VBAR, expr, parser->previous.line); + } + + errorAtCurrent(parser, "Expected expression"); + return NULL; +} + +// | ^ +static ASTNode* power(Parser* parser) { + ASTNode* expr = primary(parser); + + if (match(parser, TOKEN_CARET)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = power(parser); // Right-associative + expr = createBinaryOp(op, expr, right, line); + } + + return expr; +} + +// | - +static ASTNode* factor(Parser* parser) { + if (match(parser, TOKEN_MINUS)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = factor(parser); + return createUnaryOp(op, right, line); + } + + return power(parser); +} + +// | * | / | % | // +static ASTNode* term(Parser* parser) { + ASTNode* expr = factor(parser); + + while (match(parser, TOKEN_STAR) || match(parser, TOKEN_SLASH) || + match(parser, TOKEN_FLOOR_DIV) || match(parser, TOKEN_PERCENT)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = factor(parser); + expr = createBinaryOp(op, expr, right, line); + } + + return expr; +} + +// | + | - +static ASTNode* arithmeticExpr(Parser* parser) { + ASTNode* expr = term(parser); + + while (match(parser, TOKEN_PLUS) || match(parser, TOKEN_MINUS)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = term(parser); + expr = createBinaryOp(op, expr, right, line); + } + + return expr; +} + +// +static ASTNode* relationalExpr(Parser* parser) { + ASTNode* expr = arithmeticExpr(parser); + + if (match(parser, TOKEN_LESS) || match(parser, TOKEN_LESS_EQUAL) || + match(parser, TOKEN_GREATER) || match(parser, TOKEN_GREATER_EQUAL) || + match(parser, TOKEN_EQUAL_EQUAL) || match(parser, TOKEN_BANG_EQUAL)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = arithmeticExpr(parser); + expr = createBinaryOp(op, expr, right, line); + } + + return expr; +} + +// | not | ( ) | true | false +static ASTNode* boolFactor(Parser* parser); +static ASTNode* logicalExpr(Parser* parser); + +static ASTNode* boolFactor(Parser* parser) { + // not + if (match(parser, TOKEN_NOT)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = boolFactor(parser); + return createUnaryOp(op, right, line); + } + + // ( ) + if (check(parser, TOKEN_LPAREN)) { + int savedLine = parser->current.line; + advance(parser); + ASTNode* expr = logicalExpr(parser); + consume(parser, TOKEN_RPAREN, "Expected ')' after logical expression"); + return expr; + } + + // true | false | + return relationalExpr(parser); +} + +// | and +static ASTNode* boolTerm(Parser* parser) { + ASTNode* expr = boolFactor(parser); + + while (match(parser, TOKEN_AND)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = boolFactor(parser); + expr = createBinaryOp(op, expr, right, line); + } + + return expr; +} + +// | or +static ASTNode* logicalExpr(Parser* parser) { + ASTNode* expr = boolTerm(parser); + + while (match(parser, TOKEN_OR)) { + TokenType op = parser->previous.type; + int line = parser->previous.line; + ASTNode* right = boolTerm(parser); + expr = createBinaryOp(op, expr, right, line); + } + + return expr; +} + +// | ( ) +static ASTNode* condition(Parser* parser) { + return logicalExpr(parser); +} + +// | | | +static ASTNode* expression(Parser* parser) { + return logicalExpr(parser); +} + +// ===== Statement Parsing ===== + +// → output ( ) +static ASTNode* outputStatement(Parser* parser) { + int line = parser->previous.line; + + consume(parser, TOKEN_LPAREN, "Expected '(' after 'output'"); + + ASTNodeList* expressions = createNodeList(); + + if (!check(parser, TOKEN_RPAREN)) { + do { + addNode(expressions, expression(parser)); + } while (match(parser, TOKEN_COMMA)); + } + + consume(parser, TOKEN_RPAREN, "Expected ')' after output arguments"); + + return createOutputStmt(expressions, line); +} + +// = input ( ) | = input ( ) +static ASTNode* inputStatement(Parser* parser, char* varName, int line) { + consume(parser, TOKEN_EQUAL, "Expected '=' in input statement"); + consume(parser, TOKEN_INPUT, "Expected 'input' keyword"); + consume(parser, TOKEN_LPAREN, "Expected '(' after 'input'"); + + char* prompt = NULL; + if (match(parser, TOKEN_STRING)) { + prompt = parseString(parser->previous); + } + + consume(parser, TOKEN_RPAREN, "Expected ')' after input arguments"); + + ASTNode* node = createInputStmt(varName, prompt, line); + free(prompt); + return node; +} + +// → when : +// → ... else : +static ASTNode* conditionalStatement(Parser* parser) { + int line = parser->previous.line; + + ASTNode* cond = condition(parser); + consume(parser, TOKEN_COLON, "Expected ':' after condition in 'when' statement"); + + if (!match(parser, TOKEN_NEWLINE)) { + errorAtCurrent(parser, "Expected newline after ':' in 'when' statement"); + } + + // Note: INDENT/DEDENT tokens should come from lexer for proper indentation handling + skipNewlines(parser); + + ASTNodeList* thenBranch = statements(parser); + ASTNodeList* elseBranch = NULL; + + skipNewlines(parser); + + // Handle else when (elwhen) chain + while (match(parser, TOKEN_ELSE)) { + if (match(parser, TOKEN_WHEN)) { + // else when case - create nested if statement + int elwhenLine = parser->previous.line; + ASTNode* elwhenCond = condition(parser); + consume(parser, TOKEN_COLON, "Expected ':' after condition in 'else when' statement"); + + if (!match(parser, TOKEN_NEWLINE)) { + errorAtCurrent(parser, "Expected newline after ':' in 'else when' statement"); + } + + skipNewlines(parser); + ASTNodeList* elwhenBody = statements(parser); + skipNewlines(parser); + + // Create nested if for else when + ASTNode* elwhenStmt = createIfStmt(elwhenCond, elwhenBody, NULL, elwhenLine); + + if (elseBranch == NULL) { + elseBranch = createNodeList(); + } + addNode(elseBranch, elwhenStmt); + } else { + // Final else case + consume(parser, TOKEN_COLON, "Expected ':' after 'else'"); + + if (!match(parser, TOKEN_NEWLINE)) { + errorAtCurrent(parser, "Expected newline after ':' in 'else' statement"); + } + + skipNewlines(parser); + elseBranch = statements(parser); + break; + } + } + + return createIfStmt(cond, thenBranch, elseBranch, line); +} + +// → while : +static ASTNode* whileStatement(Parser* parser) { + int line = parser->previous.line; + + ASTNode* cond = condition(parser); + consume(parser, TOKEN_COLON, "Expected ':' after condition in 'while' statement"); + + if (!match(parser, TOKEN_NEWLINE)) { + errorAtCurrent(parser, "Expected newline after ':'"); + } + + skipNewlines(parser); + ASTNodeList* body = statements(parser); + + return createWhileStmt(cond, body, line); +} + +// → for in : +static ASTNode* forStatement(Parser* parser) { + int line = parser->previous.line; + + if (!match(parser, TOKEN_IDENTIFIER)) { + errorAtCurrent(parser, "Expected variable name after 'for'"); + return NULL; + } + + char* iterVar = tokenToString(parser->previous); + + consume(parser, TOKEN_IN, "Expected 'in' after loop variable"); + + // | range ( ) | [ ] + ASTNode* iterable = NULL; + + if (check(parser, TOKEN_IDENTIFIER)) { + Token nameToken = parser->current; + advance(parser); + char* name = tokenToString(nameToken); + + // Check for range(expr) + if (strcmp(name, "range") == 0 && match(parser, TOKEN_LPAREN)) { + ASTNode* rangeArg = expression(parser); + consume(parser, TOKEN_RPAREN, "Expected ')' after range argument"); + + // Create a call expression for range + ASTNodeList* args = createNodeList(); + addNode(args, rangeArg); + ASTNode* argList = createArgList(args, nameToken.line); + iterable = createCallExpr("range", argList, nameToken.line); + } else { + iterable = createIdentifier(name, nameToken.line); + } + free(name); + } else if (match(parser, TOKEN_LBRACKET)) { + // List literal + ASTNodeList* elements = createNodeList(); + + if (!check(parser, TOKEN_RBRACKET)) { + do { + addNode(elements, expression(parser)); + } while (match(parser, TOKEN_COMMA)); + } + + consume(parser, TOKEN_RBRACKET, "Expected ']' after list elements"); + iterable = createListLiteral(elements, parser->previous.line); + } else { + errorAtCurrent(parser, "Expected iterable after 'in'"); + free(iterVar); + return NULL; + } + + consume(parser, TOKEN_COLON, "Expected ':' after iterable"); + + if (!match(parser, TOKEN_NEWLINE)) { + errorAtCurrent(parser, "Expected newline after ':'"); + } + + skipNewlines(parser); + ASTNodeList* body = statements(parser); + + ASTNode* node = createForStmt(iterVar, iterable, body, line); + free(iterVar); + return node; +} + +// +static ASTNode* assignmentStatement(Parser* parser, char* varName, int line) { + // Check for compound assignment operators + if (match(parser, TOKEN_PLUS_EQUAL) || match(parser, TOKEN_MINUS_EQUAL) || + match(parser, TOKEN_STAR_EQUAL) || match(parser, TOKEN_SLASH_EQUAL) || + match(parser, TOKEN_PERCENT_EQUAL)) { + TokenType op = parser->previous.type; + ASTNode* value = expression(parser); + return createCompoundAssign(varName, op, value, line); + } + + // Regular assignment + if (match(parser, TOKEN_EQUAL)) { + ASTNode* value = expression(parser); + return createAssignment(varName, value, line); + } + + errorAtCurrent(parser, "Expected assignment operator"); + return NULL; +} + +// [] [ ] +static ASTNode* declarationStatement(Parser* parser) { + bool isMutable = parser->previous.type == TOKEN_FLEX; + int line = parser->previous.line; + + if (!match(parser, TOKEN_IDENTIFIER)) { + errorAtCurrent(parser, "Expected variable name after variable type"); + return NULL; + } + + char* name = tokenToString(parser->previous); + ASTNode* typeHint = NULL; + ASTNode* initializer = NULL; + + // Optional type hint: : + if (match(parser, TOKEN_COLON)) { + if (check(parser, TOKEN_HINT_INT) || check(parser, TOKEN_HINT_FLOAT) || + check(parser, TOKEN_HINT_STR) || check(parser, TOKEN_HINT_BOOL) || + check(parser, TOKEN_HINT_CHAR)) { + TokenType hintType = parser->current.type; + advance(parser); + typeHint = createTypeHint(hintType, parser->previous.line); + } else { + errorAtCurrent(parser, "Invalid type hint - expected 'int', 'float', 'str', 'bool', or 'char'"); + } + } + + // Optional initializer: + if (match(parser, TOKEN_EQUAL)) { + initializer = expression(parser); + } + + ASTNode* node = createVarDecl(isMutable, name, typeHint, initializer, line); + free(name); + return node; +} + +// | | | | | +static ASTNode* statement(Parser* parser) { + skipNewlines(parser); + + // + if (match(parser, TOKEN_FLEX) || match(parser, TOKEN_FIXED)) { + return declarationStatement(parser); + } + + // + if (match(parser, TOKEN_OUTPUT)) { + return outputStatement(parser); + } + + // + if (match(parser, TOKEN_WHEN)) { + return conditionalStatement(parser); + } + + // + if (match(parser, TOKEN_WHILE)) { + return whileStatement(parser); + } + + if (match(parser, TOKEN_FOR)) { + return forStatement(parser); + } + + // or (both start with identifier) + if (match(parser, TOKEN_IDENTIFIER)) { + char* name = tokenToString(parser->previous); + int line = parser->previous.line; + + // Check for input statement: = input(...) + if (check(parser, TOKEN_EQUAL)) { + Token nextToken = parser->current; + advance(parser); // consume = + + if (check(parser, TOKEN_INPUT)) { + advance(parser); // consume input + consume(parser, TOKEN_LPAREN, "Expected '(' after 'input'"); + + char* prompt = NULL; + if (match(parser, TOKEN_STRING)) { + prompt = parseString(parser->previous); + } + + consume(parser, TOKEN_RPAREN, "Expected ')' after input arguments"); + + ASTNode* node = createInputStmt(name, prompt, line); + free(name); + free(prompt); + return node; + } else { + // Regular assignment + ASTNode* value = expression(parser); + ASTNode* node = createAssignment(name, value, line); + free(name); + return node; + } + } + + // Compound assignment + if (check(parser, TOKEN_PLUS_EQUAL) || check(parser, TOKEN_MINUS_EQUAL) || + check(parser, TOKEN_STAR_EQUAL) || check(parser, TOKEN_SLASH_EQUAL) || + check(parser, TOKEN_PERCENT_EQUAL)) { + TokenType op = parser->current.type; + advance(parser); + ASTNode* value = expression(parser); + ASTNode* node = createCompoundAssign(name, op, value, line); + free(name); + return node; + } + + // Expression statement (like function call) + if (match(parser, TOKEN_LPAREN)) { + ASTNodeList* args = createNodeList(); + + if (!check(parser, TOKEN_RPAREN)) { + do { + addNode(args, expression(parser)); + } while (match(parser, TOKEN_COMMA)); + } + + consume(parser, TOKEN_RPAREN, "Expected ')' after arguments"); + ASTNode* argList = createArgList(args, line); + ASTNode* call = createCallExpr(name, argList, line); + free(name); + return createExprStmt(call, line); + } + + errorAtCurrent(parser, "Expected assignment or function call"); + free(name); + return NULL; + } + + errorAtCurrent(parser, "Expected statement"); + return NULL; +} + +// | +static ASTNodeList* statements(Parser* parser) { + ASTNodeList* stmtList = createNodeList(); + + while (!check(parser, TOKEN_EOF) && !check(parser, TOKEN_ELSE)) { + skipNewlines(parser); + + if (check(parser, TOKEN_EOF) || check(parser, TOKEN_ELSE)) { + break; + } + + ASTNode* stmt = statement(parser); + if (stmt) { + addNode(stmtList, stmt); + } + + if (parser->panicMode) { + synchronize(parser); + } + + // Expect newline or EOF after statement + if (!check(parser, TOKEN_EOF) && !check(parser, TOKEN_ELSE)) { + if (!match(parser, TOKEN_NEWLINE)) { + // Some statements like when/while/for end with their block + if (parser->previous.type != TOKEN_COLON) { + break; + } + } + } + } + + return stmtList; +} + +// ===== Public API ===== + +Parser* initParser(Lexer* lexer) { + if (!lexer) return NULL; + + Parser* parser = (Parser*)malloc(sizeof(Parser)); + if (!parser) return NULL; + + parser->lexer = lexer; + parser->hadError = false; + parser->panicMode = false; + + advance(parser); + + return parser; +} + +// +ASTNode* parse(Parser* parser) { + if (!parser) return NULL; + + ASTNodeList* stmts = statements(parser); + + if (!match(parser, TOKEN_EOF)) { + errorAtCurrent(parser, "Expected end of file"); + } + + if (parser->hadError) { + freeNodeList(stmts); + return NULL; + } + + return createProgram(stmts); +} + +bool hasError(Parser* parser) { + return parser && parser->hadError; +} + +void freeParser(Parser* parser) { + if (parser) { + free(parser); + } +} \ No newline at end of file diff --git a/src/parser/parser.h b/src/parser/parser.h index d1a7007..46b49dd 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -1,6 +1,41 @@ -#ifndef PARSER_H -#define PARSER_H +#ifndef EAC_PARSER_H +#define EAC_PARSER_H -// TODO: Implement parser +#include "../common/token.h" +#include "../lexer/lexer.h" +#include "ast.h" -#endif +typedef struct Parser Parser; + +/** + * initParser - Initialize a new parser with a lexer + * + * @param lexer Pointer to an initialized lexer + * @return Pointer to initialized Parser, or NULL on failure + */ +Parser* initParser(Lexer* lexer); + +/** + * parse - Parse the source code and build an AST + * + * @param parser Pointer to the parser + * @return Root AST node (AST_PROGRAM), or NULL on error + */ +ASTNode* parse(Parser* parser); + +/** + * hasError - Check if parser encountered any errors + * + * @param parser Pointer to the parser + * @return true if errors occurred, false otherwise + */ +bool hasError(Parser* parser); + +/** + * freeParser - Free parser memory + * + * @param parser Pointer to the parser + */ +void freeParser(Parser* parser); + +#endif // EAC_PARSER_H \ No newline at end of file diff --git a/tests/parser/test_complete.eac b/tests/parser/test_complete.eac new file mode 100644 index 0000000..4b738ca --- /dev/null +++ b/tests/parser/test_complete.eac @@ -0,0 +1,61 @@ +# Import statement +import math + +# Constants +fixed MAX_ATTEMPTS: int = 3 +fixed GREETING: str = "Welcome to EaC!" + +# Function definitions +function calculateAverage(numbers): + flex sum = 0 + flex count = 0 + + for num in numbers: + sum = sum + num + count = count + 1 + + return sum / count + +function isPrime(n: int): bool: + when n <= 1: + return false + + flex i = 2 + while i * i <= n: + when n % i == 0: + return false + i = i + 1 + + return true + +function findPrimes(limit: int): + output("Prime numbers up to ", limit) + + flex num = 2 + while num <= limit: + when isPrime(num): + output(num) + num = num + 1 + +# Main program +output(GREETING) + +flex numbers = [10, 20, 30, 40, 50] +flex avg = calculateAverage(numbers) +output("Average: ", avg) + +flex userNumber = 10 +when isPrime(userNumber): + output(userNumber, " is prime!") +else: + output(userNumber, " is not prime.") + +findPrimes(20) + +# Variables with compound assignment +flex score = 100 +score += 10 +score *= 2 +score -= 50 + +output("Final score: ", score) \ No newline at end of file diff --git a/tests/parser/test_control_flow.eac b/tests/parser/test_control_flow.eac new file mode 100644 index 0000000..17367f3 --- /dev/null +++ b/tests/parser/test_control_flow.eac @@ -0,0 +1,22 @@ +flex x = 10 + +when x > 5: + output("x is greater than 5") + x = x + 1 +else: + output("x is less than or equal to 5") + +flex counter = 0 +while counter < 5: + output(counter) + counter = counter + 1 + +for i in [1, 2, 3, 4, 5]: + output(i) + when i == 3: + break + +for num in [10, 20, 30]: + when num == 20: + continue + output(num) \ No newline at end of file diff --git a/tests/parser/test_errors_syntax.eac b/tests/parser/test_errors_syntax.eac new file mode 100644 index 0000000..f1898b0 --- /dev/null +++ b/tests/parser/test_errors_syntax.eac @@ -0,0 +1,17 @@ +# Error 1: Missing colon after condition +flex x = 10 +when x > 5 + output("This is wrong") + +# Error 2: Missing closing parenthesis +flex result = add(5, 10 + +# Error 3: Missing equals in variable declaration +flex name "Alice" + +# Error 4: Invalid operator sequence +flex bad = 5 + + 3 + +# Error 5: Missing colon after when condition +when x > 10 + output("Missing colon") \ No newline at end of file diff --git a/tests/parser/test_expressions.eac b/tests/parser/test_expressions.eac new file mode 100644 index 0000000..e976a6e --- /dev/null +++ b/tests/parser/test_expressions.eac @@ -0,0 +1,13 @@ +flex a = 5 + 3 * 2 +flex b = (10 - 4) / 2 +flex c = 2 ^ 3 +flex d = 15 // 4 +flex e = 17 % 5 + +flex x = 10 +flex y = 20 +flex result = x > y and y < 30 +flex flag = not result or x == 10 + +flex absolute = |x - y| +flex list = [1, 2, 3, 4, 5] \ No newline at end of file diff --git a/tests/parser/test_functions.eac b/tests/parser/test_functions.eac new file mode 100644 index 0000000..d6e40a2 --- /dev/null +++ b/tests/parser/test_functions.eac @@ -0,0 +1,16 @@ +function add(a: int, b: int): int: + return a + b + +function greet(name: str): + output("Hello, ", name) + +function factorial(n: int): int: + when n <= 1: + return 1 + else: + return n * factorial(n - 1) + +function fibonacci(n: int): int: + when n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) \ No newline at end of file diff --git a/tests/parser/test_var_decl.eac b/tests/parser/test_var_decl.eac new file mode 100644 index 0000000..e7a222c --- /dev/null +++ b/tests/parser/test_var_decl.eac @@ -0,0 +1,7 @@ +flex x: int = 10 +fixed PI: float = 3.14159 +flex name: str = "Alice" +flex isActive: bool = true + +flex counter = 0 +fixed MAX_SIZE = 100 \ No newline at end of file