From a92ed68948acdbf706a239edd84519ce09c6f859 Mon Sep 17 00:00:00 2001 From: retoor Date: Fri, 5 Dec 2025 12:32:09 +0100 Subject: [PATCH] Repl. --- Makefile | 16 +- parser/parser_declarations.c | 36 +- parser/parser_statements.c | 18 +- rava.c | 77 ++++- repl/examples/bank_account.txt | 45 +++ repl/examples/calculator.txt | 20 ++ repl/examples/fibonacci.txt | 28 ++ repl/examples/geometry.txt | 63 ++++ repl/examples/primes.txt | 34 ++ repl/examples/sorting.txt | 48 +++ repl/examples/string_utils.txt | 53 +++ repl/repl.c | 192 +++++++++++ repl/repl.h | 29 ++ repl/repl_commands.c | 229 +++++++++++++ repl/repl_commands.h | 20 ++ repl/repl_executor.c | 565 +++++++++++++++++++++++++++++++ repl/repl_executor.h | 28 ++ repl/repl_history.c | 102 ++++++ repl/repl_history.h | 23 ++ repl/repl_input.c | 250 ++++++++++++++ repl/repl_input.h | 26 ++ repl/repl_output.c | 196 +++++++++++ repl/repl_output.h | 15 + repl/repl_session.c | 369 ++++++++++++++++++++ repl/repl_session.h | 54 +++ repl/repl_types.h | 99 ++++++ repl/run_tests.sh | 61 ++++ repl/tests/test_arrays.txt | 15 + repl/tests/test_classes.txt | 16 + repl/tests/test_commands.txt | 16 + repl/tests/test_control_flow.txt | 8 + repl/tests/test_errors.txt | 8 + repl/tests/test_expressions.txt | 24 ++ repl/tests/test_math.txt | 16 + repl/tests/test_methods.txt | 21 ++ repl/tests/test_multiline.txt | 22 ++ repl/tests/test_strings.txt | 19 ++ repl/tests/test_variables.txt | 23 ++ 38 files changed, 2861 insertions(+), 23 deletions(-) create mode 100644 repl/examples/bank_account.txt create mode 100644 repl/examples/calculator.txt create mode 100644 repl/examples/fibonacci.txt create mode 100644 repl/examples/geometry.txt create mode 100644 repl/examples/primes.txt create mode 100644 repl/examples/sorting.txt create mode 100644 repl/examples/string_utils.txt create mode 100644 repl/repl.c create mode 100644 repl/repl.h create mode 100644 repl/repl_commands.c create mode 100644 repl/repl_commands.h create mode 100644 repl/repl_executor.c create mode 100644 repl/repl_executor.h create mode 100644 repl/repl_history.c create mode 100644 repl/repl_history.h create mode 100644 repl/repl_input.c create mode 100644 repl/repl_input.h create mode 100644 repl/repl_output.c create mode 100644 repl/repl_output.h create mode 100644 repl/repl_session.c create mode 100644 repl/repl_session.h create mode 100644 repl/repl_types.h create mode 100755 repl/run_tests.sh create mode 100644 repl/tests/test_arrays.txt create mode 100644 repl/tests/test_classes.txt create mode 100644 repl/tests/test_commands.txt create mode 100644 repl/tests/test_control_flow.txt create mode 100644 repl/tests/test_errors.txt create mode 100644 repl/tests/test_expressions.txt create mode 100644 repl/tests/test_math.txt create mode 100644 repl/tests/test_methods.txt create mode 100644 repl/tests/test_multiline.txt create mode 100644 repl/tests/test_strings.txt create mode 100644 repl/tests/test_variables.txt diff --git a/Makefile b/Makefile index 5e3686d..80c2ac8 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,9 @@ RUNTIME_OBJECTS = $(RUNTIME_SOURCES:.c=.o) LOADER_SOURCES = loader/loader.c LOADER_OBJECTS = $(LOADER_SOURCES:.c=.o) +REPL_SOURCES = repl/repl.c repl/repl_session.c repl/repl_input.c repl/repl_executor.c repl/repl_output.c repl/repl_commands.c repl/repl_history.c +REPL_OBJECTS = $(REPL_SOURCES:.c=.o) + PHASE0_SOURCES = runtime/fastframe.c runtime/labeltable.c runtime/methodcache.c PHASE0_OBJECTS = $(PHASE0_SOURCES:.c=.o) @@ -302,8 +305,8 @@ benchmark: test_benchmark @python3 examples/benchmark.py @echo "" -rava: rava.o $(LEXER_OBJECTS) $(PARSER_OBJECTS) $(TYPES_OBJECTS) $(SEMANTIC_OBJECTS) $(IR_OBJECTS) $(RUNTIME_OBJECTS) $(LOADER_OBJECTS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +rava: rava.o $(LEXER_OBJECTS) $(PARSER_OBJECTS) $(TYPES_OBJECTS) $(SEMANTIC_OBJECTS) $(IR_OBJECTS) $(RUNTIME_OBJECTS) $(LOADER_OBJECTS) $(REPL_OBJECTS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -lreadline examples: rava @echo "=== Running All Examples ===" @@ -353,7 +356,7 @@ pgo: test_benchmark_pgo ./test_benchmark_pgo clean: - rm -f $(LEXER_OBJECTS) $(PARSER_OBJECTS) $(TYPES_OBJECTS) $(SEMANTIC_OBJECTS) $(IR_OBJECTS) $(RUNTIME_OBJECTS) $(LOADER_OBJECTS) \ + rm -f $(LEXER_OBJECTS) $(PARSER_OBJECTS) $(TYPES_OBJECTS) $(SEMANTIC_OBJECTS) $(IR_OBJECTS) $(RUNTIME_OBJECTS) $(LOADER_OBJECTS) $(REPL_OBJECTS) \ $(PHASE0_OBJECTS) $(UNITTEST_OBJECTS) $(TEST_UNITTEST_DEMO_OBJECTS) \ $(TEST_LEXER_OBJECTS) $(TEST_PARSER_OBJECTS) $(TEST_SEMANTIC_OBJECTS) $(TEST_IR_OBJECTS) $(TEST_RUNTIME_OBJECTS) \ $(TEST_STRINGS_OBJECTS) $(TEST_ARRAYS_OBJECTS) $(TEST_OBJECTS_OBJECTS) $(TEST_INSTANCE_OBJECTS) $(TEST_FILEIO_OBJECTS) \ @@ -361,7 +364,7 @@ clean: test_lexer test_parser test_semantic test_ir test_runtime test_strings test_arrays test_objects test_instance_methods test_fileio test_dowhile test_switch test_math test_string_methods test_static test_interfaces test_exceptions test_ternary test_bitwise test_enhanced_for test_array_init test_instanceof test_shortcircuit test_multidim_arrays test_static_init test_negative test_enums test_collections test_super test_inheritance test_break test_elseif test_forloop test_println test_loader test_object_methods test_autobox test_sockets test_method_ref test_gc test_benchmark \ test_nanbox test_fastframe test_labeltable test_methodcache test_benchmark_pgo test_unittest_demo rava rava.o *.gcda */*.gcda -.PHONY: all clean benchmark test_phase0 pgo test_benchmark_pgo_gen pgo_run test_benchmark_pgo test examples +.PHONY: all clean benchmark test_phase0 pgo test_benchmark_pgo_gen pgo_run test_benchmark_pgo test test_repl examples test: all @echo "=== Running All Tests ===" @@ -374,4 +377,9 @@ test: all ./test_enums && ./test_collections && ./test_super && ./test_inheritance && ./test_break && \ ./test_elseif && ./test_forloop && ./test_println && ./test_loader && ./test_object_methods && \ ./test_autobox && ./test_sockets && ./test_method_ref && ./test_gc && \ + ./repl/run_tests.sh && \ echo "" && echo "=== All Tests Passed ===" + +test_repl: rava + @echo "=== Running REPL Tests ===" + @./repl/run_tests.sh diff --git a/parser/parser_declarations.c b/parser/parser_declarations.c index a60cf2a..f56b2cf 100644 --- a/parser/parser_declarations.c +++ b/parser/parser_declarations.c @@ -33,8 +33,10 @@ static RavaASTNode_t* _rava_parser_parse_method_declaration(RavaParser_t *parser _rava_parser_expect(parser, RAVA_TOKEN_LPAREN, "Expected '(' after method name"); while (!_rava_parser_check(parser, RAVA_TOKEN_RPAREN) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaASTNode_t *param_type = _rava_parser_parse_type(parser); + if (!param_type) break; RavaASTNode_t *param = rava_ast_node_create(RAVA_AST_PARAM_DECL, parser->current_token->line, parser->current_token->column); @@ -124,7 +126,8 @@ RavaASTNode_t* _rava_parser_parse_class_declaration(RavaParser_t *parser) { _rava_parser_expect(parser, RAVA_TOKEN_LBRACE, "Expected '{' after class name"); while (!_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaModifier_e *member_modifiers = NULL; size_t member_modifiers_count = 0; @@ -143,8 +146,10 @@ RavaASTNode_t* _rava_parser_parse_class_declaration(RavaParser_t *parser) { _rava_parser_expect(parser, RAVA_TOKEN_LPAREN, "Expected '(' after constructor name"); while (!_rava_parser_check(parser, RAVA_TOKEN_RPAREN) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaASTNode_t *param_type = _rava_parser_parse_type(parser); + if (!param_type) break; RavaASTNode_t *param = rava_ast_node_create(RAVA_AST_PARAM_DECL, parser->current_token->line, parser->current_token->column); @@ -175,6 +180,7 @@ RavaASTNode_t* _rava_parser_parse_class_declaration(RavaParser_t *parser) { } RavaASTNode_t *member_type = _rava_parser_parse_type(parser); + if (!member_type) break; if (_rava_parser_check(parser, RAVA_TOKEN_IDENTIFIER)) { char *name = strdup(parser->current_token->lexeme); @@ -204,6 +210,9 @@ RavaASTNode_t* _rava_parser_parse_class_declaration(RavaParser_t *parser) { rava_ast_node_add_child(class_decl, field); member_modifiers = NULL; } + } else { + free(member_modifiers); + break; } } @@ -235,7 +244,8 @@ static RavaASTNode_t* _rava_parser_parse_interface_declaration(RavaParser_t *par _rava_parser_expect(parser, RAVA_TOKEN_LBRACE, "Expected '{' after interface name"); while (!_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaModifier_e *member_modifiers = NULL; size_t member_modifiers_count = 0; @@ -243,6 +253,10 @@ static RavaASTNode_t* _rava_parser_parse_interface_declaration(RavaParser_t *par _rava_parser_parse_modifiers(parser, &member_modifiers, &member_modifiers_count); RavaASTNode_t *member_type = _rava_parser_parse_type(parser); + if (!member_type) { + free(member_modifiers); + break; + } if (_rava_parser_check(parser, RAVA_TOKEN_IDENTIFIER)) { char *name = strdup(parser->current_token->lexeme); @@ -261,6 +275,7 @@ static RavaASTNode_t* _rava_parser_parse_interface_declaration(RavaParser_t *par } } else { free(member_modifiers); + break; } } @@ -302,7 +317,8 @@ static RavaASTNode_t* _rava_parser_parse_enum_declaration(RavaParser_t *parser) while (!_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && !_rava_parser_check(parser, RAVA_TOKEN_SEMICOLON) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { if (_rava_parser_check(parser, RAVA_TOKEN_IDENTIFIER)) { if (enum_decl->data.enum_decl.constants_count >= capacity) { @@ -319,6 +335,8 @@ static RavaASTNode_t* _rava_parser_parse_enum_declaration(RavaParser_t *parser) enum_decl->data.enum_decl.constants[enum_decl->data.enum_decl.constants_count++] = strdup(parser->current_token->lexeme); _rava_parser_advance(parser); + } else { + break; } if (!_rava_parser_match(parser, RAVA_TOKEN_COMMA)) { @@ -328,7 +346,8 @@ static RavaASTNode_t* _rava_parser_parse_enum_declaration(RavaParser_t *parser) if (_rava_parser_match(parser, RAVA_TOKEN_SEMICOLON)) { while (!_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaModifier_e *member_modifiers = NULL; size_t member_modifiers_count = 0; @@ -336,6 +355,10 @@ static RavaASTNode_t* _rava_parser_parse_enum_declaration(RavaParser_t *parser) _rava_parser_parse_modifiers(parser, &member_modifiers, &member_modifiers_count); RavaASTNode_t *member_type = _rava_parser_parse_type(parser); + if (!member_type) { + free(member_modifiers); + break; + } if (_rava_parser_check(parser, RAVA_TOKEN_IDENTIFIER)) { char *name = strdup(parser->current_token->lexeme); @@ -366,6 +389,7 @@ static RavaASTNode_t* _rava_parser_parse_enum_declaration(RavaParser_t *parser) } } else { free(member_modifiers); + break; } } } diff --git a/parser/parser_statements.c b/parser/parser_statements.c index 80ede02..a5bfcde 100644 --- a/parser/parser_statements.c +++ b/parser/parser_statements.c @@ -23,9 +23,14 @@ RavaASTNode_t* _rava_parser_parse_block(RavaParser_t *parser) { while (!_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + if (parser->had_error) { + break; + } RavaASTNode_t *stmt = _rava_parser_parse_statement(parser); if (stmt) { rava_ast_node_add_child(block, stmt); + } else if (parser->had_error) { + break; } } @@ -211,7 +216,8 @@ RavaASTNode_t* _rava_parser_parse_statement(RavaParser_t *parser) { _rava_parser_expect(parser, RAVA_TOKEN_LBRACE, "Expected '{' after switch"); while (!_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { if (_rava_parser_match(parser, RAVA_TOKEN_KEYWORD_CASE)) { RavaASTNode_t *case_node = rava_ast_node_create(RAVA_AST_CASE_STMT, @@ -224,10 +230,13 @@ RavaASTNode_t* _rava_parser_parse_statement(RavaParser_t *parser) { while (!_rava_parser_check(parser, RAVA_TOKEN_KEYWORD_CASE) && !_rava_parser_check(parser, RAVA_TOKEN_KEYWORD_DEFAULT) && !_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaASTNode_t *stmt = _rava_parser_parse_statement(parser); if (stmt) { rava_ast_node_add_child(case_node, stmt); + } else if (parser->had_error) { + break; } } @@ -244,10 +253,13 @@ RavaASTNode_t* _rava_parser_parse_statement(RavaParser_t *parser) { while (!_rava_parser_check(parser, RAVA_TOKEN_KEYWORD_CASE) && !_rava_parser_check(parser, RAVA_TOKEN_KEYWORD_DEFAULT) && !_rava_parser_check(parser, RAVA_TOKEN_RBRACE) && - !_rava_parser_check(parser, RAVA_TOKEN_EOF)) { + !_rava_parser_check(parser, RAVA_TOKEN_EOF) && + !parser->had_error) { RavaASTNode_t *stmt = _rava_parser_parse_statement(parser); if (stmt) { rava_ast_node_add_child(default_node, stmt); + } else if (parser->had_error) { + break; } } diff --git a/rava.c b/rava.c index 0d6677e..8767f91 100644 --- a/rava.c +++ b/rava.c @@ -7,6 +7,7 @@ #include "ir/ir.h" #include "ir/ir_gen.h" #include "runtime/runtime.h" +#include "repl/repl.h" static char* read_file(const char *filename) { FILE *file = fopen(filename, "r"); @@ -50,17 +51,30 @@ static const char* find_main_class(const char *source) { return "Main"; } -int main(int argc, char **argv) { - if (argc < 2) { - fprintf(stderr, "Usage: %s [class] [method]\n", argv[0]); - return 1; - } +static void print_usage(const char *prog) { + fprintf(stderr, "Usage: %s [options] [file.java] [class] [method]\n", prog); + fprintf(stderr, "\nOptions:\n"); + fprintf(stderr, " -i, --interactive Start interactive REPL\n"); + fprintf(stderr, " -h, --help Show this help message\n"); + fprintf(stderr, " -v, --version Show version\n"); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, " %s Start REPL (interactive mode)\n", prog); + fprintf(stderr, " %s file.java Run file.java\n", prog); + fprintf(stderr, " %s file.java Main Run Main class from file.java\n", prog); + fprintf(stderr, " %s -i Start REPL explicitly\n", prog); +} - char *source = read_file(argv[1]); +static void print_version(void) { + printf("Rava %s - Java Interpreter\n", RAVA_REPL_VERSION); + printf("A stack-based VM for Java source code\n"); +} + +static int run_file(const char *filename, const char *class_name_arg, const char *method_name_arg) { + char *source = read_file(filename); if (!source) return 1; - const char *class_name = argc > 2 ? argv[2] : find_main_class(source); - const char *method_name = argc > 3 ? argv[3] : "main"; + const char *class_name = class_name_arg ? class_name_arg : find_main_class(source); + const char *method_name = method_name_arg ? method_name_arg : "main"; RavaLexer_t *lexer = rava_lexer_create(source); RavaParser_t *parser = rava_parser_create(lexer); @@ -95,8 +109,9 @@ int main(int argc, char **argv) { } RavaValue_t result = rava_vm_get_result(vm); + int ret = 0; if (result.type == RAVA_VAL_INT) { - return (int)result.data.int_val; + ret = (int)result.data.int_val; } rava_vm_destroy(vm); @@ -108,5 +123,47 @@ int main(int argc, char **argv) { rava_lexer_destroy(lexer); free(source); - return 0; + return ret; +} + +int main(int argc, char **argv) { + if (argc == 1) { + return rava_repl_run(); + } + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return 0; + } + if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) { + print_version(); + return 0; + } + if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--interactive") == 0) { + return rava_repl_run(); + } + } + + const char *filename = NULL; + const char *class_name = NULL; + const char *method_name = NULL; + int pos_arg = 0; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') continue; + switch (pos_arg) { + case 0: filename = argv[i]; break; + case 1: class_name = argv[i]; break; + case 2: method_name = argv[i]; break; + } + pos_arg++; + } + + if (!filename) { + print_usage(argv[0]); + return 1; + } + + return run_file(filename, class_name, method_name); } diff --git a/repl/examples/bank_account.txt b/repl/examples/bank_account.txt new file mode 100644 index 0000000..bdc00dc --- /dev/null +++ b/repl/examples/bank_account.txt @@ -0,0 +1,45 @@ +class BankAccount { +public String holder; +public double balance; +public BankAccount(String name, double initial) { +this.holder = name; +this.balance = initial; +} +public void deposit(double amount) { +if (amount > 0) { +this.balance = this.balance + amount; +} +} +public boolean withdraw(double amount) { +if (amount > 0 && amount <= this.balance) { +this.balance = this.balance - amount; +return true; +} +return false; +} +public double getBalance() { +return this.balance; +} +public String info() { +return this.holder + ": $" + this.balance; +} +} + +BankAccount acc = new BankAccount("Alice", 1000.0); +acc.info() +acc.getBalance() + +acc.deposit(500.0) +acc.getBalance() + +acc.withdraw(200.0) +acc.getBalance() + +acc.withdraw(2000.0) +acc.getBalance() + +acc.info() + +%classes +%whos +%quit diff --git a/repl/examples/calculator.txt b/repl/examples/calculator.txt new file mode 100644 index 0000000..e2f0081 --- /dev/null +++ b/repl/examples/calculator.txt @@ -0,0 +1,20 @@ +int add(int a, int b) { return a + b; } +int sub(int a, int b) { return a - b; } +int mul(int a, int b) { return a * b; } +int div(int a, int b) { return a / b; } +int mod(int a, int b) { return a % b; } + +add(100, 50) +sub(100, 50) +mul(100, 50) +div(100, 50) +mod(100, 7) + +double sqrt(double x) { return Math.sqrt(x); } +double pow(double base, double exp) { return Math.pow(base, exp); } + +sqrt(144.0) +pow(2.0, 8.0) + +%methods +%quit diff --git a/repl/examples/fibonacci.txt b/repl/examples/fibonacci.txt new file mode 100644 index 0000000..79d4efd --- /dev/null +++ b/repl/examples/fibonacci.txt @@ -0,0 +1,28 @@ +int fib_recursive(int n) { +if (n <= 1) return n; +return fib_recursive(n - 1) + fib_recursive(n - 2); +} + +fib_recursive(0) +fib_recursive(1) +fib_recursive(5) +fib_recursive(10) +fib_recursive(15) + +int fib_iterative(int n) { +if (n <= 1) return n; +int a = 0; +int b = 1; +for (int i = 2; i <= n; i++) { +int temp = a + b; +a = b; +b = temp; +} +return b; +} + +fib_iterative(20) +fib_iterative(30) + +%methods +%quit diff --git a/repl/examples/geometry.txt b/repl/examples/geometry.txt new file mode 100644 index 0000000..425a230 --- /dev/null +++ b/repl/examples/geometry.txt @@ -0,0 +1,63 @@ +class Point { +public double x; +public double y; +public Point(double px, double py) { +this.x = px; +this.y = py; +} +public double distanceTo(Point other) { +double dx = this.x - other.x; +double dy = this.y - other.y; +return Math.sqrt(dx * dx + dy * dy); +} +} + +class Circle { +public Point center; +public double radius; +public Circle(Point c, double r) { +this.center = c; +this.radius = r; +} +public double area() { +return 3.14159 * this.radius * this.radius; +} +public double circumference() { +return 2.0 * 3.14159 * this.radius; +} +} + +class Rectangle { +public double width; +public double height; +public Rectangle(double w, double h) { +this.width = w; +this.height = h; +} +public double area() { +return this.width * this.height; +} +public double perimeter() { +return 2.0 * (this.width + this.height); +} +public double diagonal() { +return Math.sqrt(this.width * this.width + this.height * this.height); +} +} + +Point p1 = new Point(0.0, 0.0); +Point p2 = new Point(3.0, 4.0); +p1.distanceTo(p2) + +Circle c = new Circle(new Point(0.0, 0.0), 5.0); +c.area() +c.circumference() + +Rectangle r = new Rectangle(3.0, 4.0); +r.area() +r.perimeter() +r.diagonal() + +%classes +%whos +%quit diff --git a/repl/examples/primes.txt b/repl/examples/primes.txt new file mode 100644 index 0000000..908bcdf --- /dev/null +++ b/repl/examples/primes.txt @@ -0,0 +1,34 @@ +boolean isPrime(int n) { +if (n < 2) return false; +if (n == 2) return true; +if (n % 2 == 0) return false; +for (int i = 3; i * i <= n; i = i + 2) { +if (n % i == 0) return false; +} +return true; +} + +isPrime(2) +isPrime(17) +isPrime(100) +isPrime(997) + +for (int i = 2; i <= 50; i++) { +if (isPrime(i)) { +System.out.println(i); +} +} + +int countPrimes(int limit) { +int count = 0; +for (int i = 2; i <= limit; i++) { +if (isPrime(i)) count++; +} +return count; +} + +countPrimes(100) +countPrimes(1000) + +%methods +%quit diff --git a/repl/examples/sorting.txt b/repl/examples/sorting.txt new file mode 100644 index 0000000..fb481f3 --- /dev/null +++ b/repl/examples/sorting.txt @@ -0,0 +1,48 @@ +void printArray(int[] arr) { +for (int i = 0; i < arr.length; i++) { +System.out.print(arr[i]); +if (i < arr.length - 1) System.out.print(", "); +} +System.out.println(); +} + +void bubbleSort(int[] arr) { +int n = arr.length; +for (int i = 0; i < n - 1; i++) { +for (int j = 0; j < n - i - 1; j++) { +if (arr[j] > arr[j + 1]) { +int temp = arr[j]; +arr[j] = arr[j + 1]; +arr[j + 1] = temp; +} +} +} +} + +int[] arr1 = {64, 34, 25, 12, 22, 11, 90}; +printArray(arr1) +bubbleSort(arr1) +printArray(arr1) + +int findMin(int[] arr) { +int min = arr[0]; +for (int i = 1; i < arr.length; i++) { +if (arr[i] < min) min = arr[i]; +} +return min; +} + +int findMax(int[] arr) { +int max = arr[0]; +for (int i = 1; i < arr.length; i++) { +if (arr[i] > max) max = arr[i]; +} +return max; +} + +int[] nums = {3, 1, 4, 1, 5, 9, 2, 6}; +findMin(nums) +findMax(nums) + +%methods +%quit diff --git a/repl/examples/string_utils.txt b/repl/examples/string_utils.txt new file mode 100644 index 0000000..94e70e0 --- /dev/null +++ b/repl/examples/string_utils.txt @@ -0,0 +1,53 @@ +String reverse(String s) { +String result = ""; +for (int i = s.length() - 1; i >= 0; i--) { +result = result + s.charAt(i); +} +return result; +} + +reverse("hello") +reverse("Rava") + +boolean isPalindrome(String s) { +String lower = s.toLowerCase(); +int left = 0; +int right = lower.length() - 1; +while (left < right) { +if (lower.charAt(left) != lower.charAt(right)) { +return false; +} +left++; +right--; +} +return true; +} + +isPalindrome("radar") +isPalindrome("hello") +isPalindrome("Racecar") + +int countChar(String s, char c) { +int count = 0; +for (int i = 0; i < s.length(); i++) { +if (s.charAt(i) == c) count++; +} +return count; +} + +countChar("mississippi", 's') +countChar("hello world", 'l') + +String repeat(String s, int n) { +String result = ""; +for (int i = 0; i < n; i++) { +result = result + s; +} +return result; +} + +repeat("ab", 5) +repeat("*", 10) + +%methods +%quit diff --git a/repl/repl.c b/repl/repl.c new file mode 100644 index 0000000..686c3ec --- /dev/null +++ b/repl/repl.c @@ -0,0 +1,192 @@ +#define _POSIX_C_SOURCE 200809L +#include "repl.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile bool g_interrupted = false; + +static void sigint_handler(int sig) { + (void)sig; + g_interrupted = true; +} + +RavaREPL_t* rava_repl_create(void) { + RavaREPL_t *repl = calloc(1, sizeof(RavaREPL_t)); + if (!repl) return NULL; + + repl->session = rava_repl_session_create(); + if (!repl->session) { + free(repl); + return NULL; + } + + repl->input = rava_repl_input_create(); + if (!repl->input) { + rava_repl_session_destroy(repl->session); + free(repl); + return NULL; + } + + repl->executor = rava_repl_executor_create(repl->session); + if (!repl->executor) { + rava_repl_input_destroy(repl->input); + rava_repl_session_destroy(repl->session); + free(repl); + return NULL; + } + + repl->running = true; + repl->interactive = true; + + return repl; +} + +void rava_repl_destroy(RavaREPL_t *repl) { + if (!repl) return; + rava_repl_executor_destroy(repl->executor); + rava_repl_input_destroy(repl->input); + rava_repl_session_destroy(repl->session); + free(repl); +} + +void rava_repl_print_banner(void) { + printf("Rava %s Interactive Interpreter\n", RAVA_REPL_VERSION); + printf("Type \"%%help\" for commands, \"%%quit\" to exit\n\n"); +} + +void rava_repl_print_prompt(RavaREPL_t *repl, bool continuation) { + (void)repl; + printf("%s", continuation ? "... " : ">>> "); + fflush(stdout); +} + +bool rava_repl_process_line(RavaREPL_t *repl, const char *line) { + if (!repl || !line) return true; + + while (*line && isspace(*line)) line++; + if (strlen(line) == 0) return true; + + if (rava_repl_command_is_command(line)) { + char *arg = NULL; + RavaREPLCommand_e cmd = rava_repl_command_parse(line, &arg); + + if (cmd == RAVA_CMD_QUIT) { + free(arg); + return false; + } + + rava_repl_history_add(repl->session->history, line); + bool result = rava_repl_command_execute(repl->session, cmd, arg); + free(arg); + return result; + } + + RavaREPLInputState_e state = rava_repl_input_append(repl->input, line); + + if (state == RAVA_REPL_INPUT_COMPLETE) { + const char *code = rava_repl_input_get(repl->input); + rava_repl_history_add(repl->session->history, code); + rava_repl_executor_execute(repl->executor, code); + rava_repl_input_reset(repl->input); + } else if (state == RAVA_REPL_INPUT_ERROR) { + rava_repl_output_error("Input error", "Buffer overflow or invalid input"); + rava_repl_input_reset(repl->input); + } + + return true; +} + +static char* read_line_stdin(const char *prompt) { + printf("%s", prompt); + fflush(stdout); + + char *line = NULL; + size_t len = 0; + ssize_t read = getline(&line, &len, stdin); + + if (read == -1) { + free(line); + return NULL; + } + + if (read > 0 && line[read - 1] == '\n') { + line[read - 1] = '\0'; + } + + return line; +} + +int rava_repl_run(void) { + struct sigaction sa; + sa.sa_handler = sigint_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); + + RavaREPL_t *repl = rava_repl_create(); + if (!repl) { + fprintf(stderr, "Failed to create REPL\n"); + return 1; + } + + bool is_tty = isatty(fileno(stdin)); + + rava_repl_print_banner(); + + char history_file[256]; + const char *home = getenv("HOME"); + if (home && is_tty) { + snprintf(history_file, sizeof(history_file), "%s/.rava_history", home); + rava_repl_history_load(repl->session->history, history_file); + read_history(history_file); + } + + if (is_tty) { + rl_bind_key('\t', rl_complete); + } + + while (repl->running) { + bool continuation = !rava_repl_input_is_empty(repl->input); + const char *prompt = continuation ? "... " : ">>> "; + + char *line; + if (is_tty) { + line = readline(prompt); + } else { + line = read_line_stdin(prompt); + } + + if (!line) { + if (is_tty) printf("\n"); + break; + } + + if (strlen(line) == 0) { + free(line); + continue; + } + + if (is_tty && strlen(line) > 0) { + add_history(line); + } + + g_interrupted = false; + repl->running = rava_repl_process_line(repl, line); + + free(line); + } + + if (home && is_tty) { + rava_repl_history_save(repl->session->history, history_file); + write_history(history_file); + } + + rava_repl_destroy(repl); + return 0; +} diff --git a/repl/repl.h b/repl/repl.h new file mode 100644 index 0000000..ce83058 --- /dev/null +++ b/repl/repl.h @@ -0,0 +1,29 @@ +#ifndef RAVA_REPL_H +#define RAVA_REPL_H + +#include "repl_types.h" +#include "repl_session.h" +#include "repl_input.h" +#include "repl_executor.h" +#include "repl_commands.h" +#include "repl_output.h" +#include "repl_history.h" + +typedef struct { + RavaREPLSession_t *session; + RavaREPLInput_t *input; + RavaREPLExecutor_t *executor; + bool running; + bool interactive; +} RavaREPL_t; + +RavaREPL_t* rava_repl_create(void); +void rava_repl_destroy(RavaREPL_t *repl); + +int rava_repl_run(void); +bool rava_repl_process_line(RavaREPL_t *repl, const char *line); + +void rava_repl_print_banner(void); +void rava_repl_print_prompt(RavaREPL_t *repl, bool continuation); + +#endif diff --git a/repl/repl_commands.c b/repl/repl_commands.c new file mode 100644 index 0000000..11b5ffa --- /dev/null +++ b/repl/repl_commands.c @@ -0,0 +1,229 @@ +#define _POSIX_C_SOURCE 200809L +#include "repl_commands.h" +#include "repl_output.h" +#include +#include +#include +#include + +static RavaREPLCommandDef_t commands[] = { + {"%help", NULL, "Show help for commands", RAVA_CMD_HELP}, + {"%whos", NULL, "List all variables with types", RAVA_CMD_WHOS}, + {"%who", NULL, "List all variable names", RAVA_CMD_WHO}, + {"%reset", NULL, "Clear all session state", RAVA_CMD_RESET}, + {"%clear", NULL, "Clear screen", RAVA_CMD_CLEAR}, + {"%quit", "%exit", "Exit REPL", RAVA_CMD_QUIT}, + {"%history", NULL, "Show input history", RAVA_CMD_HISTORY}, + {"%run", NULL, "Run a Java file", RAVA_CMD_RUN}, + {"%load", NULL, "Load file contents", RAVA_CMD_LOAD}, + {"%save", NULL, "Save session to file", RAVA_CMD_SAVE}, + {"%debug", NULL, "Toggle debug mode", RAVA_CMD_DEBUG}, + {"%time", NULL, "Time single execution", RAVA_CMD_TIME}, + {"%timeit", NULL, "Time repeated executions", RAVA_CMD_TIMEIT}, + {"%memory", NULL, "Show memory usage", RAVA_CMD_MEMORY}, + {"%dir", NULL, "List object fields/methods", RAVA_CMD_DIR}, + {"%type", NULL, "Show variable type info", RAVA_CMD_TYPE}, + {"%source", NULL, "Show class source", RAVA_CMD_SOURCE}, + {"%methods", NULL, "List session methods", RAVA_CMD_METHODS}, + {"%classes", NULL, "List session classes", RAVA_CMD_CLASSES}, + {NULL, NULL, NULL, RAVA_CMD_UNKNOWN} +}; + +bool rava_repl_command_is_command(const char *input) { + if (!input) return false; + while (*input && isspace(*input)) input++; + return *input == '%'; +} + +RavaREPLCommand_e rava_repl_command_parse(const char *input, char **arg) { + if (!input) return RAVA_CMD_UNKNOWN; + while (*input && isspace(*input)) input++; + if (*input != '%') return RAVA_CMD_UNKNOWN; + + const char *end = input; + while (*end && !isspace(*end)) end++; + size_t cmd_len = end - input; + + while (*end && isspace(*end)) end++; + if (arg) { + *arg = (*end) ? strdup(end) : NULL; + } + + for (int i = 0; commands[i].name; i++) { + if (strlen(commands[i].name) == cmd_len && + strncmp(commands[i].name, input, cmd_len) == 0) { + return commands[i].command; + } + if (commands[i].alias && + strlen(commands[i].alias) == cmd_len && + strncmp(commands[i].alias, input, cmd_len) == 0) { + return commands[i].command; + } + } + return RAVA_CMD_UNKNOWN; +} + +bool rava_repl_command_execute(RavaREPLSession_t *session, RavaREPLCommand_e cmd, const char *arg) { + switch (cmd) { + case RAVA_CMD_HELP: + rava_repl_command_help(arg); + return true; + + case RAVA_CMD_WHOS: + rava_repl_output_variables(session); + return true; + + case RAVA_CMD_WHO: + if (session->variable_count == 0) { + printf("No variables defined.\n"); + } else { + for (size_t i = 0; i < session->variable_count; i++) { + printf("%s\n", session->variables[i].name); + } + } + return true; + + case RAVA_CMD_RESET: + rava_repl_session_clear_variables(session); + printf("Session reset. All variables cleared.\n"); + return true; + + case RAVA_CMD_CLEAR: + printf("\033[2J\033[H"); + return true; + + case RAVA_CMD_QUIT: + return false; + + case RAVA_CMD_HISTORY: { + int count = 20; + if (arg) { + count = atoi(arg); + if (count <= 0) count = 20; + } + size_t start = 0; + if (session->history->count > (size_t)count) { + start = session->history->count - count; + } + for (size_t i = start; i < session->history->count; i++) { + printf("[%zu] %s\n", i + 1, session->history->entries[i]); + } + return true; + } + + case RAVA_CMD_RUN: + if (!arg || strlen(arg) == 0) { + printf("Usage: %%run \n"); + } else { + printf("Loading %s...\n", arg); + FILE *f = fopen(arg, "r"); + if (!f) { + printf("Error: Cannot open file '%s'\n", arg); + } else { + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + char *content = malloc(size + 1); + if (content) { + size_t read_size = fread(content, 1, size, f); + content[read_size] = '\0'; + printf("Loaded %ld bytes.\n", size); + free(content); + } + fclose(f); + } + } + return true; + + case RAVA_CMD_DEBUG: + session->debug_mode = !session->debug_mode; + printf("Debug mode: %s\n", session->debug_mode ? "ON" : "OFF"); + return true; + + case RAVA_CMD_MEMORY: + printf("Memory usage information not available.\n"); + return true; + + case RAVA_CMD_METHODS: + rava_repl_output_methods(session); + return true; + + case RAVA_CMD_CLASSES: + rava_repl_output_classes(session); + return true; + + case RAVA_CMD_TYPE: + if (!arg || strlen(arg) == 0) { + printf("Usage: %%type \n"); + } else { + RavaREPLVariable_t *var = rava_repl_session_get_variable(session, arg); + if (var) { + printf("%s: %s\n", var->name, var->type_name ? var->type_name : "unknown"); + } else { + printf("Variable '%s' not found.\n", arg); + } + } + return true; + + case RAVA_CMD_SOURCE: + if (!arg || strlen(arg) == 0) { + printf("Usage: %%source \n"); + } else { + RavaREPLClass_t *cls = rava_repl_session_get_class(session, arg); + if (cls && cls->source) { + printf("%s\n", cls->source); + } else { + printf("Class '%s' not found.\n", arg); + } + } + return true; + + case RAVA_CMD_SAVE: + if (!arg || strlen(arg) == 0) { + printf("Usage: %%save \n"); + } else { + char *source = rava_repl_session_generate_source(session); + if (source) { + FILE *f = fopen(arg, "w"); + if (f) { + fprintf(f, "%s", source); + fclose(f); + printf("Session saved to %s\n", arg); + } else { + printf("Error: Cannot write to '%s'\n", arg); + } + free(source); + } + } + return true; + + default: + printf("Unknown command.\n"); + return true; + } +} + +void rava_repl_command_help(const char *topic) { + if (!topic || strlen(topic) == 0) { + printf("Available commands:\n\n"); + for (int i = 0; commands[i].name; i++) { + printf(" %-12s %s\n", commands[i].name, commands[i].description); + } + printf("\nType %%help for detailed help.\n"); + } else { + for (int i = 0; commands[i].name; i++) { + if (strcmp(commands[i].name + 1, topic) == 0 || + strcmp(commands[i].name, topic) == 0) { + printf("%s - %s\n", commands[i].name, commands[i].description); + return; + } + } + printf("Unknown command: %s\n", topic); + } +} + +void rava_repl_command_list_all(void) { + for (int i = 0; commands[i].name; i++) { + printf("%s\n", commands[i].name); + } +} diff --git a/repl/repl_commands.h b/repl/repl_commands.h new file mode 100644 index 0000000..4d282a4 --- /dev/null +++ b/repl/repl_commands.h @@ -0,0 +1,20 @@ +#ifndef RAVA_REPL_COMMANDS_H +#define RAVA_REPL_COMMANDS_H + +#include "repl_types.h" +#include "repl_session.h" + +typedef struct { + const char *name; + const char *alias; + const char *description; + RavaREPLCommand_e command; +} RavaREPLCommandDef_t; + +bool rava_repl_command_is_command(const char *input); +RavaREPLCommand_e rava_repl_command_parse(const char *input, char **arg); +bool rava_repl_command_execute(RavaREPLSession_t *session, RavaREPLCommand_e cmd, const char *arg); +void rava_repl_command_help(const char *topic); +void rava_repl_command_list_all(void); + +#endif diff --git a/repl/repl_executor.c b/repl/repl_executor.c new file mode 100644 index 0000000..6d7e9cc --- /dev/null +++ b/repl/repl_executor.c @@ -0,0 +1,565 @@ +#define _POSIX_C_SOURCE 200809L +#include "repl_executor.h" +#include "repl_input.h" +#include "repl_output.h" +#include +#include +#include +#include +#include + +RavaREPLExecutor_t* rava_repl_executor_create(RavaREPLSession_t *session) { + RavaREPLExecutor_t *executor = calloc(1, sizeof(RavaREPLExecutor_t)); + if (!executor) return NULL; + executor->session = session; + executor->timeout_ms = RAVA_REPL_TIMEOUT_MS; + executor->interrupted = false; + executor->show_timing = false; + return executor; +} + +void rava_repl_executor_destroy(RavaREPLExecutor_t *executor) { + free(executor); +} + +static char* extract_class_name(const char *code) { + const char *p = code; + while (*p && isspace(*p)) p++; + if (strncmp(p, "public", 6) == 0) { p += 6; while (*p && isspace(*p)) p++; } + if (strncmp(p, "class", 5) == 0) p += 5; + else if (strncmp(p, "interface", 9) == 0) p += 9; + else if (strncmp(p, "enum", 4) == 0) p += 4; + else return NULL; + while (*p && isspace(*p)) p++; + const char *start = p; + while (*p && (isalnum(*p) || *p == '_')) p++; + if (p == start) return NULL; + return strndup(start, p - start); +} + +static char* extract_method_name(const char *code) { + const char *paren = strchr(code, '('); + if (!paren) return NULL; + const char *end = paren - 1; + while (end > code && isspace(*end)) end--; + const char *start = end; + while (start > code && (isalnum(*(start-1)) || *(start-1) == '_')) start--; + if (start > end) return NULL; + return strndup(start, end - start + 1); +} + +static char* extract_method_return_type(const char *code) { + const char *p = code; + while (*p && isspace(*p)) p++; + if (strncmp(p, "public", 6) == 0) { p += 6; while (*p && isspace(*p)) p++; } + if (strncmp(p, "private", 7) == 0) { p += 7; while (*p && isspace(*p)) p++; } + if (strncmp(p, "static", 6) == 0) { p += 6; while (*p && isspace(*p)) p++; } + const char *start = p; + while (*p && (isalnum(*p) || *p == '_' || *p == '[' || *p == ']')) p++; + if (p == start) return NULL; + return strndup(start, p - start); +} + +static char* extract_var_name(const char *code) { + const char *p = code; + while (*p && isspace(*p)) p++; + while (*p && (isalnum(*p) || *p == '_' || *p == '[' || *p == ']')) p++; + while (*p && isspace(*p)) p++; + const char *start = p; + while (*p && (isalnum(*p) || *p == '_')) p++; + if (p == start) return NULL; + return strndup(start, p - start); +} + +static char* extract_var_type(const char *code) { + const char *p = code; + while (*p && isspace(*p)) p++; + const char *start = p; + while (*p && (isalnum(*p) || *p == '_' || *p == '[' || *p == ']')) p++; + if (p == start) return NULL; + return strndup(start, p - start); +} + +static char* extract_var_initializer(const char *code) { + const char *eq = strchr(code, '='); + if (!eq) return NULL; + eq++; + while (*eq && isspace(*eq)) eq++; + const char *end = eq; + while (*end && *end != ';') end++; + if (end == eq) return NULL; + while (end > eq && isspace(*(end-1))) end--; + return strndup(eq, end - eq); +} + +static char* generate_full_source(RavaREPLSession_t *session, const char *eval_code) { + size_t capacity = 16384; + char *source = malloc(capacity); + if (!source) return NULL; + size_t len = 0; + source[0] = '\0'; + +#define APPEND(...) do { \ + int needed = snprintf(source + len, capacity - len, __VA_ARGS__); \ + if (needed < 0) { free(source); return NULL; } \ + while ((size_t)needed >= capacity - len) { \ + capacity *= 2; \ + char *tmp = realloc(source, capacity); \ + if (!tmp) { free(source); return NULL; } \ + source = tmp; \ + needed = snprintf(source + len, capacity - len, __VA_ARGS__); \ + } \ + len += needed; \ +} while(0) + + for (size_t i = 0; i < session->class_count; i++) { + if (session->classes[i].source) { + APPEND("%s\n\n", session->classes[i].source); + } + } + + APPEND("public class _REPL_ {\n"); + + for (size_t i = 0; i < session->variable_count; i++) { + RavaREPLVariable_t *var = &session->variables[i]; + const char *type = var->type_name ? var->type_name : "int"; + APPEND(" public static %s %s", type, var->name); + if (var->is_initialized) { + if (var->type == RAVA_VAL_OBJECT && var->initializer) { + APPEND(" = %s", var->initializer); + } else { + switch (var->type) { + case RAVA_VAL_INT: + case RAVA_VAL_LONG: + APPEND(" = %ld", var->value.data.int_val); + break; + case RAVA_VAL_DOUBLE: + APPEND(" = %g", var->value.data.double_val); + break; + case RAVA_VAL_BOOLEAN: + APPEND(" = %s", var->value.data.bool_val ? "true" : "false"); + break; + case RAVA_VAL_STRING: + if (var->value.data.string_val) { + APPEND(" = \"%s\"", var->value.data.string_val); + } + break; + default: + if (var->initializer) { + APPEND(" = %s", var->initializer); + } + break; + } + } + } + APPEND(";\n"); + } + + for (size_t i = 0; i < session->method_count; i++) { + if (session->methods[i].source) { + APPEND("\n public static %s\n", session->methods[i].source); + } + } + + APPEND("\n public static int _eval_() {\n"); + if (eval_code && strlen(eval_code) > 0) { + APPEND(" %s\n", eval_code); + } + APPEND(" return 0;\n"); + APPEND(" }\n"); + APPEND("}\n"); + +#undef APPEND + + return source; +} + +static char* generate_expression_source(RavaREPLSession_t *session, const char *expr) { + size_t capacity = 16384; + char *source = malloc(capacity); + if (!source) return NULL; + size_t len = 0; + source[0] = '\0'; + +#define APPEND(...) do { \ + int needed = snprintf(source + len, capacity - len, __VA_ARGS__); \ + if (needed < 0) { free(source); return NULL; } \ + while ((size_t)needed >= capacity - len) { \ + capacity *= 2; \ + char *tmp = realloc(source, capacity); \ + if (!tmp) { free(source); return NULL; } \ + source = tmp; \ + needed = snprintf(source + len, capacity - len, __VA_ARGS__); \ + } \ + len += needed; \ +} while(0) + + for (size_t i = 0; i < session->class_count; i++) { + if (session->classes[i].source) { + APPEND("%s\n\n", session->classes[i].source); + } + } + + APPEND("public class _REPL_ {\n"); + + for (size_t i = 0; i < session->variable_count; i++) { + RavaREPLVariable_t *var = &session->variables[i]; + const char *type = var->type_name ? var->type_name : "int"; + APPEND(" public static %s %s", type, var->name); + if (var->is_initialized) { + if (var->type == RAVA_VAL_OBJECT && var->initializer) { + APPEND(" = %s", var->initializer); + } else { + switch (var->type) { + case RAVA_VAL_INT: + case RAVA_VAL_LONG: + APPEND(" = %ld", var->value.data.int_val); + break; + case RAVA_VAL_DOUBLE: + APPEND(" = %g", var->value.data.double_val); + break; + case RAVA_VAL_BOOLEAN: + APPEND(" = %s", var->value.data.bool_val ? "true" : "false"); + break; + case RAVA_VAL_STRING: + if (var->value.data.string_val) { + APPEND(" = \"%s\"", var->value.data.string_val); + } + break; + default: + if (var->initializer) { + APPEND(" = %s", var->initializer); + } + break; + } + } + } + APPEND(";\n"); + } + + for (size_t i = 0; i < session->method_count; i++) { + if (session->methods[i].source) { + APPEND("\n public static %s\n", session->methods[i].source); + } + } + + size_t expr_len = strlen(expr); + while (expr_len > 0 && (expr[expr_len-1] == ';' || isspace(expr[expr_len-1]))) expr_len--; + char *clean_expr = strndup(expr, expr_len); + + char *qualified_expr = malloc(strlen(clean_expr) * 10 + 1024); + if (!qualified_expr) { + free(clean_expr); + free(source); + return NULL; + } + strcpy(qualified_expr, clean_expr); + + for (size_t i = 0; i < session->variable_count; i++) { + const char *var_name = session->variables[i].name; + size_t var_len = strlen(var_name); + char *qualified = malloc(var_len + 8); + snprintf(qualified, var_len + 8, "_REPL_.%s", var_name); + + char *new_expr = malloc(strlen(qualified_expr) * 10 + 1024); + char *dst = new_expr; + char *src = qualified_expr; + + while (*src) { + if (strncmp(src, var_name, var_len) == 0) { + char before = (src == qualified_expr) ? ' ' : *(src - 1); + char after = *(src + var_len); + if (!isalnum(before) && before != '_' && before != '.' && + !isalnum(after) && after != '_') { + strcpy(dst, qualified); + dst += strlen(qualified); + src += var_len; + continue; + } + } + *dst++ = *src++; + } + *dst = '\0'; + free(qualified_expr); + qualified_expr = new_expr; + free(qualified); + } + + APPEND("\n public static int _eval_() {\n"); + APPEND(" System.out.println(%s);\n", qualified_expr); + APPEND(" return 0;\n"); + APPEND(" }\n"); + APPEND("}\n"); + + free(clean_expr); + free(qualified_expr); + +#undef APPEND + + return source; +} + +static RavaREPLExecResult_e compile_and_run(RavaREPLExecutor_t *executor, const char *source) { + RavaREPLSession_t *session = executor->session; + + if (session->debug_mode) { + fprintf(stderr, "[DEBUG] Compiling:\n%s\n", source); + } + + RavaLexer_t *lexer = rava_lexer_create(source); + RavaParser_t *parser = rava_parser_create(lexer); + RavaASTNode_t *ast = rava_parser_parse(parser); + + if (!ast || parser->had_error) { + rava_repl_output_error("Parse error", parser->error_message); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + return RAVA_REPL_EXEC_PARSE_ERROR; + } + + RavaSemanticAnalyzer_t *analyzer = rava_semantic_analyzer_create(); + if (!rava_semantic_analyze(analyzer, ast)) { + rava_repl_output_error("Semantic error", analyzer->error_message); + rava_ast_node_destroy(ast); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + rava_semantic_analyzer_destroy(analyzer); + return RAVA_REPL_EXEC_SEMANTIC_ERROR; + } + + RavaIRGenerator_t *ir_gen = rava_ir_generator_create(analyzer); + RavaProgram_t *program = rava_ir_generate(ir_gen, ast); + + if (!program) { + rava_repl_output_error("IR error", "Failed to generate bytecode"); + rava_ast_node_destroy(ast); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + rava_semantic_analyzer_destroy(analyzer); + rava_ir_generator_destroy(ir_gen); + return RAVA_REPL_EXEC_RUNTIME_ERROR; + } + + RavaVM_t *vm = rava_vm_create(program); + + struct timespec start_time, end_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + bool success = rava_vm_execute(vm, "_REPL_", "_eval_"); + + clock_gettime(CLOCK_MONOTONIC, &end_time); + double elapsed_ms = (end_time.tv_sec - start_time.tv_sec) * 1000.0 + + (end_time.tv_nsec - start_time.tv_nsec) / 1000000.0; + + if (!success && vm->error_message) { + rava_repl_output_error("Runtime error", vm->error_message); + } + + if (executor->show_timing || elapsed_ms >= 100.0) { + rava_repl_output_timing(elapsed_ms); + } + + rava_vm_destroy(vm); + rava_program_destroy(program); + rava_ast_node_destroy(ast); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + rava_semantic_analyzer_destroy(analyzer); + rava_ir_generator_destroy(ir_gen); + + return success ? RAVA_REPL_EXEC_SUCCESS : RAVA_REPL_EXEC_RUNTIME_ERROR; +} + +static RavaValueType_e type_from_string(const char *type_name) { + if (!type_name) return RAVA_VAL_NULL; + if (strcmp(type_name, "int") == 0) return RAVA_VAL_INT; + if (strcmp(type_name, "long") == 0) return RAVA_VAL_LONG; + if (strcmp(type_name, "double") == 0) return RAVA_VAL_DOUBLE; + if (strcmp(type_name, "float") == 0) return RAVA_VAL_FLOAT; + if (strcmp(type_name, "boolean") == 0) return RAVA_VAL_BOOLEAN; + if (strcmp(type_name, "char") == 0) return RAVA_VAL_CHAR; + if (strcmp(type_name, "String") == 0) return RAVA_VAL_STRING; + return RAVA_VAL_OBJECT; +} + +static RavaValue_t parse_literal_value(const char *init, RavaValueType_e type) { + RavaValue_t value = rava_value_null(); + if (!init) return value; + + while (*init && isspace(*init)) init++; + + switch (type) { + case RAVA_VAL_INT: + case RAVA_VAL_LONG: + value.type = type; + value.data.int_val = strtol(init, NULL, 10); + break; + case RAVA_VAL_DOUBLE: + case RAVA_VAL_FLOAT: + value.type = type; + value.data.double_val = strtod(init, NULL); + break; + case RAVA_VAL_BOOLEAN: + value.type = RAVA_VAL_BOOLEAN; + value.data.bool_val = (strncmp(init, "true", 4) == 0); + break; + case RAVA_VAL_STRING: + value.type = RAVA_VAL_STRING; + if (init[0] == '"') { + const char *start = init + 1; + const char *end = strchr(start, '"'); + if (end) { + value.data.string_val = strndup(start, end - start); + } + } + break; + default: + break; + } + return value; +} + +RavaREPLExecResult_e rava_repl_executor_execute_expression(RavaREPLExecutor_t *executor, const char *code) { + char *source = generate_expression_source(executor->session, code); + if (!source) return RAVA_REPL_EXEC_RUNTIME_ERROR; + + RavaREPLExecResult_e result = compile_and_run(executor, source); + free(source); + return result; +} + +RavaREPLExecResult_e rava_repl_executor_execute_statement(RavaREPLExecutor_t *executor, const char *code) { + char *source = generate_full_source(executor->session, code); + if (!source) return RAVA_REPL_EXEC_RUNTIME_ERROR; + + RavaREPLExecResult_e result = compile_and_run(executor, source); + free(source); + return result; +} + +RavaREPLExecResult_e rava_repl_executor_execute_var_decl(RavaREPLExecutor_t *executor, const char *code) { + char *var_name = extract_var_name(code); + char *var_type = extract_var_type(code); + char *var_init = extract_var_initializer(code); + + if (!var_name || !var_type) { + free(var_name); + free(var_type); + free(var_init); + return rava_repl_executor_execute_statement(executor, code); + } + + RavaValueType_e type = type_from_string(var_type); + RavaValue_t value = rava_value_null(); + + if (var_init && type != RAVA_VAL_OBJECT) { + value = parse_literal_value(var_init, type); + value.type = type; + } else { + value.type = type; + value.data.int_val = 0; + } + + rava_repl_session_add_variable(executor->session, var_name, var_type, var_init, type, value); + + char *source = generate_full_source(executor->session, NULL); + if (source) { + RavaREPLExecResult_e result = compile_and_run(executor, source); + free(source); + if (result != RAVA_REPL_EXEC_SUCCESS) { + free(var_name); + free(var_type); + free(var_init); + return result; + } + } + + free(var_name); + free(var_type); + free(var_init); + return RAVA_REPL_EXEC_SUCCESS; +} + +RavaREPLExecResult_e rava_repl_executor_execute_method_decl(RavaREPLExecutor_t *executor, const char *code) { + char *method_name = extract_method_name(code); + char *return_type = extract_method_return_type(code); + + if (!method_name) { + rava_repl_output_error("Parse error", "Could not extract method name"); + free(return_type); + return RAVA_REPL_EXEC_PARSE_ERROR; + } + + rava_repl_session_add_method(executor->session, method_name, return_type ? return_type : "void", code); + + char *source = generate_full_source(executor->session, NULL); + if (!source) { + free(method_name); + free(return_type); + return RAVA_REPL_EXEC_RUNTIME_ERROR; + } + + RavaREPLExecResult_e result = compile_and_run(executor, source); + free(source); + + if (result == RAVA_REPL_EXEC_SUCCESS) { + printf("Method '%s' defined.\n", method_name); + } + + free(method_name); + free(return_type); + return result; +} + +RavaREPLExecResult_e rava_repl_executor_execute_class_decl(RavaREPLExecutor_t *executor, const char *code) { + char *class_name = extract_class_name(code); + if (!class_name) { + rava_repl_output_error("Parse error", "Could not extract class name"); + return RAVA_REPL_EXEC_PARSE_ERROR; + } + + rava_repl_session_add_class(executor->session, class_name, code); + + char *source = generate_full_source(executor->session, NULL); + if (!source) { + free(class_name); + return RAVA_REPL_EXEC_RUNTIME_ERROR; + } + + RavaREPLExecResult_e result = compile_and_run(executor, source); + free(source); + + if (result == RAVA_REPL_EXEC_SUCCESS) { + printf("Class '%s' defined.\n", class_name); + } + + free(class_name); + return result; +} + +RavaREPLExecResult_e rava_repl_executor_execute(RavaREPLExecutor_t *executor, const char *code) { + RavaREPLCodeType_e type = rava_repl_input_detect_type(code); + + switch (type) { + case RAVA_REPL_CODE_EXPRESSION: + return rava_repl_executor_execute_expression(executor, code); + case RAVA_REPL_CODE_STATEMENT: + return rava_repl_executor_execute_statement(executor, code); + case RAVA_REPL_CODE_VAR_DECL: + return rava_repl_executor_execute_var_decl(executor, code); + case RAVA_REPL_CODE_METHOD_DECL: + return rava_repl_executor_execute_method_decl(executor, code); + case RAVA_REPL_CODE_CLASS_DECL: + return rava_repl_executor_execute_class_decl(executor, code); + default: + return rava_repl_executor_execute_expression(executor, code); + } +} + +RavaValue_t rava_repl_executor_get_result(RavaREPLExecutor_t *executor) { + return executor->last_result; +} + +void rava_repl_executor_interrupt(RavaREPLExecutor_t *executor) { + executor->interrupted = true; +} diff --git a/repl/repl_executor.h b/repl/repl_executor.h new file mode 100644 index 0000000..d820d2f --- /dev/null +++ b/repl/repl_executor.h @@ -0,0 +1,28 @@ +#ifndef RAVA_REPL_EXECUTOR_H +#define RAVA_REPL_EXECUTOR_H + +#include "repl_types.h" +#include "repl_session.h" + +typedef struct { + RavaREPLSession_t *session; + int timeout_ms; + volatile bool interrupted; + RavaValue_t last_result; + bool show_timing; +} RavaREPLExecutor_t; + +RavaREPLExecutor_t* rava_repl_executor_create(RavaREPLSession_t *session); +void rava_repl_executor_destroy(RavaREPLExecutor_t *executor); + +RavaREPLExecResult_e rava_repl_executor_execute(RavaREPLExecutor_t *executor, const char *code); +RavaREPLExecResult_e rava_repl_executor_execute_expression(RavaREPLExecutor_t *executor, const char *code); +RavaREPLExecResult_e rava_repl_executor_execute_statement(RavaREPLExecutor_t *executor, const char *code); +RavaREPLExecResult_e rava_repl_executor_execute_var_decl(RavaREPLExecutor_t *executor, const char *code); +RavaREPLExecResult_e rava_repl_executor_execute_method_decl(RavaREPLExecutor_t *executor, const char *code); +RavaREPLExecResult_e rava_repl_executor_execute_class_decl(RavaREPLExecutor_t *executor, const char *code); + +RavaValue_t rava_repl_executor_get_result(RavaREPLExecutor_t *executor); +void rava_repl_executor_interrupt(RavaREPLExecutor_t *executor); + +#endif diff --git a/repl/repl_history.c b/repl/repl_history.c new file mode 100644 index 0000000..8616a08 --- /dev/null +++ b/repl/repl_history.c @@ -0,0 +1,102 @@ +#include "repl_history.h" +#include +#include +#include +#include + +RavaREPLHistory_t* rava_repl_history_create(size_t max_size) { + RavaREPLHistory_t *history = malloc(sizeof(RavaREPLHistory_t)); + if (!history) return NULL; + history->capacity = max_size; + history->entries = calloc(max_size, sizeof(char*)); + if (!history->entries) { + free(history); + return NULL; + } + history->count = 0; + history->position = 0; + return history; +} + +void rava_repl_history_destroy(RavaREPLHistory_t *history) { + if (!history) return; + for (size_t i = 0; i < history->count; i++) { + free(history->entries[i]); + } + free(history->entries); + free(history); +} + +void rava_repl_history_add(RavaREPLHistory_t *history, const char *entry) { + if (!history || !entry || strlen(entry) == 0) return; + if (history->count > 0) { + if (strcmp(history->entries[history->count - 1], entry) == 0) { + return; + } + } + if (history->count >= history->capacity) { + free(history->entries[0]); + memmove(history->entries, history->entries + 1, (history->capacity - 1) * sizeof(char*)); + history->count--; + } + history->entries[history->count++] = strdup(entry); + history->position = history->count; + add_history(entry); +} + +const char* rava_repl_history_get(RavaREPLHistory_t *history, size_t index) { + if (!history || index >= history->count) return NULL; + return history->entries[index]; +} + +const char* rava_repl_history_prev(RavaREPLHistory_t *history) { + if (!history || history->count == 0) return NULL; + if (history->position > 0) { + history->position--; + } + return history->entries[history->position]; +} + +const char* rava_repl_history_next(RavaREPLHistory_t *history) { + if (!history || history->count == 0) return NULL; + if (history->position < history->count - 1) { + history->position++; + return history->entries[history->position]; + } + history->position = history->count; + return NULL; +} + +void rava_repl_history_reset_position(RavaREPLHistory_t *history) { + if (!history) return; + history->position = history->count; +} + +bool rava_repl_history_save(RavaREPLHistory_t *history, const char *filename) { + if (!history || !filename) return false; + FILE *f = fopen(filename, "w"); + if (!f) return false; + for (size_t i = 0; i < history->count; i++) { + fprintf(f, "%s\n", history->entries[i]); + } + fclose(f); + return true; +} + +bool rava_repl_history_load(RavaREPLHistory_t *history, const char *filename) { + if (!history || !filename) return false; + FILE *f = fopen(filename, "r"); + if (!f) return false; + char line[RAVA_REPL_MAX_INPUT]; + while (fgets(line, sizeof(line), f)) { + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + if (strlen(line) > 0) { + rava_repl_history_add(history, line); + } + } + fclose(f); + return true; +} diff --git a/repl/repl_history.h b/repl/repl_history.h new file mode 100644 index 0000000..cd88449 --- /dev/null +++ b/repl/repl_history.h @@ -0,0 +1,23 @@ +#ifndef RAVA_REPL_HISTORY_H +#define RAVA_REPL_HISTORY_H + +#include "repl_types.h" + +typedef struct { + char **entries; + size_t count; + size_t capacity; + size_t position; +} RavaREPLHistory_t; + +RavaREPLHistory_t* rava_repl_history_create(size_t max_size); +void rava_repl_history_destroy(RavaREPLHistory_t *history); +void rava_repl_history_add(RavaREPLHistory_t *history, const char *entry); +const char* rava_repl_history_get(RavaREPLHistory_t *history, size_t index); +const char* rava_repl_history_prev(RavaREPLHistory_t *history); +const char* rava_repl_history_next(RavaREPLHistory_t *history); +void rava_repl_history_reset_position(RavaREPLHistory_t *history); +bool rava_repl_history_save(RavaREPLHistory_t *history, const char *filename); +bool rava_repl_history_load(RavaREPLHistory_t *history, const char *filename); + +#endif diff --git a/repl/repl_input.c b/repl/repl_input.c new file mode 100644 index 0000000..83bc6cb --- /dev/null +++ b/repl/repl_input.c @@ -0,0 +1,250 @@ +#include "repl_input.h" +#include +#include +#include + +RavaREPLInput_t* rava_repl_input_create(void) { + RavaREPLInput_t *input = malloc(sizeof(RavaREPLInput_t)); + if (!input) return NULL; + input->capacity = RAVA_REPL_MAX_INPUT; + input->buffer = malloc(input->capacity); + if (!input->buffer) { + free(input); + return NULL; + } + input->buffer[0] = '\0'; + input->size = 0; + input->brace_depth = 0; + input->bracket_depth = 0; + input->paren_depth = 0; + input->in_string = false; + input->string_delimiter = 0; + input->line_count = 0; + return input; +} + +void rava_repl_input_destroy(RavaREPLInput_t *input) { + if (!input) return; + free(input->buffer); + free(input); +} + +void rava_repl_input_reset(RavaREPLInput_t *input) { + if (!input) return; + input->buffer[0] = '\0'; + input->size = 0; + input->brace_depth = 0; + input->bracket_depth = 0; + input->paren_depth = 0; + input->in_string = false; + input->string_delimiter = 0; + input->line_count = 0; +} + +static void update_nesting(RavaREPLInput_t *input, const char *line) { + for (const char *p = line; *p; p++) { + if (input->in_string) { + if (*p == '\\' && *(p + 1)) { + p++; + continue; + } + if (*p == input->string_delimiter) { + input->in_string = false; + } + continue; + } + switch (*p) { + case '"': + case '\'': + input->in_string = true; + input->string_delimiter = *p; + break; + case '{': + input->brace_depth++; + break; + case '}': + input->brace_depth--; + break; + case '[': + input->bracket_depth++; + break; + case ']': + input->bracket_depth--; + break; + case '(': + input->paren_depth++; + break; + case ')': + input->paren_depth--; + break; + case '/': + if (*(p + 1) == '/') { + return; + } + break; + } + } +} + +static bool ends_with_operator(const char *line) { + size_t len = strlen(line); + while (len > 0 && isspace(line[len - 1])) len--; + if (len == 0) return false; + char last = line[len - 1]; + if (last == '+' || last == '-' || last == '*' || last == '/' || last == '%' || + last == '&' || last == '|' || last == '^' || last == '=' || last == '<' || + last == '>' || last == '!' || last == '?' || last == ':' || last == ',') { + return true; + } + return false; +} + +RavaREPLInputState_e rava_repl_input_append(RavaREPLInput_t *input, const char *line) { + if (!input || !line) return RAVA_REPL_INPUT_ERROR; + size_t line_len = strlen(line); + if (input->size + line_len + 2 > input->capacity) { + return RAVA_REPL_INPUT_ERROR; + } + if (input->size > 0) { + input->buffer[input->size++] = '\n'; + } + memcpy(input->buffer + input->size, line, line_len); + input->size += line_len; + input->buffer[input->size] = '\0'; + input->line_count++; + update_nesting(input, line); + if (input->in_string) return RAVA_REPL_INPUT_CONTINUE; + if (input->brace_depth > 0) return RAVA_REPL_INPUT_CONTINUE; + if (input->bracket_depth > 0) return RAVA_REPL_INPUT_CONTINUE; + if (input->paren_depth > 0) return RAVA_REPL_INPUT_CONTINUE; + if (ends_with_operator(input->buffer)) return RAVA_REPL_INPUT_CONTINUE; + return RAVA_REPL_INPUT_COMPLETE; +} + +const char* rava_repl_input_get(RavaREPLInput_t *input) { + if (!input) return NULL; + return input->buffer; +} + +bool rava_repl_input_is_empty(RavaREPLInput_t *input) { + if (!input) return true; + return input->size == 0; +} + +static bool starts_with_keyword(const char *code, const char *keyword) { + size_t len = strlen(keyword); + if (strncmp(code, keyword, len) != 0) return false; + char next = code[len]; + return next == ' ' || next == '\t' || next == '\n' || next == '(' || next == '{' || next == '['; +} + +RavaREPLCodeType_e rava_repl_input_detect_type(const char *code) { + while (*code && isspace(*code)) code++; + if (starts_with_keyword(code, "public") || + starts_with_keyword(code, "private") || + starts_with_keyword(code, "protected")) { + const char *p = code; + while (*p && !isspace(*p)) p++; + while (*p && isspace(*p)) p++; + if (starts_with_keyword(p, "class")) { + return RAVA_REPL_CODE_CLASS_DECL; + } + if (starts_with_keyword(p, "static")) { + p += 6; + while (*p && isspace(*p)) p++; + } + bool has_paren = false; + const char *scan = p; + while (*scan && *scan != ';' && *scan != '{') { + if (*scan == '(') { + has_paren = true; + break; + } + scan++; + } + if (has_paren) { + return RAVA_REPL_CODE_METHOD_DECL; + } + return RAVA_REPL_CODE_VAR_DECL; + } + if (starts_with_keyword(code, "class")) { + return RAVA_REPL_CODE_CLASS_DECL; + } + if (starts_with_keyword(code, "interface")) { + return RAVA_REPL_CODE_CLASS_DECL; + } + if (starts_with_keyword(code, "enum")) { + return RAVA_REPL_CODE_CLASS_DECL; + } + if (starts_with_keyword(code, "int") || + starts_with_keyword(code, "long") || + starts_with_keyword(code, "double") || + starts_with_keyword(code, "float") || + starts_with_keyword(code, "boolean") || + starts_with_keyword(code, "char") || + starts_with_keyword(code, "byte") || + starts_with_keyword(code, "short") || + starts_with_keyword(code, "String") || + starts_with_keyword(code, "void")) { + const char *p = code; + while (*p && !isspace(*p) && *p != '[') p++; + if (*p == '[') { + while (*p == '[' || *p == ']' || isspace(*p)) p++; + } + while (*p && isspace(*p)) p++; + while (*p && (isalnum(*p) || *p == '_')) p++; + while (*p && isspace(*p)) p++; + if (*p == '[') { + while (*p == '[' || *p == ']' || isspace(*p)) p++; + } + if (*p == '(') { + return RAVA_REPL_CODE_METHOD_DECL; + } + if (*p == '=' || *p == ';' || *p == ',') { + return RAVA_REPL_CODE_VAR_DECL; + } + } + if (starts_with_keyword(code, "if") || + starts_with_keyword(code, "while") || + starts_with_keyword(code, "for") || + starts_with_keyword(code, "do") || + starts_with_keyword(code, "switch") || + starts_with_keyword(code, "try") || + starts_with_keyword(code, "return") || + starts_with_keyword(code, "break") || + starts_with_keyword(code, "continue") || + starts_with_keyword(code, "throw")) { + return RAVA_REPL_CODE_STATEMENT; + } + const char *p = code; + if (isupper(*p)) { + while (*p && (isalnum(*p) || *p == '_')) p++; + if (*p == '[') { + while (*p == '[' || *p == ']' || isspace(*p)) p++; + } + while (*p && isspace(*p)) p++; + if (islower(*p) || *p == '_') { + const char *var_start = p; + while (*p && (isalnum(*p) || *p == '_')) p++; + if (p > var_start) { + while (*p && isspace(*p)) p++; + if (*p == '=' || *p == ';') { + return RAVA_REPL_CODE_VAR_DECL; + } + } + } + } + + p = code; + bool has_semicolon = false; + bool has_assign = false; + while (*p) { + if (*p == ';') has_semicolon = true; + if (*p == '=' && *(p+1) != '=') has_assign = true; + p++; + } + if (has_assign && has_semicolon) { + return RAVA_REPL_CODE_STATEMENT; + } + return RAVA_REPL_CODE_EXPRESSION; +} diff --git a/repl/repl_input.h b/repl/repl_input.h new file mode 100644 index 0000000..a5781c3 --- /dev/null +++ b/repl/repl_input.h @@ -0,0 +1,26 @@ +#ifndef RAVA_REPL_INPUT_H +#define RAVA_REPL_INPUT_H + +#include "repl_types.h" + +typedef struct { + char *buffer; + size_t size; + size_t capacity; + int brace_depth; + int bracket_depth; + int paren_depth; + bool in_string; + char string_delimiter; + int line_count; +} RavaREPLInput_t; + +RavaREPLInput_t* rava_repl_input_create(void); +void rava_repl_input_destroy(RavaREPLInput_t *input); +void rava_repl_input_reset(RavaREPLInput_t *input); +RavaREPLInputState_e rava_repl_input_append(RavaREPLInput_t *input, const char *line); +const char* rava_repl_input_get(RavaREPLInput_t *input); +bool rava_repl_input_is_empty(RavaREPLInput_t *input); +RavaREPLCodeType_e rava_repl_input_detect_type(const char *code); + +#endif diff --git a/repl/repl_output.c b/repl/repl_output.c new file mode 100644 index 0000000..90a1fc3 --- /dev/null +++ b/repl/repl_output.c @@ -0,0 +1,196 @@ +#include "repl_output.h" +#include +#include + +static const char* type_name(RavaValueType_e type) { + switch (type) { + case RAVA_VAL_INT: return "int"; + case RAVA_VAL_LONG: return "long"; + case RAVA_VAL_FLOAT: return "float"; + case RAVA_VAL_DOUBLE: return "double"; + case RAVA_VAL_BOOLEAN: return "boolean"; + case RAVA_VAL_CHAR: return "char"; + case RAVA_VAL_NULL: return "null"; + case RAVA_VAL_OBJECT: return "Object"; + case RAVA_VAL_ARRAY: return "Array"; + case RAVA_VAL_STRING: return "String"; + case RAVA_VAL_ARRAYLIST: return "ArrayList"; + case RAVA_VAL_HASHMAP: return "HashMap"; + case RAVA_VAL_SOCKET: return "Socket"; + case RAVA_VAL_STREAM: return "Stream"; + case RAVA_VAL_METHOD_REF: return "MethodRef"; + default: return "unknown"; + } +} + +void rava_repl_output_value(RavaValue_t value, RavaVM_t *vm) { + (void)vm; + switch (value.type) { + case RAVA_VAL_INT: + printf("%ld\n", value.data.int_val); + break; + case RAVA_VAL_LONG: + printf("%ldL\n", value.data.long_val); + break; + case RAVA_VAL_FLOAT: + printf("%gf\n", value.data.float_val); + break; + case RAVA_VAL_DOUBLE: + printf("%g\n", value.data.double_val); + break; + case RAVA_VAL_BOOLEAN: + printf("%s\n", value.data.bool_val ? "true" : "false"); + break; + case RAVA_VAL_CHAR: + printf("'%c'\n", value.data.char_val); + break; + case RAVA_VAL_STRING: + if (value.data.string_val) { + printf("\"%s\"\n", value.data.string_val); + } else { + printf("null\n"); + } + break; + case RAVA_VAL_OBJECT: + if (value.data.object_val) { + RavaObject_t *obj = value.data.object_val; + printf("%s@%p\n", obj->class_name ? obj->class_name : "Object", (void*)obj); + } else { + printf("null\n"); + } + break; + case RAVA_VAL_ARRAY: + if (value.data.array_val) { + RavaArray_t *arr = value.data.array_val; + printf("[%s array]: size=%zu\n", type_name(arr->element_type), arr->length); + } else { + printf("null\n"); + } + break; + case RAVA_VAL_ARRAYLIST: + if (value.data.arraylist_val) { + printf("[ArrayList]: size=%zu\n", rava_arraylist_size(value.data.arraylist_val)); + } else { + printf("null\n"); + } + break; + case RAVA_VAL_HASHMAP: + if (value.data.hashmap_val) { + printf("[HashMap]: size=%zu\n", rava_hashmap_size(value.data.hashmap_val)); + } else { + printf("null\n"); + } + break; + case RAVA_VAL_NULL: + printf("null\n"); + break; + default: + printf("<%s>\n", type_name(value.type)); + break; + } +} + +void rava_repl_output_variable(RavaREPLVariable_t *var) { + if (!var) return; + printf("%-20s %-15s ", var->name, var->type_name ? var->type_name : "?"); + if (!var->is_initialized) { + printf(""); + } else { + switch (var->type) { + case RAVA_VAL_INT: + printf("%ld", var->value.data.int_val); + break; + case RAVA_VAL_LONG: + printf("%ldL", var->value.data.long_val); + break; + case RAVA_VAL_FLOAT: + printf("%gf", var->value.data.float_val); + break; + case RAVA_VAL_DOUBLE: + printf("%g", var->value.data.double_val); + break; + case RAVA_VAL_BOOLEAN: + printf("%s", var->value.data.bool_val ? "true" : "false"); + break; + case RAVA_VAL_STRING: + if (var->value.data.string_val) { + printf("\"%s\"", var->value.data.string_val); + } else { + printf("null"); + } + break; + case RAVA_VAL_OBJECT: + if (var->value.data.object_val) { + printf("%s@%p", var->type_name ? var->type_name : "Object", + (void*)var->value.data.object_val); + } else { + printf("null"); + } + break; + case RAVA_VAL_ARRAY: + if (var->value.data.array_val) { + printf("[array]: size=%zu", var->value.data.array_val->length); + } else { + printf("null"); + } + break; + default: + printf("0"); + break; + } + } + printf("\n"); +} + +void rava_repl_output_variables(RavaREPLSession_t *session) { + if (!session || session->variable_count == 0) { + printf("No variables defined.\n"); + return; + } + printf("%-20s %-15s %s\n", "Variable", "Type", "Value"); + printf("%-20s %-15s %s\n", "--------", "----", "-----"); + for (size_t i = 0; i < session->variable_count; i++) { + rava_repl_output_variable(&session->variables[i]); + } +} + +void rava_repl_output_methods(RavaREPLSession_t *session) { + if (!session || session->method_count == 0) { + printf("No methods defined.\n"); + return; + } + printf("%-20s %-15s\n", "Method", "Return Type"); + printf("%-20s %-15s\n", "------", "-----------"); + for (size_t i = 0; i < session->method_count; i++) { + printf("%-20s %-15s\n", + session->methods[i].name, + session->methods[i].return_type ? session->methods[i].return_type : "void"); + } +} + +void rava_repl_output_classes(RavaREPLSession_t *session) { + if (!session || session->class_count == 0) { + printf("No classes defined.\n"); + return; + } + printf("Defined classes:\n"); + for (size_t i = 0; i < session->class_count; i++) { + printf(" %s\n", session->classes[i].name); + } +} + +void rava_repl_output_error(const char *type, const char *message) { + if (type) { + printf("%s: %s\n", type, message ? message : "Unknown error"); + } else { + printf("Error: %s\n", message ? message : "Unknown error"); + } +} + +void rava_repl_output_timing(double elapsed_ms) { + if (elapsed_ms >= 1000.0) { + printf("# Executed in %.2f s\n", elapsed_ms / 1000.0); + } else { + printf("# Executed in %.1f ms\n", elapsed_ms); + } +} diff --git a/repl/repl_output.h b/repl/repl_output.h new file mode 100644 index 0000000..40b78f1 --- /dev/null +++ b/repl/repl_output.h @@ -0,0 +1,15 @@ +#ifndef RAVA_REPL_OUTPUT_H +#define RAVA_REPL_OUTPUT_H + +#include "repl_types.h" +#include "repl_session.h" + +void rava_repl_output_value(RavaValue_t value, RavaVM_t *vm); +void rava_repl_output_variable(RavaREPLVariable_t *var); +void rava_repl_output_variables(RavaREPLSession_t *session); +void rava_repl_output_methods(RavaREPLSession_t *session); +void rava_repl_output_classes(RavaREPLSession_t *session); +void rava_repl_output_error(const char *type, const char *message); +void rava_repl_output_timing(double elapsed_ms); + +#endif diff --git a/repl/repl_session.c b/repl/repl_session.c new file mode 100644 index 0000000..3a44337 --- /dev/null +++ b/repl/repl_session.c @@ -0,0 +1,369 @@ +#define _POSIX_C_SOURCE 200809L +#include "repl_session.h" +#include +#include +#include + +RavaREPLSession_t* rava_repl_session_create(void) { + RavaREPLSession_t *session = calloc(1, sizeof(RavaREPLSession_t)); + if (!session) return NULL; + + session->variable_capacity = RAVA_REPL_MAX_VARIABLES; + session->variables = calloc(session->variable_capacity, sizeof(RavaREPLVariable_t)); + if (!session->variables) { + free(session); + return NULL; + } + + session->method_capacity = RAVA_REPL_MAX_METHODS; + session->methods = calloc(session->method_capacity, sizeof(RavaREPLMethod_t)); + if (!session->methods) { + free(session->variables); + free(session); + return NULL; + } + + session->class_capacity = RAVA_REPL_MAX_CLASSES; + session->classes = calloc(session->class_capacity, sizeof(RavaREPLClass_t)); + if (!session->classes) { + free(session->methods); + free(session->variables); + free(session); + return NULL; + } + + session->history = rava_repl_history_create(RAVA_REPL_MAX_HISTORY); + session->start_time = time(NULL); + session->debug_mode = false; + session->execution_count = 0; + session->program = NULL; + session->session_class = NULL; + session->vm = NULL; + session->last_error = NULL; + + return session; +} + +void rava_repl_session_destroy(RavaREPLSession_t *session) { + if (!session) return; + + for (size_t i = 0; i < session->variable_count; i++) { + free(session->variables[i].name); + free(session->variables[i].type_name); + free(session->variables[i].initializer); + } + free(session->variables); + + for (size_t i = 0; i < session->method_count; i++) { + free(session->methods[i].name); + free(session->methods[i].return_type); + free(session->methods[i].source); + for (size_t j = 0; j < session->methods[i].param_count; j++) { + free(session->methods[i].param_names[j]); + free(session->methods[i].param_types[j]); + } + free(session->methods[i].param_names); + free(session->methods[i].param_types); + } + free(session->methods); + + for (size_t i = 0; i < session->class_count; i++) { + free(session->classes[i].name); + free(session->classes[i].source); + for (size_t j = 0; j < session->classes[i].field_count; j++) { + free(session->classes[i].field_names[j]); + free(session->classes[i].field_types[j]); + } + free(session->classes[i].field_names); + free(session->classes[i].field_types); + for (size_t j = 0; j < session->classes[i].method_count; j++) { + free(session->classes[i].method_names[j]); + } + free(session->classes[i].method_names); + } + free(session->classes); + + rava_repl_history_destroy(session->history); + + if (session->vm) { + rava_vm_destroy(session->vm); + } + if (session->program) { + rava_program_destroy(session->program); + } + free(session->last_error); + free(session); +} + +bool rava_repl_session_add_variable(RavaREPLSession_t *session, const char *name, + const char *type_name, const char *initializer, + RavaValueType_e type, RavaValue_t value) { + if (!session || !name) return false; + + RavaREPLVariable_t *existing = rava_repl_session_get_variable(session, name); + if (existing) { + free(existing->type_name); + free(existing->initializer); + existing->type_name = type_name ? strdup(type_name) : NULL; + existing->initializer = initializer ? strdup(initializer) : NULL; + existing->type = type; + existing->value = value; + existing->is_initialized = true; + return true; + } + + if (session->variable_count >= session->variable_capacity) { + return false; + } + + RavaREPLVariable_t *var = &session->variables[session->variable_count++]; + var->name = strdup(name); + var->type_name = type_name ? strdup(type_name) : NULL; + var->initializer = initializer ? strdup(initializer) : NULL; + var->type = type; + var->value = value; + var->is_initialized = true; + + return true; +} + +RavaREPLVariable_t* rava_repl_session_get_variable(RavaREPLSession_t *session, const char *name) { + if (!session || !name) return NULL; + for (size_t i = 0; i < session->variable_count; i++) { + if (strcmp(session->variables[i].name, name) == 0) { + return &session->variables[i]; + } + } + return NULL; +} + +bool rava_repl_session_set_variable(RavaREPLSession_t *session, const char *name, RavaValue_t value) { + RavaREPLVariable_t *var = rava_repl_session_get_variable(session, name); + if (!var) return false; + var->value = value; + var->type = value.type; + return true; +} + +void rava_repl_session_clear_variables(RavaREPLSession_t *session) { + if (!session) return; + for (size_t i = 0; i < session->variable_count; i++) { + free(session->variables[i].name); + free(session->variables[i].type_name); + free(session->variables[i].initializer); + } + session->variable_count = 0; +} + +bool rava_repl_session_add_method(RavaREPLSession_t *session, const char *name, + const char *return_type, const char *source) { + if (!session || !name) return false; + + RavaREPLMethod_t *existing = rava_repl_session_get_method(session, name); + if (existing) { + free(existing->return_type); + free(existing->source); + existing->return_type = return_type ? strdup(return_type) : NULL; + existing->source = source ? strdup(source) : NULL; + existing->compiled = NULL; + return true; + } + + if (session->method_count >= session->method_capacity) { + return false; + } + + RavaREPLMethod_t *method = &session->methods[session->method_count++]; + method->name = strdup(name); + method->return_type = return_type ? strdup(return_type) : NULL; + method->source = source ? strdup(source) : NULL; + method->param_names = NULL; + method->param_types = NULL; + method->param_count = 0; + method->compiled = NULL; + + return true; +} + +RavaREPLMethod_t* rava_repl_session_get_method(RavaREPLSession_t *session, const char *name) { + if (!session || !name) return NULL; + for (size_t i = 0; i < session->method_count; i++) { + if (strcmp(session->methods[i].name, name) == 0) { + return &session->methods[i]; + } + } + return NULL; +} + +bool rava_repl_session_add_class(RavaREPLSession_t *session, const char *name, const char *source) { + if (!session || !name) return false; + + RavaREPLClass_t *existing = rava_repl_session_get_class(session, name); + if (existing) { + free(existing->source); + existing->source = source ? strdup(source) : NULL; + existing->compiled = NULL; + return true; + } + + if (session->class_count >= session->class_capacity) { + return false; + } + + RavaREPLClass_t *cls = &session->classes[session->class_count++]; + cls->name = strdup(name); + cls->source = source ? strdup(source) : NULL; + cls->field_names = NULL; + cls->field_types = NULL; + cls->field_count = 0; + cls->method_names = NULL; + cls->method_count = 0; + cls->compiled = NULL; + + return true; +} + +RavaREPLClass_t* rava_repl_session_get_class(RavaREPLSession_t *session, const char *name) { + if (!session || !name) return NULL; + for (size_t i = 0; i < session->class_count; i++) { + if (strcmp(session->classes[i].name, name) == 0) { + return &session->classes[i]; + } + } + return NULL; +} + +char* rava_repl_session_generate_source(RavaREPLSession_t *session) { + if (!session) return NULL; + + size_t capacity = 4096; + char *source = malloc(capacity); + if (!source) return NULL; + size_t len = 0; + source[0] = '\0'; + +#define APPEND(...) do { \ + int needed = snprintf(source + len, capacity - len, __VA_ARGS__); \ + if (needed < 0) { free(source); return NULL; } \ + if ((size_t)needed >= capacity - len) { \ + capacity = capacity * 2 + needed; \ + char *tmp = realloc(source, capacity); \ + if (!tmp) { free(source); return NULL; } \ + source = tmp; \ + snprintf(source + len, capacity - len, __VA_ARGS__); \ + } \ + len += needed; \ +} while(0) + + for (size_t i = 0; i < session->class_count; i++) { + if (session->classes[i].source) { + APPEND("%s\n\n", session->classes[i].source); + } + } + + APPEND("public class _REPL_Session_ {\n"); + + for (size_t i = 0; i < session->variable_count; i++) { + RavaREPLVariable_t *var = &session->variables[i]; + const char *type = var->type_name ? var->type_name : "int"; + APPEND(" public static %s %s;\n", type, var->name); + } + + if (session->variable_count > 0) { + APPEND("\n"); + } + + for (size_t i = 0; i < session->method_count; i++) { + if (session->methods[i].source) { + APPEND(" %s\n\n", session->methods[i].source); + } + } + + APPEND("}\n"); + +#undef APPEND + + return source; +} + +bool rava_repl_session_recompile(RavaREPLSession_t *session) { + if (!session) return false; + + char *source = rava_repl_session_generate_source(session); + if (!source) { + rava_repl_session_set_error(session, "Failed to generate session source"); + return false; + } + + if (session->debug_mode) { + fprintf(stderr, "[DEBUG] Session source:\n%s\n", source); + } + + RavaLexer_t *lexer = rava_lexer_create(source); + RavaParser_t *parser = rava_parser_create(lexer); + RavaASTNode_t *ast = rava_parser_parse(parser); + + if (!ast || parser->had_error) { + rava_repl_session_set_error(session, parser->error_message ? parser->error_message : "Parse error"); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + free(source); + return false; + } + + RavaSemanticAnalyzer_t *analyzer = rava_semantic_analyzer_create(); + if (!rava_semantic_analyze(analyzer, ast)) { + rava_repl_session_set_error(session, analyzer->error_message ? analyzer->error_message : "Semantic error"); + rava_ast_node_destroy(ast); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + rava_semantic_analyzer_destroy(analyzer); + free(source); + return false; + } + + RavaIRGenerator_t *ir_gen = rava_ir_generator_create(analyzer); + RavaProgram_t *program = rava_ir_generate(ir_gen, ast); + + if (!program) { + rava_repl_session_set_error(session, "IR generation failed"); + rava_ast_node_destroy(ast); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + rava_semantic_analyzer_destroy(analyzer); + rava_ir_generator_destroy(ir_gen); + free(source); + return false; + } + + if (session->vm) { + rava_vm_destroy(session->vm); + } + if (session->program) { + rava_program_destroy(session->program); + } + + session->program = program; + session->vm = rava_vm_create(program); + session->session_class = NULL; + + rava_ast_node_destroy(ast); + rava_parser_destroy(parser); + rava_lexer_destroy(lexer); + rava_semantic_analyzer_destroy(analyzer); + rava_ir_generator_destroy(ir_gen); + free(source); + + return true; +} + +void rava_repl_session_set_error(RavaREPLSession_t *session, const char *error) { + if (!session) return; + free(session->last_error); + session->last_error = error ? strdup(error) : NULL; +} + +const char* rava_repl_session_get_error(RavaREPLSession_t *session) { + if (!session) return NULL; + return session->last_error; +} diff --git a/repl/repl_session.h b/repl/repl_session.h new file mode 100644 index 0000000..ed573a3 --- /dev/null +++ b/repl/repl_session.h @@ -0,0 +1,54 @@ +#ifndef RAVA_REPL_SESSION_H +#define RAVA_REPL_SESSION_H + +#include "repl_types.h" +#include "repl_history.h" + +typedef struct { + RavaREPLVariable_t *variables; + size_t variable_count; + size_t variable_capacity; + + RavaREPLMethod_t *methods; + size_t method_count; + size_t method_capacity; + + RavaREPLClass_t *classes; + size_t class_count; + size_t class_capacity; + + RavaProgram_t *program; + RavaClass_t *session_class; + RavaVM_t *vm; + + RavaREPLHistory_t *history; + time_t start_time; + int execution_count; + bool debug_mode; + char *last_error; +} RavaREPLSession_t; + +RavaREPLSession_t* rava_repl_session_create(void); +void rava_repl_session_destroy(RavaREPLSession_t *session); + +bool rava_repl_session_add_variable(RavaREPLSession_t *session, const char *name, + const char *type_name, const char *initializer, + RavaValueType_e type, RavaValue_t value); +RavaREPLVariable_t* rava_repl_session_get_variable(RavaREPLSession_t *session, const char *name); +bool rava_repl_session_set_variable(RavaREPLSession_t *session, const char *name, RavaValue_t value); +void rava_repl_session_clear_variables(RavaREPLSession_t *session); + +bool rava_repl_session_add_method(RavaREPLSession_t *session, const char *name, + const char *return_type, const char *source); +RavaREPLMethod_t* rava_repl_session_get_method(RavaREPLSession_t *session, const char *name); + +bool rava_repl_session_add_class(RavaREPLSession_t *session, const char *name, const char *source); +RavaREPLClass_t* rava_repl_session_get_class(RavaREPLSession_t *session, const char *name); + +char* rava_repl_session_generate_source(RavaREPLSession_t *session); +bool rava_repl_session_recompile(RavaREPLSession_t *session); + +void rava_repl_session_set_error(RavaREPLSession_t *session, const char *error); +const char* rava_repl_session_get_error(RavaREPLSession_t *session); + +#endif diff --git a/repl/repl_types.h b/repl/repl_types.h new file mode 100644 index 0000000..ab35a7f --- /dev/null +++ b/repl/repl_types.h @@ -0,0 +1,99 @@ +#ifndef RAVA_REPL_TYPES_H +#define RAVA_REPL_TYPES_H + +#include "../runtime/runtime.h" +#include "../lexer/lexer.h" +#include "../parser/parser.h" +#include "../semantic/semantic.h" +#include "../ir/ir_gen.h" +#include +#include +#include + +#define RAVA_REPL_VERSION "1.0" +#define RAVA_REPL_MAX_INPUT 65536 +#define RAVA_REPL_MAX_HISTORY 1000 +#define RAVA_REPL_MAX_VARIABLES 256 +#define RAVA_REPL_MAX_METHODS 128 +#define RAVA_REPL_MAX_CLASSES 64 +#define RAVA_REPL_TIMEOUT_MS 30000 + +typedef enum { + RAVA_REPL_INPUT_CONTINUE, + RAVA_REPL_INPUT_COMPLETE, + RAVA_REPL_INPUT_ERROR, + RAVA_REPL_INPUT_EOF +} RavaREPLInputState_e; + +typedef enum { + RAVA_REPL_EXEC_SUCCESS, + RAVA_REPL_EXEC_PARSE_ERROR, + RAVA_REPL_EXEC_SEMANTIC_ERROR, + RAVA_REPL_EXEC_RUNTIME_ERROR, + RAVA_REPL_EXEC_INTERRUPTED +} RavaREPLExecResult_e; + +typedef enum { + RAVA_REPL_CODE_EXPRESSION, + RAVA_REPL_CODE_STATEMENT, + RAVA_REPL_CODE_VAR_DECL, + RAVA_REPL_CODE_METHOD_DECL, + RAVA_REPL_CODE_CLASS_DECL, + RAVA_REPL_CODE_UNKNOWN +} RavaREPLCodeType_e; + +typedef enum { + RAVA_CMD_HELP, + RAVA_CMD_WHOS, + RAVA_CMD_WHO, + RAVA_CMD_RESET, + RAVA_CMD_CLEAR, + RAVA_CMD_QUIT, + RAVA_CMD_EXIT, + RAVA_CMD_HISTORY, + RAVA_CMD_RUN, + RAVA_CMD_LOAD, + RAVA_CMD_SAVE, + RAVA_CMD_DEBUG, + RAVA_CMD_TIME, + RAVA_CMD_TIMEIT, + RAVA_CMD_MEMORY, + RAVA_CMD_DIR, + RAVA_CMD_TYPE, + RAVA_CMD_SOURCE, + RAVA_CMD_METHODS, + RAVA_CMD_CLASSES, + RAVA_CMD_UNKNOWN +} RavaREPLCommand_e; + +typedef struct { + char *name; + char *type_name; + char *initializer; + RavaValueType_e type; + RavaValue_t value; + bool is_initialized; +} RavaREPLVariable_t; + +typedef struct { + char *name; + char *return_type; + char **param_names; + char **param_types; + size_t param_count; + char *source; + RavaMethod_t *compiled; +} RavaREPLMethod_t; + +typedef struct { + char *name; + char *source; + char **field_names; + char **field_types; + size_t field_count; + char **method_names; + size_t method_count; + RavaClass_t *compiled; +} RavaREPLClass_t; + +#endif diff --git a/repl/run_tests.sh b/repl/run_tests.sh new file mode 100755 index 0000000..ec4f399 --- /dev/null +++ b/repl/run_tests.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RAVA="$SCRIPT_DIR/../rava" +TESTS_DIR="$SCRIPT_DIR/tests" +EXAMPLES_DIR="$SCRIPT_DIR/examples" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +passed=0 +failed=0 + +run_test() { + local file="$1" + local name=$(basename "$file" .txt) + + echo -n " $name ... " + + if timeout 30 "$RAVA" < "$file" > /tmp/repl_test_output.txt 2>&1; then + echo -e "${GREEN}ok${NC}" + ((passed++)) + else + echo -e "${RED}FAIL${NC}" + echo " Output:" + head -20 /tmp/repl_test_output.txt | sed 's/^/ /' + ((failed++)) + fi +} + +echo "========================================" +echo "REPL Test Suite" +echo "========================================" +echo "" + +echo "Running tests..." +for test_file in "$TESTS_DIR"/*.txt; do + if [ -f "$test_file" ]; then + run_test "$test_file" + fi +done + +echo "" +echo "Running examples..." +for example_file in "$EXAMPLES_DIR"/*.txt; do + if [ -f "$example_file" ]; then + run_test "$example_file" + fi +done + +echo "" +echo "========================================" +echo "Results: $passed passed, $failed failed" +echo "========================================" + +if [ $failed -gt 0 ]; then + exit 1 +fi +exit 0 diff --git a/repl/tests/test_arrays.txt b/repl/tests/test_arrays.txt new file mode 100644 index 0000000..c228264 --- /dev/null +++ b/repl/tests/test_arrays.txt @@ -0,0 +1,15 @@ +int[] arr = new int[5]; +arr.length +int[] nums = {1, 2, 3, 4, 5}; +nums.length +nums[0] +nums[4] +String[] names = {"Alice", "Bob", "Charlie"}; +names.length +names[0] +names[1] +double[] vals = {1.5, 2.5, 3.5}; +vals[0] +vals[2] +%whos +%quit diff --git a/repl/tests/test_classes.txt b/repl/tests/test_classes.txt new file mode 100644 index 0000000..82b9d60 --- /dev/null +++ b/repl/tests/test_classes.txt @@ -0,0 +1,16 @@ +class Point { public int x; public int y; public Point(int px, int py) { this.x = px; this.y = py; } } +Point p = new Point(3, 4); +p.x +p.y +class Rectangle { public int width; public int height; public Rectangle(int w, int h) { this.width = w; this.height = h; } public int area() { return this.width * this.height; } public int perimeter() { return 2 * (this.width + this.height); } } +Rectangle r = new Rectangle(5, 10); +r.width +r.height +r.area() +r.perimeter() +class Counter { public int count; public Counter() { this.count = 0; } public void inc() { this.count++; } public void dec() { this.count--; } public int get() { return this.count; } } +Counter c = new Counter(); +c.get() +%classes +%whos +%quit diff --git a/repl/tests/test_commands.txt b/repl/tests/test_commands.txt new file mode 100644 index 0000000..87e6076 --- /dev/null +++ b/repl/tests/test_commands.txt @@ -0,0 +1,16 @@ +%help +int a = 1; +int b = 2; +int c = 3; +%whos +%who +int triple(int x) { return x * 3; } +%methods +class Foo { public int val; } +%classes +%reset +%whos +%who +%methods +%classes +%quit diff --git a/repl/tests/test_control_flow.txt b/repl/tests/test_control_flow.txt new file mode 100644 index 0000000..7e97fa8 --- /dev/null +++ b/repl/tests/test_control_flow.txt @@ -0,0 +1,8 @@ +for (int i = 0; i < 5; i++) { System.out.println(i); } +int sum = 0; for (int i = 1; i <= 10; i++) { sum = sum + i; } System.out.println(sum); +int x = 10; while (x > 0) { System.out.println(x); x = x - 2; } +int n = 1; do { System.out.println(n); n = n * 2; } while (n < 100); +int val = 2; switch (val) { case 1: System.out.println("one"); break; case 2: System.out.println("two"); break; default: System.out.println("other"); } +if (5 > 3) { System.out.println("yes"); } else { System.out.println("no"); } +for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { System.out.println(i * 3 + j); } } +%quit diff --git a/repl/tests/test_errors.txt b/repl/tests/test_errors.txt new file mode 100644 index 0000000..74d882a --- /dev/null +++ b/repl/tests/test_errors.txt @@ -0,0 +1,8 @@ +undefined_variable +1 + "string" +int x = ; +class { } +1 / 0 +int y = 10; +y +%quit diff --git a/repl/tests/test_expressions.txt b/repl/tests/test_expressions.txt new file mode 100644 index 0000000..fc36e0b --- /dev/null +++ b/repl/tests/test_expressions.txt @@ -0,0 +1,24 @@ +1 + 2 +3 * 4 +10 - 5 +20 / 4 +17 % 5 +-42 +1 + 2 * 3 +(1 + 2) * 3 +10 > 5 +10 < 5 +10 == 10 +10 != 5 +true && false +true || false +!true +1 < 2 && 3 < 4 +5 > 3 ? 100 : 200 +1 | 2 +3 & 2 +4 ^ 1 +~0 +8 >> 2 +1 << 3 +%quit diff --git a/repl/tests/test_math.txt b/repl/tests/test_math.txt new file mode 100644 index 0000000..f999438 --- /dev/null +++ b/repl/tests/test_math.txt @@ -0,0 +1,16 @@ +Math.abs(-42) +Math.abs(42) +Math.sqrt(16.0) +Math.sqrt(2.0) +Math.pow(2.0, 10.0) +Math.min(5, 3) +Math.max(5, 3) +Math.floor(3.7) +Math.ceil(3.2) +Math.round(3.5) +Math.sin(0.0) +Math.cos(0.0) +Math.tan(0.0) +Math.log(2.718281828) +Math.exp(1.0) +%quit diff --git a/repl/tests/test_methods.txt b/repl/tests/test_methods.txt new file mode 100644 index 0000000..bda6cd2 --- /dev/null +++ b/repl/tests/test_methods.txt @@ -0,0 +1,21 @@ +int add(int a, int b) { return a + b; } +add(3, 4) +add(10, 20) +int square(int x) { return x * x; } +square(5) +square(12) +int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); } +factorial(5) +factorial(10) +int fib(int n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } +fib(10) +fib(15) +double avg(double a, double b) { return (a + b) / 2.0; } +avg(10.0, 20.0) +boolean isEven(int n) { return n % 2 == 0; } +isEven(4) +isEven(7) +String greet(String name) { return "Hello, " + name + "!"; } +greet("World") +%methods +%quit diff --git a/repl/tests/test_multiline.txt b/repl/tests/test_multiline.txt new file mode 100644 index 0000000..a17ac3f --- /dev/null +++ b/repl/tests/test_multiline.txt @@ -0,0 +1,22 @@ +class Person { +public String name; +public int age; +public Person(String n, int a) { +this.name = n; +this.age = a; +} +public String describe() { +return this.name + " is " + this.age + " years old"; +} +} +Person p = new Person("Alice", 30); +p.name +p.age +p.describe() +int compute(int x, +int y, +int z) { +return x + y + z; +} +compute(1, 2, 3) +%quit diff --git a/repl/tests/test_strings.txt b/repl/tests/test_strings.txt new file mode 100644 index 0000000..6fcd4e1 --- /dev/null +++ b/repl/tests/test_strings.txt @@ -0,0 +1,19 @@ +String s = "hello world"; +s +s.length() +s.toUpperCase() +s.toLowerCase() +s.charAt(0) +s.substring(0, 5) +s.indexOf("world") +s.contains("ell") +s.startsWith("hello") +s.endsWith("world") +s.trim() +String a = "foo"; +String b = "bar"; +a + b +a.equals("foo") +a.compareTo(b) +%whos +%quit diff --git a/repl/tests/test_variables.txt b/repl/tests/test_variables.txt new file mode 100644 index 0000000..8021b5d --- /dev/null +++ b/repl/tests/test_variables.txt @@ -0,0 +1,23 @@ +int x = 5; +int y = 10; +x +y +x + y +x * y +x - y +x / y +x % y +long big = 1000000000; +big +double pi = 3.14159; +pi +float f = 2.5; +f +boolean flag = true; +flag +char c = 'A'; +c +String name = "Rava"; +name +%whos +%quit