diff --git a/config.mk b/config.mk index f2e1eee..cfdb608 100644 --- a/config.mk +++ b/config.mk @@ -1,29 +1,11 @@ -# Configuration for C² project -CC := gcc -CFLAGS := -std=gnu99 -Wall -Wextra -pedantic -Iinclude -D_GNU_SOURCE -LDFLAGS := +CC = gcc +CFLAGS = -std=gnu99 -Wall -Wextra -pedantic -Iinclude -D_GNU_SOURCE -g -O0 -DDEBUG +LDFLAGS = -TARGET := csq-x86 +SRCDIR = src +BUILDDIR = build +BINDIR = bin -# Directories -SRCDIR := src -INCDIR := include -BUILDDIR := build -BINDIR := bin - -# Build type: debug (default) or release -# Usage: make BUILD_TYPE=release -BUILD_TYPE ?= debug - -ifeq ($(BUILD_TYPE),release) - CFLAGS += -O2 -DNDEBUG -else - CFLAGS += -g -O0 -DDEBUG -endif - -# Source files (automatically find all .c files in src directory) -SRCS := $(shell find $(SRCDIR) -name "*.c") -OBJS := $(patsubst $(SRCDIR)/%.c, $(BUILDDIR)/%.o, $(SRCS)) - -# Target binary path -TARGET := $(BINDIR)/$(TARGET) +TARGET = csq-x86 +SRCS = $(shell find $(SRCDIR) -name '*.c') +OBJS = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRCS)) diff --git a/include/csquare/opt-common.h b/include/csquare/opt-common.h index 40d1d33..f237a25 100644 --- a/include/csquare/opt-common.h +++ b/include/csquare/opt-common.h @@ -12,7 +12,7 @@ typedef struct { bool show_help; } csq_options; -typedef void (*opt_func_t)(csq_options* opts, const char* arg); +typedef void (*opt_func_t)(csq_options *opts, const char *arg); typedef struct { const char *long_name; @@ -22,7 +22,6 @@ typedef struct { opt_func_t func; } opt_map_t; - csq_options *options_parse(int argc, char *argv[]); void options_free(csq_options *opts); diff --git a/include/csquare/tests/lexer.h b/include/csquare/tests/lexer.h new file mode 100644 index 0000000..8ab006d --- /dev/null +++ b/include/csquare/tests/lexer.h @@ -0,0 +1,7 @@ +#ifndef _LEXER_TESTS_H +#define _LEXER_TESTS_H + +int test_percent_error(void); +int test_multiple_errors(void); + +#endif diff --git a/include/csquare/tests/tests.h b/include/csquare/tests/tests.h new file mode 100644 index 0000000..076e623 --- /dev/null +++ b/include/csquare/tests/tests.h @@ -0,0 +1,40 @@ +#ifndef _TESTS_H +#define _TESTS_H + +#include + +#define CLR_RESET "\x1b[0m" +#define CLR_RED "\x1b[31m" +#define CLR_GREEN "\x1b[32m" +#define CLR_BLUE "\x1b[34m" +#define CLR_DIM "\x1b[90m" + +extern int tests_failed; + +#define TEST(name) int name(void) + +#define RUN_TEST(name) \ + do { \ + printf("Running" CLR_BLUE " %-32s" CLR_RESET, #name); \ + int r = name(); \ + if (r == 0) { \ + printf(CLR_GREEN "[PASS]" CLR_RESET "\n"); \ + } else { \ + printf(CLR_RED "[FAIL]" CLR_RESET "\n\n"); \ + tests_failed++; \ + } \ + } while (0) + +#define ASSERT(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + printf("\n " CLR_RED "Assertion failed:" CLR_RESET " "); \ + printf(fmt, ##__VA_ARGS__); \ + printf("\n"); \ + return 1; \ + } \ + } while (0) + +int tests_main(void); + +#endif diff --git a/makefile b/makefile index 36124cd..45707e5 100644 --- a/makefile +++ b/makefile @@ -1,32 +1,61 @@ +# ============================= +# Makefile for C² Compiler +# Supports normal and test builds +# ============================= + include config.mk -.PHONY: all clean directories rebuild +.PHONY: all clean rebuild info directories -# Default target +# ----------------------------- +# Build targets +# ----------------------------- all: directories $(TARGET) rebuild: clean all -directories: - @mkdir -p $(BUILDDIR) $(BINDIR) $(shell find $(SRCDIR) -type d | sed 's/$(SRCDIR)/$(BUILDDIR)/') - -# Target executable +# Normal build $(TARGET): $(OBJS) @echo "Linking $(TARGET)" - $(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) + $(CC) $(CFLAGS) $(OBJS) -o $(BINDIR)/$(TARGET) $(LDFLAGS) + +# test build with tests +TEST_TARGET := csq-x86-test +TEST_OBJS := $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%_test.o,$(SRCS)) + +$(TEST_TARGET): directories $(TEST_OBJS) + @echo "Linking test binary $(BINDIR)/$(TEST_TARGET)" + $(CC) $(CFLAGS) $(TEST_OBJS) -o $(BINDIR)/$(TEST_TARGET) $(LDFLAGS) -# Object files from C source files +# ----------------------------- +# Compile source files +# ----------------------------- $(BUILDDIR)/%.o: $(SRCDIR)/%.c @echo "Compiling $<" @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ -# Clean build artifacts +$(BUILDDIR)/%_test.o: $(SRCDIR)/%.c + @echo "Compiling test $<" + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -DCSQ_RUN_TESTS -c $< -o $@ + +# ----------------------------- +# Token/Directory management +# ----------------------------- +directories: + @mkdir -p $(BUILDDIR) $(BINDIR) $(shell find $(SRCDIR) -type d | sed 's|$(SRCDIR)|$(BUILDDIR)|') + +# ----------------------------- +# Cleaning +# ----------------------------- clean: @echo "Cleaning build artifacts" @rm -rf $(BUILDDIR) $(BINDIR) -# Print Makefile information +# ----------------------------- +# test info +# ----------------------------- info: @echo "C² Build Configuration:" @echo " Compiler: $(CC)" @@ -36,5 +65,7 @@ info: @echo " Build dir: $(BUILDDIR)" @echo " Binary dir: $(BINDIR)" @echo " Target: $(TARGET)" + @echo " test Target: $(TEST_TARGET)" @echo " Sources: $(SRCS)" @echo " Objects: $(OBJS)" + @echo " test Objects: $(TEST_OBJS)" diff --git a/src/lexer/lex_symbol.c b/src/lexer/lex_symbol.c index 14b503d..8bcb68c 100644 --- a/src/lexer/lex_symbol.c +++ b/src/lexer/lex_symbol.c @@ -34,11 +34,10 @@ token *lex_symbol(const char *p, int *len, int *line, int *col) { best_len = 1; best_type = T_ERROR; *len = best_len; - *col = (*col) + (*line); return NULL; } *len = best_len; - *col = (*col) + (*line); + *col = (*col) + (*len); return new_token(p, best_len, best_type, *line, *col - *len); } diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index fd268ee..6d7f6d4 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -113,7 +113,7 @@ token_list *lex(const char *src) { sprintf(buf, msg, c); token *tk; token *errtk = - error_token(strdup(buf), p, 1, line, col, SYNERR_UNKNOWN_CHARACTER); + error_token(strdup(buf), p, 1, line, col - 1, SYNERR_UNKNOWN_CHARACTER); if (isdigit(c)) { tk = lex_digit(p, &consumed, &line, &col); diff --git a/src/main.c b/src/main.c index cd4bc80..5b52aa8 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,8 @@ #include "csquare/error.h" #include "csquare/lexer/lexer.h" #include "csquare/opt-common.h" +#include "csquare/tests/tests.h" +#include #include #include @@ -65,6 +67,9 @@ int main(int argc, char *argv[]) { if (!opts) return EXIT_FAILURE; +#ifdef CSQ_RUN_TESTS + return tests_main(); +#else if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); return EXIT_FAILURE; @@ -96,9 +101,11 @@ int main(int argc, char *argv[]) { highlight_start, highlight_len); print_error(&e); } + + free_token_list(lexed); + free(src); } +#endif - free_token_list(lexed); - free(src); return 0; } diff --git a/src/tests/lexer.c b/src/tests/lexer.c new file mode 100644 index 0000000..ebe021f --- /dev/null +++ b/src/tests/lexer.c @@ -0,0 +1,63 @@ +#include "csquare/lexer/lexer.h" +#include "csquare/tests/tests.h" +#include +#include + +TEST(test_percent_error) { + const char *src = "void main() {\n %\n}"; + token_list *tokens = lex(src); + + token *percent = NULL; + + for (size_t i = 0; i < tokens->count; i++) { + if (tokens->tokens[i]->start[0] == '%') { + percent = tokens->tokens[i]; + break; + } + } + + ASSERT(percent != NULL, "Percent token not found"); + ASSERT(percent->type == T_ERROR, "Percent token type not ERROR"); + ASSERT(percent->line == 2, "Line mismatch (got %d, expected 2)", + percent->line); + ASSERT(percent->col == 5, "Column mismatch (got %d, expected 5)", + percent->col); + + ASSERT(strcmp(percent->errmsg, "unknown character \x1b[32m'%'\x1b[0m") == 0, + "Error message mismatch (got \"%s\")", percent->errmsg); + + free_token_list(tokens); + return 0; +} + +TEST(test_multiple_errors) { + const char *src = "abc $ % @"; + token_list *tokens = lex(src); + + const char *expected_msgs[] = {"unknown character \x1b[32m'$'\x1b[0m", + "unknown character \x1b[32m'%'\x1b[0m", + "unknown character \x1b[32m'@'\x1b[0m"}; + + int found = 0; + + for (size_t i = 0; i < tokens->count; i++) { + token *tk = tokens->tokens[i]; + + if (tk->type == T_ERROR) { + ASSERT(found < 3, "Too many error tokens found"); + + ASSERT(strcmp(tk->errmsg, expected_msgs[found]) == 0, + "Message mismatch\n" + " got: \"%s\"\n" + " expected: \"%s\"", + tk->errmsg, expected_msgs[found]); + + found++; + } + } + + ASSERT(found == 3, "Expected 3 error tokens, got %d", found); + + free_token_list(tokens); + return 0; +} diff --git a/src/tests/tests.c b/src/tests/tests.c new file mode 100644 index 0000000..bab039b --- /dev/null +++ b/src/tests/tests.c @@ -0,0 +1,21 @@ +#include "csquare/tests/tests.h" +#include "csquare/tests/lexer.h" + +int tests_failed = 0; + +int tests_main(void) { + printf("\n=== C² Test Suite ===\n\n"); + + RUN_TEST(test_multiple_errors); + RUN_TEST(test_percent_error); + + printf("\n"); + + if (tests_failed == 0) { + printf(CLR_GREEN "All tests passed" CLR_RESET "\n\n"); + return 0; + } else { + printf(CLR_RED "%d test(s) failed" CLR_RESET "\n\n", tests_failed); + return 1; + } +}