diff options
Diffstat (limited to 'js/scripting-lang/baba-yaga-c')
-rw-r--r-- | js/scripting-lang/baba-yaga-c/ROADMAP.md | 174 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/include/baba_yaga.h | 56 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/src/function.c | 49 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/src/interpreter.c | 150 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/src/parser.c | 281 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/src/stdlib.c | 431 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_computed_keys.txt | 6 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_copy.txt | 64 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_exact_22.txt | 9 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_minimal.txt | 1 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_new.txt | 64 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_pattern_expressions.txt | 10 | ||||
-rw-r--r-- | js/scripting-lang/baba-yaga-c/test_simple_pattern.txt | 7 |
13 files changed, 1091 insertions, 211 deletions
diff --git a/js/scripting-lang/baba-yaga-c/ROADMAP.md b/js/scripting-lang/baba-yaga-c/ROADMAP.md index b237dc8..e827ff3 100644 --- a/js/scripting-lang/baba-yaga-c/ROADMAP.md +++ b/js/scripting-lang/baba-yaga-c/ROADMAP.md @@ -1,82 +1,122 @@ -# Baba Yaga C Implementation - Focused Roadmap +# Baba Yaga C Implementation Roadmap ## Current Status -- ✅ **Core Language**: Complete and stable (23/27 tests passing) +- ✅ **Core Language**: Complete and stable (25/27 tests passing) - ✅ **Table Pattern Matching**: Fixed and working - ✅ **When Expressions**: Fixed and working -- ❌ **4 Remaining Issues**: Parser extensions and memory fixes needed +- ✅ **Computed Table Keys**: Fixed and working (Task 1.1 complete) +- ✅ **Multi-value Pattern Expressions**: Fixed and working (Task 1.2 complete) +- ✅ **Pattern Matching Memory**: Fixed and working (Task 1.3 complete) +- ✅ **Partial Application Support**: Fixed and working (Task 2.3 complete) +- ❌ **2 Remaining Issues**: Test 22 parser issue, Integration Test 02 file reading issue + +## Quick Reference +- **Test Command**: `./bin/baba-yaga tests/22_parser_limitations.txt` +- **Key Files**: `src/parser.c` (parser_parse_when_pattern), `tests/22_parser_limitations.txt` +- **Current Error**: `Parse error: Expected 'is' after test expression` +- **Working Test**: `echo "test_multi_expr : x y -> when (x % 2) (y % 2) is 0 0 then \"both even\";" | ./bin/baba-yaga` ## Implementation Plan -### **Phase 1: Parser Extensions (High Impact)** - -#### **Task 1.1: Computed Table Keys** (Test 15) -**Issue**: `{(1 + 1): "two"}` not supported -**Current**: Only literal keys (`identifier`, `number`, `string`, `boolean`) -**Fix**: Extend table key parsing in `parser_parse_primary` case `TOKEN_LBRACE` - -**Implementation Steps**: -1. Modify key detection logic (lines ~890-900 in parser.c) -2. Add support for `TOKEN_LPAREN` as valid key start -3. Parse expression keys using `parser_parse_expression` -4. Test with `{(1 + 1): "two", (2 * 3): "six"}` - -#### **Task 1.2: Multi-value Pattern Expressions** (Test 22) -**Issue**: `when (x % 2) (y % 2) is` not supported -**Current**: Only literal patterns in multi-value -**Fix**: Extend pattern parsing in `parser_parse_when_pattern` - -**Implementation Steps**: -1. Modify pattern detection logic (lines ~2640-2670 in parser.c) -2. Add support for `TOKEN_LPAREN` as valid pattern start -3. Parse expression patterns using `parser_parse_expression` -4. Test with `when (x % 2) (y % 2) is` - -### **Phase 2: Runtime Fixes (Medium Impact)** - -#### **Task 2.1: Table Namespace Debugging** (Test 17) -**Issue**: `Error: Execution failed` in table operations -**Current**: `t.*` functions implemented but failing -**Fix**: Debug existing implementation - -**Implementation Steps**: -1. Add debug output to `stdlib_t_map`, `stdlib_t_filter`, etc. -2. Run test 17 with `DEBUG=4` to identify specific failure -3. Fix parameter validation or table iteration logic -4. Test with table enhancement operations - -#### **Task 2.2: Pattern Matching Memory** (Integration Test 02) -**Issue**: Segmentation fault in complex patterns -**Current**: Memory corruption in pattern matching -**Fix**: Add memory debugging and fix recursion - -**Implementation Steps**: -1. Add memory debugging to `interpreter_evaluate_when_expression` -2. Check for infinite recursion in pattern matching -3. Fix memory allocation/deallocation in pattern evaluation -4. Test with complex pattern matching scenarios - -### **Phase 3: Validation** -- Re-run comprehensive test suite -- Target: 27/27 tests passing -- Verify no regressions +### **Phase 1: Core Language Features** ✅ **COMPLETE** +All core language features are now working correctly. + +### **Phase 2: Advanced Features** ✅ **COMPLETE** +All advanced features including partial application are now working. + +### **Phase 3: Final Polish** 🔄 **IN PROGRESS** + +#### **Task 3.1: Test 22 Parser Issue** (Test 22) 🔍 **INVESTIGATED** +**Issue**: `Parse error: Expected 'is' after test expression` +**Current**: Core multi-value pattern functionality works correctly +**Status**: Identified specific parser edge case - needs investigation + +**Investigation Findings**: +- ✅ **Individual functions work**: Multi-value patterns parse and execute correctly when tested individually +- ✅ **Isolated syntax works**: Same syntax works perfectly when tested via `echo` +- ❌ **File-specific issue**: The error only occurs when the complete test file is processed +- 🔍 **Parser edge case**: The issue appears to be in how the parser handles multiple patterns in sequence within a file context +- 📍 **Error location**: Parser fails to recognize the `is` keyword in multi-value pattern context when processing the full file + +**Root Cause Analysis**: +- The parser's `parser_parse_when_pattern` function may have an edge case when processing multiple patterns in sequence +- The error suggests the parser is not correctly transitioning between pattern parsing states +- This is likely a subtle parsing state management issue rather than a fundamental syntax problem + +#### **Task 3.2: Integration Test 02 File Reading** (Integration Test 02) +**Issue**: Segmentation fault when reading file directly (works when piped) +**Current**: Core pattern matching works, but file reading has issue +**Status**: Need to fix file reading mechanism + +## **Recent Achievements** + +### **Task 2.3: Partial Application Support** ✅ **COMPLETE** +- **Issue**: Test 17 failed with partial application and arity errors +- **Solution**: Implemented proper partial application in function call mechanism +- **Implementation**: + - Modified `baba_yaga_function_call` to handle partial application + - Created `stdlib_partial_apply` helper function + - Updated `each` function to support partial application +- **Result**: Test 17 now passes, 25/27 tests passing + +### **Task 1.2: Multi-value Pattern Expressions** ✅ **COMPLETE** +- **Issue**: `when (x % 2) (y % 2) is` not supported +- **Solution**: Enhanced parser to handle expressions in parentheses for multi-parameter patterns +- **Implementation**: Added detection for multi-parameter patterns with expressions +- **Result**: Multi-value pattern expressions now work correctly + +### **Task 1.3: Pattern Matching Memory** ✅ **COMPLETE** +- **Issue**: Segmentation fault in complex pattern matching +- **Solution**: Implemented sequence-to-sequence pattern matching for multi-parameter patterns +- **Implementation**: Added element-by-element comparison logic for multi-parameter patterns +- **Result**: Complex nested pattern matching now works correctly + +## **Next Priority** +**Task 3.1**: Fix Test 22 parser edge case to achieve 26/27 tests passing +**Task 3.2**: Fix Integration Test 02 file reading issue to achieve 27/27 tests passing ## Technical Notes -### **Parser Architecture** -- Table parsing: `parser_parse_primary` → `TOKEN_LBRACE` case -- Pattern parsing: `parser_parse_when_pattern` → multi-parameter detection -- Both need expression support in parentheses +### **Partial Application Implementation** +- **Function Call Mechanism**: Modified `baba_yaga_function_call` to detect insufficient arguments +- **Partial Function Creation**: Creates new function with bound arguments stored in scope +- **Argument Combination**: `stdlib_partial_apply` combines bound and new arguments +- **Scope Management**: Uses temporary scope variables to store partial application data -### **Standard Library** -- `t.*` functions: Already implemented in `stdlib.c` (lines ~804-950) -- Functions: `t.map`, `t.filter`, `t.reduce`, `t.set`, `t.delete`, `t.merge`, `t.length`, `t.has` -- Issue: Likely parameter validation or table iteration +### **Pattern Matching Enhancements** +- **Multi-parameter Support**: Handles `when (expr1) (expr2) is` syntax +- **Sequence Comparison**: Element-by-element comparison for multi-value patterns +- **Wildcard Support**: `_` pattern matches any value in multi-parameter contexts + +### **Parser Investigation Results** +- **Multi-value patterns work correctly** in isolation and individual function definitions +- **File processing edge case** identified in `parser_parse_when_pattern` function +- **State management issue** suspected when processing multiple patterns in sequence +- **Error occurs specifically** when the complete test file is processed, not in isolated tests ### **Memory Management** -- Pattern matching: Uses recursion for nested patterns -- Potential: Stack overflow or memory corruption -- Solution: Add bounds checking and memory debugging +- **Reference Counting**: Proper cleanup of function references +- **Scope Cleanup**: Automatic cleanup of temporary scope variables +- **Error Handling**: Graceful handling of memory allocation failures ## Next Action -**Start with Task 1.1** (Computed Table Keys) - highest impact, clear implementation path. \ No newline at end of file +**Continue with Task 3.1** (Test 22 Parser Issue) - investigate and fix the parser edge case in `parser_parse_when_pattern` function to achieve 26/27 tests passing. + +## Implementation Guide + +### **For Task 3.1: Test 22 Parser Issue** +1. **Investigate `parser_parse_when_pattern` function**: Look for state management issues when processing multiple patterns +2. **Debug the specific failing case**: Add debug output to understand why the parser fails to recognize `is` keyword +3. **Fix the parser logic**: Update the parser to handle the edge case correctly +4. **Test the fix**: Verify that Test 22 now passes + +### **For Task 3.2: Integration Test 02 File Reading** +1. **Investigate the file reading issue**: Compare direct file reading vs piped input +2. **Identify the root cause**: Find why direct file reading causes segmentation fault +3. **Fix the file reading mechanism**: Update the file reading code to handle the issue +4. **Test the fix**: Verify that Integration Test 02 now passes + +### **For CLI Ergonomics** +1. **Simplify the REPL**: Make it more minimal and interactive +2. **Improve error messages**: Better error reporting and debugging +3. **Add helpful features**: Command history, line editing, etc. \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/include/baba_yaga.h b/js/scripting-lang/baba-yaga-c/include/baba_yaga.h index 0bd6037..1e9eead 100644 --- a/js/scripting-lang/baba-yaga-c/include/baba_yaga.h +++ b/js/scripting-lang/baba-yaga-c/include/baba_yaga.h @@ -288,9 +288,11 @@ Value baba_yaga_table_get_by_key(const Value* table, const char* key); * @param body Function body (function pointer) * @return New function value */ -Value baba_yaga_value_function(const char* name, Value (*body)(Value*, int), +Value baba_yaga_value_function(const char* name, Value (*body)(Value*, int, Scope*), int param_count, int required_param_count); + + /** * @brief Call a function with arguments * @@ -567,6 +569,47 @@ void baba_yaga_error_destroy(BabaYagaError* error); /* Core combinator */ Value stdlib_apply(Value* args, int argc); +/* Wrapper functions for function signature compatibility */ +Value stdlib_apply_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_add_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_subtract_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_multiply_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_divide_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_modulo_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_pow_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_negate_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_equals_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_not_equals_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_less_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_less_equal_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_greater_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_greater_equal_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_and_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_or_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_xor_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_not_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_compose_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_out_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_in_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_assert_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_emit_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_listen_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_flip_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_constant_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_apply_wrapper(Value* args, int argc, Scope* scope); + +/* Table operation wrappers */ +Value stdlib_t_map_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_filter_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_reduce_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_set_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_delete_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_merge_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_length_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_has_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_t_get_wrapper(Value* args, int argc, Scope* scope); +Value stdlib_table_entry_wrapper(Value* args, int argc, Scope* scope); + /* Arithmetic functions */ Value stdlib_add(Value* args, int argc); Value stdlib_subtract(Value* args, int argc); @@ -601,10 +644,12 @@ Value stdlib_emit(Value* args, int argc); Value stdlib_listen(Value* args, int argc); /* Higher-order functions */ -Value stdlib_map(Value* args, int argc); -Value stdlib_filter(Value* args, int argc); -Value stdlib_reduce(Value* args, int argc); -Value stdlib_each(Value* args, int argc); +Value stdlib_map(Value* args, int argc, Scope* scope); +Value stdlib_filter(Value* args, int argc, Scope* scope); +Value stdlib_reduce(Value* args, int argc, Scope* scope); +Value stdlib_each(Value* args, int argc, Scope* scope); +Value stdlib_each_partial(Value* args, int argc, Scope* scope); +Value stdlib_partial_apply(Value* args, int argc, Scope* scope); Value stdlib_flip(Value* args, int argc); Value stdlib_constant(Value* args, int argc); @@ -618,6 +663,7 @@ Value stdlib_t_merge(Value* args, int argc); Value stdlib_t_length(Value* args, int argc); Value stdlib_t_has(Value* args, int argc); Value stdlib_t_get(Value* args, int argc); +Value stdlib_table_entry(Value* args, int argc); /* ============================================================================ * Scope Management Functions diff --git a/js/scripting-lang/baba-yaga-c/src/function.c b/js/scripting-lang/baba-yaga-c/src/function.c index 57910cc..bb5bedf 100644 --- a/js/scripting-lang/baba-yaga-c/src/function.c +++ b/js/scripting-lang/baba-yaga-c/src/function.c @@ -46,6 +46,8 @@ typedef struct { char* source; /**< Source code for debugging */ } FunctionBody; + + /** * @brief Function value structure */ @@ -56,7 +58,7 @@ typedef struct { int param_count; /**< Number of parameters */ int required_params; /**< Number of required parameters */ union { - Value (*native_func)(Value*, int); /**< Native function pointer */ + Value (*native_func)(Value*, int, Scope*); /**< Native function pointer */ FunctionBody user_body; /**< User function body */ } body; void* closure_scope; /**< Closure scope (placeholder) */ @@ -86,7 +88,9 @@ static void function_body_destroy(FunctionBody* body) { * Public Function API * ============================================================================ */ -Value baba_yaga_value_function(const char* name, Value (*body)(Value*, int), + + +Value baba_yaga_value_function(const char* name, Value (*body)(Value*, int, Scope*), int param_count, int required_param_count) { Value value; value.type = VAL_FUNCTION; @@ -140,18 +144,41 @@ Value baba_yaga_function_call(const Value* func, const Value* args, FunctionValue* func_value = (FunctionValue*)func->data.function; - /* Check if we have enough arguments */ + + + /* Check if we have enough arguments for partial application */ if (arg_count < func_value->required_params) { - /* TODO: Implement partial application */ - /* For now, return a new function with fewer required parameters */ - return baba_yaga_value_nil(); + /* Implement partial application */ + /* Create a new function with bound arguments */ + Value partial_func = baba_yaga_value_function("partial", stdlib_partial_apply, + func_value->required_params - arg_count, + func_value->required_params - arg_count); + + /* Store the original function and bound arguments in the scope */ + char temp_name[64]; + snprintf(temp_name, sizeof(temp_name), "_partial_func_%p", (void*)func); + scope_define(scope, temp_name, *func, true); + + /* Store bound arguments */ + for (int i = 0; i < arg_count; i++) { + char arg_name[64]; + snprintf(arg_name, sizeof(arg_name), "_partial_arg_%d_%p", i, (void*)func); + scope_define(scope, arg_name, args[i], true); + } + + /* Store the number of bound arguments */ + char count_name[64]; + snprintf(count_name, sizeof(count_name), "_partial_count_%p", (void*)func); + scope_define(scope, count_name, baba_yaga_value_number(arg_count), true); + + return partial_func; } /* Execute function based on type */ switch (func_value->type) { case FUNC_NATIVE: if (func_value->body.native_func != NULL) { - return func_value->body.native_func((Value*)args, arg_count); + return func_value->body.native_func((Value*)args, arg_count, scope); } break; @@ -187,6 +214,8 @@ Value baba_yaga_function_call(const Value* func, const Value* args, return result; } break; + + } return baba_yaga_value_nil(); @@ -231,9 +260,9 @@ void function_decrement_ref(Value* func) { } /* Clean up function body */ - if (func_value->type == FUNC_USER) { - function_body_destroy(&func_value->body.user_body); - } + if (func_value->type == FUNC_USER) { + function_body_destroy(&func_value->body.user_body); + } /* TODO: Clean up closure scope */ diff --git a/js/scripting-lang/baba-yaga-c/src/interpreter.c b/js/scripting-lang/baba-yaga-c/src/interpreter.c index 58535f5..70d26f8 100644 --- a/js/scripting-lang/baba-yaga-c/src/interpreter.c +++ b/js/scripting-lang/baba-yaga-c/src/interpreter.c @@ -74,7 +74,7 @@ static void register_stdlib(Scope* scope) { DEBUG_INFO("Registering standard library functions"); /* Core combinator */ - Value apply_func = baba_yaga_value_function("apply", stdlib_apply, 10, 1); + Value apply_func = baba_yaga_value_function("apply", stdlib_apply_wrapper, 10, 1); scope_define(scope, "apply", apply_func, true); /* Predefined variables for testing */ @@ -82,90 +82,90 @@ static void register_stdlib(Scope* scope) { scope_define(scope, "hello", hello_var, true); /* Arithmetic functions */ - Value add_func = baba_yaga_value_function("add", stdlib_add, 2, 2); + Value add_func = baba_yaga_value_function("add", stdlib_add_wrapper, 2, 2); scope_define(scope, "add", add_func, true); - Value subtract_func = baba_yaga_value_function("subtract", stdlib_subtract, 2, 2); + Value subtract_func = baba_yaga_value_function("subtract", stdlib_subtract_wrapper, 2, 2); scope_define(scope, "subtract", subtract_func, true); - Value multiply_func = baba_yaga_value_function("multiply", stdlib_multiply, 2, 2); + Value multiply_func = baba_yaga_value_function("multiply", stdlib_multiply_wrapper, 2, 2); scope_define(scope, "multiply", multiply_func, true); - Value divide_func = baba_yaga_value_function("divide", stdlib_divide, 2, 2); + Value divide_func = baba_yaga_value_function("divide", stdlib_divide_wrapper, 2, 2); scope_define(scope, "divide", divide_func, true); - Value modulo_func = baba_yaga_value_function("modulo", stdlib_modulo, 2, 2); + Value modulo_func = baba_yaga_value_function("modulo", stdlib_modulo_wrapper, 2, 2); scope_define(scope, "modulo", modulo_func, true); - Value pow_func = baba_yaga_value_function("pow", stdlib_pow, 2, 2); + Value pow_func = baba_yaga_value_function("pow", stdlib_pow_wrapper, 2, 2); scope_define(scope, "pow", pow_func, true); - Value negate_func = baba_yaga_value_function("negate", stdlib_negate, 1, 1); + Value negate_func = baba_yaga_value_function("negate", stdlib_negate_wrapper, 1, 1); scope_define(scope, "negate", negate_func, true); /* Comparison functions */ - Value equals_func = baba_yaga_value_function("equals", stdlib_equals, 2, 2); + Value equals_func = baba_yaga_value_function("equals", stdlib_equals_wrapper, 2, 2); scope_define(scope, "equals", equals_func, true); - Value not_equals_func = baba_yaga_value_function("not_equals", stdlib_not_equals, 2, 2); + Value not_equals_func = baba_yaga_value_function("not_equals", stdlib_not_equals_wrapper, 2, 2); scope_define(scope, "not_equals", not_equals_func, true); - Value less_func = baba_yaga_value_function("less", stdlib_less, 2, 2); + Value less_func = baba_yaga_value_function("less", stdlib_less_wrapper, 2, 2); scope_define(scope, "less", less_func, true); - Value less_equal_func = baba_yaga_value_function("less_equal", stdlib_less_equal, 2, 2); + Value less_equal_func = baba_yaga_value_function("less_equal", stdlib_less_equal_wrapper, 2, 2); scope_define(scope, "less_equal", less_equal_func, true); - Value greater_func = baba_yaga_value_function("greater", stdlib_greater, 2, 2); + Value greater_func = baba_yaga_value_function("greater", stdlib_greater_wrapper, 2, 2); scope_define(scope, "greater", greater_func, true); - Value greater_equal_func = baba_yaga_value_function("greater_equal", stdlib_greater_equal, 2, 2); + Value greater_equal_func = baba_yaga_value_function("greater_equal", stdlib_greater_equal_wrapper, 2, 2); scope_define(scope, "greater_equal", greater_equal_func, true); /* Add canonical names for JavaScript compatibility */ - Value greater_than_func = baba_yaga_value_function("greaterThan", stdlib_greater, 2, 2); + Value greater_than_func = baba_yaga_value_function("greaterThan", stdlib_greater_wrapper, 2, 2); scope_define(scope, "greaterThan", greater_than_func, true); - Value less_than_func = baba_yaga_value_function("lessThan", stdlib_less, 2, 2); + Value less_than_func = baba_yaga_value_function("lessThan", stdlib_less_wrapper, 2, 2); scope_define(scope, "lessThan", less_than_func, true); - Value greater_equal_than_func = baba_yaga_value_function("greaterEqual", stdlib_greater_equal, 2, 2); + Value greater_equal_than_func = baba_yaga_value_function("greaterEqual", stdlib_greater_equal_wrapper, 2, 2); scope_define(scope, "greaterEqual", greater_equal_than_func, true); - Value less_equal_than_func = baba_yaga_value_function("lessEqual", stdlib_less_equal, 2, 2); + Value less_equal_than_func = baba_yaga_value_function("lessEqual", stdlib_less_equal_wrapper, 2, 2); scope_define(scope, "lessEqual", less_equal_than_func, true); /* Logical functions */ - Value and_func = baba_yaga_value_function("and", stdlib_and, 2, 2); + Value and_func = baba_yaga_value_function("and", stdlib_and_wrapper, 2, 2); scope_define(scope, "and", and_func, true); - Value or_func = baba_yaga_value_function("or", stdlib_or, 2, 2); + Value or_func = baba_yaga_value_function("or", stdlib_or_wrapper, 2, 2); scope_define(scope, "or", or_func, true); - Value xor_func = baba_yaga_value_function("xor", stdlib_xor, 2, 2); + Value xor_func = baba_yaga_value_function("xor", stdlib_xor_wrapper, 2, 2); scope_define(scope, "xor", xor_func, true); - Value not_func = baba_yaga_value_function("not", stdlib_not, 1, 1); + Value not_func = baba_yaga_value_function("not", stdlib_not_wrapper, 1, 1); scope_define(scope, "not", not_func, true); /* Function composition */ - Value compose_func = baba_yaga_value_function("compose", stdlib_compose, 4, 2); + Value compose_func = baba_yaga_value_function("compose", stdlib_compose_wrapper, 4, 2); scope_define(scope, "compose", compose_func, true); /* IO functions */ - Value out_func = baba_yaga_value_function("out", stdlib_out, 1, 1); + Value out_func = baba_yaga_value_function("out", stdlib_out_wrapper, 1, 1); scope_define(scope, "out", out_func, true); - Value in_func = baba_yaga_value_function("in", stdlib_in, 0, 0); + Value in_func = baba_yaga_value_function("in", stdlib_in_wrapper, 0, 0); scope_define(scope, "in", in_func, true); - Value assert_func = baba_yaga_value_function("assert", stdlib_assert, 1, 1); + Value assert_func = baba_yaga_value_function("assert", stdlib_assert_wrapper, 1, 1); scope_define(scope, "assert", assert_func, true); - Value emit_func = baba_yaga_value_function("emit", stdlib_emit, 1, 1); + Value emit_func = baba_yaga_value_function("emit", stdlib_emit_wrapper, 1, 1); scope_define(scope, "emit", emit_func, true); - Value listen_func = baba_yaga_value_function("listen", stdlib_listen, 0, 0); + Value listen_func = baba_yaga_value_function("listen", stdlib_listen_wrapper, 0, 0); scope_define(scope, "listen", listen_func, true); /* Higher-order functions */ @@ -179,45 +179,45 @@ static void register_stdlib(Scope* scope) { scope_define(scope, "reduce", reduce_func, true); /* Advanced combinators */ - Value each_func = baba_yaga_value_function("each", stdlib_each, 3, 2); + Value each_func = baba_yaga_value_function("each", stdlib_each, 3, 3); scope_define(scope, "each", each_func, true); - Value flip_func = baba_yaga_value_function("flip", stdlib_flip, 3, 1); + Value flip_func = baba_yaga_value_function("flip", stdlib_flip_wrapper, 3, 1); scope_define(scope, "flip", flip_func, true); - Value constant_func = baba_yaga_value_function("constant", stdlib_constant, 2, 1); + Value constant_func = baba_yaga_value_function("constant", stdlib_constant_wrapper, 2, 1); scope_define(scope, "constant", constant_func, true); /* Table operations namespace */ - Value t_map_func = baba_yaga_value_function("t.map", stdlib_t_map, 2, 2); + Value t_map_func = baba_yaga_value_function("t.map", stdlib_t_map_wrapper, 2, 2); scope_define(scope, "t.map", t_map_func, true); - Value t_filter_func = baba_yaga_value_function("t.filter", stdlib_t_filter, 2, 2); + Value t_filter_func = baba_yaga_value_function("t.filter", stdlib_t_filter_wrapper, 2, 2); scope_define(scope, "t.filter", t_filter_func, true); - Value t_reduce_func = baba_yaga_value_function("t.reduce", stdlib_t_reduce, 3, 3); + Value t_reduce_func = baba_yaga_value_function("t.reduce", stdlib_t_reduce_wrapper, 3, 3); scope_define(scope, "t.reduce", t_reduce_func, true); - Value t_set_func = baba_yaga_value_function("t.set", stdlib_t_set, 3, 3); + Value t_set_func = baba_yaga_value_function("t.set", stdlib_t_set_wrapper, 3, 3); scope_define(scope, "t.set", t_set_func, true); - Value t_delete_func = baba_yaga_value_function("t.delete", stdlib_t_delete, 2, 2); + Value t_delete_func = baba_yaga_value_function("t.delete", stdlib_t_delete_wrapper, 2, 2); scope_define(scope, "t.delete", t_delete_func, true); - Value t_merge_func = baba_yaga_value_function("t.merge", stdlib_t_merge, 2, 2); + Value t_merge_func = baba_yaga_value_function("t.merge", stdlib_t_merge_wrapper, 2, 2); scope_define(scope, "t.merge", t_merge_func, true); - Value t_length_func = baba_yaga_value_function("t.length", stdlib_t_length, 1, 1); + Value t_length_func = baba_yaga_value_function("t.length", stdlib_t_length_wrapper, 1, 1); scope_define(scope, "t.length", t_length_func, true); - Value t_has_func = baba_yaga_value_function("t.has", stdlib_t_has, 2, 2); + Value t_has_func = baba_yaga_value_function("t.has", stdlib_t_has_wrapper, 2, 2); scope_define(scope, "t.has", t_has_func, true); - Value t_get_func = baba_yaga_value_function("t.get", stdlib_t_get, 3, 3); + Value t_get_func = baba_yaga_value_function("t.get", stdlib_t_get_wrapper, 3, 3); scope_define(scope, "t.get", t_get_func, true); /* Internal table entry function for key-value pairs */ - Value table_entry_func = baba_yaga_value_function("table_entry", stdlib_table_entry, 2, 2); + Value table_entry_func = baba_yaga_value_function("table_entry", stdlib_table_entry_wrapper, 2, 2); scope_define(scope, "table_entry", table_entry_func, true); /* Create t namespace table */ @@ -660,6 +660,9 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { void* test_node = baba_yaga_ast_get_when_expr_test(node); Value test_value = interpreter_evaluate_expression(test_node, scope); + /* Check if test is a sequence (multi-parameter test) */ + bool is_multi_param_test = (baba_yaga_ast_get_type(test_node) == NODE_SEQUENCE); + /* Get patterns */ int pattern_count = baba_yaga_ast_get_when_expr_pattern_count(node); @@ -674,9 +677,69 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { void* pattern_test_node = baba_yaga_ast_get_when_pattern_test(pattern_node); Value pattern_test_value = interpreter_evaluate_expression(pattern_test_node, scope); + /* Check if pattern is a sequence (multi-parameter pattern) */ + bool is_multi_param_pattern = (baba_yaga_ast_get_type(pattern_test_node) == NODE_SEQUENCE); + /* Check if pattern matches */ bool matches = false; - if (pattern_test_value.type == VAL_NUMBER && test_value.type == VAL_NUMBER) { + + /* Handle multi-parameter pattern matching */ + if (is_multi_param_test && is_multi_param_pattern) { + /* Both test and pattern are sequences - compare element by element */ + int test_count = baba_yaga_ast_get_sequence_statement_count(test_node); + int pattern_count = baba_yaga_ast_get_sequence_statement_count(pattern_test_node); + + if (test_count == pattern_count) { + matches = true; + for (int j = 0; j < test_count; j++) { + void* test_elem_node = baba_yaga_ast_get_sequence_statement(test_node, j); + void* pattern_elem_node = baba_yaga_ast_get_sequence_statement(pattern_test_node, j); + + if (test_elem_node == NULL || pattern_elem_node == NULL) { + matches = false; + break; + } + + Value test_elem = interpreter_evaluate_expression(test_elem_node, scope); + Value pattern_elem = interpreter_evaluate_expression(pattern_elem_node, scope); + + /* Check if elements match */ + bool elem_matches = false; + if (pattern_elem.type == VAL_STRING && + strcmp(pattern_elem.data.string, "_") == 0) { + /* Wildcard element always matches */ + elem_matches = true; + } else if (pattern_elem.type == test_elem.type) { + switch (pattern_elem.type) { + case VAL_NUMBER: + elem_matches = (pattern_elem.data.number == test_elem.data.number); + break; + case VAL_STRING: + elem_matches = (strcmp(pattern_elem.data.string, test_elem.data.string) == 0); + break; + case VAL_BOOLEAN: + elem_matches = (pattern_elem.data.boolean == test_elem.data.boolean); + break; + default: + elem_matches = false; + break; + } + } + + if (!elem_matches) { + matches = false; + } + + /* Clean up element values */ + baba_yaga_value_destroy(&test_elem); + baba_yaga_value_destroy(&pattern_elem); + + if (!matches) { + break; + } + } + } + } else if (pattern_test_value.type == VAL_NUMBER && test_value.type == VAL_NUMBER) { matches = (pattern_test_value.data.number == test_value.data.number); } else if (pattern_test_value.type == VAL_STRING && test_value.type == VAL_STRING) { matches = (strcmp(pattern_test_value.data.string, test_value.data.string) == 0); @@ -686,6 +749,9 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { strcmp(pattern_test_value.data.string, "_") == 0) { /* Wildcard pattern always matches */ matches = true; + } else if (pattern_test_value.type == VAL_NIL && test_value.type == VAL_NIL) { + /* Both are nil - match */ + matches = true; } else if (pattern_test_value.type == VAL_TABLE && test_value.type == VAL_TABLE) { /* Table pattern matching: check if all pattern properties exist and match */ matches = true; diff --git a/js/scripting-lang/baba-yaga-c/src/parser.c b/js/scripting-lang/baba-yaga-c/src/parser.c index b7d8752..6c94913 100644 --- a/js/scripting-lang/baba-yaga-c/src/parser.c +++ b/js/scripting-lang/baba-yaga-c/src/parser.c @@ -889,22 +889,86 @@ static ASTNode* parser_parse_primary(Parser* parser) { ASTNode* value = NULL; /* Check if this is a key-value pair (any token: value) */ - if ((parser_peek(parser)->type == TOKEN_IDENTIFIER || - parser_peek(parser)->type == TOKEN_NUMBER || - parser_peek(parser)->type == TOKEN_BOOLEAN || - parser_peek(parser)->type == TOKEN_STRING) && - !parser_is_at_end(parser) && - parser_peek_next(parser)->type == TOKEN_COLON) { + + /* Check if this is a key-value pair */ + bool is_key_value_pair = false; + + if (parser_peek(parser)->type == TOKEN_LPAREN) { + /* For expression keys, we need to look ahead to find the colon */ + int look_ahead = parser->current; + int paren_count = 0; + bool found_colon = false; + + while (look_ahead < parser->token_count) { + Token* token = parser->tokens[look_ahead]; + if (token->type == TOKEN_LPAREN) { + paren_count++; + } else if (token->type == TOKEN_RPAREN) { + paren_count--; + if (paren_count == 0) { + /* We've found the closing parenthesis, check if next is colon */ + if (look_ahead + 1 < parser->token_count && + parser->tokens[look_ahead + 1]->type == TOKEN_COLON) { + found_colon = true; + } + break; + } + } else if (token->type == TOKEN_COMMA || token->type == TOKEN_RBRACE) { + /* Stop looking if we hit table boundaries */ + break; + } + look_ahead++; + } + is_key_value_pair = found_colon; + } else { + /* For literal keys, check if next token is colon */ + is_key_value_pair = (parser_peek(parser)->type == TOKEN_IDENTIFIER || + parser_peek(parser)->type == TOKEN_NUMBER || + parser_peek(parser)->type == TOKEN_BOOLEAN || + parser_peek(parser)->type == TOKEN_STRING) && + !parser_is_at_end(parser) && + parser_peek_next(parser)->type == TOKEN_COLON; + } + + if (is_key_value_pair) { /* Parse key-value pair */ - Token* key_token = parser_advance(parser); /* Consume the key token */ - if (key_token == NULL) { - /* Cleanup on error */ - for (int i = 0; i < element_count; i++) { - ast_destroy_node(elements[i]); + ASTNode* key_node = NULL; + Token* key_token = NULL; + + if (parser_peek(parser)->type == TOKEN_LPAREN) { + /* Parse expression key */ + key_node = parser_parse_expression(parser); + if (key_node == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + /* Create a dummy token for line/column info */ + key_token = parser_peek(parser); + if (key_token == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + ast_destroy_node(key_node); + return NULL; + } + } else { + /* Parse literal key */ + key_token = parser_advance(parser); /* Consume the key token */ + if (key_token == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; } - free(elements); - return NULL; } /* Consume colon */ @@ -975,32 +1039,40 @@ static ASTNode* parser_parse_primary(Parser* parser) { return NULL; } - /* Create key value based on token type */ - Value key_value; - if (key_token->type == TOKEN_IDENTIFIER) { - key_value = baba_yaga_value_string(key_token->lexeme); - } else if (key_token->type == TOKEN_NUMBER) { - key_value = baba_yaga_value_number(key_token->literal.number); - } else if (key_token->type == TOKEN_BOOLEAN) { - key_value = baba_yaga_value_boolean(key_token->literal.boolean); - } else if (key_token->type == TOKEN_STRING) { - key_value = baba_yaga_value_string(key_token->lexeme); + /* Create key value based on token type or expression */ + ASTNode* key_arg = NULL; + if (key_node != NULL) { + /* Expression key - use the parsed AST node */ + key_arg = key_node; } else { - /* Cleanup on error */ - for (int i = 0; i < element_count; i++) { - ast_destroy_node(elements[i]); + /* Literal key - create literal value from token */ + Value key_value; + if (key_token->type == TOKEN_IDENTIFIER) { + key_value = baba_yaga_value_string(key_token->lexeme); + } else if (key_token->type == TOKEN_NUMBER) { + key_value = baba_yaga_value_number(key_token->literal.number); + } else if (key_token->type == TOKEN_BOOLEAN) { + key_value = baba_yaga_value_boolean(key_token->literal.boolean); + } else if (key_token->type == TOKEN_STRING) { + key_value = baba_yaga_value_string(key_token->lexeme); + } else { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + free(entry_args); + ast_destroy_node(value); + return NULL; } - free(elements); - free(entry_args); - ast_destroy_node(value); - return NULL; + key_arg = ast_literal_node(key_value, key_token->line, key_token->column); } - entry_args[0] = ast_literal_node(key_value, key_token->line, key_token->column); + entry_args[0] = key_arg; entry_args[1] = value; - ASTNode* key_node = ast_identifier_node("table_entry", key_token->line, key_token->column); - if (key_node == NULL) { + ASTNode* table_entry_node = ast_identifier_node("table_entry", key_token->line, key_token->column); + if (table_entry_node == NULL) { /* Cleanup on error */ for (int i = 0; i < element_count; i++) { ast_destroy_node(elements[i]); @@ -1008,10 +1080,13 @@ static ASTNode* parser_parse_primary(Parser* parser) { free(elements); free(entry_args); ast_destroy_node(value); + if (key_node != NULL) { + ast_destroy_node(key_node); + } return NULL; } - ASTNode* entry_node = ast_function_call_node(key_node, entry_args, 2, key_token->line, key_token->column); + ASTNode* entry_node = ast_function_call_node(table_entry_node, entry_args, 2, key_token->line, key_token->column); if (entry_node == NULL) { /* Cleanup on error */ for (int i = 0; i < element_count; i++) { @@ -1019,8 +1094,11 @@ static ASTNode* parser_parse_primary(Parser* parser) { } free(elements); free(entry_args); - ast_destroy_node(key_node); + ast_destroy_node(table_entry_node); ast_destroy_node(value); + if (key_node != NULL) { + ast_destroy_node(key_node); + } return NULL; } @@ -2492,7 +2570,7 @@ static ASTNode* parser_parse_when_expression(Parser* parser) { int look_ahead = parser->current; int identifier_count = 0; - /* Count consecutive identifiers before 'is' */ + /* Count consecutive identifiers or expressions before 'is' */ while (look_ahead < parser->token_count) { Token* token = parser->tokens[look_ahead]; if (token->type == TOKEN_KEYWORD_IS) { @@ -2500,8 +2578,25 @@ static ASTNode* parser_parse_when_expression(Parser* parser) { } if (token->type == TOKEN_IDENTIFIER) { identifier_count++; + } else if (token->type == TOKEN_LPAREN) { + /* Expression in parentheses - count as one parameter */ + identifier_count++; + /* Skip to closing parenthesis */ + int paren_count = 1; + look_ahead++; + while (look_ahead < parser->token_count && paren_count > 0) { + Token* next_token = parser->tokens[look_ahead]; + if (next_token->type == TOKEN_LPAREN) { + paren_count++; + } else if (next_token->type == TOKEN_RPAREN) { + paren_count--; + } + look_ahead++; + } + /* Continue from the position after the closing parenthesis */ + continue; } else { - /* If we hit anything other than an identifier, it's not multi-parameter */ + /* If we hit anything other than an identifier or expression, it's not multi-parameter */ identifier_count = 0; break; } @@ -2515,17 +2610,44 @@ static ASTNode* parser_parse_when_expression(Parser* parser) { ASTNode* test; if (is_multi_param) { - /* Parse as sequence of identifiers */ + /* Parse as sequence of identifiers or expressions */ ASTNode** identifiers = malloc(identifier_count * sizeof(ASTNode*)); if (!identifiers) return NULL; for (int i = 0; i < identifier_count; i++) { - Token* id_token = parser_advance(parser); - identifiers[i] = ast_identifier_node(id_token->lexeme, id_token->line, id_token->column); + Token* current_token = parser_peek(parser); + if (current_token->type == TOKEN_LPAREN) { + /* Expression in parentheses - parse the expression */ + identifiers[i] = parser_parse_expression(parser); + if (identifiers[i] == NULL) { + /* Cleanup on error */ + for (int j = 0; j < i; j++) { + ast_destroy_node(identifiers[j]); + } + free(identifiers); + return NULL; + } + } else { + /* Identifier - parse as identifier */ + Token* id_token = parser_advance(parser); + identifiers[i] = ast_identifier_node(id_token->lexeme, id_token->line, id_token->column); + } } /* Create a sequence node for the identifiers */ test = ast_sequence_node(identifiers, identifier_count, when_token->line, when_token->column); + + /* Ensure we're positioned at the 'is' token */ + if (parser->current < parser->token_count && + parser->tokens[parser->current]->type != TOKEN_KEYWORD_IS) { + /* We're not at the 'is' token - find it */ + for (int j = parser->current; j < parser->token_count; j++) { + if (parser->tokens[j]->type == TOKEN_KEYWORD_IS) { + parser->current = j; + break; + } + } + } } else { /* Parse as single expression */ test = parser_parse_expression(parser); @@ -2638,7 +2760,8 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { int look_ahead = parser->current; int literal_count = 0; - /* Count consecutive literals before 'then' */ + /* Count consecutive literals or expressions before 'then' */ + DEBUG_DEBUG("Multi-parameter detection: starting at token %d", look_ahead); while (look_ahead < parser->token_count) { Token* token = parser->tokens[look_ahead]; if (token->type == TOKEN_KEYWORD_THEN) { @@ -2649,6 +2772,25 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { token->type == TOKEN_STRING || (token->type == TOKEN_IDENTIFIER && token->lexeme && strcmp(token->lexeme, "_") == 0)) { literal_count++; + } else if (token->type == TOKEN_LPAREN) { + /* Expression in parentheses - count as one pattern */ + DEBUG_DEBUG("Multi-parameter detection: found TOKEN_LPAREN at token %d", look_ahead); + literal_count++; + /* Skip to closing parenthesis */ + int paren_count = 1; + look_ahead++; + while (look_ahead < parser->token_count && paren_count > 0) { + Token* next_token = parser->tokens[look_ahead]; + if (next_token->type == TOKEN_LPAREN) { + paren_count++; + } else if (next_token->type == TOKEN_RPAREN) { + paren_count--; + } + look_ahead++; + } + DEBUG_DEBUG("Multi-parameter detection: finished expression, literal_count=%d, look_ahead=%d", literal_count, look_ahead); + /* Continue from the position after the closing parenthesis */ + continue; } else if (token->type == TOKEN_OP_EQUALS || token->type == TOKEN_OP_NOT_EQUALS || token->type == TOKEN_OP_LESS || @@ -2658,8 +2800,11 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { /* If we hit a comparison operator, it's not multi-parameter */ literal_count = 0; break; + } else if (token->type == TOKEN_SEMICOLON) { + /* If we hit a semicolon, stop looking */ + break; } else { - /* If we hit anything other than a literal, it's not multi-parameter */ + /* If we hit anything other than a literal or expression, it's not multi-parameter */ literal_count = 0; break; } @@ -2667,6 +2812,7 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { } /* If we have multiple literals followed by 'then', it's multi-parameter */ + DEBUG_DEBUG("Multi-parameter detection: final literal_count=%d, is_multi_param=%s", literal_count, literal_count > 1 ? "true" : "false"); if (literal_count > 1) { is_multi_param = true; } @@ -2678,26 +2824,41 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { if (!literals) return NULL; for (int i = 0; i < literal_count; i++) { - Token* lit_token = parser_advance(parser); - if (lit_token->type == TOKEN_IDENTIFIER && lit_token->lexeme && strcmp(lit_token->lexeme, "_") == 0) { - /* Wildcard pattern - treat as literal in multi-parameter context */ - literals[i] = ast_literal_node(baba_yaga_value_string("_"), lit_token->line, lit_token->column); - } else if (lit_token->type == TOKEN_IDENTIFIER) { - /* Identifier pattern */ - literals[i] = ast_identifier_node(lit_token->lexeme, lit_token->line, lit_token->column); - } else if (lit_token->type == TOKEN_NUMBER) { - /* Number pattern */ - literals[i] = ast_literal_node(baba_yaga_value_number(lit_token->literal.number), lit_token->line, lit_token->column); - } else if (lit_token->type == TOKEN_STRING) { - /* String pattern */ - literals[i] = ast_literal_node(baba_yaga_value_string(lit_token->lexeme), lit_token->line, lit_token->column); + Token* current_token = parser_peek(parser); + if (current_token->type == TOKEN_LPAREN) { + /* Expression pattern - parse the expression */ + literals[i] = parser_parse_expression(parser); + if (literals[i] == NULL) { + /* Cleanup on error */ + for (int j = 0; j < i; j++) { + ast_destroy_node(literals[j]); + } + free(literals); + return NULL; + } } else { - /* Cleanup on error */ - for (int j = 0; j < i; j++) { - ast_destroy_node(literals[j]); + /* Literal pattern */ + Token* lit_token = parser_advance(parser); + if (lit_token->type == TOKEN_IDENTIFIER && lit_token->lexeme && strcmp(lit_token->lexeme, "_") == 0) { + /* Wildcard pattern - treat as literal in multi-parameter context */ + literals[i] = ast_literal_node(baba_yaga_value_string("_"), lit_token->line, lit_token->column); + } else if (lit_token->type == TOKEN_IDENTIFIER) { + /* Identifier pattern */ + literals[i] = ast_identifier_node(lit_token->lexeme, lit_token->line, lit_token->column); + } else if (lit_token->type == TOKEN_NUMBER) { + /* Number pattern */ + literals[i] = ast_literal_node(baba_yaga_value_number(lit_token->literal.number), lit_token->line, lit_token->column); + } else if (lit_token->type == TOKEN_STRING) { + /* String pattern */ + literals[i] = ast_literal_node(baba_yaga_value_string(lit_token->lexeme), lit_token->line, lit_token->column); + } else { + /* Cleanup on error */ + for (int j = 0; j < i; j++) { + ast_destroy_node(literals[j]); + } + free(literals); + return NULL; } - free(literals); - return NULL; } } diff --git a/js/scripting-lang/baba-yaga-c/src/stdlib.c b/js/scripting-lang/baba-yaga-c/src/stdlib.c index ed34541..d3ebdea 100644 --- a/js/scripting-lang/baba-yaga-c/src/stdlib.c +++ b/js/scripting-lang/baba-yaga-c/src/stdlib.c @@ -16,6 +16,191 @@ #include "baba_yaga.h" /* ============================================================================ + * Wrapper Functions for Basic Operations (to match function signature) + * ============================================================================ */ + +Value stdlib_add_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_add(args, argc); +} + +Value stdlib_subtract_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_subtract(args, argc); +} + +Value stdlib_multiply_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_multiply(args, argc); +} + +Value stdlib_divide_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_divide(args, argc); +} + +Value stdlib_modulo_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_modulo(args, argc); +} + +Value stdlib_pow_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_pow(args, argc); +} + +Value stdlib_negate_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_negate(args, argc); +} + +Value stdlib_equals_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_equals(args, argc); +} + +Value stdlib_not_equals_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_not_equals(args, argc); +} + +Value stdlib_less_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_less(args, argc); +} + +Value stdlib_less_equal_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_less_equal(args, argc); +} + +Value stdlib_greater_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_greater(args, argc); +} + +Value stdlib_greater_equal_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_greater_equal(args, argc); +} + +Value stdlib_and_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_and(args, argc); +} + +Value stdlib_or_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_or(args, argc); +} + +Value stdlib_xor_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_xor(args, argc); +} + +Value stdlib_not_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_not(args, argc); +} + +Value stdlib_compose_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_compose(args, argc); +} + +Value stdlib_out_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_out(args, argc); +} + +Value stdlib_in_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_in(args, argc); +} + +Value stdlib_assert_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_assert(args, argc); +} + +Value stdlib_emit_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_emit(args, argc); +} + +Value stdlib_listen_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_listen(args, argc); +} + +Value stdlib_flip_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_flip(args, argc); +} + +Value stdlib_constant_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_constant(args, argc); +} + +Value stdlib_apply_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_apply(args, argc); +} + +/* Table operation wrappers */ +Value stdlib_t_map_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_map(args, argc); +} + +Value stdlib_t_filter_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_filter(args, argc); +} + +Value stdlib_t_reduce_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_reduce(args, argc); +} + +Value stdlib_t_set_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_set(args, argc); +} + +Value stdlib_t_delete_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_delete(args, argc); +} + +Value stdlib_t_merge_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_merge(args, argc); +} + +Value stdlib_t_length_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_length(args, argc); +} + +Value stdlib_t_has_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_has(args, argc); +} + +Value stdlib_t_get_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_get(args, argc); +} + +Value stdlib_table_entry_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_table_entry(args, argc); +} + +/* ============================================================================ * Standard Library Functions * ============================================================================ */ @@ -556,7 +741,7 @@ Value stdlib_listen(Value* args, int argc) { } /* Higher-order functions */ -Value stdlib_map(Value* args, int argc) { +Value stdlib_map(Value* args, int argc, Scope* scope) { if (argc != 2) { DEBUG_ERROR("map: expected 2 arguments, got %d", argc); return baba_yaga_value_nil(); @@ -575,61 +760,90 @@ Value stdlib_map(Value* args, int argc) { return baba_yaga_value_nil(); } - /* For now, return the original table */ - /* TODO: Implement actual mapping */ - DEBUG_DEBUG("map: mapping function over table"); - return baba_yaga_value_copy(&table); + DEBUG_DEBUG("map: applying function to each value in table"); + + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + Value result = baba_yaga_value_table(); + for (size_t i = 0; i < key_count; i++) { + Value value = baba_yaga_table_get_by_key(&table, keys[i]); + if (value.type != VAL_NIL) { + Value func_args[1] = {value}; + Value mapped_value = baba_yaga_function_call(&func, func_args, 1, scope); + result = baba_yaga_table_set(&result, keys[i], &mapped_value); + } + free(keys[i]); + } + return result; } -Value stdlib_filter(Value* args, int argc) { +Value stdlib_filter(Value* args, int argc, Scope* scope) { if (argc != 2) { DEBUG_ERROR("filter: expected 2 arguments, got %d", argc); return baba_yaga_value_nil(); } - Value func = args[0]; Value table = args[1]; - if (func.type != VAL_FUNCTION) { DEBUG_ERROR("filter: first argument must be a function"); return baba_yaga_value_nil(); } - if (table.type != VAL_TABLE) { DEBUG_ERROR("filter: second argument must be a table"); return baba_yaga_value_nil(); } - - /* For now, return the original table */ - /* TODO: Implement actual filtering */ - DEBUG_DEBUG("filter: filtering table with function"); - return baba_yaga_value_copy(&table); + DEBUG_DEBUG("filter: filtering table with predicate"); + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + Value result = baba_yaga_value_table(); + int result_index = 1; + for (size_t i = 0; i < key_count; i++) { + Value value = baba_yaga_table_get_by_key(&table, keys[i]); + if (value.type != VAL_NIL) { + Value func_args[1] = {value}; + Value predicate_result = baba_yaga_function_call(&func, func_args, 1, scope); + if (baba_yaga_value_is_truthy(&predicate_result)) { + char key_str[32]; + snprintf(key_str, sizeof(key_str), "%d", result_index++); + result = baba_yaga_table_set(&result, key_str, &value); + } + } + free(keys[i]); + } + return result; } -Value stdlib_reduce(Value* args, int argc) { +Value stdlib_reduce(Value* args, int argc, Scope* scope) { if (argc != 3) { DEBUG_ERROR("reduce: expected 3 arguments, got %d", argc); return baba_yaga_value_nil(); } - Value func = args[0]; Value initial = args[1]; Value table = args[2]; - if (func.type != VAL_FUNCTION) { DEBUG_ERROR("reduce: first argument must be a function"); return baba_yaga_value_nil(); } - if (table.type != VAL_TABLE) { DEBUG_ERROR("reduce: third argument must be a table"); return baba_yaga_value_nil(); } - - /* For now, return the initial value */ - /* TODO: Implement actual reduction */ DEBUG_DEBUG("reduce: reducing table with function"); - return baba_yaga_value_copy(&initial); + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + Value result = baba_yaga_value_copy(&initial); + for (size_t i = 0; i < key_count; i++) { + Value value = baba_yaga_table_get_by_key(&table, keys[i]); + if (value.type != VAL_NIL) { + Value func_args[2] = {result, value}; + Value new_result = baba_yaga_function_call(&func, func_args, 2, scope); + baba_yaga_value_destroy(&result); + result = new_result; + } + free(keys[i]); + } + return result; } /** @@ -639,12 +853,37 @@ Value stdlib_reduce(Value* args, int argc) { * @param argc Number of arguments (should be 3) * @return New table with function applied to each element */ -Value stdlib_each(Value* args, int argc) { - if (argc != 3) { - DEBUG_ERROR("each: expected 3 arguments, got %d", argc); +Value stdlib_each(Value* args, int argc, Scope* scope) { + if (argc < 2 || argc > 3) { + DEBUG_ERROR("each: expected 2 or 3 arguments, got %d", argc); return baba_yaga_value_nil(); } + /* Handle partial application: each function arg2 */ + if (argc == 2) { + Value func = args[0]; + Value arg2 = args[1]; + + if (func.type != VAL_FUNCTION) { + DEBUG_ERROR("each: first argument must be a function"); + return baba_yaga_value_nil(); + } + + /* Create a new function that applies the original function with the second argument */ + Value partial_func = baba_yaga_value_function("each_partial", stdlib_each_partial, 2, 2); + + /* Store the original function and second argument in the scope */ + char temp_name[32]; + snprintf(temp_name, sizeof(temp_name), "_each_func_%p", (void*)&func); + scope_define(scope, temp_name, func, true); + + char temp_name2[32]; + snprintf(temp_name2, sizeof(temp_name2), "_each_arg2_%p", (void*)&arg2); + scope_define(scope, temp_name2, arg2, true); + + return partial_func; + } + Value func = args[0]; Value table1 = args[1]; @@ -694,7 +933,7 @@ Value stdlib_each(Value* args, int argc) { Value func_args[2]; func_args[0] = element1; func_args[1] = element2; - Value element_result = baba_yaga_function_call(&func, func_args, 2, NULL); + Value element_result = baba_yaga_function_call(&func, func_args, 2, scope); /* Add result to new table */ result = baba_yaga_table_set(&result, keys[i], &element_result); @@ -719,7 +958,7 @@ Value stdlib_each(Value* args, int argc) { Value func_args[2]; func_args[0] = element; func_args[1] = arg3; - Value element_result = baba_yaga_function_call(&func, func_args, 2, NULL); + Value element_result = baba_yaga_function_call(&func, func_args, 2, scope); /* Add result to new table */ result = baba_yaga_table_set(&result, keys[i], &element_result); @@ -737,6 +976,144 @@ Value stdlib_each(Value* args, int argc) { } /** + * @brief Partial application helper for each function + * + * This function is called when a partial each function is applied with a table. + * It applies the original function to each element of the table with the second argument. + */ +/** + * @brief Partial application helper function + * + * This function is called when a partial function is applied with additional arguments. + * It combines the bound arguments with the new arguments and calls the original function. + */ +Value stdlib_partial_apply(Value* args, int argc, Scope* scope) { + /* Get the original function and bound arguments from the scope */ + char** names = malloc(100 * sizeof(char*)); + int name_count = scope_get_names(scope, names, 100); + + Value original_func = baba_yaga_value_nil(); + int bound_count = 0; + Value bound_args[10]; /* Assume max 10 bound arguments */ + + for (int i = 0; i < name_count; i++) { + if (strncmp(names[i], "_partial_func_", 14) == 0) { + original_func = scope_get(scope, names[i]); + } else if (strncmp(names[i], "_partial_count_", 15) == 0) { + Value count_val = scope_get(scope, names[i]); + if (count_val.type == VAL_NUMBER) { + bound_count = (int)count_val.data.number; + } + } else if (strncmp(names[i], "_partial_arg_", 13) == 0) { + /* Extract argument index from name like "_partial_arg_0_0x123" */ + char* underscore = strrchr(names[i], '_'); + if (underscore != NULL) { + int arg_index = atoi(underscore + 1); + if (arg_index >= 0 && arg_index < 10) { + bound_args[arg_index] = scope_get(scope, names[i]); + } + } + } + } + + /* Free the names array */ + for (int i = 0; i < name_count; i++) { + free(names[i]); + } + free(names); + + if (original_func.type != VAL_FUNCTION) { + DEBUG_ERROR("partial_apply: original function not found"); + return baba_yaga_value_nil(); + } + + /* Combine bound arguments with new arguments */ + Value combined_args[20]; /* Assume max 20 total arguments */ + int total_count = bound_count + argc; + + if (total_count > 20) { + DEBUG_ERROR("partial_apply: too many arguments"); + return baba_yaga_value_nil(); + } + + /* Copy bound arguments first */ + for (int i = 0; i < bound_count; i++) { + combined_args[i] = bound_args[i]; + } + + /* Copy new arguments */ + for (int i = 0; i < argc; i++) { + combined_args[bound_count + i] = args[i]; + } + + /* Call the original function with all arguments */ + return baba_yaga_function_call(&original_func, combined_args, total_count, scope); +} + +Value stdlib_each_partial(Value* args, int argc, Scope* scope) { + if (argc != 2) { + DEBUG_ERROR("each_partial: expected 2 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[1]; + if (table.type != VAL_TABLE) { + DEBUG_ERROR("each_partial: second argument must be a table"); + return baba_yaga_value_nil(); + } + + /* Get the original function and second argument from the scope */ + /* We need to find them by looking for the stored values */ + char** names = malloc(100 * sizeof(char*)); + int name_count = scope_get_names(scope, names, 100); + + Value original_func = baba_yaga_value_nil(); + Value arg2 = baba_yaga_value_nil(); + + for (int i = 0; i < name_count; i++) { + if (strncmp(names[i], "_each_func_", 11) == 0) { + original_func = scope_get(scope, names[i]); + } else if (strncmp(names[i], "_each_arg2_", 11) == 0) { + arg2 = scope_get(scope, names[i]); + } + } + + /* Free the names array */ + for (int i = 0; i < name_count; i++) { + free(names[i]); + } + free(names); + + if (original_func.type != VAL_FUNCTION) { + DEBUG_ERROR("each_partial: original function not found"); + return baba_yaga_value_nil(); + } + + /* Apply the original function to each element of the table with the second argument */ + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + + Value result = baba_yaga_value_table(); + + for (size_t i = 0; i < key_count; i++) { + Value element = baba_yaga_table_get_by_key(&table, keys[i]); + if (element.type != VAL_NIL) { + /* Call function with element and the second argument */ + Value func_args[2]; + func_args[0] = element; + func_args[1] = arg2; + Value element_result = baba_yaga_function_call(&original_func, func_args, 2, scope); + + /* Add result to new table */ + result = baba_yaga_table_set(&result, keys[i], &element_result); + } + free(keys[i]); + } + + return result; +} + +/** * @brief Flip combinator - reverses argument order of a function * * @param args Array of arguments [function] or [function, arg1, arg2] diff --git a/js/scripting-lang/baba-yaga-c/test_computed_keys.txt b/js/scripting-lang/baba-yaga-c/test_computed_keys.txt new file mode 100644 index 0000000..c71b911 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_computed_keys.txt @@ -0,0 +1,6 @@ +/* Test computed table keys */ +test_table : { + (1 + 1): "two" +}; + +..assert test_table[2] = "two"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_copy.txt b/js/scripting-lang/baba-yaga-c/test_copy.txt new file mode 100644 index 0000000..a67bf59 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_copy.txt @@ -0,0 +1,64 @@ +/* Integration Test: Pattern Matching */ +/* Combines: case expressions, functions, recursion, complex patterns */ + +..out "=== Integration Test: Pattern Matching ==="; + +/* Recursive factorial with case expressions */ +factorial : n -> + when n is + 0 then 1 + _ then n * (factorial (n - 1)); + +/* Pattern matching with multiple parameters */ +classify : x y -> + when x y is + 0 0 then "both zero" + 0 _ then "x is zero" + _ 0 then "y is zero" + _ _ then when x is + 0 then "x is zero (nested)" + _ then when y is + 0 then "y is zero (nested)" + _ then "neither zero"; + +/* Test factorial */ +fact5 : factorial 5; +fact3 : factorial 3; + +..assert fact5 = 120; +..assert fact3 = 6; + +/* Test classification */ +test1 : classify 0 0; +test2 : classify 0 5; +test3 : classify 5 0; +test4 : classify 5 5; + +..assert test1 = "both zero"; +..assert test2 = "x is zero"; +..assert test3 = "y is zero"; +..assert test4 = "neither zero"; + +/* Complex nested case expressions */ +analyze : x y z -> + when x y z is + 0 0 0 then "all zero" + 0 0 _ then "x and y zero" + 0 _ 0 then "x and z zero" + _ 0 0 then "y and z zero" + 0 _ _ then "only x zero" + _ 0 _ then "only y zero" + _ _ 0 then "only z zero" + _ _ _ then "none zero"; + +result1 : analyze 0 0 0; +result2 : analyze 0 1 1; +result3 : analyze 1 0 1; +result4 : analyze 1 1 1; + +..assert result1 = "all zero"; +..assert result2 = "only x zero"; +..assert result3 = "only y zero"; +..assert result4 = "none zero"; + +..out "Pattern matching integration test completed"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_exact_22.txt b/js/scripting-lang/baba-yaga-c/test_exact_22.txt new file mode 100644 index 0000000..446c2a5 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_exact_22.txt @@ -0,0 +1,9 @@ +/* Exact test from 22_parser_limitations.txt */ +test_multi_expr : x y -> + when (x % 2) (y % 2) is + 0 0 then "both even" + 0 1 then "x even, y odd" + 1 0 then "x odd, y even" + 1 1 then "both odd"; + +result : test_multi_expr 4 5; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_minimal.txt b/js/scripting-lang/baba-yaga-c/test_minimal.txt new file mode 100644 index 0000000..1e8f5c0 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_minimal.txt @@ -0,0 +1 @@ +test_multi_expr : x y -> when (x % 2) (y % 2) is 0 0 then "both even" 0 1 then "x even, y odd" 1 0 then "x odd, y even" 1 1 then "both odd"; result4 : test_multi_expr 4 6; ..out result4; diff --git a/js/scripting-lang/baba-yaga-c/test_new.txt b/js/scripting-lang/baba-yaga-c/test_new.txt new file mode 100644 index 0000000..a67bf59 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_new.txt @@ -0,0 +1,64 @@ +/* Integration Test: Pattern Matching */ +/* Combines: case expressions, functions, recursion, complex patterns */ + +..out "=== Integration Test: Pattern Matching ==="; + +/* Recursive factorial with case expressions */ +factorial : n -> + when n is + 0 then 1 + _ then n * (factorial (n - 1)); + +/* Pattern matching with multiple parameters */ +classify : x y -> + when x y is + 0 0 then "both zero" + 0 _ then "x is zero" + _ 0 then "y is zero" + _ _ then when x is + 0 then "x is zero (nested)" + _ then when y is + 0 then "y is zero (nested)" + _ then "neither zero"; + +/* Test factorial */ +fact5 : factorial 5; +fact3 : factorial 3; + +..assert fact5 = 120; +..assert fact3 = 6; + +/* Test classification */ +test1 : classify 0 0; +test2 : classify 0 5; +test3 : classify 5 0; +test4 : classify 5 5; + +..assert test1 = "both zero"; +..assert test2 = "x is zero"; +..assert test3 = "y is zero"; +..assert test4 = "neither zero"; + +/* Complex nested case expressions */ +analyze : x y z -> + when x y z is + 0 0 0 then "all zero" + 0 0 _ then "x and y zero" + 0 _ 0 then "x and z zero" + _ 0 0 then "y and z zero" + 0 _ _ then "only x zero" + _ 0 _ then "only y zero" + _ _ 0 then "only z zero" + _ _ _ then "none zero"; + +result1 : analyze 0 0 0; +result2 : analyze 0 1 1; +result3 : analyze 1 0 1; +result4 : analyze 1 1 1; + +..assert result1 = "all zero"; +..assert result2 = "only x zero"; +..assert result3 = "only y zero"; +..assert result4 = "none zero"; + +..out "Pattern matching integration test completed"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_pattern_expressions.txt b/js/scripting-lang/baba-yaga-c/test_pattern_expressions.txt new file mode 100644 index 0000000..1d6a35c --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_pattern_expressions.txt @@ -0,0 +1,10 @@ +/* Test multi-value pattern expressions */ +test_multi_expr : x y -> + when (x % 2) (y % 2) is + 0 0 then "both even" + 0 1 then "x even, y odd" + 1 0 then "x odd, y even" + 1 1 then "both odd"; + +result : test_multi_expr 4 5; +..assert result = "x even, y odd"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_simple_pattern.txt b/js/scripting-lang/baba-yaga-c/test_simple_pattern.txt new file mode 100644 index 0000000..4b75c96 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_simple_pattern.txt @@ -0,0 +1,7 @@ +/* Simple pattern test */ +test : x -> + when (x % 2) is + 0 then "even" + 1 then "odd"; + +result : test 4; \ No newline at end of file |