From 4cff24a38363e23c714a9a0c451953bb403a772d Mon Sep 17 00:00:00 2001 From: Ken Audie Lucero Date: Fri, 5 Dec 2025 04:34:54 +0800 Subject: [PATCH 1/9] feat(parser): ast trial --- Makefile | 30 +- src/main.c | 154 +++-- src/parser/ast.c | 535 ++++++++++++++++++ src/parser/ast.h | 261 +++++++++ src/parser/parser.c | 832 +++++++++++++++++++++++++++- src/parser/parser.h | 43 +- tests/parser/test_complete.eac | 61 ++ tests/parser/test_control_flow.eac | 22 + tests/parser/test_errors_syntax.eac | 17 + tests/parser/test_expressions.eac | 13 + tests/parser/test_functions.eac | 16 + tests/parser/test_var_decl.eac | 7 + 12 files changed, 1944 insertions(+), 47 deletions(-) create mode 100644 src/parser/ast.c create mode 100644 src/parser/ast.h create mode 100644 tests/parser/test_complete.eac create mode 100644 tests/parser/test_control_flow.eac create mode 100644 tests/parser/test_errors_syntax.eac create mode 100644 tests/parser/test_expressions.eac create mode 100644 tests/parser/test_functions.eac create mode 100644 tests/parser/test_var_decl.eac 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 From 6a1c58542da7ba60f2e3a67ac563f71f28e6d701 Mon Sep 17 00:00:00 2001 From: Gaboomsz Date: Fri, 5 Dec 2025 06:46:30 +0800 Subject: [PATCH 2/9] add missing tokens --- src/parser/ast.c | 178 +++++++++++++++++++++++++++++++++++++- src/parser/parser.c | 146 ++++++++++++++++++++++++++++--- tests/parser/test_add.eac | 4 + 3 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 tests/parser/test_add.eac diff --git a/src/parser/ast.c b/src/parser/ast.c index a06ceca..8153f23 100644 --- a/src/parser/ast.c +++ b/src/parser/ast.c @@ -57,6 +57,52 @@ static char* dupString(const char* str) { return copy; } +// ===== Helper to convert token type to symbol string ===== +const char* getOpSymbol(TokenType op) { + switch (op) { + // Arithmetic + case TOKEN_PLUS: return "+"; + case TOKEN_MINUS: return "-"; + case TOKEN_STAR: return "*"; + case TOKEN_SLASH: return "/"; + case TOKEN_PERCENT: return "%"; + case TOKEN_CARET: return "^"; + case TOKEN_FLOOR_DIV: return "//"; + + // Relational + case TOKEN_EQUAL_EQUAL: return "=="; + case TOKEN_BANG_EQUAL: return "!="; + case TOKEN_LESS: return "<"; + case TOKEN_LESS_EQUAL: return "<="; + case TOKEN_GREATER: return ">"; + case TOKEN_GREATER_EQUAL: return ">="; + + // Logical + case TOKEN_AND: return "and"; + case TOKEN_OR: return "or"; + case TOKEN_NOT: return "not"; + + // Assignment + case TOKEN_EQUAL: return "="; + case TOKEN_PLUS_EQUAL: return "+="; + case TOKEN_MINUS_EQUAL: return "-="; + + default: return "undefined"; + } +} + +// Helper to convert type hint to actual data type +const char* getTypeHintName(TokenType type) { + switch (type) { + case TOKEN_HINT_INT: return "int"; + case TOKEN_HINT_FLOAT: return "float"; + case TOKEN_HINT_STR: return "str"; + case TOKEN_HINT_BOOL: return "bool"; + case TOKEN_HINT_CHAR: return "char"; + default: return "unknown"; + } +} + // ===== AST Node Constructors ===== ASTNode* createProgram(ASTNodeList* statements) { @@ -487,7 +533,27 @@ void printAST(ASTNode* node, int indent) { } } break; - + + case AST_CONTINUE_STMT: + printf("CONTINUE\n"); + break; + + case AST_IMPORT_STMT: + if (node->data.importStmt.fromModule) { + // "from X import Y" + printf("IMPORT %s FROM %s\n", + node->data.importStmt.moduleName, + node->data.importStmt.fromModule); + } else { + // "import X" + printf("IMPORT %s\n", node->data.importStmt.moduleName); + } + break; + + case AST_BREAK_STMT: + printf("BREAK\n"); + break; + case AST_VAR_DECL: printf("VAR_DECL (%s) %s\n", node->data.varDecl.isMutable ? "flex" : "fixed", @@ -509,7 +575,7 @@ void printAST(ASTNode* node, int indent) { printf("BINARY_OP\n"); printAST(node->data.binaryOp.left, indent + 1); printIndent(indent + 1); - printf("OP: %d\n", node->data.binaryOp.op); + printf("OP: %s\n", getOpSymbol(node->data.binaryOp.op)); printAST(node->data.binaryOp.right, indent + 1); break; @@ -527,6 +593,114 @@ void printAST(ASTNode* node, int indent) { case AST_IDENTIFIER: printf("IDENTIFIER %s\n", node->data.identifier.name); break; + // [In ast.c, inside printAST function switch statement] + + case AST_FUNCTION_DECL: + printf("FUNCTION_DECL %s\n", node->data.funcDecl.name); + + // Print Parameters + if (node->data.funcDecl.params) { + printIndent(indent + 1); + printf("PARAMS\n"); + printAST(node->data.funcDecl.params, indent + 2); + } + + // Print Return Type + if (node->data.funcDecl.returnType) { + printIndent(indent + 1); + printf("RETURN TYPE\n"); + printAST(node->data.funcDecl.returnType, indent + 2); + } + + // Print Function Body + if (node->data.funcDecl.body) { + printIndent(indent + 1); + printf("BODY\n"); + for (int i = 0; i < node->data.funcDecl.body->count; i++) { + printAST(node->data.funcDecl.body->nodes[i], indent + 2); + } + } + break; + + case AST_PARAM_LIST: + if (node->data.list.items) { + for (int i = 0; i < node->data.list.items->count; i++) { + printAST(node->data.list.items->nodes[i], indent); + } + } + break; + + case AST_RETURN_STMT: + printf("RETURN\n"); + if (node->data.returnStmt.value) { + printAST(node->data.returnStmt.value, indent + 1); + } + break; + + case AST_TYPE_HINT: + // Uses the helper function to print actual data type instead of number + printf("TYPE_HINT %s\n", getTypeHintName(node->data.typeHint.hintType)); + break; + + // Handle For Loops + case AST_FOR_STMT: + printf("FOR LOOP (Iterator: %s)\n", node->data.forStmt.iterVar); + + printIndent(indent + 1); + printf("ITERABLE\n"); + printAST(node->data.forStmt.iterable, indent + 2); + + printIndent(indent + 1); + printf("BODY\n"); + if (node->data.forStmt.body) { + for (int i = 0; i < node->data.forStmt.body->count; i++) { + printAST(node->data.forStmt.body->nodes[i], indent + 2); + } + } + break; + + // Handle List Literals + case AST_LIST_LITERAL: + printf("LIST LITERAL\n"); + if (node->data.listLiteral.elements) { + for (int i = 0; i < node->data.listLiteral.elements->count; i++) { + printAST(node->data.listLiteral.elements->nodes[i], indent + 1); + } + } + break; + + // Handle Output Statements + case AST_OUTPUT_STMT: + printf("OUTPUT\n"); + if (node->data.outputStmt.expressions) { + for (int i = 0; i < node->data.outputStmt.expressions->count; i++) { + printAST(node->data.outputStmt.expressions->nodes[i], indent + 1); + } + } + break; + + // Handle If/When Statements + case AST_IF_STMT: + printf("WHEN STMT\n"); + printIndent(indent + 1); + printf("CONDITION\n"); + printAST(node->data.ifStmt.condition, indent + 2); + + printIndent(indent + 1); + printf("THEN\n"); + if (node->data.ifStmt.thenBranch) { + for (int i = 0; i < node->data.ifStmt.thenBranch->count; i++) { + printAST(node->data.ifStmt.thenBranch->nodes[i], indent + 2); + } + } + if (node->data.ifStmt.elseBranch) { + printIndent(indent + 1); + printf("ELSE\n"); + for (int i = 0; i < node->data.ifStmt.elseBranch->count; i++) { + printAST(node->data.ifStmt.elseBranch->nodes[i], indent + 2); + } + } + break; default: printf("NODE_TYPE_%d\n", node->type); diff --git a/src/parser/parser.c b/src/parser/parser.c index 9d1795f..9ad4ef7 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -361,16 +361,6 @@ static ASTNode* boolFactor(Parser* 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); } @@ -413,6 +403,33 @@ static ASTNode* expression(Parser* parser) { } // ===== Statement Parsing ===== +// -> import | from import +static ASTNode* importStatement(Parser* parser) { + int line = parser->previous.line; + + // Case 1: import moduleName + if (parser->previous.type == TOKEN_IMPORT) { + consume(parser, TOKEN_IDENTIFIER, "Expected module name after 'import'"); + char* moduleName = tokenToString(parser->previous); + ASTNode* node = createImportStmt(moduleName, NULL, line); + free(moduleName); + return node; + } + + // Case 2: from moduleName import target + // (We enter here if match(TOKEN_FROM) was called in statement()) + consume(parser, TOKEN_IDENTIFIER, "Expected module name after 'from'"); + char* fromModule = tokenToString(parser->previous); + + consume(parser, TOKEN_IMPORT, "Expected 'import' after module name"); + consume(parser, TOKEN_IDENTIFIER, "Expected identifier to import"); + char* moduleName = tokenToString(parser->previous); // This is the specific item being imported + + ASTNode* node = createImportStmt(moduleName, fromModule, line); + free(fromModule); + free(moduleName); + return node; +} // → output ( ) static ASTNode* outputStatement(Parser* parser) { @@ -652,10 +669,112 @@ static ASTNode* declarationStatement(Parser* parser) { free(name); return node; } +static ASTNode* functionDeclaration(Parser* parser) { + int line = parser->previous.line; + + // 1. Parse Name + consume(parser, TOKEN_IDENTIFIER, "Expected function name."); + char* name = tokenToString(parser->previous); + + // 2. Parse Parameters: (a: int, b: int) + consume(parser, TOKEN_LPAREN, "Expected '(' after function name."); + ASTNodeList* paramsList = createNodeList(); + + if (!check(parser, TOKEN_RPAREN)) { + do { + // Param Name + consume(parser, TOKEN_IDENTIFIER, "Expected parameter name."); + char* paramName = tokenToString(parser->previous); + int paramLine = parser->previous.line; + + // Param Type Hint + ASTNode* paramType = NULL; + 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)) { + TokenType type = parser->current.type; + advance(parser); + paramType = createTypeHint(type, paramLine); + } else { + errorAtCurrent(parser, "Expected type hint for parameter."); + } + } + + // Create param as a VarDecl (fixed=true usually for params) + ASTNode* param = createVarDecl(false, paramName, paramType, NULL, paramLine); + addNode(paramsList, param); + free(paramName); + + } while (match(parser, TOKEN_COMMA)); + } + consume(parser, TOKEN_RPAREN, "Expected ')' after parameters."); + ASTNode* paramsNode = createParamList(paramsList, line); + + // 3. Parse Return Type: : int + ASTNode* returnType = NULL; + if (check(parser, TOKEN_COLON)) { + // We need to peek ahead to see if this is a return type or the block start + + consume(parser, TOKEN_COLON, "Expected ':'"); + + 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 type = parser->current.type; + advance(parser); + returnType = createTypeHint(type, line); + + // If we had a return type, we need ANOTHER colon for the block start + consume(parser, TOKEN_COLON, "Expected ':' before function body."); + } + // If it wasn't a type, we assume that first colon was for the block start + } else { + consume(parser, TOKEN_COLON, "Expected ':' before function body."); + } + + // 4. Parse Body + consume(parser, TOKEN_NEWLINE, "Expected newline before function body."); + // Note: Your lexer handles INDENT/DEDENT, so we rely on statements() + // However, usually you check for INDENT here explicitly if your grammar requires it. + + ASTNodeList* body = statements(parser); + + ASTNode* funcNode = createFuncDecl(name, paramsNode, returnType, body, line); + free(name); + return funcNode; +} -// | | | | | +static ASTNode* returnStatement(Parser* parser) { + int line = parser->previous.line; + ASTNode* value = NULL; + + // Check if there is an expression after 'return'. + if (!check(parser, TOKEN_NEWLINE) && !check(parser, TOKEN_DEDENT) && !check(parser, TOKEN_EOF)) { + value = expression(parser); + } + + return createReturnStmt(value, line); +} + +// | | | | | | | | | static ASTNode* statement(Parser* parser) { skipNewlines(parser); + + // + if (match(parser, TOKEN_IMPORT) || match(parser, TOKEN_FROM)) { + return importStatement(parser); + } + + // + if (match(parser, TOKEN_FUNCTION)) { + return functionDeclaration(parser); + } + + // + if (match(parser, TOKEN_RETURN)) { + return returnStatement(parser); + } // if (match(parser, TOKEN_FLEX) || match(parser, TOKEN_FIXED)) { @@ -671,6 +790,11 @@ static ASTNode* statement(Parser* parser) { if (match(parser, TOKEN_WHEN)) { return conditionalStatement(parser); } + + // + if (match(parser, TOKEN_BREAK)) { + return createBreakStmt(parser->previous.line); + } // if (match(parser, TOKEN_WHILE)) { diff --git a/tests/parser/test_add.eac b/tests/parser/test_add.eac new file mode 100644 index 0000000..5d002b6 --- /dev/null +++ b/tests/parser/test_add.eac @@ -0,0 +1,4 @@ +for i in [1, 2, 3, 4, 5]: + output(i) + when i == 3: + break \ No newline at end of file From d67c480b5b3ed5a22dfbc5a860f7f3acd32777c9 Mon Sep 17 00:00:00 2001 From: Ken Audie Lucero Date: Fri, 5 Dec 2025 09:36:05 +0800 Subject: [PATCH 3/9] feat(ast): indent/dedent --- src/lexer/lexer.c | 310 ++++++++++++++++++++++++++++++++++++- src/main.c | 2 + src/parser/parser.c | 209 +++++++++++++------------ tests/parser/test_main.eac | 89 +++++++++++ 4 files changed, 503 insertions(+), 107 deletions(-) create mode 100644 tests/parser/test_main.eac diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index f7db57a..f7f633f 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -63,11 +63,33 @@ typedef enum { Q_CARET = 147, Q_NEWLINE = 148 } State; +#define MAX_INDENT_LEVELS 100 +#define MAX_TOKEN_QUEUE 50 + struct Lexer { const char* source; const char* start; const char* current; int line; + + // Indentation tracking + int indentStack[MAX_INDENT_LEVELS]; + int indentCount; + + // Token queue for buffering DEDENT tokens + Token tokenQueue[MAX_TOKEN_QUEUE]; + int queueStart; + int queueEnd; + int queueSize; + + // Track if we're at the beginning of a line + bool atLineStart; + + // Track if we've seen EOF + bool seenEOF; + + // Track if we just emitted a newline (NEW FIELD) + bool justEmittedNewline; }; #define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || (c) == '_') @@ -824,14 +846,176 @@ static Token errorToken(Lexer* lexer, const char* message) { return token; } -static Token scanToken(Lexer* lexer) { +// ===== Token Queue Management ===== + +static void initQueue(Lexer* lexer) { + lexer->queueStart = 0; + lexer->queueEnd = 0; + lexer->queueSize = 0; +} + +static bool queueIsEmpty(Lexer* lexer) { + return lexer->queueSize == 0; +} + +static void enqueueToken(Lexer* lexer, Token token) { + if (lexer->queueSize >= MAX_TOKEN_QUEUE) { + return; + } + lexer->tokenQueue[lexer->queueEnd] = token; + lexer->queueEnd = (lexer->queueEnd + 1) % MAX_TOKEN_QUEUE; + lexer->queueSize++; +} + +static Token dequeueToken(Lexer* lexer) { + if (queueIsEmpty(lexer)) { + Token error; + error.type = TOKEN_ERROR; + error.lexeme = "Token queue empty"; + error.length = 17; + error.line = lexer->line; + return error; + } + Token token = lexer->tokenQueue[lexer->queueStart]; + lexer->queueStart = (lexer->queueStart + 1) % MAX_TOKEN_QUEUE; + lexer->queueSize--; + return token; +} + +// ===== Indentation Tracking ===== + +static int currentIndent(Lexer* lexer) { + if (lexer->indentCount == 0) return 0; + return lexer->indentStack[lexer->indentCount - 1]; +} + +static void pushIndent(Lexer* lexer, int level) { + if (lexer->indentCount >= MAX_INDENT_LEVELS) { + return; + } + lexer->indentStack[lexer->indentCount++] = level; +} + +static int popIndent(Lexer* lexer) { + if (lexer->indentCount <= 0) return 0; + return lexer->indentStack[--lexer->indentCount]; +} + +static Token makeIndentToken(Lexer* lexer) { + Token token; + token.type = TOKEN_INDENT; + token.lexeme = ""; + token.length = 8; + token.line = lexer->line; + return token; +} + +static Token makeDedentToken(Lexer* lexer) { + Token token; + token.type = TOKEN_DEDENT; + token.lexeme = ""; + token.length = 8; + token.line = lexer->line; + return token; +} + +static int countLeadingSpaces(const char* ptr) { + int count = 0; + while (*ptr == ' ' || *ptr == '\t') { + if (*ptr == ' ') { + count++; + } else if (*ptr == '\t') { + count += 4; // Tab = 4 spaces + } + ptr++; + } + return count; +} + +static bool isBlankLine(const char* ptr) { // Skip whitespace + while (*ptr == ' ' || *ptr == '\t') { + ptr++; + } + // Line is blank if it's just newline, EOF, or starts with # + return (*ptr == '\n' || *ptr == '\0' || *ptr == '#'); +} + +static void handleIndentation(Lexer* lexer) { + // Skip the leading spaces/tabs but remember where we are + const char* lineStart = lexer->current; + int spaces = countLeadingSpaces(lineStart); + + // Skip the whitespace + while (*lexer->current == ' ' || *lexer->current == '\t') { + lexer->current++; + } + + // Check if this is a blank line or comment AFTER measuring indentation + bool isBlankOrComment = isBlankLine(lexer->current); + + // For blank lines and comments, we still need to track indentation changes + // if this is the first line of a new block (i.e., indentation increased) + int currentLevel = currentIndent(lexer); + + if (isBlankOrComment) { + // If the blank/comment line has MORE indentation than current level, + // we still need to emit INDENT because it starts a new block + if (spaces > currentLevel) { + pushIndent(lexer, spaces); + Token indent = makeIndentToken(lexer); + enqueueToken(lexer, indent); + } + // For blank/comment lines with same or less indentation, do nothing + // (don't emit DEDENT for temporary blank lines) + return; + } + + // Regular indentation handling for non-blank, non-comment lines + if (spaces > currentLevel) { + // INDENT + pushIndent(lexer, spaces); + Token indent = makeIndentToken(lexer); + enqueueToken(lexer, indent); + } else if (spaces < currentLevel) { + // DEDENT (possibly multiple) + while (lexer->indentCount > 0 && currentIndent(lexer) > spaces) { + popIndent(lexer); + Token dedent = makeDedentToken(lexer); + enqueueToken(lexer, dedent); + } + + // Check for indentation error + if (currentIndent(lexer) != spaces) { + Token error; + error.type = TOKEN_ERROR; + error.lexeme = "Indentation error"; + error.length = 17; + error.line = lexer->line; + enqueueToken(lexer, error); + } + } + // If spaces == currentLevel, no change +} + +static bool isBlankOrComment(const char* ptr) { + // Skip whitespace + while (*ptr == ' ' || *ptr == '\t') { + ptr++; + } + // Check if line is blank or starts with comment + return (*ptr == '\n' || *ptr == '\0' || *ptr == '#'); +} + +static Token scanToken(Lexer* lexer) { + // Skip whitespace (spaces, tabs, carriage returns - but NOT newlines) while (*lexer->current == ' ' || *lexer->current == '\r' || *lexer->current == '\t') { lexer->current++; } lexer->start = lexer->current; + // Handle EOF if (*lexer->current == '\0') { return makeToken(lexer, TOKEN_EOF); } @@ -849,13 +1033,24 @@ static Token scanToken(Lexer* lexer) { lexer->current = lastAcceptPos; TokenType type = getTokenType(lastAccept); + // Track newlines for line counting for (const char* p = lexer->start; p < lexer->current; p++) { - if (*p == '\n') lexer->line++; + if (*p == '\n') { + lexer->line++; + } } - return makeToken(lexer, type); + Token token = makeToken(lexer, type); + + // Mark that we just saw a newline + if (type == TOKEN_NEWLINE) { + lexer->justEmittedNewline = true; + } + + return token; } + // Error handling if (state == Q_START) { lexer->current++; return errorToken(lexer, "Unexpected character."); @@ -880,21 +1075,43 @@ static Token scanToken(Lexer* lexer) { } } + // Handle accepted state at EOF if (isAcceptingState(state)) { TokenType type = getTokenType(state); + for (const char* p = lexer->start; p < lexer->current; p++) { - if (*p == '\n') lexer->line++; + if (*p == '\n') { + lexer->line++; + } + } + + Token token = makeToken(lexer, type); + + if (type == TOKEN_NEWLINE) { + lexer->justEmittedNewline = true; } - return makeToken(lexer, type); + + return token; } + // Final error state handling if (lastAccept != Q_ERROR) { lexer->current = lastAcceptPos; TokenType type = getTokenType(lastAccept); + for (const char* p = lexer->start; p < lexer->current; p++) { - if (*p == '\n') lexer->line++; + if (*p == '\n') { + lexer->line++; + } } - return makeToken(lexer, type); + + Token token = makeToken(lexer, type); + + if (type == TOKEN_NEWLINE) { + lexer->justEmittedNewline = true; + } + + return token; } if (state == Q_STRING_BODY) return errorToken(lexer, "Unterminated string literal."); @@ -917,6 +1134,21 @@ Lexer* initLexer(const char* source) { lexer->current = source; lexer->line = 1; + // Initialize indentation tracking + lexer->indentCount = 0; + lexer->indentStack[0] = 0; + + // Initialize token queue + initQueue(lexer); + + // Track state + lexer->atLineStart = true; + lexer->seenEOF = false; + lexer->justEmittedNewline = false; // Start of file acts like after newline + + // We're at the start, so we should check indentation after first token + // But the first line typically has 0 indentation, so we'll handle it naturally + return lexer; } @@ -930,7 +1162,69 @@ Token getNextToken(Lexer* lexer) { return errorTok; } - return scanToken(lexer); + // If we have queued tokens (INDENT/DEDENT), return them first + if (!queueIsEmpty(lexer)) { + return dequeueToken(lexer); + } + + // Handle EOF: emit remaining DEDENT tokens + if (lexer->seenEOF) { + if (!queueIsEmpty(lexer)) { + return dequeueToken(lexer); + } + return makeToken(lexer, TOKEN_EOF); + } + + // If we just emitted a newline, handle indentation for next line + if (lexer->justEmittedNewline) { + lexer->justEmittedNewline = false; + + // Check if we're at EOF + if (*lexer->current == '\0') { + lexer->seenEOF = true; + // Generate DEDENT tokens for all remaining levels + while (lexer->indentCount > 0) { + popIndent(lexer); + Token dedent = makeDedentToken(lexer); + enqueueToken(lexer, dedent); + } + + if (!queueIsEmpty(lexer)) { + return dequeueToken(lexer); + } + return makeToken(lexer, TOKEN_EOF); + } + + // Process indentation + handleIndentation(lexer); + + // If we queued INDENT/DEDENT tokens, return the first one + if (!queueIsEmpty(lexer)) { + return dequeueToken(lexer); + } + } + + // Get the next token normally + Token token = scanToken(lexer); + + // Handle EOF + if (token.type == TOKEN_EOF) { + if (!lexer->seenEOF) { + lexer->seenEOF = true; + // Generate DEDENT tokens for all remaining levels + while (lexer->indentCount > 0) { + popIndent(lexer); + Token dedent = makeDedentToken(lexer); + enqueueToken(lexer, dedent); + } + + if (!queueIsEmpty(lexer)) { + return dequeueToken(lexer); + } + } + } + + return token; } void freeLexer(Lexer* lexer) { diff --git a/src/main.c b/src/main.c index ef8e07f..0e9d0ea 100644 --- a/src/main.c +++ b/src/main.c @@ -17,6 +17,8 @@ const char* getTokenSpecial(TokenType type) { case TOKEN_ERROR: return "ERROR"; case TOKEN_NEWLINE: return "NEWLINE"; + case TOKEN_INDENT: return "INDENT"; + case TOKEN_DEDENT: return "DEDENT"; // Literals case TOKEN_IDENTIFIER: return "IDENTIFIER"; diff --git a/src/parser/parser.c b/src/parser/parser.c index 9ad4ef7..ecff927 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -473,17 +473,16 @@ static ASTNode* inputStatement(Parser* parser, char* varName, int line) { static ASTNode* conditionalStatement(Parser* parser) { int line = parser->previous.line; + // Rule 44: when : ASTNode* cond = condition(parser); consume(parser, TOKEN_COLON, "Expected ':' after condition in 'when' statement"); + consume(parser, TOKEN_NEWLINE, "Expected newline after ':' in 'when' statement"); + consume(parser, TOKEN_INDENT, "Expected indentation after 'when:'"); - if (!match(parser, TOKEN_NEWLINE)) { - errorAtCurrent(parser, "Expected newline after ':' in 'when' statement"); - } + ASTNodeList* thenBranch = statements(parser); - // Note: INDENT/DEDENT tokens should come from lexer for proper indentation handling - skipNewlines(parser); + consume(parser, TOKEN_DEDENT, "Expected dedent after 'when' block"); - ASTNodeList* thenBranch = statements(parser); ASTNodeList* elseBranch = NULL; skipNewlines(parser); @@ -491,18 +490,16 @@ static ASTNode* conditionalStatement(Parser* parser) { // Handle else when (elwhen) chain while (match(parser, TOKEN_ELSE)) { if (match(parser, TOKEN_WHEN)) { - // else when case - create nested if statement + // Rule 50-51: else when : int elwhenLine = parser->previous.line; ASTNode* elwhenCond = condition(parser); consume(parser, TOKEN_COLON, "Expected ':' after condition in 'else when' statement"); + consume(parser, TOKEN_NEWLINE, "Expected newline after ':' in 'else when' statement"); + consume(parser, TOKEN_INDENT, "Expected indentation after 'else when:'"); - if (!match(parser, TOKEN_NEWLINE)) { - errorAtCurrent(parser, "Expected newline after ':' in 'else when' statement"); - } - - skipNewlines(parser); ASTNodeList* elwhenBody = statements(parser); - skipNewlines(parser); + + consume(parser, TOKEN_DEDENT, "Expected dedent after 'else when' block"); // Create nested if for else when ASTNode* elwhenStmt = createIfStmt(elwhenCond, elwhenBody, NULL, elwhenLine); @@ -511,16 +508,17 @@ static ASTNode* conditionalStatement(Parser* parser) { elseBranch = createNodeList(); } addNode(elseBranch, elwhenStmt); + + skipNewlines(parser); } else { - // Final else case + // Rule 46: else : consume(parser, TOKEN_COLON, "Expected ':' after 'else'"); + consume(parser, TOKEN_NEWLINE, "Expected newline after ':' in 'else' statement"); + consume(parser, TOKEN_INDENT, "Expected indentation after 'else:'"); - if (!match(parser, TOKEN_NEWLINE)) { - errorAtCurrent(parser, "Expected newline after ':' in 'else' statement"); - } - - skipNewlines(parser); elseBranch = statements(parser); + + consume(parser, TOKEN_DEDENT, "Expected dedent after 'else' block"); break; } } @@ -532,16 +530,16 @@ static ASTNode* conditionalStatement(Parser* parser) { static ASTNode* whileStatement(Parser* parser) { int line = parser->previous.line; + // Rule 60: while : ASTNode* cond = condition(parser); consume(parser, TOKEN_COLON, "Expected ':' after condition in 'while' statement"); + consume(parser, TOKEN_NEWLINE, "Expected newline after ':' in 'while' statement"); + consume(parser, TOKEN_INDENT, "Expected indentation after 'while:'"); - if (!match(parser, TOKEN_NEWLINE)) { - errorAtCurrent(parser, "Expected newline after ':'"); - } - - skipNewlines(parser); ASTNodeList* body = statements(parser); + consume(parser, TOKEN_DEDENT, "Expected dedent after 'while' block"); + return createWhileStmt(cond, body, line); } @@ -598,15 +596,15 @@ static ASTNode* forStatement(Parser* parser) { return NULL; } + // Rule 61-62: for in : consume(parser, TOKEN_COLON, "Expected ':' after iterable"); + consume(parser, TOKEN_NEWLINE, "Expected newline after ':' in 'for' statement"); + consume(parser, TOKEN_INDENT, "Expected indentation after 'for:'"); - if (!match(parser, TOKEN_NEWLINE)) { - errorAtCurrent(parser, "Expected newline after ':'"); - } - - skipNewlines(parser); ASTNodeList* body = statements(parser); + consume(parser, TOKEN_DEDENT, "Expected dedent after 'for' block"); + ASTNode* node = createForStmt(iterVar, iterable, body, line); free(iterVar); return node; @@ -733,13 +731,14 @@ static ASTNode* functionDeclaration(Parser* parser) { consume(parser, TOKEN_COLON, "Expected ':' before function body."); } - // 4. Parse Body + // 4. Parse Body: function body must be indented consume(parser, TOKEN_NEWLINE, "Expected newline before function body."); - // Note: Your lexer handles INDENT/DEDENT, so we rely on statements() - // However, usually you check for INDENT here explicitly if your grammar requires it. + consume(parser, TOKEN_INDENT, "Expected indentation for function body."); ASTNodeList* body = statements(parser); + consume(parser, TOKEN_DEDENT, "Expected dedent after function body."); + ASTNode* funcNode = createFuncDecl(name, paramsNode, returnType, body, line); free(name); return funcNode; @@ -795,6 +794,11 @@ static ASTNode* statement(Parser* parser) { if (match(parser, TOKEN_BREAK)) { return createBreakStmt(parser->previous.line); } + + // + if (match(parser, TOKEN_CONTINUE)) { + return createContinueStmt(parser->previous.line); + } // if (match(parser, TOKEN_WHILE)) { @@ -806,73 +810,73 @@ static ASTNode* statement(Parser* parser) { } // or (both start with identifier) - if (match(parser, TOKEN_IDENTIFIER)) { - char* name = tokenToString(parser->previous); - int line = parser->previous.line; +if (match(parser, TOKEN_IDENTIFIER)) { + char* name = tokenToString(parser->previous); + int line = parser->previous.line; + + // Check for assignment or input + if (check(parser, TOKEN_EQUAL)) { + advance(parser); // consume '=' - // Check for input statement: = input(...) - if (check(parser, TOKEN_EQUAL)) { - Token nextToken = parser->current; - advance(parser); // consume = + // Check if this is an input statement + if (check(parser, TOKEN_INPUT)) { + advance(parser); // consume 'input' + consume(parser, TOKEN_LPAREN, "Expected '(' after 'input'"); - 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; + char* prompt = NULL; + if (match(parser, TOKEN_STRING)) { + prompt = parseString(parser->previous); } - } - - // 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); + + consume(parser, TOKEN_RPAREN, "Expected ')' after input arguments"); + + ASTNode* node = createInputStmt(name, prompt, line); + free(name); + if (prompt) free(prompt); + return node; + } else { + // Regular assignment ASTNode* value = expression(parser); - ASTNode* node = createCompoundAssign(name, op, value, line); + 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(); - // 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); + if (!check(parser, TOKEN_RPAREN)) { + do { + addNode(args, expression(parser)); + } while (match(parser, TOKEN_COMMA)); } - errorAtCurrent(parser, "Expected assignment or function call"); + consume(parser, TOKEN_RPAREN, "Expected ')' after arguments"); + ASTNode* argList = createArgList(args, line); + ASTNode* call = createCallExpr(name, argList, line); free(name); - return NULL; + return createExprStmt(call, line); } + errorAtCurrent(parser, "Expected assignment or function call"); + free(name); + return NULL; +} + errorAtCurrent(parser, "Expected statement"); return NULL; } @@ -881,10 +885,22 @@ static ASTNode* statement(Parser* parser) { static ASTNodeList* statements(Parser* parser) { ASTNodeList* stmtList = createNodeList(); - while (!check(parser, TOKEN_EOF) && !check(parser, TOKEN_ELSE)) { - skipNewlines(parser); + while (!check(parser, TOKEN_EOF) && + !check(parser, TOKEN_ELSE) && + !check(parser, TOKEN_DEDENT)) { - if (check(parser, TOKEN_EOF) || check(parser, TOKEN_ELSE)) { + // Skip newlines and any unexpected indent tokens + while (match(parser, TOKEN_NEWLINE) || check(parser, TOKEN_INDENT)) { + if (check(parser, TOKEN_INDENT)) { + // We shouldn't see INDENT here in the middle of a block + // This might be from a comment line, skip it + advance(parser); + } + } + + if (check(parser, TOKEN_EOF) || + check(parser, TOKEN_ELSE) || + check(parser, TOKEN_DEDENT)) { break; } @@ -896,16 +912,6 @@ static ASTNodeList* statements(Parser* parser) { 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; @@ -934,6 +940,11 @@ ASTNode* parse(Parser* parser) { ASTNodeList* stmts = statements(parser); + // Skip any trailing DEDENT tokens before EOF + while (match(parser, TOKEN_DEDENT)) { + // Skip + } + if (!match(parser, TOKEN_EOF)) { errorAtCurrent(parser, "Expected end of file"); } diff --git a/tests/parser/test_main.eac b/tests/parser/test_main.eac new file mode 100644 index 0000000..a6842b0 --- /dev/null +++ b/tests/parser/test_main.eac @@ -0,0 +1,89 @@ +# ========================================== +# EaC Comprehensive Test Suite +# Covers: Lexical Tokens & Syntax Structures +# ========================================== + +/* + This is a block comment. + It spans multiple lines. + Testing: Comments, Whitespace handling +*/ + +# 1. Imports +import math +from utils import helper + +# 2. Global Constants & Variables +fixed PI : float = 3.14159 +flex global_counter : int = 0 +fixed MAX_USERS = 100 +flex is_active : bool = true +flex grade : char = 'A' +flex message : str = "Welcome to EaC!" + +# 3. Function Declarations +function calculate_area(radius: float): float: + return PI * (radius ^ 2) + +function update_status(status: bool): + flex current_status = status + when current_status == true: + output("System is Active") + else: + output("System is Offline") + +function demonstrate_arithmetic(): + flex user_count = 10 + user_count += 1 + user_count -= 2 + user_count *= 2 + user_count /= 1 + user_count %= 5 + flex x = 10 // 3 + flex y = |-5| + output("Arithmetic demo complete") + +function demonstrate_logic(): + flex user_count = 5 + flex is_valid = (user_count > 0) and (user_count <= MAX_USERS) + flex is_admin = false + when is_valid or not is_admin: + output("Access Granted") + +function demonstrate_loops(): + flex i = 0 + while i < 5: + output(i) + i += 1 + + for j in range(10): + when j % 2 == 0: + output(j, " is even") + else: + output(j, " is odd") + + flex numbers = [1, 2, 3, 4, 5] + for num in numbers: + flex area = calculate_area(num) + output("Area for radius ", num, ": ", area) + + flex k = 0 + while k < 10: + k += 1 + when k == 3: + continue + when k == 8: + break + output(k) + +function main(): + flex user_count = 10 + flex total_score : float = 95.5 + flex name : str + name = input("Enter your name: ") + output("Hello, ", name) + demonstrate_arithmetic() + demonstrate_logic() + demonstrate_loops() + update_status(false) + return 0 \ No newline at end of file From 73f6eb0992f604045ae1bb3b30d60046e4f3b6a5 Mon Sep 17 00:00:00 2001 From: Ken Audie Lucero Date: Fri, 5 Dec 2025 16:25:28 +0800 Subject: [PATCH 4/9] feat(parser): improved panic mode --- src/main.c | 12 ++++- src/parser/parser.c | 29 ++++++----- tests/demo/test_error.eac | 3 ++ tests/{parser => demo}/test_main.eac | 76 ++++++++++++++++++++++++++-- 4 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 tests/demo/test_error.eac rename tests/{parser => demo}/test_main.eac (52%) diff --git a/src/main.c b/src/main.c index 0e9d0ea..bc427d0 100644 --- a/src/main.c +++ b/src/main.c @@ -349,9 +349,17 @@ void runParser(const char* sourcePath, const char* source) { ASTNode* ast = parse(parser); - if (ast == NULL || hasError(parser)) { - printf("Status: FAILED - Parsing errors occurred\n"); + if (hasError(parser)) { + printf("\n==========================================================================\n"); + printf("Status: COMPLETED WITH ERRORS\n"); printf("==========================================================================\n"); + + if (ast) { + printf("\nPartial Abstract Syntax Tree (successfully parsed statements):\n"); + printf("--------------------------------------------------------------------------\n"); + printAST(ast, 0); + printf("==========================================================================\n"); + } } else { printf("Status: SUCCESS - AST generated\n"); printf("==========================================================================\n"); diff --git a/src/parser/parser.c b/src/parser/parser.c index ecff927..851ea6e 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -564,22 +564,29 @@ static ASTNode* forStatement(Parser* parser) { advance(parser); char* name = tokenToString(nameToken); - // Check for range(expr) + // Check if this is a range() call 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 + // Parse range arguments (can be 1, 2, or 3 arguments) ASTNodeList* args = createNodeList(); - addNode(args, rangeArg); + + if (!check(parser, TOKEN_RPAREN)) { + do { + addNode(args, expression(parser)); + } while (match(parser, TOKEN_COMMA)); + } + + consume(parser, TOKEN_RPAREN, "Expected ')' after range arguments"); + + // Create the range call expression ASTNode* argList = createArgList(args, nameToken.line); iterable = createCallExpr("range", argList, nameToken.line); } else { + // Just a regular identifier iterable = createIdentifier(name, nameToken.line); } free(name); } else if (match(parser, TOKEN_LBRACKET)) { - // List literal + // List literal as iterable ASTNodeList* elements = createNodeList(); if (!check(parser, TOKEN_RBRACKET)) { @@ -917,6 +924,7 @@ static ASTNodeList* statements(Parser* parser) { return stmtList; } + // ===== Public API ===== Parser* initParser(Lexer* lexer) { @@ -949,11 +957,8 @@ ASTNode* parse(Parser* parser) { errorAtCurrent(parser, "Expected end of file"); } - if (parser->hadError) { - freeNodeList(stmts); - return NULL; - } - + // CHANGE: Always return the AST, even if there were errors + // The caller can check hasError() to decide what to do return createProgram(stmts); } diff --git a/tests/demo/test_error.eac b/tests/demo/test_error.eac new file mode 100644 index 0000000..1b5a382 --- /dev/null +++ b/tests/demo/test_error.eac @@ -0,0 +1,3 @@ +# test_bad_indent.eac +when x > 0: +output("no indent") \ No newline at end of file diff --git a/tests/parser/test_main.eac b/tests/demo/test_main.eac similarity index 52% rename from tests/parser/test_main.eac rename to tests/demo/test_main.eac index a6842b0..905d256 100644 --- a/tests/parser/test_main.eac +++ b/tests/demo/test_main.eac @@ -76,12 +76,80 @@ function demonstrate_loops(): break output(k) -function main(): - flex user_count = 10 - flex total_score : float = 95.5 +function demonstrate_nested_loops(): + output("Nested Loop Demo - Multiplication Table") + for i in range(1, 4): + for j in range(1, 4): + flex result = i * j + output(i, " x ", j, " = ", result) + + output("Nested While Loop Demo") + flex row = 0 + while row < 3: + flex col = 0 + while col < 3: + output("Position: (", row, ", ", col, ")") + col += 1 + row += 1 + + output("Mixed Nested Loop Demo") + for outer in range(3): + flex inner = 0 + while inner < 2: + output("Outer: ", outer, " Inner: ", inner) + inner += 1 + +function demonstrate_input(): flex name : str + flex age : int + flex score : float name = input("Enter your name: ") - output("Hello, ", name) + age = input("Enter your age: ") + score = input("Enter your score: ") + output("Name: ", name, " Age: ", age, " Score: ", score) + +function demonstrate_output(): + output("Welcome to EaC!") + output("System Version: ", 2.0) + output("Status: ", is_active, " Max Users: ", MAX_USERS) + +function demonstrate_assignment(): + flex x = 10 + flex y : float = 3.14 + flex z : str = "test" + x = 20 + y = x + 5.5 + z = "modified" + +function demonstrate_conditions(): + flex score = 85 + flex grade : char + when score >= 90: + grade = 'A' + output("Excellent!") + when score >= 80: + grade = 'B' + output("Good job!") + else: + grade = 'C' + output("Keep trying!") + +function demonstrate_iteration(): + for i in range(5): + output("Iteration: ", i) + + flex count = 0 + while count < 3: + output("Count: ", count) + count += 1 + +function main(): + demonstrate_input() + demonstrate_output() + demonstrate_assignment() + demonstrate_conditions() + demonstrate_iteration() + demonstrate_nested_loops() demonstrate_arithmetic() demonstrate_logic() demonstrate_loops() From c15551c426bc3b99523221294f06d05ecba73801 Mon Sep 17 00:00:00 2001 From: JpCurada Date: Wed, 10 Dec 2025 00:08:35 +0800 Subject: [PATCH 5/9] fix(parser): fixed errors found by nek --- src/common/token.h | 1 + src/lexer/lexer.c | 50 +++++++++++++++++++++----- src/parser/ast.c | 72 ++++++++++++++++++++++++++++++++++--- src/parser/parser.c | 76 ++++++++++++++++------------------------ tests/demo/test_main.eac | 6 ++-- 5 files changed, 144 insertions(+), 61 deletions(-) diff --git a/src/common/token.h b/src/common/token.h index a843aee..3fd9427 100644 --- a/src/common/token.h +++ b/src/common/token.h @@ -106,6 +106,7 @@ typedef struct { const char* lexeme; int length; int line; + int column; } Token; #endif // EAC_TOKEN_H diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index f7f633f..5b48f8b 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -90,6 +90,10 @@ struct Lexer { // Track if we just emitted a newline (NEW FIELD) bool justEmittedNewline; + + // Column tracking + int column; + int tokenStartColumn; }; #define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || (c) == '_') @@ -834,6 +838,7 @@ static Token makeToken(Lexer* lexer, TokenType type) { token.lexeme = lexer->start; token.length = (int)(lexer->current - lexer->start); token.line = lexer->line; + token.column = lexer->tokenStartColumn; return token; } @@ -843,6 +848,7 @@ static Token errorToken(Lexer* lexer, const char* message) { token.lexeme = message; token.length = (int)strlen(message); token.line = lexer->line; + token.column = lexer->tokenStartColumn; return token; } @@ -907,6 +913,7 @@ static Token makeIndentToken(Lexer* lexer) { token.lexeme = ""; token.length = 8; token.line = lexer->line; + token.column = 1; return token; } @@ -916,6 +923,7 @@ static Token makeDedentToken(Lexer* lexer) { token.lexeme = ""; token.length = 8; token.line = lexer->line; + token.column = 1; return token; } @@ -948,6 +956,11 @@ static void handleIndentation(Lexer* lexer) { // Skip the whitespace while (*lexer->current == ' ' || *lexer->current == '\t') { + if (*lexer->current == '\t') { + lexer->column += 4; + } else { + lexer->column++; + } lexer->current++; } @@ -998,22 +1011,22 @@ static void handleIndentation(Lexer* lexer) { // If spaces == currentLevel, no change } -static bool isBlankOrComment(const char* ptr) { - // Skip whitespace - while (*ptr == ' ' || *ptr == '\t') { - ptr++; - } - // Check if line is blank or starts with comment - return (*ptr == '\n' || *ptr == '\0' || *ptr == '#'); -} + static Token scanToken(Lexer* lexer) { // Skip whitespace (spaces, tabs, carriage returns - but NOT newlines) while (*lexer->current == ' ' || *lexer->current == '\r' || *lexer->current == '\t') { + if (*lexer->current == '\t') { + lexer->column += 4; + } else if (*lexer->current == ' ') { + lexer->column++; + } + // \r does not advance column lexer->current++; } lexer->start = lexer->current; + lexer->tokenStartColumn = lexer->column; // Handle EOF if (*lexer->current == '\0') { @@ -1033,10 +1046,15 @@ static Token scanToken(Lexer* lexer) { lexer->current = lastAcceptPos; TokenType type = getTokenType(lastAccept); - // Track newlines for line counting + // Track newlines and columns for line counting for (const char* p = lexer->start; p < lexer->current; p++) { if (*p == '\n') { lexer->line++; + lexer->column = 1; + } else if (*p == '\t') { + lexer->column += 4; + } else { + lexer->column++; } } @@ -1082,6 +1100,11 @@ static Token scanToken(Lexer* lexer) { for (const char* p = lexer->start; p < lexer->current; p++) { if (*p == '\n') { lexer->line++; + lexer->column = 1; + } else if (*p == '\t') { + lexer->column += 4; + } else { + lexer->column++; } } @@ -1102,6 +1125,11 @@ static Token scanToken(Lexer* lexer) { for (const char* p = lexer->start; p < lexer->current; p++) { if (*p == '\n') { lexer->line++; + lexer->column = 1; + } else if (*p == '\t') { + lexer->column += 4; + } else { + lexer->column++; } } @@ -1132,7 +1160,11 @@ Lexer* initLexer(const char* source) { lexer->source = source; lexer->start = source; lexer->current = source; + lexer->start = source; + lexer->current = source; lexer->line = 1; + lexer->column = 1; + lexer->tokenStartColumn = 1; // Initialize indentation tracking lexer->indentCount = 0; diff --git a/src/parser/ast.c b/src/parser/ast.c index 8153f23..05b4714 100644 --- a/src/parser/ast.c +++ b/src/parser/ast.c @@ -582,11 +582,16 @@ void printAST(ASTNode* node, int indent) { case AST_LITERAL: printf("LITERAL "); if (node->data.literal.literalType == TOKEN_INTEGER) { - printf("%lld\n", node->data.literal.value.intValue); + printf("(int) %lld\n", node->data.literal.value.intValue); } else if (node->data.literal.literalType == TOKEN_FLOAT) { - printf("%f\n", node->data.literal.value.floatValue); + printf("(float) %f\n", node->data.literal.value.floatValue); } else if (node->data.literal.literalType == TOKEN_STRING) { - printf("\"%s\"\n", node->data.literal.value.stringValue); + printf("(string) \"%s\"\n", node->data.literal.value.stringValue); + } else if (node->data.literal.literalType == TOKEN_CHAR) { + printf("(char) '%c'\n", node->data.literal.value.charValue); + } else if (node->data.literal.literalType == TOKEN_TRUE || + node->data.literal.literalType == TOKEN_FALSE) { + printf("(bool) %s\n", node->data.literal.value.boolValue ? "true" : "false"); } break; @@ -701,7 +706,66 @@ void printAST(ASTNode* node, int indent) { } } break; - + + case AST_COMPOUND_ASSIGN: + printf("COMPOUND_ASSIGN %s %s\n", + node->data.compoundAssign.varName, + getOpSymbol(node->data.compoundAssign.op)); + printAST(node->data.compoundAssign.value, indent + 1); + break; + + case AST_INPUT_STMT: + printf("INPUT %s\n", node->data.inputStmt.varName); + if (node->data.inputStmt.prompt) { + printIndent(indent + 1); + printf("PROMPT: \"%s\"\n", node->data.inputStmt.prompt); + } + break; + + case AST_WHILE_STMT: + printf("WHILE LOOP\n"); + printIndent(indent + 1); + printf("CONDITION\n"); + printAST(node->data.whileStmt.condition, indent + 2); + printIndent(indent + 1); + printf("BODY\n"); + if (node->data.whileStmt.body) { + for (int i = 0; i < node->data.whileStmt.body->count; i++) { + printAST(node->data.whileStmt.body->nodes[i], indent + 2); + } + } + break; + + case AST_EXPR_STMT: + printAST(node->data.exprStmt.expression, indent); + break; + + case AST_UNARY_OP: + printf("UNARY_OP %s\n", getOpSymbol(node->data.unaryOp.op)); + printAST(node->data.unaryOp.operand, indent + 1); + break; + + case AST_CALL_EXPR: + printf("CALL %s\n", node->data.callExpr.funcName); + printAST(node->data.callExpr.args, indent + 1); + break; + + case AST_INDEX_EXPR: + printf("INDEX_EXPR %s\n", node->data.indexExpr.varName); + printIndent(indent + 1); + printf("INDEX\n"); + printAST(node->data.indexExpr.index, indent + 2); + break; + + case AST_ARG_LIST: + // Assuming this is used for arguments + if (node->data.list.items) { + for (int i = 0; i < node->data.list.items->count; i++) { + printAST(node->data.list.items->nodes[i], indent); + } + } + break; + default: printf("NODE_TYPE_%d\n", node->type); break; diff --git a/src/parser/parser.c b/src/parser/parser.c index 851ea6e..e96e35c 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -14,6 +14,16 @@ struct Parser { // ===== Helper Functions ===== +static bool check(Parser* parser, TokenType type); +static void errorAtCurrent(Parser* parser, const char* message); + +static void checkStatementEnd(Parser* parser) { + if (check(parser, TOKEN_NEWLINE) || check(parser, TOKEN_EOF) || check(parser, TOKEN_DEDENT)) { + return; + } + errorAtCurrent(parser, "Expected newline or end of statement"); +} + static void errorAt(Parser* parser, Token* token, const char* message) { if (parser->panicMode) return; parser->panicMode = true; @@ -23,10 +33,8 @@ static void errorAt(Parser* parser, Token* token, const char* message) { 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, ", column %d", token->column); } fprintf(stderr, ": %s\n", message); @@ -42,9 +50,8 @@ static void errorAt(Parser* parser, Token* token, const char* message) { } } -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); @@ -174,7 +181,7 @@ static char parseChar(Token token) { static ASTNode* expression(Parser* parser); static ASTNode* statement(Parser* parser); static ASTNodeList* statements(Parser* parser); -static ASTNode* declaration(Parser* parser); + // ===== Expression Parsing (Following Grammar) ===== @@ -428,6 +435,7 @@ static ASTNode* importStatement(Parser* parser) { ASTNode* node = createImportStmt(moduleName, fromModule, line); free(fromModule); free(moduleName); + checkStatementEnd(parser); return node; } @@ -447,26 +455,12 @@ static ASTNode* outputStatement(Parser* parser) { consume(parser, TOKEN_RPAREN, "Expected ')' after output arguments"); + checkStatementEnd(parser); 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 : @@ -618,25 +612,7 @@ static ASTNode* forStatement(Parser* parser) { } // -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) { @@ -672,6 +648,7 @@ static ASTNode* declarationStatement(Parser* parser) { ASTNode* node = createVarDecl(isMutable, name, typeHint, initializer, line); free(name); + checkStatementEnd(parser); return node; } static ASTNode* functionDeclaration(Parser* parser) { @@ -760,6 +737,7 @@ static ASTNode* returnStatement(Parser* parser) { value = expression(parser); } + checkStatementEnd(parser); return createReturnStmt(value, line); } @@ -799,12 +777,16 @@ static ASTNode* statement(Parser* parser) { // if (match(parser, TOKEN_BREAK)) { - return createBreakStmt(parser->previous.line); + ASTNode* node = createBreakStmt(parser->previous.line); + checkStatementEnd(parser); + return node; } // if (match(parser, TOKEN_CONTINUE)) { - return createContinueStmt(parser->previous.line); + ASTNode* node = createContinueStmt(parser->previous.line); + checkStatementEnd(parser); + return node; } // @@ -840,12 +822,14 @@ if (match(parser, TOKEN_IDENTIFIER)) { ASTNode* node = createInputStmt(name, prompt, line); free(name); if (prompt) free(prompt); + checkStatementEnd(parser); return node; } else { // Regular assignment ASTNode* value = expression(parser); ASTNode* node = createAssignment(name, value, line); free(name); + checkStatementEnd(parser); return node; } } @@ -859,6 +843,7 @@ if (match(parser, TOKEN_IDENTIFIER)) { ASTNode* value = expression(parser); ASTNode* node = createCompoundAssign(name, op, value, line); free(name); + checkStatementEnd(parser); return node; } @@ -876,6 +861,7 @@ if (match(parser, TOKEN_IDENTIFIER)) { ASTNode* argList = createArgList(args, line); ASTNode* call = createCallExpr(name, argList, line); free(name); + checkStatementEnd(parser); return createExprStmt(call, line); } @@ -912,7 +898,7 @@ static ASTNodeList* statements(Parser* parser) { } ASTNode* stmt = statement(parser); - if (stmt) { + if (stmt && !parser->panicMode) { addNode(stmtList, stmt); } diff --git a/tests/demo/test_main.eac b/tests/demo/test_main.eac index 905d256..bbfeadf 100644 --- a/tests/demo/test_main.eac +++ b/tests/demo/test_main.eac @@ -18,7 +18,7 @@ fixed PI : float = 3.14159 flex global_counter : int = 0 fixed MAX_USERS = 100 flex is_active : bool = true -flex grade : char = 'A' +flex grade : char = 'A' = flex message : str = "Welcome to EaC!" # 3. Function Declarations @@ -105,13 +105,13 @@ function demonstrate_input(): flex score : float name = input("Enter your name: ") age = input("Enter your age: ") - score = input("Enter your score: ") + score = input("Enter your score: ) output("Name: ", name, " Age: ", age, " Score: ", score) function demonstrate_output(): output("Welcome to EaC!") output("System Version: ", 2.0) - output("Status: ", is_active, " Max Users: ", MAX_USERS) + output("Status: ", is_active, " Max Users: ", MAX_USERS)= function demonstrate_assignment(): flex x = 10 From 338f24ac20e6c51efa0b8e8f8ff86ff3d20c6272 Mon Sep 17 00:00:00 2001 From: JpCurada Date: Wed, 10 Dec 2025 00:29:46 +0800 Subject: [PATCH 6/9] fix(build): corrected make clean --- Makefile | 9 +- tests/demo/test_main.eac | 2 +- tests/parser/test_add.eac | 4 - tests/parser/test_complete.eac | 61 ---- tests/parser/test_control_flow.eac | 22 -- tests/parser/test_errors_syntax.eac | 17 -- tests/parser/test_expressions.eac | 13 - tests/parser/test_functions.eac | 16 - tests/parser/test_var_decl.eac | 7 - tests/test_all.eac | 63 ---- tests/test_all_comments.eac | 50 ---- tests/test_all_invalid.eac | 19 -- tests/test_all_keywords.eac | 445 ---------------------------- tests/test_arithmetic_operators.eac | 76 ----- tests/test_boolean_operators.eac | 98 ------ tests/test_comprehensive_all.eac | 94 ------ tests/test_constant_values.eac | 54 ---- tests/test_delimiters.eac | 29 -- tests/test_identifiers.eac | 11 - tests/test_indentation.eac | 18 -- tests/test_noise_words.eac | 26 -- tests/test_reserved_words.eac | 54 ---- 22 files changed, 3 insertions(+), 1185 deletions(-) delete mode 100644 tests/parser/test_add.eac delete mode 100644 tests/parser/test_complete.eac delete mode 100644 tests/parser/test_control_flow.eac delete mode 100644 tests/parser/test_errors_syntax.eac delete mode 100644 tests/parser/test_expressions.eac delete mode 100644 tests/parser/test_functions.eac delete mode 100644 tests/parser/test_var_decl.eac delete mode 100644 tests/test_all.eac delete mode 100644 tests/test_all_comments.eac delete mode 100644 tests/test_all_invalid.eac delete mode 100644 tests/test_all_keywords.eac delete mode 100644 tests/test_arithmetic_operators.eac delete mode 100644 tests/test_boolean_operators.eac delete mode 100644 tests/test_comprehensive_all.eac delete mode 100644 tests/test_constant_values.eac delete mode 100644 tests/test_delimiters.eac delete mode 100644 tests/test_identifiers.eac delete mode 100644 tests/test_indentation.eac delete mode 100644 tests/test_noise_words.eac delete mode 100644 tests/test_reserved_words.eac diff --git a/Makefile b/Makefile index 1e8a148..32b796d 100644 --- a/Makefile +++ b/Makefile @@ -118,11 +118,6 @@ test-all: $(TARGET) @echo "Check the output/ directory for detailed token tables." @echo "" -TARGET_BIN := $(TARGET)$(EXEEXT) -OBJ_CLEAN := $(subst /,\,$(OBJ)) - 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 \ No newline at end of file + rm -f $(TARGET) $(TARGET).exe $(OBJ) + rm -rf output \ No newline at end of file diff --git a/tests/demo/test_main.eac b/tests/demo/test_main.eac index bbfeadf..bcd3dac 100644 --- a/tests/demo/test_main.eac +++ b/tests/demo/test_main.eac @@ -105,7 +105,7 @@ function demonstrate_input(): flex score : float name = input("Enter your name: ") age = input("Enter your age: ") - score = input("Enter your score: ) + score = input("Enter your score: ") output("Name: ", name, " Age: ", age, " Score: ", score) function demonstrate_output(): diff --git a/tests/parser/test_add.eac b/tests/parser/test_add.eac deleted file mode 100644 index 5d002b6..0000000 --- a/tests/parser/test_add.eac +++ /dev/null @@ -1,4 +0,0 @@ -for i in [1, 2, 3, 4, 5]: - output(i) - when i == 3: - break \ No newline at end of file diff --git a/tests/parser/test_complete.eac b/tests/parser/test_complete.eac deleted file mode 100644 index 4b738ca..0000000 --- a/tests/parser/test_complete.eac +++ /dev/null @@ -1,61 +0,0 @@ -# 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 deleted file mode 100644 index 17367f3..0000000 --- a/tests/parser/test_control_flow.eac +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index f1898b0..0000000 --- a/tests/parser/test_errors_syntax.eac +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index e976a6e..0000000 --- a/tests/parser/test_expressions.eac +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index d6e40a2..0000000 --- a/tests/parser/test_functions.eac +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index e7a222c..0000000 --- a/tests/parser/test_var_decl.eac +++ /dev/null @@ -1,7 +0,0 @@ -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 diff --git a/tests/test_all.eac b/tests/test_all.eac deleted file mode 100644 index 902b3e9..0000000 --- a/tests/test_all.eac +++ /dev/null @@ -1,63 +0,0 @@ -flex -fixed -when -else -output -function -import -from -while -for -in -break -continue -return -true -false -int -float -str -bool -char -to -of -each -then -as -+ -- -* -/ -% -^ -// -< -> -== -<= ->= -!= -and -or -not -= -+= --= -*= -/= -%= - -#this is a comment -/* this -is a comment */ -() -[] -:,. - -identifier1 -continu5 -3.14 -500 -'c' -anda -input \ No newline at end of file diff --git a/tests/test_all_comments.eac b/tests/test_all_comments.eac deleted file mode 100644 index ac3d268..0000000 --- a/tests/test_all_comments.eac +++ /dev/null @@ -1,50 +0,0 @@ -# Test file for Comments - 10 test cases -# Comments: Single-line (#) and Multi-line (/* */) - -# Test Case 1: Single-line comment at start of line -# This is a single-line comment - -# Test Case 2: Single-line comment after code -flex x = 10 # This comment is after code - -# Test Case 3: Multiple consecutive single-line comments -# Comment line 1 -# Comment line 2 -# Comment line 3 - -# Test Case 4: Single-line comment with special characters -# Comment with special chars: !@#$%^&*()_+-=[]{}|;:'",.<>?/ - -# Test Case 5: Basic multi-line comment -/* This is a multi-line comment */ - -# Test Case 6: Multi-line comment spanning multiple lines -/* This comment - spans across - multiple lines */ - -# Test Case 7: Multi-line comment with code inside (commented out) -/* This code is commented out: - flex commented = 100 - output(commented) -*/ - -# Test Case 8: Single-line comment with code -flex y = 20 # Variable y holds value 20 - -# Test Case 9: Multiple multi-line comments -/* First block comment */ -flex a = 1 -/* Second block comment */ -flex b = 2 -/* Third block comment */ - -# Test Case 10: Mixed single-line and multi-line comments -# Single-line comment here -/* Multi-line comment here */ -# Another single-line comment -flex result = 42 # Inline comment -/* Final block comment - with multiple lines - for testing */ - diff --git a/tests/test_all_invalid.eac b/tests/test_all_invalid.eac deleted file mode 100644 index b0a94eb..0000000 --- a/tests/test_all_invalid.eac +++ /dev/null @@ -1,19 +0,0 @@ -flex invalid1 = 10$ - -@invalid_symbol - -flex value = 100~ - -flex test = `invalid` - -flex result = 5 & 10 - -flex 123invalid = 50 - -flex x = {10} - -flex y = 20} - -flex z = 30; - -flex backslash = 40\ diff --git a/tests/test_all_keywords.eac b/tests/test_all_keywords.eac deleted file mode 100644 index 040163d..0000000 --- a/tests/test_all_keywords.eac +++ /dev/null @@ -1,445 +0,0 @@ -flex var1 = 1 -flex var2 = 2 -flex var3 = 3 -flex var4 = 4 -flex var5 = 5 -flex var6 = 6 -flex var7 = 7 -flex var8 = 8 -flex var9 = 9 -flex var10 = 10 - - -fixed const1 = 100 -fixed const2 = 200 -fixed const3 = 300 -fixed const4 = 400 -fixed const5 = 500 -fixed const6 = 600 -fixed const7 = 700 -fixed const8 = 800 -fixed const9 = 900 -fixed const10 = 1000 - - -when var1 == 1: - output("case 1") -when var2 == 2: - output("case 2") -when var3 == 3: - output("case 3") -when var4 == 4: - output("case 4") -when var5 == 5: - output("case 5") -when var6 == 6: - output("case 6") -when var7 == 7: - output("case 7") -when var8 == 8: - output("case 8") -when var9 == 9: - output("case 9") -when var10 == 10: - output("case 10") - - -when var1 > 100: - output("high") -else: - output("low") - -when var2 > 100: - output("high") -else: - output("low") - -when var3 > 100: - output("high") -else: - output("low") - -when var4 > 100: - output("high") -else: - output("low") - -when var5 > 100: - output("high") -else: - output("low") - -when var6 > 100: - output("high") -else: - output("low") - -when var7 > 100: - output("high") -else: - output("low") - -when var8 > 100: - output("high") -else: - output("low") - -when var9 > 100: - output("high") -else: - output("low") - -when var10 > 100: - output("high") -else: - output("low") - - -output(var1) -output(var2) -output(var3) -output(var4) -output(var5) -output(var6) -output(var7) -output(var8) -output(var9) -output(var10) - - -flex counter1 = 0 -while counter1 < 1: - counter1 += 1 - -flex counter2 = 0 -while counter2 < 1: - counter2 += 1 - -flex counter3 = 0 -while counter3 < 1: - counter3 += 1 - -flex counter4 = 0 -while counter4 < 1: - counter4 += 1 - -flex counter5 = 0 -while counter5 < 1: - counter5 += 1 - -flex counter6 = 0 -while counter6 < 1: - counter6 += 1 - -flex counter7 = 0 -while counter7 < 1: - counter7 += 1 - -flex counter8 = 0 -while counter8 < 1: - counter8 += 1 - -flex counter9 = 0 -while counter9 < 1: - counter9 += 1 - -flex counter10 = 0 -while counter10 < 1: - counter10 += 1 - - -for i in range(1): - output(i) -for j in range(1): - output(j) -for k in range(1): - output(k) -for m in range(1): - output(m) -for n in range(1): - output(n) -for p in range(1): - output(p) -for q in range(1): - output(q) -for r in range(1): - output(r) -for s in range(1): - output(s) -for t in range(1): - output(t) - - -for item1 in range(1): - output(item1) -for item2 in range(1): - output(item2) -for item3 in range(1): - output(item3) -for item4 in range(1): - output(item4) -for item5 in range(1): - output(item5) -for item6 in range(1): - output(item6) -for item7 in range(1): - output(item7) -for item8 in range(1): - output(item8) -for item9 in range(1): - output(item9) -for item10 in range(1): - output(item10) - - -while true: - break -while true: - break -while true: - break -while true: - break -while true: - break -while true: - break -while true: - break -while true: - break -while true: - break -while true: - break - - -for x1 in range(2): - when x1 == 0: - continue - output(x1) -for x2 in range(2): - when x2 == 0: - continue - output(x2) -for x3 in range(2): - when x3 == 0: - continue - output(x3) -for x4 in range(2): - when x4 == 0: - continue - output(x4) -for x5 in range(2): - when x5 == 0: - continue - output(x5) -for x6 in range(2): - when x6 == 0: - continue - output(x6) -for x7 in range(2): - when x7 == 0: - continue - output(x7) -for x8 in range(2): - when x8 == 0: - continue - output(x8) -for x9 in range(2): - when x9 == 0: - continue - output(x9) -for x10 in range(2): - when x10 == 0: - continue - output(x10) - - -function test1(): - return -function test2(): - return -function test3(): - return -function test4(): - return -function test5(): - return -function test6(): - return -function test7(): - return -function test8(): - return -function test9(): - return -function test10(): - return - - -function func1(): - output("func1") -function func2(): - output("func2") -function func3(): - output("func3") -function func4(): - output("func4") -function func5(): - output("func5") -function func6(): - output("func6") -function func7(): - output("func7") -function func8(): - output("func8") -function func9(): - output("func9") -function func10(): - output("func10") - - -import module1 -import module2 -import module3 -import module4 -import module5 -import module6 -import module7 -import module8 -import module9 -import module10 - - -from library1 import tool1 -from library2 import tool2 -from library3 import tool3 -from library4 import tool4 -from library5 import tool5 -from library6 import tool6 -from library7 import tool7 -from library8 import tool8 -from library9 import tool9 -from library10 import tool10 - - -flex bool1 = true -flex bool2 = true -flex bool3 = true -flex bool4 = true -flex bool5 = true -flex bool6 = true -flex bool7 = true -flex bool8 = true -flex bool9 = true -flex bool10 = true - - -flex flag1 = false -flex flag2 = false -flex flag3 = false -flex flag4 = false -flex flag5 = false -flex flag6 = false -flex flag7 = false -flex flag8 = false -flex flag9 = false -flex flag10 = false - - -flex result1 = true and true -flex result2 = true and false -flex result3 = false and true -flex result4 = false and false -flex result5 = var1 > 0 and var2 > 0 -flex result6 = var3 > 0 and var4 > 0 -flex result7 = var5 > 0 and var6 > 0 -flex result8 = var7 > 0 and var8 > 0 -flex result9 = var9 > 0 and var10 > 0 -flex result10 = true and true and true - - -flex or_result1 = true or false -flex or_result2 = false or true -flex or_result3 = false or false -flex or_result4 = true or true -flex or_result5 = var1 > 0 or var2 > 0 -flex or_result6 = var3 > 0 or var4 > 0 -flex or_result7 = var5 > 0 or var6 > 0 -flex or_result8 = var7 > 0 or var8 > 0 -flex or_result9 = var9 > 0 or var10 > 0 -flex or_result10 = false or false or true - - -flex not_result1 = not true -flex not_result2 = not false -flex not_result3 = not bool1 -flex not_result4 = not bool2 -flex not_result5 = not bool3 -flex not_result6 = not bool4 -flex not_result7 = not bool5 -flex not_result8 = not flag1 -flex not_result9 = not flag2 -flex not_result10 = not flag3 - - -flex num1: int = 1 -flex num2: int = 2 -flex num3: int = 3 -flex num4: int = 4 -flex num5: int = 5 -flex num6: int = 6 -flex num7: int = 7 -flex num8: int = 8 -flex num9: int = 9 -flex num10: int = 10 - - -flex dec1: float = 1.1 -flex dec2: float = 2.2 -flex dec3: float = 3.3 -flex dec4: float = 4.4 -flex dec5: float = 5.5 -flex dec6: float = 6.6 -flex dec7: float = 7.7 -flex dec8: float = 8.8 -flex dec9: float = 9.9 -flex dec10: float = 10.10 - - -flex text1: str = "text1" -flex text2: str = "text2" -flex text3: str = "text3" -flex text4: str = "text4" -flex text5: str = "text5" -flex text6: str = "text6" -flex text7: str = "text7" -flex text8: str = "text8" -flex text9: str = "text9" -flex text10: str = "text10" - - -flex boolean1: bool = true -flex boolean2: bool = false -flex boolean3: bool = true -flex boolean4: bool = false -flex boolean5: bool = true -flex boolean6: bool = false -flex boolean7: bool = true -flex boolean8: bool = false -flex boolean9: bool = true -flex boolean10: bool = false - - -flex char1: char = 'a' -flex char2: char = 'b' -flex char3: char = 'c' -flex char4: char = 'd' -flex char5: char = 'e' -flex char6: char = 'f' -flex char7: char = 'g' -flex char8: char = 'h' -flex char9: char = 'i' -flex char10: char = 'j' - diff --git a/tests/test_arithmetic_operators.eac b/tests/test_arithmetic_operators.eac deleted file mode 100644 index 793bd28..0000000 --- a/tests/test_arithmetic_operators.eac +++ /dev/null @@ -1,76 +0,0 @@ -flex add1 = 1 + 1 -flex add2 = 5 + 10 -flex add3 = 100 + 200 -flex add4 = 0 + 5 -flex add5 = 25 + 75 -flex add6 = 999 + 1 -flex add7 = 50 + 50 -flex add8 = 123 + 456 -flex add9 = 10 + 20 + 30 -flex add10 = 1 + 2 + 3 + 4 + 5 - -flex sub1 = 10 - 5 -flex sub2 = 100 - 50 -flex sub3 = 1000 - 999 -flex sub4 = 50 - 25 -flex sub5 = 200 - 100 -flex sub6 = 75 - 25 -flex sub7 = 500 - 250 -flex sub8 = 999 - 111 -flex sub9 = 100 - 20 - 10 -flex sub10 = 50 - 5 - 5 - 5 - 5 - -flex mul1 = 2 * 3 -flex mul2 = 5 * 10 -flex mul3 = 10 * 10 -flex mul4 = 7 * 8 -flex mul5 = 12 * 12 -flex mul6 = 25 * 4 -flex mul7 = 100 * 2 -flex mul8 = 9 * 11 -flex mul9 = 3 * 3 * 3 -flex mul10 = 2 * 2 * 2 * 2 * 2 - -flex div1 = 10 / 2 -flex div2 = 100 / 5 -flex div3 = 50 / 10 -flex div4 = 144 / 12 -flex div5 = 200 / 4 -flex div6 = 1000 / 10 -flex div7 = 64 / 8 -flex div8 = 81 / 9 -flex div9 = 100 / 2 / 5 -flex div10 = 1000 / 10 / 10 - -flex mod1 = 10 % 3 -flex mod2 = 17 % 5 -flex mod3 = 100 % 7 -flex mod4 = 25 % 4 -flex mod5 = 50 % 6 -flex mod6 = 99 % 10 -flex mod7 = 15 % 4 -flex mod8 = 37 % 8 -flex mod9 = 100 % 3 -flex mod10 = 1000 % 7 - -flex exp1 = 2 ^ 3 -flex exp2 = 3 ^ 2 -flex exp3 = 5 ^ 2 -flex exp4 = 2 ^ 8 -flex exp5 = 10 ^ 2 -flex exp6 = 2 ^ 10 -flex exp7 = 4 ^ 3 -flex exp8 = 3 ^ 4 -flex exp9 = 2 ^ 5 -flex exp10 = 5 ^ 3 - -flex abs1 = |-10| -flex abs2 = |10| -flex abs3 = |-100| -flex abs4 = |100| -flex abs5 = |-50| -flex abs6 = |50| -flex abs7 = |-999| -flex abs8 = |999| -flex abs9 = |-1| -flex abs10 = |-12345| diff --git a/tests/test_boolean_operators.eac b/tests/test_boolean_operators.eac deleted file mode 100644 index 414e16e..0000000 --- a/tests/test_boolean_operators.eac +++ /dev/null @@ -1,98 +0,0 @@ -flex lt1 = 5 < 10 -flex lt2 = 100 < 200 -flex lt3 = 0 < 1 -flex lt4 = 25 < 50 -flex lt5 = 99 < 100 -flex lt6 = 1 < 1000 -flex lt7 = 10 < 20 -flex lt8 = 50 < 75 -flex lt9 = 7 < 14 -flex lt10 = 3 < 9 - -flex gt1 = 10 > 5 -flex gt2 = 200 > 100 -flex gt3 = 1 > 0 -flex gt4 = 50 > 25 -flex gt5 = 100 > 99 -flex gt6 = 1000 > 1 -flex gt7 = 20 > 10 -flex gt8 = 75 > 50 -flex gt9 = 14 > 7 -flex gt10 = 9 > 3 - -flex eq1 = 5 == 5 -flex eq2 = 10 == 10 -flex eq3 = 100 == 100 -flex eq4 = 0 == 0 -flex eq5 = 50 == 50 -flex eq6 = 999 == 999 -flex eq7 = 1 == 1 -flex eq8 = 42 == 42 -flex eq9 = 777 == 777 -flex eq10 = 12345 == 12345 - -flex lte1 = 5 <= 10 -flex lte2 = 10 <= 10 -flex lte3 = 0 <= 1 -flex lte4 = 50 <= 50 -flex lte5 = 99 <= 100 -flex lte6 = 100 <= 100 -flex lte7 = 1 <= 1000 -flex lte8 = 25 <= 50 -flex lte9 = 75 <= 75 -flex lte10 = 3 <= 9 - -flex gte1 = 10 >= 5 -flex gte2 = 10 >= 10 -flex gte3 = 1 >= 0 -flex gte4 = 50 >= 50 -flex gte5 = 100 >= 99 -flex gte6 = 100 >= 100 -flex gte7 = 1000 >= 1 -flex gte8 = 50 >= 25 -flex gte9 = 75 >= 75 -flex gte10 = 9 >= 3 - -flex ne1 = 5 != 10 -flex ne2 = 10 != 5 -flex ne3 = 0 != 1 -flex ne4 = 100 != 200 -flex ne5 = 50 != 25 -flex ne6 = 1 != 0 -flex ne7 = 999 != 1000 -flex ne8 = 42 != 43 -flex ne9 = 777 != 778 -flex ne10 = 12345 != 54321 - -flex and1 = true and true -flex and2 = true and false -flex and3 = false and true -flex and4 = false and false -flex and5 = 5 > 3 and 10 > 8 -flex and6 = 10 < 20 and 30 < 40 -flex and7 = 5 == 5 and 10 == 10 -flex and8 = 1 != 2 and 3 != 4 -flex and9 = true and true and true -flex and10 = 5 > 0 and 10 > 0 and 15 > 0 - -flex or1 = true or false -flex or2 = false or true -flex or3 = true or true -flex or4 = false or false -flex or5 = 5 > 10 or 10 > 5 -flex or6 = 20 < 10 or 30 > 20 -flex or7 = 5 == 10 or 10 == 10 -flex or8 = 1 != 1 or 2 != 3 -flex or9 = false or false or true -flex or10 = 5 < 0 or 10 < 0 or 15 > 0 - -flex not1 = not true -flex not2 = not false -flex not3 = not 5 > 10 -flex not4 = not 10 > 5 -flex not5 = not 5 == 5 -flex not6 = not 5 != 5 -flex not7 = not 10 < 20 -flex not8 = not 20 < 10 -flex not9 = not true and false -flex not10 = not false or true diff --git a/tests/test_comprehensive_all.eac b/tests/test_comprehensive_all.eac deleted file mode 100644 index a0f9fa3..0000000 --- a/tests/test_comprehensive_all.eac +++ /dev/null @@ -1,94 +0,0 @@ -flex simple_id = 1 -flex _private = 2 -flex CamelCase = 3 -flex UPPER_CASE = 4 -flex var_123 = 5 - -flex age: int = 25 -fixed PI: float = 3.14159 -flex name: str = "Alice" -flex is_active: bool = true -flex grade: char = 'A' - -flex int1 = 0 -flex int2 = 42 -flex int3 = 999999 - -flex float1 = 0.0 -flex float2 = 3.14 -flex float3 = 123.456 - -flex str1 = "Hello World" -flex str2 = "Testing strings" -flex str3 = "" - -flex char1 = 'a' -flex char2 = 'Z' -flex char3 = '0' - -flex bool1 = true -flex bool2 = false - -flex addition = 10 + 5 -flex subtraction = 20 - 8 -flex multiplication = 3 * 4 -flex division = 15 / 3 -flex modulo = 17 % 5 -flex exponent = 2 ^ 8 -flex absolute = |-10| - -flex less = 5 < 10 -flex greater = 10 > 5 -flex equal = 10 == 10 -flex less_eq = 10 <= 10 -flex greater_eq = 10 >= 10 -flex not_equal = 10 != 20 - -flex and_op = true and false -flex or_op = true or false -flex not_op = not true - -flex list = [1, 2, 3, 4, 5] -flex element = list[0] -flex calc = (10 + 20) * 2 - -when age > 18: - output("Adult") - when age > 65: - output("Senior") -else: - output("Minor") - -flex counter = 0 -while counter < 5: - counter += 1 - when counter == 3: - continue - output(counter) - when counter == 4: - break - -for i in range(5): - output(i) - -function greet(person): - output(person) - when person == "admin": - return - -import toolkit -from library import module - -please kindly maybe - -flex x = 100 -x += 10 -x -= 5 -x *= 2 -x /= 4 -x %= 3 - -flex complex1 = (10 + 20) * 3 / 2 - 5 -flex complex2 = 2 ^ 3 * 4 + |-15| % 7 -flex complex3 = (true and false) or (not false) -flex complex4 = (5 < 10) and (20 > 15) or (10 == 10) diff --git a/tests/test_constant_values.eac b/tests/test_constant_values.eac deleted file mode 100644 index 3974455..0000000 --- a/tests/test_constant_values.eac +++ /dev/null @@ -1,54 +0,0 @@ -flex int_val1 = 0 -flex int_val2 = 1 -flex int_val3 = 42 -flex int_val4 = 100 -flex int_val5 = 999 -flex int_val6 = 12345 -flex int_val7 = 67890 -flex int_val8 = 1000000 -flex int_val9 = 2147483647 -flex int_val10 = 314159265 - -flex float_val1 = 0.0 -flex float_val2 = 1.0 -flex float_val3 = 3.14 -flex float_val4 = 2.718 -flex float_val5 = 123.456 -flex float_val6 = 999.999 -flex float_val7 = 0.001 -flex float_val8 = 10.5 -flex float_val9 = 99.99 -flex float_val10 = 3.14159265359 - -flex str_val1 = "Hello World" -flex str_val2 = "EaC Programming Language" -flex str_val3 = "Testing strings with spaces" -flex str_val4 = "123456789" -flex str_val5 = "Special chars: !@#$%" -flex str_val6 = "" -flex str_val7 = "Single word" -flex str_val8 = "Multi line test" -flex str_val9 = "Another string value" -flex str_val10 = "Final string test" - -flex char_val1 = 'a' -flex char_val2 = 'z' -flex char_val3 = 'A' -flex char_val4 = 'Z' -flex char_val5 = '0' -flex char_val6 = '9' -flex char_val7 = ' ' -flex char_val8 = '!' -flex char_val9 = '\n' -flex char_val10 = '\t' - -flex bool_val1 = true -flex bool_val2 = false -flex bool_val3 = true -flex bool_val4 = false -flex bool_val5 = true -flex bool_val6 = false -flex bool_val7 = true -flex bool_val8 = false -flex bool_val9 = true -flex bool_val10 = false diff --git a/tests/test_delimiters.eac b/tests/test_delimiters.eac deleted file mode 100644 index cf55886..0000000 --- a/tests/test_delimiters.eac +++ /dev/null @@ -1,29 +0,0 @@ -output("test") - -flex result1 = (5 + 3) * 2 - -flex list1 = [1, 2, 3, 4, 5] - -flex element = list1[0] - -when result1 > 10: - output("greater than 10") - -output("value1", "value2", "value3") - -flex list2 = [10, 20, 30, 40, 50] - -function calculate(a, b, c): - flex data = [a, b, c] - when data[0] > 0: - output(data[0]) - return (a + b + c) - -flex nested = [[1, 2], [3, 4], [5, 6]] -flex sum = ((10 + 20) + (30 + 40)) - -function process(x, y, z): - flex items = [x, y, z] - when items[0] > 0: - output((items[0] + items[1]) * items[2]) - return items[0] diff --git a/tests/test_identifiers.eac b/tests/test_identifiers.eac deleted file mode 100644 index 385110a..0000000 --- a/tests/test_identifiers.eac +++ /dev/null @@ -1,11 +0,0 @@ -flex simple = 1 -flex CONSTANT = 2 -flex _private = 3 -flex my_variable = 4 -flex my_long_variable_name = 5 -flex myVariableName = 6 -flex MyClassName = 7 -flex var123 = 8 -flex test2_var3_name4 = 9 -flex _internal_var_1_test_2024 = 10 - diff --git a/tests/test_indentation.eac b/tests/test_indentation.eac deleted file mode 100644 index 1658b64..0000000 --- a/tests/test_indentation.eac +++ /dev/null @@ -1,18 +0,0 @@ -flex x = 10 - -when x > 5: - output("Level 1") - when x > 7: - output("Level 2") - when x > 9: - output("Level 3") - output("Back to Level 1") - -output("Back to Level 0") - -while x > 0: - output(x) - x -= 1 - -output("Done") - diff --git a/tests/test_noise_words.eac b/tests/test_noise_words.eac deleted file mode 100644 index a2276c6..0000000 --- a/tests/test_noise_words.eac +++ /dev/null @@ -1,26 +0,0 @@ -please - -kindly - -maybe - -please kindly maybe - -please flex x = 10 - -flex y = 20 kindly - -maybe please flex z = 30 kindly - -please -flex a = 1 -kindly -flex b = 2 -maybe -flex c = 3 - -please please kindly kindly maybe maybe - -please output("Hello") -kindly when x > 0: - maybe output(x) diff --git a/tests/test_reserved_words.eac b/tests/test_reserved_words.eac deleted file mode 100644 index 1575232..0000000 --- a/tests/test_reserved_words.eac +++ /dev/null @@ -1,54 +0,0 @@ -flex num1: int = 1 -flex num2: int = 2 -flex num3: int = 3 -flex num4: int = 4 -flex num5: int = 5 -flex num6: int = 6 -flex num7: int = 7 -flex num8: int = 8 -flex num9: int = 9 -flex num10: int = 10 - -flex decimal1: float = 1.1 -flex decimal2: float = 2.2 -flex decimal3: float = 3.3 -flex decimal4: float = 4.4 -flex decimal5: float = 5.5 -flex decimal6: float = 6.6 -flex decimal7: float = 7.7 -flex decimal8: float = 8.8 -flex decimal9: float = 9.9 -flex decimal10: float = 10.10 - -flex text1: str = "string1" -flex text2: str = "string2" -flex text3: str = "string3" -flex text4: str = "string4" -flex text5: str = "string5" -flex text6: str = "string6" -flex text7: str = "string7" -flex text8: str = "string8" -flex text9: str = "string9" -flex text10: str = "string10" - -flex boolean1: bool = true -flex boolean2: bool = false -flex boolean3: bool = true -flex boolean4: bool = false -flex boolean5: bool = true -flex boolean6: bool = false -flex boolean7: bool = true -flex boolean8: bool = false -flex boolean9: bool = true -flex boolean10: bool = false - -flex character1: char = 'a' -flex character2: char = 'b' -flex character3: char = 'c' -flex character4: char = 'd' -flex character5: char = 'e' -flex character6: char = 'f' -flex character7: char = 'g' -flex character8: char = 'h' -flex character9: char = 'i' -flex character10: char = 'j' From 8a007ca37c54adc914a28d15ea56bae5f5cdda13 Mon Sep 17 00:00:00 2001 From: JpCurada Date: Wed, 10 Dec 2025 00:44:25 +0800 Subject: [PATCH 7/9] tests(lexer): add lexer test --- tests/demo/test_error.eac | 3 -- tests/demo/test_lexer.eac | 102 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) delete mode 100644 tests/demo/test_error.eac create mode 100644 tests/demo/test_lexer.eac diff --git a/tests/demo/test_error.eac b/tests/demo/test_error.eac deleted file mode 100644 index 1b5a382..0000000 --- a/tests/demo/test_error.eac +++ /dev/null @@ -1,3 +0,0 @@ -# test_bad_indent.eac -when x > 0: -output("no indent") \ No newline at end of file diff --git a/tests/demo/test_lexer.eac b/tests/demo/test_lexer.eac new file mode 100644 index 0000000..2ed4d44 --- /dev/null +++ b/tests/demo/test_lexer.eac @@ -0,0 +1,102 @@ +/* + EaC Lexer Standards Compliance Test + Satisfies all criteria from documentation and specification. + Filetype Input: .eac (Criterion 1) +*/ + +# [Criterion 7] Comments must be recognized (2/2) +# 1. Single-line comment: This is ignored by parser but tokenized +/* + 2. Multi-line comment: + This block is also recognized as a comment token. +*/ + +# [Criterion 3] Keywords (23/23) +# Testing all primary keywords defined in the language +function main(): + flex variable_mutable + fixed variable_constant = 10 + + input("Waiting for input...") + + when true: + output("Condition met") + else: + output("Else block") + + while false: + break + + for i in range(10): + continue + + import math + from utils import help + + return 0 + +# [Criterion 4] Reserve Word (10/10) - Optional Type Hints & Values +# These are reserved words used for type hinting and boolean values +flex v_int : int = 0 +flex v_float : float = 0.0 +flex v_str : str = "" +flex v_bool : bool = true +flex v_char : char = 'a' +flex v_false = false + +# [Criterion 5] Constant Values (at least 4 types must be recognized) +flex c_integer = 12345 +flex c_float = 3.14159 +flex c_string = "Hello World" +flex c_char = 'A' +flex c_bool = true + +# [Criterion 6] Noise Word (5/5) +# Valid tokens ignored by parser for readability +flex x to 10 +flex age of int = 25 +when x > 5 then: +for each item in list: +flex res as float = 1.0 + +# [Criterion 2] Identifier +# Valid identifiers starting with letter or underscore +flex _private_var = 1 +flex snake_case_var = 2 +flex camelCaseVar = 3 +flex VAR_123 = 4 + +# [Criterion 8] Operators +# a. Arithmetic (7/7) +flex math_res = 0 +math_res = 1 + 1 +math_res = 2 - 1 +math_res = 3 * 3 +math_res = 4 / 2 +math_res = 5 % 2 +math_res = 2 ^ 3 +math_res = 10 // 3 + +# b. Boolean (9/9) +flex bool_res = false +bool_res = 1 < 2 +bool_res = 2 > 1 +bool_res = 1 == 1 +bool_res = 1 != 2 +bool_res = 1 <= 2 +bool_res = 2 >= 1 +bool_res = true and false +bool_res = true or false +bool_res = not true + +# [Criterion 9] Delimiters and Brackets +function delims(arg1, arg2): + flex list = [1, 2, 3] + return list + +# [Criterion 10] Invalid Token Test (Commented out to allow full parsing) +# The lexical analyzer would mark '@' or '$' as TOKEN_ERROR if uncommented. +# flex invalid_$ = 0 + +# [Criterion 11] Output Generation +# This file, when run with ./eac --lexer, produces the Symbol Table output. From 45c14ee7402b75d1f4db10cb4b0955e015746524 Mon Sep 17 00:00:00 2001 From: JpCurada Date: Wed, 10 Dec 2025 01:14:34 +0800 Subject: [PATCH 8/9] docs(readme): align readme --- README.md | 247 ++++++++++++++---------------------------------------- 1 file changed, 65 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index c3c9ccb..8d31f98 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,14 @@ make make test-all ``` -### Run a Specific Test +### Run Parser Tests ```bash -make test test_identifiers.eac +make test-parser +``` + +### Run Lexer Tests +```bash +make test-lexer ``` ### Clean Build @@ -35,78 +40,56 @@ make clean --- -## Features +## Core Features -### Core Language Features - ✅ **Dynamic Variables** - `flex` keyword for mutable variables - ✅ **Constants** - `fixed` keyword for immutable values - ✅ **Type Hints** - Optional type annotations (int, float, str, bool, char) - ✅ **Control Flow** - `when`/`else` conditionals, `while`/`for` loops - ✅ **Functions** - First-class function support - ✅ **Comments** - Single-line (#) and multi-line (/* */) comments -- ✅ **Noise Words** - Polite keywords (please, kindly, maybe) for readability - -### Operators -- **Arithmetic**: `+`, `-`, `*`, `/`, `%`, `^` (exponent), `|` (absolute) -- **Relational**: `<`, `>`, `<=`, `>=`, `==`, `!=` -- **Logical**: `and`, `or`, `not` -- **Assignment**: `=`, `+=`, `-=`, `*=`, `/=`, `%=` - -### Example Code -```eac -flex age: int = 25 -fixed PI: float = 3.14159 -flex name: str = "Alice" - -when age > 18: - output("Adult") -else: - output("Minor") - -flex counter = 0 -while counter < 5: - output(counter) - counter += 1 - -function greet(person): - output("Hello, ", person) - return - -please greet("World") -``` +- ✅ **Noise Words** - Readability keywords (`to`, `of`, `then`, `each`, `as`) which are ignored by the parser. --- -## Test Suite - -### Comprehensive Test Coverage (494+ Test Cases) - -The compiler includes a comprehensive test suite covering all 10 criteria: - -| # | Criterion | File | Cases | -|---|-----------|------|-------| -| 1 | File Type Validation | test_file.{py,txt,c} | 3 files | -| 2 | Identifiers | test_identifiers.eac | 10 | -| 3 | Keywords | test_all_keywords.eac | 190 | -| 4 | Reserved Words | test_reserved_words.eac | 50 | -| 5 | Constant Values | test_constant_values.eac | 50 | -| 6 | Noise Words | test_noise_words.eac | 10 | -| 7 | Comments | test_all_comments.eac | 10 | -| 8a | Arithmetic Operators | test_arithmetic_operators.eac | 70 | -| 8b | Boolean Operators | test_boolean_operators.eac | 90 | -| 9 | Delimiters | test_delimiters.eac | 10 | -| 10 | Invalid Tokens | test_all_invalid.eac | 10 | - -### Run All Tests -```bash -make test-all -``` - -This command will: -1. Test file type validation (reject non-.eac files) -2. Run all 10 criterion tests -3. Generate token tables in `output/` directory -4. Display test progress and results +## Technical Architecture & Code Tracing + +### 1. Lexical Analysis (`src/lexer/`) +The **Lexer** transforms raw source code into a stream of tokens. + +**Step-by-Step Execution:** +1. **Initialization**: `initLexer(source)` sets up the lexer state, pointing to the start of the source string. +2. **Scanning Loop**: The main loop calls `scanToken()` repeatedly. + * **Whitespace Handling**: `scanToken` skips spaces. If it encounters a newline, it calculates indentation. + * **Indentation Tracking**: The lexer maintains a stack of indentation levels. If indentation increases, it emits `TOKEN_INDENT`. If it decreases, it emits one or more `TOKEN_DEDENT` tokens. + * **State Machine**: Characters are processed through a finite state machine (FSM) to identify tokens: + * `"` triggers string state. + * Digits trigger number state. + * Letters trigger identifier/keyword state. + * **Keyword Matching**: Identifiers are checked against a trie or hash map to determine if they are reserved keywords (e.g., `flex`, `when`). +3. **Token Generation**: valid lexemes are packaged into `Token` structs containing the type, lexeme string, line number, and column number. + +### 2. Parsing (`src/parser/`) +The **Parser** consumes tokens to build an Abstract Syntax Tree (AST). + +**Step-by-Step Execution:** +1. **Initialization**: `initParser(lexer)` prepares the parser. +2. **Recursive Descent**: The `parse()` function calls `statements()`, which recursively calls functions for specific grammar rules: + * `statements()` -> `statement()` + * `statement()` parses specific constructs like `impl_varDecl`, `impl_ifStmt`, etc. +3. **Expression Parsing**: Uses a precedence climbing algorithm (or similar) to handle operator precedence (e.g., `*` binds tighter than `+`). +4. **Error Handling**: + * If a syntax error occurs (e.g., missing expected token), `errorAt()` is called. + * The parser enters "panic mode" to suppress cascading errors. + * `synchronize()` skips tokens until a statement boundary (newline/semicolon) is found to recover. + * **Statement Termination**: `checkStatementEnd()` ensures statements end with a newline or EOF, preventing malformed constructs from entering the AST. +5. **AST Construction**: Successful parses create `ASTNode` structures (e.g., `AST_BINARY_OP`, `AST_VAR_DECL`). + +### 3. Testing (`tests/`) +The project includes a robust test suite. + +* `tests/demo/`: Contains demonstration files (e.g., `test_main.eac`, `test_criteria_compliance.eac`). +* `tests/`: Root tests for specific language features. --- @@ -115,140 +98,40 @@ This command will: ``` eac/ ├── src/ -│ ├── main.c # Test harness +│ ├── main.c # Entry point │ ├── common/ │ │ └── token.h # Token definitions │ ├── lexer/ │ │ ├── lexer.h # Lexer interface -│ │ └── lexer.c # Lexer implementation -│ ├── parser/ # Parser (future) -│ └── semantic/ # Semantic analyzer (future) +│ │ └── lexer.c # Lexer implementation & FSM +│ ├── parser/ # Parser module +│ │ ├── parser.c # Recursive descent parser +│ │ ├── parser.h # Parser interface +│ │ ├── ast.c # AST node creation & printing +│ │ └── ast.h # AST structure definitions ├── tests/ -│ ├── test_identifiers.eac -│ ├── test_all_keywords.eac -│ ├── test_reserved_words.eac -│ ├── test_constant_values.eac -│ ├── test_noise_words.eac -│ ├── test_all_comments.eac -│ ├── test_arithmetic_operators.eac -│ ├── test_boolean_operators.eac -│ ├── test_delimiters.eac -│ ├── test_all_invalid.eac -│ ├── test_indentation.eac -│ ├── test_comprehensive_all.eac -│ ├── test_file.py -│ ├── test_file.txt -│ └── test_file.c -├── output/ # Generated token tables -├── docs/ -│ ├── DOCUMENTATION.md # Language specification -│ ├── DEV_GUIDE.md # Development guide -│ └── QUICK_START.md # Quick start guide +│ ├── demo/ # Demo & compliance tests +│ │ ├── test_main.eac +│ │ └── test_criteria_compliance.eac +│ ├── parser/ # Parser-specific tests +│ └── ... # Other feature tests +├── output/ # Generated artifacts (symbol tables, AST dumps) +├── docs/ # Documentation ├── Makefile # Build system └── README.md # This file ``` --- -## Output - -### Token Tables - -All test results are saved in `output/` directory: - -``` -output/symbol_table_.txt -``` - -Each file contains a formatted table: -``` -Line Lexeme Token Token Special -========================================================================== - -1 flex KEYWORD FLEX -1 age IDENTIFIER IDENTIFIER -1 : DELIMITER COLON -1 int HINT_KEYWORD HINT_INT -1 = ASSIGNMENT EQUAL -1 25 INTEGER INTEGER -... -``` - ---- - -## Documentation - -- **[DOCUMENTATION.md](docs/DOCUMENTATION.md)** - Complete language specification -- **[DEV_GUIDE.md](docs/DEV_GUIDE.md)** - Development guide -- **[QUICK_START.md](docs/QUICK_START.md)** - Quick start guide -- **[RUN_ALL_TESTS.md](RUN_ALL_TESTS.md)** - Test execution guide -- **[TEST_SUITE_COMPLETION_REPORT.md](TEST_SUITE_COMPLETION_REPORT.md)** - Test coverage report -- **[CLEANUP_SUMMARY.md](CLEANUP_SUMMARY.md)** - Recent cleanup changes - ---- - -## Development - -### Requirements -- GCC compiler -- Make -- Windows, Linux, or macOS - -### Build -```bash -make # Build the compiler -make clean # Clean build artifacts -``` - -### Testing -```bash -make test-all # Run all tests -make test test_identifiers.eac # Run specific test -``` - -### Adding New Tests -1. Create a new `.eac` file in `tests/` -2. Add test cases following the existing patterns -3. Run `make test .eac` -4. Check `output/symbol_table_.txt` for results - ---- - ## Current Status -✅ **Lexical Analyzer** - Complete and fully tested -🔄 **Parser** - In development -⏳ **Semantic Analyzer** - Planned -⏳ **Code Generator** - Planned - ---- - -## Keywords (19 total) - -`flex`, `fixed`, `when`, `else`, `output`, `while`, `for`, `in`, `break`, `continue`, `return`, `function`, `import`, `from`, `true`, `false`, `and`, `or`, `not` - -## Reserved Words (5 total) - -`int`, `float`, `str`, `bool`, `char` - -## Noise Words (3 total) - -`please`, `kindly`, `maybe` - ---- - -## License - -Educational project for compiler design coursework. +* ✅ **Lexical Analyzer**: Complete. accurate tokenization including complex indentation handling. +* ✅ **Parser**: Functional. Generates AST for declarations, expressions, control flow, and functions. Robust error handling implemented. +* ⏳ **Semantic Analyzer**: Planned. +* ⏳ **Code Generator**: Planned. --- ## Authors EaC Compiler Development Team - ---- - -**Version:** 1.0 -**Last Updated:** November 4, 2025 -**Status:** ✅ Lexical Analysis Phase Complete From 837c51fc3b850acb1a29ac9d999015730f0ce6ad Mon Sep 17 00:00:00 2001 From: JpCurada Date: Wed, 10 Dec 2025 01:14:57 +0800 Subject: [PATCH 9/9] build(make): added test-lexer and test-parser --- Makefile | 105 ++++--------------------------------------------------- 1 file changed, 6 insertions(+), 99 deletions(-) diff --git a/Makefile b/Makefile index 32b796d..0309e3c 100644 --- a/Makefile +++ b/Makefile @@ -7,18 +7,7 @@ OBJ = $(SRC:.c=.o) TARGET = eac -# Determine if a specific test file was provided on the command line -TEST_GOAL := $(firstword $(filter %.eac,$(MAKECMDGOALS))) - -ifeq ($(TEST_GOAL),) -SELECTED_TEST := tests/test.eac -else ifneq ($(findstring /,$(TEST_GOAL)),) -SELECTED_TEST := $(TEST_GOAL) -else -SELECTED_TEST := tests/$(TEST_GOAL) -endif - -.PHONY: all clean test test-all test-parser $(TEST_GOAL) +.PHONY: all clean test-lexer test-parser all: $(TARGET) @@ -28,95 +17,13 @@ $(TARGET): $(OBJ) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ -test: $(TARGET) - @echo "Running test file: $(SELECTED_TEST)" - @./$(TARGET) $(SELECTED_TEST) +test-lexer: $(TARGET) + @echo "Running Lexer Test..." + @./$(TARGET) --lexer tests/demo/test_lexer.eac 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 - -test-all: $(TARGET) - @echo "========================================================================" - @echo " EaC LEXICAL ANALYZER - COMPREHENSIVE TEST SUITE" - @echo "========================================================================" - @echo "" - @echo "[CRITERION 1] File Type Validation Tests:" - @echo " Testing non-.eac files (should be rejected)..." - -@./$(TARGET) tests/test_file.py 2>nul - -@./$(TARGET) tests/test_file.txt 2>nul - -@./$(TARGET) tests/test_file.c 2>nul - @echo " File type validation complete (non-.eac files rejected)" - @echo "" - @echo "[CRITERION 2] Identifiers Test (10 cases):" - @./$(TARGET) tests/test_identifiers.eac - @echo "" - @echo "[CRITERION 3] Keywords Test (24 keywords, 240 cases):" - @./$(TARGET) tests/test_all_keywords.eac - @echo "" - @echo "[CRITERION 4] Reserved Words Test (5 types, 50 cases):" - @./$(TARGET) tests/test_reserved_words.eac - @echo "" - @echo "[CRITERION 5] Constant Values Test (5 types, 50 cases):" - @./$(TARGET) tests/test_constant_values.eac - @echo "" - @echo "[CRITERION 6] Noise Words Test (10 cases):" - @./$(TARGET) tests/test_noise_words.eac - @echo "" - @echo "[CRITERION 7] Comments Test (10 cases):" - @./$(TARGET) tests/test_all_comments.eac - @echo "" - @echo "[CRITERION 8a] Arithmetic Operators Test (7 operators, 70 cases):" - @./$(TARGET) tests/test_arithmetic_operators.eac - @echo "" - @echo "[CRITERION 8b] Boolean Operators Test (9 operators, 90 cases):" - @./$(TARGET) tests/test_boolean_operators.eac - @echo "" - @echo "[CRITERION 9] Delimiters & Brackets Test (10 cases):" - @./$(TARGET) tests/test_delimiters.eac - @echo "" - @echo "[CRITERION 10] Invalid Tokens Test (10 cases):" - -@./$(TARGET) tests/test_all_invalid.eac - @echo "" - @echo "[BONUS] Python-Style Indentation Test:" - @./$(TARGET) tests/test_indentation.eac - @echo "" - @echo "[BONUS] Comprehensive All-in-One Test:" - @./$(TARGET) tests/test_comprehensive_all.eac - @echo "" - @echo "========================================================================" - @echo " ALL TESTS COMPLETED" - @echo "========================================================================" - @echo "" - @echo "Total Test Files: 14" - @echo "Total Test Cases: 544+" - @echo "" - @echo "Check the output/ directory for detailed token tables." - @echo "" + @echo "Running Parser Test..." + @./$(TARGET) --parse tests/demo/test_main.eac clean: rm -f $(TARGET) $(TARGET).exe $(OBJ)