diff --git a/.gitignore b/.gitignore index c7f8c1d..27c79bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .priv/ +build/ +programa.bl diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a5d729..7643544 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ - cmake_minimum_required(VERSION 3.20) + project(BLCeti VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 17) @@ -9,6 +9,7 @@ set(CMAKE_C_EXTENSIONS OFF) add_executable(BL src/main.c src/lexer.c + src/parser.c ) target_include_directories(BL PRIVATE include) diff --git a/build/BL b/build/BL index 5ac1260..fa1a584 100755 Binary files a/build/BL and b/build/BL differ diff --git a/build/CMakeFiles/BL.dir/DependInfo.cmake b/build/CMakeFiles/BL.dir/DependInfo.cmake index 63fcb40..f1dcfa3 100644 --- a/build/CMakeFiles/BL.dir/DependInfo.cmake +++ b/build/CMakeFiles/BL.dir/DependInfo.cmake @@ -10,6 +10,7 @@ set(CMAKE_DEPENDS_LANGUAGES set(CMAKE_DEPENDS_DEPENDENCY_FILES "/home/benicio/Escritorio/BL-Ceti/src/lexer.c" "CMakeFiles/BL.dir/src/lexer.c.o" "gcc" "CMakeFiles/BL.dir/src/lexer.c.o.d" "/home/benicio/Escritorio/BL-Ceti/src/main.c" "CMakeFiles/BL.dir/src/main.c.o" "gcc" "CMakeFiles/BL.dir/src/main.c.o.d" + "/home/benicio/Escritorio/BL-Ceti/src/parser.c" "CMakeFiles/BL.dir/src/parser.c.o" "gcc" "CMakeFiles/BL.dir/src/parser.c.o.d" "" "BL" "gcc" "CMakeFiles/BL.dir/link.d" ) diff --git a/build/CMakeFiles/BL.dir/build.make b/build/CMakeFiles/BL.dir/build.make index edf0793..d4ff4de 100644 --- a/build/CMakeFiles/BL.dir/build.make +++ b/build/CMakeFiles/BL.dir/build.make @@ -100,20 +100,36 @@ CMakeFiles/BL.dir/src/lexer.c.s: cmake_force @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/BL.dir/src/lexer.c.s" /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/benicio/Escritorio/BL-Ceti/src/lexer.c -o CMakeFiles/BL.dir/src/lexer.c.s +CMakeFiles/BL.dir/src/parser.c.o: CMakeFiles/BL.dir/flags.make +CMakeFiles/BL.dir/src/parser.c.o: /home/benicio/Escritorio/BL-Ceti/src/parser.c +CMakeFiles/BL.dir/src/parser.c.o: CMakeFiles/BL.dir/compiler_depend.ts + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Building C object CMakeFiles/BL.dir/src/parser.c.o" + /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/BL.dir/src/parser.c.o -MF CMakeFiles/BL.dir/src/parser.c.o.d -o CMakeFiles/BL.dir/src/parser.c.o -c /home/benicio/Escritorio/BL-Ceti/src/parser.c + +CMakeFiles/BL.dir/src/parser.c.i: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/BL.dir/src/parser.c.i" + /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/benicio/Escritorio/BL-Ceti/src/parser.c > CMakeFiles/BL.dir/src/parser.c.i + +CMakeFiles/BL.dir/src/parser.c.s: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/BL.dir/src/parser.c.s" + /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/benicio/Escritorio/BL-Ceti/src/parser.c -o CMakeFiles/BL.dir/src/parser.c.s + # Object files for target BL BL_OBJECTS = \ "CMakeFiles/BL.dir/src/main.c.o" \ -"CMakeFiles/BL.dir/src/lexer.c.o" +"CMakeFiles/BL.dir/src/lexer.c.o" \ +"CMakeFiles/BL.dir/src/parser.c.o" # External object files for target BL BL_EXTERNAL_OBJECTS = BL: CMakeFiles/BL.dir/src/main.c.o BL: CMakeFiles/BL.dir/src/lexer.c.o +BL: CMakeFiles/BL.dir/src/parser.c.o BL: CMakeFiles/BL.dir/build.make BL: CMakeFiles/BL.dir/compiler_depend.ts BL: CMakeFiles/BL.dir/link.txt - @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Linking C executable BL" + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_4) "Linking C executable BL" $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/BL.dir/link.txt --verbose=$(VERBOSE) # Rule to build all files generated by this target. diff --git a/build/CMakeFiles/BL.dir/cmake_clean.cmake b/build/CMakeFiles/BL.dir/cmake_clean.cmake index 43c2b9f..18a20d3 100644 --- a/build/CMakeFiles/BL.dir/cmake_clean.cmake +++ b/build/CMakeFiles/BL.dir/cmake_clean.cmake @@ -6,6 +6,8 @@ file(REMOVE_RECURSE "CMakeFiles/BL.dir/src/lexer.c.o.d" "CMakeFiles/BL.dir/src/main.c.o" "CMakeFiles/BL.dir/src/main.c.o.d" + "CMakeFiles/BL.dir/src/parser.c.o" + "CMakeFiles/BL.dir/src/parser.c.o.d" ) # Per-language clean rules from dependency scanning. diff --git a/build/CMakeFiles/BL.dir/link.d b/build/CMakeFiles/BL.dir/link.d index f3fe154..9671898 100644 --- a/build/CMakeFiles/BL.dir/link.d +++ b/build/CMakeFiles/BL.dir/link.d @@ -4,6 +4,7 @@ BL: \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/crtbeginS.o \ CMakeFiles/BL.dir/src/main.c.o \ CMakeFiles/BL.dir/src/lexer.c.o \ + CMakeFiles/BL.dir/src/parser.c.o \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/libgcc.a \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/libgcc_s_asneeded.so \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/libgcc_s_asneeded.so \ @@ -46,6 +47,8 @@ CMakeFiles/BL.dir/src/main.c.o: CMakeFiles/BL.dir/src/lexer.c.o: +CMakeFiles/BL.dir/src/parser.c.o: + /usr/lib/gcc/x86_64-pc-linux-gnu/16/libgcc.a: /usr/lib/gcc/x86_64-pc-linux-gnu/16/libgcc_s_asneeded.so: diff --git a/build/CMakeFiles/BL.dir/link.txt b/build/CMakeFiles/BL.dir/link.txt index e4f88a3..5517f0b 100644 --- a/build/CMakeFiles/BL.dir/link.txt +++ b/build/CMakeFiles/BL.dir/link.txt @@ -1 +1 @@ -/usr/bin/cc -Wl,--dependency-file=CMakeFiles/BL.dir/link.d CMakeFiles/BL.dir/src/main.c.o CMakeFiles/BL.dir/src/lexer.c.o -o BL +/usr/bin/cc -Wl,--dependency-file=CMakeFiles/BL.dir/link.d CMakeFiles/BL.dir/src/main.c.o CMakeFiles/BL.dir/src/lexer.c.o CMakeFiles/BL.dir/src/parser.c.o -o BL diff --git a/build/CMakeFiles/BL.dir/progress.make b/build/CMakeFiles/BL.dir/progress.make index 6a9dc74..a69a57e 100644 --- a/build/CMakeFiles/BL.dir/progress.make +++ b/build/CMakeFiles/BL.dir/progress.make @@ -1,4 +1,5 @@ CMAKE_PROGRESS_1 = 1 CMAKE_PROGRESS_2 = 2 CMAKE_PROGRESS_3 = 3 +CMAKE_PROGRESS_4 = 4 diff --git a/build/CMakeFiles/BL.dir/src/main.c.o b/build/CMakeFiles/BL.dir/src/main.c.o index 6c2fba0..3ef60b8 100644 Binary files a/build/CMakeFiles/BL.dir/src/main.c.o and b/build/CMakeFiles/BL.dir/src/main.c.o differ diff --git a/build/CMakeFiles/BL.dir/src/main.c.o.d b/build/CMakeFiles/BL.dir/src/main.c.o.d index d11347e..7ba4549 100644 --- a/build/CMakeFiles/BL.dir/src/main.c.o.d +++ b/build/CMakeFiles/BL.dir/src/main.c.o.d @@ -3,11 +3,13 @@ CMakeFiles/BL.dir/src/main.c.o: \ /home/benicio/Escritorio/BL-Ceti/include/lexer.h \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/include/stdbool.h \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/include/stddef.h \ - /usr/include/stdio.h /usr/include/bits/libc-header-start.h \ - /usr/include/features.h /usr/include/features-time64.h \ - /usr/include/bits/wordsize.h /usr/include/bits/timesize.h \ - /usr/include/sys/cdefs.h /usr/include/bits/long-double.h \ - /usr/include/gnu/stubs.h /usr/include/gnu/stubs-64.h \ + /home/benicio/Escritorio/BL-Ceti/include/parser.h \ + /home/benicio/Escritorio/BL-Ceti/include/lexer.h /usr/include/stdio.h \ + /usr/include/bits/libc-header-start.h /usr/include/features.h \ + /usr/include/features-time64.h /usr/include/bits/wordsize.h \ + /usr/include/bits/timesize.h /usr/include/sys/cdefs.h \ + /usr/include/bits/long-double.h /usr/include/gnu/stubs.h \ + /usr/include/gnu/stubs-64.h \ /usr/lib/gcc/x86_64-pc-linux-gnu/16/include/stdarg.h \ /usr/include/bits/types.h /usr/include/bits/typesizes.h \ /usr/include/bits/time64.h /usr/include/bits/types/__fpos_t.h \ diff --git a/build/CMakeFiles/Makefile2 b/build/CMakeFiles/Makefile2 index a25fbc0..fb0bac4 100644 --- a/build/CMakeFiles/Makefile2 +++ b/build/CMakeFiles/Makefile2 @@ -85,12 +85,12 @@ clean: CMakeFiles/BL.dir/clean CMakeFiles/BL.dir/all: $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/depend $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/build - @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=1,2,3 "Built target BL" + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=1,2,3,4 "Built target BL" .PHONY : CMakeFiles/BL.dir/all # Build rule for subdir invocation for target. CMakeFiles/BL.dir/rule: cmake_check_build_system - $(CMAKE_COMMAND) -E cmake_progress_start /home/benicio/Escritorio/BL-Ceti/build/CMakeFiles 3 + $(CMAKE_COMMAND) -E cmake_progress_start /home/benicio/Escritorio/BL-Ceti/build/CMakeFiles 4 $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 CMakeFiles/BL.dir/all $(CMAKE_COMMAND) -E cmake_progress_start /home/benicio/Escritorio/BL-Ceti/build/CMakeFiles 0 .PHONY : CMakeFiles/BL.dir/rule @@ -102,7 +102,7 @@ BL: CMakeFiles/BL.dir/rule # codegen rule for target. CMakeFiles/BL.dir/codegen: $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/codegen - @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=1,2,3 "Finished codegen for target BL" + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/benicio/Escritorio/BL-Ceti/build/CMakeFiles --progress-num=1,2,3,4 "Finished codegen for target BL" .PHONY : CMakeFiles/BL.dir/codegen # clean rule for target. diff --git a/build/CMakeFiles/progress.marks b/build/CMakeFiles/progress.marks index 00750ed..b8626c4 100644 --- a/build/CMakeFiles/progress.marks +++ b/build/CMakeFiles/progress.marks @@ -1 +1 @@ -3 +4 diff --git a/build/Makefile b/build/Makefile index 7851de6..3ca7ff5 100644 --- a/build/Makefile +++ b/build/Makefile @@ -222,6 +222,30 @@ src/main.c.s: $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/src/main.c.s .PHONY : src/main.c.s +src/parser.o: src/parser.c.o +.PHONY : src/parser.o + +# target to build an object file +src/parser.c.o: + $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/src/parser.c.o +.PHONY : src/parser.c.o + +src/parser.i: src/parser.c.i +.PHONY : src/parser.i + +# target to preprocess a source file +src/parser.c.i: + $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/src/parser.c.i +.PHONY : src/parser.c.i + +src/parser.s: src/parser.c.s +.PHONY : src/parser.s + +# target to generate assembly for a file +src/parser.c.s: + $(MAKE) $(MAKESILENT) -f CMakeFiles/BL.dir/build.make CMakeFiles/BL.dir/src/parser.c.s +.PHONY : src/parser.c.s + # Help Target help: @echo "The following are some of the valid targets for this Makefile:" @@ -241,6 +265,9 @@ help: @echo "... src/main.o" @echo "... src/main.i" @echo "... src/main.s" + @echo "... src/parser.o" + @echo "... src/parser.i" + @echo "... src/parser.s" .PHONY : help diff --git a/include/parser.h b/include/parser.h new file mode 100644 index 0000000..79a8036 --- /dev/null +++ b/include/parser.h @@ -0,0 +1,18 @@ +#ifndef BL_CETI_PARSER_H +#define BL_CETI_PARSER_H + +#include "lexer.h" + +#include +#include + +typedef struct { + char code[6]; + char message[160]; + size_t line; + size_t column; +} BlParseError; + +bool bl_parse(const BlToken *tokens, size_t count, BlParseError *error); + +#endif diff --git a/programa.bl b/programa.bl index ff8010d..2bd4be2 100644 --- a/programa.bl +++ b/programa.bl @@ -1,4 +1,7 @@ +cat > /tmp/bl-error.bl <<'EOF' fn main() { - let mensaje: String = "Hola desde BL-Ceti"; - print(mensaje); + let numero: Int = ; } +EOF + +./build/BL .C /tmp/bl-error.bl diff --git a/src/main.c b/src/main.c index 45af5e3..14ecfd6 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ - #include "lexer.h" +#include "parser.h" #include #include @@ -16,6 +16,7 @@ static char *read_file(const char *path, size_t *length) if (fseek(file, 0, SEEK_END) != 0) { fclose(file); + fprintf(stderr, "BL: no se pudo leer el archivo.\n"); return NULL; } @@ -23,6 +24,7 @@ static char *read_file(const char *path, size_t *length) if (size < 0 || fseek(file, 0, SEEK_SET) != 0) { fclose(file); + fprintf(stderr, "BL: no se pudo determinar el tamaƱo del archivo.\n"); return NULL; } @@ -46,6 +48,7 @@ static char *read_file(const char *path, size_t *length) buffer[read_count] = '\0'; *length = read_count; fclose(file); + return buffer; } @@ -54,29 +57,48 @@ static int analyze_file(const char *path) size_t length = 0; char *source = read_file(path, &length); - if (source == NULL) return 1; + if (source == NULL) { + return 1; + } - BlLexResult result = {0}; - bool success = bl_lex(source, length, &result); + BlLexResult lex_result = {0}; - if (!success) { + if (!bl_lex(source, length, &lex_result)) { fprintf(stderr, "%s:%zu:%zu: error %s: %s\n", path, - result.error.line, - result.error.column, - result.error.code, - result.error.message); - bl_lex_result_destroy(&result); + lex_result.error.line, + lex_result.error.column, + lex_result.error.code, + lex_result.error.message); + + bl_lex_result_destroy(&lex_result); free(source); return 1; } - printf("BL-Ceti: analisis lexico correcto: %s\n", path); - printf("Tokens reconocidos: %zu\n", - result.count > 0 ? result.count - 1 : 0); + BlParseError parse_error = {0}; - bl_lex_result_destroy(&result); + if (!bl_parse(lex_result.tokens, lex_result.count, &parse_error)) { + fprintf(stderr, "%s:%zu:%zu: error %s: %s\n", + path, + parse_error.line, + parse_error.column, + parse_error.code, + parse_error.message); + + bl_lex_result_destroy(&lex_result); + free(source); + return 1; + } + + printf("BL-Ceti 0.1.0: analisis correcto: %s\n", path); + printf("Tokens reconocidos: %zu\n", + lex_result.count > 0 ? lex_result.count - 1 : 0); + printf("Sintaxis: correcta\n"); + + bl_lex_result_destroy(&lex_result); free(source); + return 0; } diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..0a60fc1 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,639 @@ +#include "parser.h" + +#include +#include + +typedef struct { + const BlToken *tokens; + size_t count; + size_t position; + BlParseError *error; + bool failed; +} Parser; + +typedef struct { + bool identifier; + bool call; +} ExprInfo; + +static const BlToken *current(Parser *parser) +{ + if (parser->position >= parser->count) { + return &parser->tokens[parser->count - 1]; + } + + return &parser->tokens[parser->position]; +} + +static bool check(Parser *parser, BlTokenType type) +{ + return current(parser)->type == type; +} + +static const BlToken *advance(Parser *parser) +{ + const BlToken *token = current(parser); + + if (token->type != BL_EOF) { + parser->position++; + } + + return token; +} + +static bool fail_at(Parser *parser, const BlToken *token, + const char *code, const char *message) +{ + if (!parser->failed) { + parser->failed = true; + + snprintf(parser->error->code, sizeof parser->error->code, + "%s", code); + snprintf(parser->error->message, sizeof parser->error->message, + "%s", message); + + parser->error->line = token->line; + parser->error->column = token->column; + } + + return false; +} + +static bool expect(Parser *parser, BlTokenType type, + const char *message) +{ + if (check(parser, type)) { + advance(parser); + return true; + } + + return fail_at(parser, current(parser), "E1001", message); +} + +static bool parse_expression(Parser *parser, ExprInfo *info); +static bool parse_statement(Parser *parser); +static bool parse_block(Parser *parser); + +static bool parse_type(Parser *parser, bool allow_void) +{ + BlTokenType type = current(parser)->type; + + if (type == BL_INT_TYPE || + type == BL_FLOAT_TYPE || + type == BL_BOOL_TYPE || + type == BL_STRING_TYPE) { + advance(parser); + return true; + } + + if (allow_void && type == BL_VOID_TYPE) { + advance(parser); + return true; + } + + return fail_at(parser, current(parser), "E1001", + "Se esperaba un tipo valido."); +} + +static bool parse_primary(Parser *parser, ExprInfo *info) +{ + const BlToken *token = current(parser); + + info->identifier = false; + info->call = false; + + switch (token->type) { + case BL_INTEGER: + case BL_FLOAT: + case BL_STRING: + case BL_TRUE: + case BL_FALSE: + advance(parser); + return true; + + case BL_IDENTIFIER: + advance(parser); + + if (check(parser, BL_LPAREN)) { + advance(parser); + + if (!check(parser, BL_RPAREN)) { + ExprInfo argument; + + if (!parse_expression(parser, &argument)) { + return false; + } + + while (check(parser, BL_COMMA)) { + advance(parser); + + if (check(parser, BL_RPAREN)) { + break; + } + + if (!parse_expression(parser, &argument)) { + return false; + } + } + } + + if (!expect(parser, BL_RPAREN, + "Se esperaba ')' al cerrar la llamada.")) { + return false; + } + + info->call = true; + return true; + } + + info->identifier = true; + return true; + + case BL_LPAREN: { + advance(parser); + + ExprInfo inner; + + if (!parse_expression(parser, &inner)) { + return false; + } + + if (!expect(parser, BL_RPAREN, + "Se esperaba ')' en la expresion.")) { + return false; + } + + return true; + } + + default: + return fail_at(parser, token, "E1001", + "Se esperaba una expresion."); + } +} + +static bool parse_unary(Parser *parser, ExprInfo *info) +{ + if (check(parser, BL_MINUS) || check(parser, BL_NOT)) { + advance(parser); + + if (!parse_unary(parser, info)) { + return false; + } + + info->identifier = false; + info->call = false; + return true; + } + + return parse_primary(parser, info); +} + +static bool parse_cast(Parser *parser, ExprInfo *info) +{ + if (!parse_unary(parser, info)) { + return false; + } + + while (check(parser, BL_AS)) { + advance(parser); + + if (!parse_type(parser, false)) { + return false; + } + + info->identifier = false; + info->call = false; + } + + return true; +} + +static bool parse_multiplication(Parser *parser, ExprInfo *info) +{ + if (!parse_cast(parser, info)) { + return false; + } + + while (check(parser, BL_STAR) || + check(parser, BL_SLASH) || + check(parser, BL_PERCENT)) { + advance(parser); + + ExprInfo right; + + if (!parse_cast(parser, &right)) { + return false; + } + + info->identifier = false; + info->call = false; + } + + return true; +} + +static bool parse_addition(Parser *parser, ExprInfo *info) +{ + if (!parse_multiplication(parser, info)) { + return false; + } + + while (check(parser, BL_PLUS) || check(parser, BL_MINUS)) { + advance(parser); + + ExprInfo right; + + if (!parse_multiplication(parser, &right)) { + return false; + } + + info->identifier = false; + info->call = false; + } + + return true; +} + +static bool is_relational(BlTokenType type) +{ + return type == BL_LESS || + type == BL_LESS_EQUAL || + type == BL_GREATER || + type == BL_GREATER_EQUAL; +} + +static bool parse_relation(Parser *parser, ExprInfo *info) +{ + if (!parse_addition(parser, info)) { + return false; + } + + if (is_relational(current(parser)->type)) { + advance(parser); + + ExprInfo right; + + if (!parse_addition(parser, &right)) { + return false; + } + + info->identifier = false; + info->call = false; + + if (is_relational(current(parser)->type)) { + return fail_at(parser, current(parser), "E1005", + "Las comparaciones relacionales no se pueden encadenar."); + } + } + + return true; +} + +static bool parse_equality(Parser *parser, ExprInfo *info) +{ + if (!parse_relation(parser, info)) { + return false; + } + + if (check(parser, BL_EQUAL) || check(parser, BL_NOT_EQUAL)) { + advance(parser); + + ExprInfo right; + + if (!parse_relation(parser, &right)) { + return false; + } + + info->identifier = false; + info->call = false; + + if (check(parser, BL_EQUAL) || check(parser, BL_NOT_EQUAL)) { + return fail_at(parser, current(parser), "E1005", + "Las comparaciones de igualdad no se pueden encadenar."); + } + } + + return true; +} + +static bool parse_and(Parser *parser, ExprInfo *info) +{ + if (!parse_equality(parser, info)) { + return false; + } + + while (check(parser, BL_AND)) { + advance(parser); + + ExprInfo right; + + if (!parse_equality(parser, &right)) { + return false; + } + + info->identifier = false; + info->call = false; + } + + return true; +} + +static bool parse_expression(Parser *parser, ExprInfo *info) +{ + if (!parse_and(parser, info)) { + return false; + } + + while (check(parser, BL_OR)) { + advance(parser); + + ExprInfo right; + + if (!parse_and(parser, &right)) { + return false; + } + + info->identifier = false; + info->call = false; + } + + return true; +} + +static bool is_assignment_operator(BlTokenType type) +{ + return type == BL_ASSIGN || + type == BL_PLUS_ASSIGN || + type == BL_MINUS_ASSIGN || + type == BL_STAR_ASSIGN || + type == BL_SLASH_ASSIGN || + type == BL_PERCENT_ASSIGN; +} + +static bool parse_variable(Parser *parser) +{ + advance(parser); /* let o var */ + + if (!expect(parser, BL_IDENTIFIER, + "Se esperaba un nombre de variable.")) { + return false; + } + + if (check(parser, BL_COLON)) { + advance(parser); + + if (!parse_type(parser, false)) { + return false; + } + } + + if (!expect(parser, BL_ASSIGN, + "Se esperaba '=' para inicializar la variable.")) { + return false; + } + + ExprInfo value; + + if (!parse_expression(parser, &value)) { + return false; + } + + return expect(parser, BL_SEMICOLON, + "Se esperaba ';' al final de la declaracion."); +} + +static bool parse_if(Parser *parser) +{ + advance(parser); /* if */ + + ExprInfo condition; + + if (!parse_expression(parser, &condition)) { + return false; + } + + if (!parse_block(parser)) { + return false; + } + + if (check(parser, BL_ELSE)) { + advance(parser); + + if (check(parser, BL_IF)) { + return parse_if(parser); + } + + return parse_block(parser); + } + + return true; +} + +static bool parse_while(Parser *parser) +{ + advance(parser); /* while */ + + ExprInfo condition; + + if (!parse_expression(parser, &condition)) { + return false; + } + + return parse_block(parser); +} + +static bool parse_return(Parser *parser) +{ + advance(parser); /* return */ + + if (!check(parser, BL_SEMICOLON)) { + ExprInfo value; + + if (!parse_expression(parser, &value)) { + return false; + } + } + + return expect(parser, BL_SEMICOLON, + "Se esperaba ';' despues de return."); +} + +static bool parse_simple_statement(Parser *parser) +{ + ExprInfo expression; + + if (!parse_expression(parser, &expression)) { + return false; + } + + if (is_assignment_operator(current(parser)->type)) { + if (!expression.identifier) { + return fail_at(parser, current(parser), "E1006", + "El lado izquierdo de una asignacion debe ser una variable."); + } + + advance(parser); + + ExprInfo value; + + if (!parse_expression(parser, &value)) { + return false; + } + } else if (!expression.call) { + return fail_at(parser, current(parser), "E1007", + "Una sentencia de expresion debe ser una llamada a funcion."); + } + + return expect(parser, BL_SEMICOLON, + "Se esperaba ';' al final de la sentencia."); +} + +static bool parse_statement(Parser *parser) +{ + switch (current(parser)->type) { + case BL_LET: + case BL_VAR: + return parse_variable(parser); + + case BL_IF: + return parse_if(parser); + + case BL_WHILE: + return parse_while(parser); + + case BL_BREAK: + case BL_CONTINUE: + advance(parser); + return expect(parser, BL_SEMICOLON, + "Se esperaba ';' despues de la sentencia."); + + case BL_RETURN: + return parse_return(parser); + + case BL_LBRACE: + return parse_block(parser); + + case BL_IDENTIFIER: + case BL_INTEGER: + case BL_FLOAT: + case BL_STRING: + case BL_TRUE: + case BL_FALSE: + case BL_LPAREN: + case BL_MINUS: + case BL_NOT: + return parse_simple_statement(parser); + + default: + return fail_at(parser, current(parser), "E1001", + "Sentencia no valida."); + } +} + +static bool parse_block(Parser *parser) +{ + if (!expect(parser, BL_LBRACE, + "Se esperaba '{' para comenzar el bloque.")) { + return false; + } + + while (!check(parser, BL_RBRACE) && + !check(parser, BL_EOF) && + !parser->failed) { + if (!parse_statement(parser)) { + return false; + } + } + + return expect(parser, BL_RBRACE, + "Se esperaba '}' para cerrar el bloque."); +} + +static bool parse_function(Parser *parser) +{ + advance(parser); /* fn */ + + if (!expect(parser, BL_IDENTIFIER, + "Se esperaba el nombre de la funcion.")) { + return false; + } + + if (!expect(parser, BL_LPAREN, + "Se esperaba '(' despues del nombre de la funcion.")) { + return false; + } + + if (!check(parser, BL_RPAREN)) { + for (;;) { + if (!expect(parser, BL_IDENTIFIER, + "Se esperaba el nombre del parametro.")) { + return false; + } + + if (!expect(parser, BL_COLON, + "Se esperaba ':' despues del nombre del parametro.")) { + return false; + } + + if (!parse_type(parser, false)) { + return false; + } + + if (!check(parser, BL_COMMA)) { + break; + } + + advance(parser); + + if (check(parser, BL_RPAREN)) { + break; + } + } + } + + if (!expect(parser, BL_RPAREN, + "Se esperaba ')' despues de los parametros.")) { + return false; + } + + if (check(parser, BL_ARROW)) { + advance(parser); + + if (!parse_type(parser, true)) { + return false; + } + } + + return parse_block(parser); +} + +bool bl_parse(const BlToken *tokens, size_t count, BlParseError *error) +{ + if (tokens == NULL || count == 0 || error == NULL) { + return false; + } + + memset(error, 0, sizeof *error); + + Parser parser = { + .tokens = tokens, + .count = count, + .position = 0, + .error = error, + .failed = false + }; + + while (!check(&parser, BL_EOF) && !parser.failed) { + if (!check(&parser, BL_FN)) { + return fail_at(&parser, current(&parser), "E1001", + "En el nivel superior solo se permiten funciones."); + } + + if (!parse_function(&parser)) { + return false; + } + } + + return !parser.failed && check(&parser, BL_EOF); +}