diff options
Diffstat (limited to 'js/scripting-lang')
45 files changed, 3837 insertions, 840 deletions
diff --git a/js/scripting-lang/.clj-kondo/.cache/v1/lock b/js/scripting-lang/.clj-kondo/.cache/v1/lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/js/scripting-lang/.clj-kondo/.cache/v1/lock diff --git a/js/scripting-lang/baba-yaga-c/.gitignore b/js/scripting-lang/baba-yaga-c/.gitignore index db4b342..54f6894 100644 --- a/js/scripting-lang/baba-yaga-c/.gitignore +++ b/js/scripting-lang/baba-yaga-c/.gitignore @@ -13,7 +13,7 @@ build/ CMakeCache.txt CMakeFiles/ cmake_install.cmake -Makefile +# Makefile # Coverage *.gcno diff --git a/js/scripting-lang/baba-yaga-c/COMP.md b/js/scripting-lang/baba-yaga-c/COMP.md new file mode 100644 index 0000000..33f25ae --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/COMP.md @@ -0,0 +1,882 @@ +# Baba Yaga JavaScript Implementation Architecture + +## Overview + +Baba Yaga is a functional scripting language implemented in JavaScript with a combinator-based architecture. The language emphasizes functional programming patterns, immutable data structures, and a consistent execution model where all operations are translated to function calls. + +## Core Architecture Principles + +### 1. Combinator Foundation +All language operations are translated to function calls to standard library combinators. This eliminates parsing ambiguity while preserving intuitive syntax. + +**Key Design Decision**: Operators like `+`, `-`, `*`, `/` are translated to `add(x, y)`, `subtract(x, y)`, `multiply(x, y)`, `divide(x, y)` respectively. + +### 2. Functional Programming Paradigm +- First-class functions with support for partial application and currying +- Immutable data structures (tables are never modified in-place) +- Pattern matching through `when` expressions +- Function composition via `via` operator + +### 3. Cross-Platform Compatibility +The implementation supports Node.js, Bun, and browser environments through environment detection and platform-specific adapters. + +## Language Components + +### 1. Lexer (`lexer.js`) + +**Purpose**: Converts source code into tokens for parsing. + +**Key Features**: +- Character-by-character scanning with lookahead +- Comprehensive token type enumeration (NUMBER, PLUS, MINUS, IDENTIFIER, etc.) +- Support for comments (single-line `//` and multi-line `/* */`) +- IO operations (`..in`, `..out`, `..assert`, `..listen`, `..emit`) +- Function references (`@functionName`) and arguments (`@(expression)`) +- String literals with escape sequences (`\n`, `\t`, `\r`, `\\`, `\"`) +- Detailed position tracking (line/column) for error reporting +- Minus operator disambiguation based on spacing context + +**Token Types**: +```javascript +export const TokenType = { + NUMBER: 'NUMBER', + PLUS: 'PLUS', + MINUS: 'MINUS', + UNARY_MINUS: 'UNARY_MINUS', + BINARY_MINUS: 'BINARY_MINUS', + MULTIPLY: 'MULTIPLY', + DIVIDE: 'DIVIDE', + IDENTIFIER: 'IDENTIFIER', + ASSIGNMENT: 'ASSIGNMENT', // ':' + ARROW: 'ARROW', // '->' + CASE: 'CASE', + OF: 'OF', + WHEN: 'WHEN', + IS: 'IS', + THEN: 'THEN', + WILDCARD: 'WILDCARD', // '_' + FUNCTION: 'FUNCTION', + LEFT_PAREN: 'LEFT_PAREN', // '(' + RIGHT_PAREN: 'RIGHT_PAREN', // ')' + LEFT_BRACE: 'LEFT_BRACE', // '{' + RIGHT_BRACE: 'RIGHT_BRACE', // '}' + LEFT_BRACKET: 'LEFT_BRACKET', // '[' + RIGHT_BRACKET: 'RIGHT_BRACKET', // ']' + SEMICOLON: 'SEMICOLON', // ';' + COMMA: 'COMMA', // ',' + DOT: 'DOT', // '.' + STRING: 'STRING', + TRUE: 'TRUE', + FALSE: 'FALSE', + AND: 'AND', + OR: 'OR', + XOR: 'XOR', + NOT: 'NOT', + EQUALS: 'EQUALS', // '==' + LESS_THAN: 'LESS_THAN', // '<' + GREATER_THAN: 'GREATER_THAN', // '>' + LESS_EQUAL: 'LESS_EQUAL', // '<=' + GREATER_EQUAL: 'GREATER_EQUAL', // '>=' + NOT_EQUAL: 'NOT_EQUAL', // '!=' + MODULO: 'MODULO', // '%' + POWER: 'POWER', // '^' + IO_IN: 'IO_IN', // '..in' + IO_OUT: 'IO_OUT', // '..out' + IO_ASSERT: 'IO_ASSERT', // '..assert' + IO_LISTEN: 'IO_LISTEN', // '..listen' + IO_EMIT: 'IO_EMIT', // '..emit' + FUNCTION_REF: 'FUNCTION_REF', // '@functionName' + FUNCTION_ARG: 'FUNCTION_ARG', // '@(expression)' + COMPOSE: 'COMPOSE' // 'via' +}; +``` + +**Critical Implementation Details**: +- Minus operator disambiguation: Uses spacing context to distinguish unary vs binary minus +- Function composition: `via` keyword for function composition +- IO operations: `..` prefix for all IO operations +- Function references: `@` prefix for function references + +**Token Structure**: +```javascript +/** + * @typedef {Object} Token + * @property {string} type - The token type from TokenType enum + * @property {*} [value] - The token's value (for literals and identifiers) + * @property {string} [name] - Function name (for FUNCTION_REF tokens) + * @property {number} line - Line number where token appears (1-indexed) + * @property {number} column - Column number where token appears (1-indexed) + */ +``` + +**Minus Operator Disambiguation Logic**: +```javascript +// Check spacing to determine token type +const isUnary = !hasLeadingWhitespace(); +const isBinary = hasLeadingAndTrailingSpaces(); +const isFollowedByNumber = current + 1 < input.length && /[0-9]/.test(input[current + 1]); + +if (isUnary && isFollowedByNumber) { + // Unary minus at start of expression: -5 + tokens.push({ type: TokenType.UNARY_MINUS, line, column }); +} else if (isBinary) { + // Binary minus with spaces: 5 - 3 + tokens.push({ type: TokenType.BINARY_MINUS, line, column }); +} else if (isFollowedByNumber) { + // Minus followed by number but not at start: 5-3 (legacy) + tokens.push({ type: TokenType.MINUS, line, column }); +} else { + // Fallback to legacy MINUS token for edge cases + tokens.push({ type: TokenType.MINUS, line, column }); +} +``` + +### 2. Parser (`parser.js`) + +**Purpose**: Converts tokens into an Abstract Syntax Tree (AST) using recursive descent parsing. + +**Architecture**: Combinator-based parsing where all operators become `FunctionCall` nodes. + +**Key Parsing Functions**: + +#### Operator Precedence (highest to lowest): +1. **Primary**: Literals, identifiers, parenthesized expressions, table access +2. **Factor**: Power expressions (`^`), unary operators (`not`, `-`) +3. **Term**: Multiplication, division, modulo (`*`, `/`, `%`) +4. **Expression**: Addition, subtraction, comparisons (`+`, `-`, `=`, `<`, `>`, etc.) +5. **Application**: Function application (juxtaposition) - left-associative +6. **Composition**: Function composition (`via`) - right-associative +7. **Logical**: Logical operators (`and`, `or`, `xor`) + +#### Function Application +- **Juxtaposition**: `f x` becomes `apply(f, x)` - left-associative +- **Composition**: `f via g` becomes `compose(f, g)` - right-associative +- **Parenthesized**: `f(x)` becomes `apply(f, x)` + +#### When Expressions (Pattern Matching) +```javascript +// Syntax: when value is pattern then result pattern then result; +{ + type: 'WhenExpression', + value: [value1, value2, ...], // Can be single value or array + cases: [ + { + pattern: [pattern1, pattern2, ...], // Can be single pattern or array + result: [result1, result2, ...] // Can be single result or array + } + ] +} +``` + +**Example When Expression Parsing**: +```javascript +// Input: when x is 42 then "correct" _ then "wrong"; +{ + type: 'WhenExpression', + value: { type: 'Identifier', value: 'x' }, + cases: [ + { + pattern: [{ type: 'NumberLiteral', value: 42 }], + result: [{ type: 'StringLiteral', value: 'correct' }] + }, + { + pattern: [{ type: 'WildcardPattern' }], + result: [{ type: 'StringLiteral', value: 'wrong' }] + } + ] +} +``` + +**Multi-Value Pattern Matching**: +```javascript +// Input: when x y is 0 0 then "both zero" _ _ then "not both"; +{ + type: 'WhenExpression', + value: [ + { type: 'Identifier', value: 'x' }, + { type: 'Identifier', value: 'y' } + ], + cases: [ + { + pattern: [ + { type: 'NumberLiteral', value: 0 }, + { type: 'NumberLiteral', value: 0 } + ], + result: [{ type: 'StringLiteral', value: 'both zero' }] + }, + { + pattern: [ + { type: 'WildcardPattern' }, + { type: 'WildcardPattern' } + ], + result: [{ type: 'StringLiteral', value: 'not both' }] + } + ] +} +``` + +**Pattern Types**: +- Literals (numbers, strings, booleans) +- Wildcards (`_`) +- Function references (`@functionName`) +- Comparison expressions (`x < 0`) +- Table patterns +- Parenthesized expressions + +#### Table Literals +Supports both key-value pairs and array-like entries: +```javascript +// Key-value: {name: "Alice", age: 30} +// Array-like: {1, 2, 3} // Auto-assigned keys 1, 2, 3 +// Mixed: {1, name: "Alice", 2} +``` + +**Table Literal AST Structure**: +```javascript +{ + type: 'TableLiteral', + entries: [ + { + key: { type: 'Identifier', value: 'name' }, + value: { type: 'StringLiteral', value: 'Alice' } + }, + { + key: null, // Array-like entry + value: { type: 'NumberLiteral', value: 1 } + }, + { + key: { type: 'NumberLiteral', value: 2 }, + value: { type: 'StringLiteral', value: 'value' } + } + ] +} +``` + +**Table Access AST Structure**: +```javascript +// table.property +{ + type: 'TableAccess', + table: { type: 'Identifier', value: 'table' }, + key: { type: 'Identifier', value: 'property' } +} + +// table[key] +{ + type: 'TableAccess', + table: { type: 'Identifier', value: 'table' }, + key: { type: 'Identifier', value: 'key' } +} + +// Chained access: table.property[key].nested +{ + type: 'TableAccess', + table: { + type: 'TableAccess', + table: { + type: 'TableAccess', + table: { type: 'Identifier', value: 'table' }, + key: { type: 'Identifier', value: 'property' } + }, + key: { type: 'Identifier', value: 'key' } + }, + key: { type: 'Identifier', value: 'nested' } +} +``` + +### 3. Interpreter (`lang.js`) + +**Purpose**: Evaluates AST nodes using the combinator foundation. + +**Core Architecture**: + +#### Standard Library Initialization +The interpreter initializes a comprehensive standard library with combinator functions: + +**Higher-Order Functions**: +- `map(f, x)`: Apply function to value or collection +- `compose(f, g)`: Function composition (right-associative) +- `apply(f, x)`: Function application +- `pipe(f, g)`: Left-to-right function composition +- `filter(p, x)`: Filter based on predicate +- `reduce(f, init, x)`: Reduce with binary function +- `curry(f, x, y)`: Currying support + +**Standard Library Implementation Example**: +```javascript +function initializeStandardLibrary(scope) { + // Map: Apply a function to a value or collection + scope.map = function(f, x) { + if (typeof f !== 'function') { + throw new Error('map: first argument must be a function'); + } + + if (x === undefined) { + // Partial application: return a function that waits for the second argument + return function(x) { + return scope.map(f, x); + }; + } + + // Handle tables (APL-style element-wise operations) + if (typeof x === 'object' && x !== null && !Array.isArray(x)) { + const result = {}; + for (const [key, value] of Object.entries(x)) { + result[key] = f(value); + } + return result; + } + + // Default: apply to single value + return f(x); + }; + + // Compose: Combine two functions into a new function + scope.compose = function(f, g) { + if (typeof f !== 'function') { + throw new Error(`compose: first argument must be a function, got ${typeof f}`); + } + + if (g === undefined) { + // Partial application: return a function that waits for the second argument + return function(g) { + if (typeof g !== 'function') { + throw new Error(`compose: second argument must be a function, got ${typeof g}`); + } + return function(x) { + return f(g(x)); + }; + }; + } + + if (typeof g !== 'function') { + throw new Error(`compose: second argument must be a function, got ${typeof g}`); + } + + return function(x) { + return f(g(x)); + }; + }; + + // Apply: Apply a function to an argument + scope.apply = function(f, x) { + if (typeof f !== 'function') { + throw new Error('apply: first argument must be a function'); + } + + if (x === undefined) { + // Partial application: return a function that waits for the second argument + return function(x) { + return f(x); + }; + } + + // Full application: apply the function to the argument + return f(x); + }; +} +``` + +**Arithmetic Combinators**: +- `add(x, y)`, `subtract(x, y)`, `multiply(x, y)`, `divide(x, y)` +- `modulo(x, y)`, `power(x, y)`, `negate(x)` + +**Arithmetic Combinator Implementation**: +```javascript +// Add: Add two numbers +scope.add = function(x, y) { + if (y === undefined) { + // Partial application: return a function that waits for the second argument + return function(y) { + return x + y; + }; + } + return x + y; +}; + +// Subtract: Subtract second number from first +scope.subtract = function(x, y) { + if (y === undefined) { + // Partial application: return a function that waits for the second argument + return function(y) { + return x - y; + }; + } + return x - y; +}; + +// Multiply: Multiply two numbers +scope.multiply = function(x, y) { + if (y === undefined) { + // Partial application: return a function that waits for the second argument + return function(y) { + return x * y; + }; + } + return x * y; +}; + +// Divide: Divide first number by second +scope.divide = function(x, y) { + if (y === undefined) { + // Partial application: return a function that waits for the second argument + return function(y) { + if (y === 0) { + throw new Error('Division by zero'); + } + return x / y; + }; + } + if (y === 0) { + throw new Error('Division by zero'); + } + return x / y; +}; + +// Negate: Negate a number +scope.negate = function(x) { + return -x; +}; +``` + +**Comparison Combinators**: +- `equals(x, y)`, `notEquals(x, y)` +- `lessThan(x, y)`, `greaterThan(x, y)` +- `lessEqual(x, y)`, `greaterEqual(x, y)` + +**Logical Combinators**: +- `logicalAnd(x, y)`, `logicalOr(x, y)`, `logicalXor(x, y)`, `logicalNot(x)` + +**Enhanced Combinators**: +- `identity(x)`: Returns input unchanged +- `constant(x)`: Creates constant function +- `flip(f, x, y)`: Flips argument order +- `each(f, x)`: Multi-argument element-wise operations + +#### Table Operations Namespace (`t.`) +Immutable table operations: +- `t.map(f, table)`: Apply function to table values +- `t.filter(p, table)`: Filter table values +- `t.reduce(f, init, table)`: Reduce table values +- `t.set(table, key, value)`: Immutable set +- `t.delete(table, key)`: Immutable delete +- `t.merge(table1, table2)`: Immutable merge +- `t.pairs(table)`, `t.keys(table)`, `t.values(table)` +- `t.length(table)`, `t.has(table, key)`, `t.get(table, key, default)` + +#### Scope Management +- **Global Scope**: Prototypal inheritance for variable lookup +- **Local Scope**: Function parameters create new scope inheriting from global +- **Forward Declaration**: Recursive functions supported through placeholder creation + +**Scope Management Pattern**: +```javascript +// Global scope: Object with standard library functions +const globalScope = { ...initialState }; +initializeStandardLibrary(globalScope); + +// Local scope: Prototypal inheritance from global scope +const localScope = Object.create(globalScope); +// Local variables shadow global variables + +// Forward declaration for recursive functions: +// 1. Create placeholder function in global scope +// 2. Evaluate function definition (can reference placeholder) +// 3. Replace placeholder with actual function +``` + +#### Evaluation Functions +1. **`evalNode(node)`**: Global scope evaluation +2. **`localEvalNodeWithScope(node, scope)`**: Local scope evaluation +3. **`localEvalNode(node)`**: Internal recursion + +#### IO Operations +- **`..in`**: Read from standard input +- **`..out expression`**: Write expression result to standard output +- **`..assert expression`**: Assert condition is true +- **`..listen`**: Get current state from external system +- **`..emit expression`**: Send value to external system + +**IO Operations Implementation**: +```javascript +// IO Input: Read from standard input +case 'IOInExpression': + const rl = createReadline(); + return new Promise((resolve) => { + rl.question('', (input) => { + rl.close(); + const num = parseInt(input); + resolve(isNaN(num) ? input : num); + }); + }); + +// IO Output: Write to standard output +case 'IOOutExpression': + const outputValue = evalNode(node.value); + safeConsoleLog(outputValue); + ioOperationsPerformed = true; + return outputValue; + +// IO Assert: Assert condition is true +case 'IOAssertExpression': + const assertionValue = evalNode(node.value); + if (!assertionValue) { + throw new Error('Assertion failed'); + } + return assertionValue; + +// IO Listen: Get current state from external system +case 'IOListenExpression': + if (environment && typeof environment.getCurrentState === 'function') { + return environment.getCurrentState(); + } else { + return { status: 'placeholder', message: 'State not available in standalone mode' }; + } + +// IO Emit: Send value to external system +case 'IOEmitExpression': + const emitValue = evalNode(node.value); + if (environment && typeof environment.emitValue === 'function') { + environment.emitValue(emitValue); + } else { + safeConsoleLog('[EMIT]', emitValue); + } + ioOperationsPerformed = true; + return emitValue; +``` + +## Data Types + +### 1. Primitives +- **Numbers**: JavaScript numbers (integers and floats) +- **Strings**: JavaScript strings with escape sequences +- **Booleans**: `true` and `false` +- **Functions**: First-class functions with partial application + +### 2. Tables +- **Structure**: JavaScript objects with string/number keys +- **Immutability**: All operations return new tables +- **APL-style Operations**: Element-wise operations on table values +- **Access**: Dot notation (`table.property`) and bracket notation (`table[key]`) + +### 3. Special Types +- **Wildcard Pattern**: `_` (matches any value) +- **Function References**: `@functionName` +- **Function Arguments**: `@(expression)` + +## Function System + +### 1. Function Definitions +```javascript +// Arrow functions +f : x y -> x + y; + +// Traditional functions +function(x, y) : x + y; + +// Table functions +{add: x y -> x + y, multiply: x y -> x * y} +``` + +### 2. Function Application +- **Juxtaposition**: `f x` (left-associative) +- **Parenthesized**: `f(x)` +- **Composition**: `f via g` (right-associative) + +### 3. Partial Application +All functions support partial application: +```javascript +add 5 // Returns function that adds 5 +map @add 5 // Returns function that adds 5 to each element +``` + +**Partial Application Implementation Pattern**: +```javascript +// All standard library functions follow this pattern: +function exampleFunction(x, y) { + if (y === undefined) { + // Partial application: return a function that waits for the second argument + return function(y) { + return exampleFunction(x, y); + }; + } + // Full application: perform the operation + return x + y; // or whatever the operation is +} + +// This enables currying patterns: +const addFive = add 5; // Returns function that adds 5 +const result = addFive 3; // Returns 8 +const double = multiply 2; // Returns function that multiplies by 2 +const doubled = map @double; // Returns function that doubles each element +``` + +## Error Handling + +### 1. Lexer Errors +- Unexpected characters with line/column information +- Malformed tokens (invalid numbers, strings, etc.) + +### 2. Parser Errors +- Unexpected tokens with context +- Missing delimiters (parentheses, braces, etc.) +- Malformed expressions + +### 3. Interpreter Errors +- Undefined variables/functions +- Type mismatches +- Division by zero +- Table access errors +- Pattern matching failures + +### 4. Call Stack Tracking +Comprehensive call stack tracking for debugging: +```javascript +const callStackTracker = { + stack: [], + push: (functionName, context) => { /* ... */ }, + pop: () => { /* ... */ }, + reset: () => { /* ... */ }, + getTrace: () => { /* ... */ } +}; +``` + +**Call Stack Tracking**: +```javascript +const callStackTracker = { + stack: [], + push: (functionName, context) => { /* ... */ }, + pop: () => { /* ... */ }, + reset: () => { /* ... */ }, + getTrace: () => { /* ... */ } +}; +``` + +## Cross-Platform Support + +### 1. Environment Detection +```javascript +const isNode = typeof process !== 'undefined' && process.versions && process.versions.node; +const isBun = typeof process !== 'undefined' && process.versions && process.versions.bun; +const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; +``` + +### 2. Platform-Specific Adapters +- **Readline**: Node.js/Bun use `readline`, browser uses `prompt()` +- **File System**: Node.js/Bun use `fs`, browser uses mock +- **Console**: Safe console logging across platforms +- **Process Exit**: Node.js/Bun use `process.exit()`, browser throws error + +## Debug Support + +### 1. Debug Mode +```javascript +const DEBUG = (isNode && process.env.DEBUG) || (isBrowser && window.DEBUG) || false; +``` + +### 2. Debug Functions +- `debugLog(message, data)`: Safe logging across platforms +- `debugError(message, error)`: Error logging with stack traces +- Comprehensive debug output in parser and interpreter + +## External System Integration + +### 1. Environment Interface +```javascript +/** + * @typedef {Object} Environment + * @property {Function} getCurrentState - Returns current state from external system + * @property {Function} emitValue - Sends value to external system + */ +``` + +### 2. IO Operations +- **Listen**: `environment.getCurrentState()` +- **Emit**: `environment.emitValue(value)` + +## Performance Considerations + +### 1. Lazy Evaluation +- Functions are only evaluated when called +- Partial application enables deferred execution + +### 2. Immutable Data +- Tables are never modified in-place +- New structures created for transformations + +### 3. Scope Optimization +- Prototypal inheritance for efficient variable lookup +- Local scopes inherit from global scope + +## Compatibility Requirements for C Implementation + +### 1. Token Types +Must implement all token types from `TokenType` enumeration with identical names and semantics. + +### 2. AST Node Types +Must support all AST node types with identical structure: +- `Program`, `NumberLiteral`, `StringLiteral`, `BooleanLiteral` +- `Identifier`, `FunctionCall`, `FunctionDeclaration`, `FunctionDefinition` +- `Assignment`, `WhenExpression`, `WildcardPattern` +- `TableLiteral`, `TableAccess`, `FunctionReference` +- IO expression types (`IOInExpression`, `IOOutExpression`, etc.) + +### 3. Standard Library Functions +Must implement all standard library functions with identical signatures and behavior: +- Higher-order functions (`map`, `compose`, `apply`, etc.) +- Arithmetic combinators (`add`, `subtract`, `multiply`, etc.) +- Comparison combinators (`equals`, `lessThan`, etc.) +- Logical combinators (`logicalAnd`, `logicalOr`, etc.) +- Table operations namespace (`t.map`, `t.filter`, etc.) + +### 4. Operator Precedence +Must implement identical operator precedence and associativity rules. + +### 5. Function Application +Must support juxtaposition (left-associative) and composition (right-associative) with identical semantics. + +### 6. Pattern Matching +Must implement `when` expressions with identical pattern matching semantics. + +### 7. Error Handling +Must provide similar error messages and context information. + +### 8. IO Operations +Must support all IO operations (`..in`, `..out`, `..assert`, `..listen`, `..emit`) with identical behavior. + +## Testing Strategy + +The JavaScript implementation includes comprehensive test suites that should be used to validate C implementation compatibility: + +1. **Lexer Tests**: Token recognition and error handling +2. **Parser Tests**: AST generation and operator precedence +3. **Interpreter Tests**: Expression evaluation and function behavior +4. **Integration Tests**: End-to-end language features +5. **Error Tests**: Error handling and reporting + +**Test File Structure**: +``` +tests/ +├── 01_lexer_basic.txt +├── 02_arithmetic_operations.txt +├── 03_comparison_operators.txt +├── 04_logical_operators.txt +├── 05_io_operations.txt +├── 06_function_definitions.txt +├── 07_case_expressions.txt +├── 08_first_class_functions.txt +├── 09_tables.txt +├── 10_standard_library.txt +├── 11_edge_cases.txt +├── 12_advanced_tables.txt +├── 13_standard_library_complete.txt +├── 14_error_handling.txt +├── 15_performance_stress.txt +├── 16_function_composition.txt +├── 17_table_enhancements.txt +├── 18_each_combinator.txt +├── 19_embedded_functions.txt +├── 20_via_operator.txt +├── 21_enhanced_case_statements.txt +├── 22_parser_limitations.txt +├── 23_minus_operator_spacing.txt +└── integration_*.txt +``` + +**Example Test Format**: +```javascript +// Test file: 02_arithmetic_operations.txt +// Test basic arithmetic operations + +// Test addition +x : 5 + 3; +..out x; // Expected: 8 + +// Test subtraction +y : 10 - 4; +..out y; // Expected: 6 + +// Test multiplication +z : 6 * 7; +..out z; // Expected: 42 + +// Test division +w : 20 / 4; +..out w; // Expected: 5 + +// Test unary minus +neg : -5; +..out neg; // Expected: -5 + +// Test operator precedence +result : 2 + 3 * 4; +..out result; // Expected: 14 (not 20) +``` + +**Critical Test Cases for C Implementation**: +1. **Operator Precedence**: Ensure `2 + 3 * 4` evaluates to 14, not 20 +2. **Function Application**: Test juxtaposition `f x` vs parenthesized `f(x)` +3. **Partial Application**: Verify `add 5` returns a function +4. **Pattern Matching**: Test `when` expressions with various patterns +5. **Table Operations**: Verify immutable table operations +6. **Error Handling**: Test division by zero, undefined variables, etc. +7. **IO Operations**: Test all IO operations (`..in`, `..out`, `..assert`, etc.) +8. **Function Composition**: Test `via` operator and `compose` function +9. **Scope Management**: Test variable shadowing and recursive functions +10. **Edge Cases**: Test empty programs, malformed syntax, etc. + +## Conclusion + +The JavaScript implementation provides a robust, well-documented foundation for the baba yaga scripting language. The C implementation should maintain strict compatibility with this architecture to ensure consistent behavior across platforms and enable seamless migration between implementations. + +Key architectural decisions that must be preserved: +1. Combinator foundation for all operations +2. Functional programming paradigm with immutable data +3. Comprehensive standard library with partial application support +4. Pattern matching through when expressions +5. Cross-platform IO operations +6. Detailed error reporting and debugging support + +## Implementation Checklist for C Team + +### Phase 1: Core Infrastructure +- [ ] Implement all token types from `TokenType` enumeration +- [ ] Implement lexer with character-by-character scanning +- [ ] Implement parser with recursive descent parsing +- [ ] Implement basic AST node types +- [ ] Implement operator precedence and associativity rules + +### Phase 2: Standard Library +- [ ] Implement all arithmetic combinators (`add`, `subtract`, `multiply`, `divide`, etc.) +- [ ] Implement all comparison combinators (`equals`, `lessThan`, etc.) +- [ ] Implement all logical combinators (`logicalAnd`, `logicalOr`, etc.) +- [ ] Implement higher-order functions (`map`, `compose`, `apply`, etc.) +- [ ] Implement table operations namespace (`t.map`, `t.filter`, etc.) + +### Phase 3: Language Features +- [ ] Implement function definitions and application +- [ ] Implement partial application and currying +- [ ] Implement `when` expressions with pattern matching +- [ ] Implement table literals and access +- [ ] Implement function composition with `via` operator + +### Phase 4: IO and Integration +- [ ] Implement all IO operations (`..in`, `..out`, `..assert`, `..listen`, `..emit`) +- [ ] Implement environment interface for external system integration +- [ ] Implement cross-platform compatibility layer +- [ ] Implement error handling and debugging support + +### Phase 5: Testing and Validation +- [ ] Run all JavaScript test suites against C implementation +- [ ] Verify identical behavior for all language constructs +- [ ] Test edge cases and error conditions +- [ ] Performance testing and optimization + +## References + +- **Source Files**: `lexer.js`, `parser.js`, `lang.js` +- **Test Suite**: `tests/` directory with comprehensive test cases +- **Documentation**: `tutorials/` directory with language tutorials +- **Web Interface**: `web/` directory with AST viewer and interactive examples + +The C implementation should strive for 100% compatibility with the JavaScript version to ensure a seamless developer experience across both platforms. \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/Makefile b/js/scripting-lang/baba-yaga-c/Makefile new file mode 100644 index 0000000..3cffe4f --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/Makefile @@ -0,0 +1,78 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Werror -std=gnu99 -g -O2 +LDFLAGS = -lm + +# Debug flags +DEBUG_CFLAGS = -Wall -Wextra -Werror -std=gnu99 -g -O0 -DDEBUG +RELEASE_CFLAGS = -Wall -Wextra -Werror -std=gnu99 -g -O2 + +# Static analysis tools +CLANG_TIDY = clang-tidy +CPPCHECK = cppcheck + +# Memory checking +VALGRIND = valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes + +# Directories +SRCDIR = src +INCDIR = include +OBJDIR = obj +BINDIR = bin +TESTDIR = tests + +# Files +SOURCES = $(wildcard $(SRCDIR)/*.c) +OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) +TARGET = $(BINDIR)/baba-yaga + +.PHONY: all clean test check style memcheck coverage docs debug release + +all: $(TARGET) + +$(TARGET): $(OBJECTS) | $(BINDIR) + $(CC) $(OBJECTS) -o $@ $(LDFLAGS) + +$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) + $(CC) $(CFLAGS) -I$(INCDIR) -c $< -o $@ + +$(BINDIR) $(OBJDIR): + mkdir -p $@ + +clean: + rm -rf $(OBJDIR) $(BINDIR) + +# Debug and release builds +debug: CFLAGS = $(DEBUG_CFLAGS) +debug: clean $(TARGET) + +release: CFLAGS = $(RELEASE_CFLAGS) +release: clean $(TARGET) + +# Quality checks +check: style memcheck + +style: + $(CLANG_TIDY) $(SOURCES) -- -I$(INCDIR) + $(CPPCHECK) --enable=all --std=c99 $(SRCDIR) + +memcheck: $(TARGET) + $(VALGRIND) $(TARGET) --test $(TESTDIR) + +test: $(TARGET) + @echo "Running tests..." + @for test_file in $(TESTDIR)/*.txt; do \ + if [ -f "$$test_file" ]; then \ + echo "Testing $$(basename $$test_file)"; \ + $(TARGET) -t "$$test_file" || exit 1; \ + fi; \ + done + @echo "All tests passed!" + +coverage: CFLAGS += -fprofile-arcs -ftest-coverage +coverage: LDFLAGS += -lgcov +coverage: clean $(TARGET) + $(TARGET) --test $(TESTDIR) + gcov $(SOURCES) + +docs: + doxygen Doxyfile \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/REQ.md b/js/scripting-lang/baba-yaga-c/REQ.md new file mode 100644 index 0000000..81a8c90 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/REQ.md @@ -0,0 +1,214 @@ +# Requirements and Implementation Guidance for Baba Yaga C Implementation + +## Response to C Implementation Team Questions + +### Scope Chain Semantics + +**Q: When evaluating a sequence of statements, are all variable declarations and lookups expected to occur in the same (global) scope?** + +**A:** **CORRECTION**: The JavaScript implementation uses a **hybrid scope model** with both global and local scopes. + +**Key Points:** +- **Global Scope**: All top-level variable declarations and function definitions are stored in a single global environment object +- **Local Scopes**: Function calls create new local scopes using prototypal inheritance (`Object.create(globalScope)`) +- **Variable Lookup**: Local scopes inherit from global scope, allowing access to global variables +- **Function Parameters**: Create local variables in the function's scope + +**Q: Are there any cases where a new scope is created implicitly?** + +**A:** **CORRECTION**: Yes, the JavaScript implementation creates local scopes for function calls. + +**Scope Creation:** +- **Function Calls**: Create new local scopes using `Object.create(globalScope)` +- **Function Parameters**: Become local variables in the function scope +- **`when` expressions**: No new scope created +- **Table literals**: No new scope created +- **Other constructs**: No new scope created + +### Variable Lookup and Shadowing + +**Q: If a variable is declared in a sequence, is it immediately available for lookup in subsequent statements?** + +**A:** **CORRECTION**: Yes, for global variables. However, local variables in function scopes are only available within that function. + +**Global Scope Behavior:** +``` +x : 5; +y : 3; +sum : x + y; // x and y are immediately available in global scope +``` + +**Local Scope Behavior:** +``` +func : (param) -> { + local : param + 1; // local is only available within this function + return local; +}; +// local is NOT available here in global scope +``` + +**Q: How does the JS implementation handle variable shadowing or redeclaration?** + +**A:** **CORRECTION**: The JavaScript implementation uses **prototypal inheritance shadowing** for local scopes and **overwrites** for global redeclaration. + +**Behavior:** +- **Global Scope**: Variable redeclaration overwrites the previous value (no error) +- **Local Scopes**: Function parameters and local variables shadow global variables +- **Lookup Order**: Local scope first, then global scope (prototypal inheritance) +- **No Block Scoping**: No nested block-level scopes exist + +**Global Redeclaration Example:** +``` +x : 5; +x : 10; // x is now 10, previous value 5 is lost +result : x; // result = 10 +``` + +**IMPORTANT CORRECTION**: The above redeclaration behavior appears to be incorrect based on functional programming principles and test evidence. See the "Variable Redeclaration" section below for the corrected implementation. + +**Local Shadowing Example:** +``` +global_var : 5; +func : (global_var) -> { + // global_var parameter shadows the global variable + return global_var + 1; // uses parameter, not global +}; +result : func(10); // result = 11, global_var still = 5 +``` + +### Table Pattern Matching + +**Q: When matching a table pattern in a when expression, is the pattern table compared by key and value only?** + +**A:** Yes, table pattern matching is a **simple key-value comparison**. The JavaScript implementation performs a shallow comparison of table properties. + +**Matching Rules:** +- Keys must match exactly (string comparison) +- Values must be equal (using `===` semantics) +- No prototype chain traversal +- No hidden property checking +- No deep object comparison + +**Example:** +``` +table : {a: 1, b: 2}; +result : when table + {a: 1, b: 2} -> "exact match" + {a: 1} -> "partial match" + _ -> "no match" +``` + +**Q: Are there edge cases in table pattern matching?** + +**A:** The JavaScript implementation treats table patterns as simple object comparisons. No special edge cases beyond standard JavaScript object equality semantics. + +### Function Call Semantics + +**Q: What are the exact rules for when an identifier is treated as a function vs. a value?** + +**A:** The JavaScript implementation uses **parse-time function detection** based on syntax, not runtime type checking. + +**Function Call Rules:** +1. **Parse-time detection**: If an identifier is followed by parentheses `()` or expressions that could be arguments, it's treated as a function call +2. **Scope creation**: Function calls create new local scopes using prototypal inheritance +3. **No runtime type checking**: The system doesn't verify if the identifier actually contains a function +4. **Runtime errors**: If you call a non-function value, it will attempt to execute it as a function (causes runtime error) + +**Examples:** +``` +x : 5; +func : (a, b) -> a + b; + +result1 : func(1, 2); // Function call - works +result2 : x(1, 2); // Non-function call - runtime error +``` + +**Important:** The distinction is made at parse time based on syntax, not at runtime based on the actual value type. + +### Test 05 IO Operations + +**Q: Are there any known quirks regarding order of evaluation or scope for IO operations?** + +**A:** IO operations follow the same global scope rules as all other operations. + +**IO Behavior:** +- IO operations use the current scope (global or local) +- No special scoping for IO functions +- Order of evaluation follows normal left-to-right sequence evaluation +- IO operations can reference any variables in the current scope + +**Example:** +``` +x : 5; +print(x); // Prints 5 +y : 10; +print(y); // Prints 10 +print(x + y); // Prints 15 +``` + +### Implementation Recommendations + +**For C Implementation:** + +1. **Hybrid Scope Model**: Implement both global scope and local function scopes +2. **Prototypal Inheritance**: Local scopes should inherit from global scope +3. **Variable Lookup Order**: Local scope first, then global scope +4. **Function Parameter Scoping**: Function parameters create local variables +5. **Simple Table Comparison**: Use shallow key-value comparison for table pattern matching +6. **Parse-time Function Detection**: Determine function calls at parse time, not runtime +7. **Allow Global Redeclaration**: Permit variable redeclaration in global scope without errors + +**Key Implementation Pattern:** +```c +// Global environment +typedef struct { + char* name; + Value value; +} Variable; + +Variable* global_env[MAX_VARS]; +int global_env_size = 0; + +// Local scope (for function calls) +typedef struct { + Variable* local_vars[MAX_VARS]; + int local_size; + Variable** parent_scope; // Reference to global scope +} LocalScope; + +// Variable lookup: check local scope first, then global scope +``` + +## Variable Redeclaration + +**Q: Is variable redeclaration allowed?** +**A:** **NO** - Variable redeclaration is NOT allowed in Baba Yaga. This is a functional programming language where all values are immutable once declared. + +**Evidence:** +- None of the test files contain variable redeclarations +- Functional programming principles require immutability +- Current C implementation correctly prevents redeclaration (returns false if variable exists) + +**Implementation:** When defining a variable, if it already exists in the current scope, return an error rather than overwriting the value. + +**Note:** The JS team's previous response about allowing redeclaration appears to be incorrect or outdated. The language design clearly favors functional programming principles. + +### Testing Strategy + +**Focus Areas for C Implementation:** +1. Verify hybrid scope model (global + local function scopes) +2. Test variable shadowing in function parameters +3. Confirm table pattern matching uses simple key-value comparison +4. Validate parse-time function call detection +5. Ensure IO operations use current scope (global or local) + +**Critical Test Cases:** +- Variable redeclaration in global scope (should overwrite, not error) +- Function parameter shadowing of global variables +- Cross-statement variable access in global scope +- Local variable isolation within functions +- Table pattern matching with exact and partial matches +- Function calls vs. value references +- IO operations with variables from current scope + +This should resolve the scope-related test failures and ensure the C implementation matches the JavaScript reference semantics exactly. diff --git a/js/scripting-lang/baba-yaga-c/ROADMAP.md b/js/scripting-lang/baba-yaga-c/ROADMAP.md index 2d8b827..5f44ca4 100644 --- a/js/scripting-lang/baba-yaga-c/ROADMAP.md +++ b/js/scripting-lang/baba-yaga-c/ROADMAP.md @@ -1,748 +1,209 @@ -# Baba Yaga C Implementation - Complete Roadmap - -## Executive Summary - -**Current Status**: ✅ **Major Breakthrough Achieved** - Infix operator system now working -**Overall Progress**: ~85% Complete -**Test Results**: 26/26 basic tests PASS, 51/66 standard library tests PASS, 5/27 JavaScript tests PASS -**Critical Fix Applied**: ✅ **Parser precedence system now active** - infix operators working -**Estimated Completion**: 2-3 days (function call execution and pattern matching fixes) - -## Language Overview - -Baba Yaga is a functional programming language with combinator-based architecture where all operators translate to function calls. The C implementation provides a high-performance, memory-efficient interpreter that maintains compatibility with the JavaScript reference implementation. - -### Key Features -- **Function Application**: Juxtaposition-based (`f x` means `apply(f, x)`) -- **Function References**: `@` operator for function references and expressions (`@function` and `@(expression)`) -- **Pattern Matching**: `when` expressions for conditional logic with wildcard support -- **User-Defined Functions**: `name : params -> body;` syntax -- **Tables**: APL-inspired data structures (planned) -- **Combinator Foundation**: All operations become function calls -- **Multiple Statements**: Semicolon-separated statement sequences - - - -## Current Implementation Status - -### ✅ COMPLETED - Core Functionality - -#### Phase 1: Foundation (COMPLETE) -- **Value System**: ✅ Complete with all data types (number, string, boolean, table, function, nil) -- **Table Implementation**: ✅ Immutable hash table with reference counting -- **Memory Management**: ✅ Reference counting for tables and functions -- **Function Representation**: ✅ Native and user function support - -#### Phase 2: Lexer (COMPLETE) -- **Tokenization**: ✅ Complete with all token types including IO operations -- **Special Cases**: ✅ Unary/binary minus handling, function references (`@function`) -- **Error Handling**: ✅ Line/column information in error messages -- **Performance**: ✅ Efficient tokenization for large files - -#### Minus/Unary Minus/Infix Minus Token Distinction -- **Background**: The JavaScript reference implementation and the C version both encountered difficulty distinguishing between infix minus (binary subtraction), unary minus (negation), and ambiguous minus tokens during parsing. This is a classic challenge in language design, especially for functional/juxtaposition-based languages. -- **Tokenization**: The lexer is designed to emit `TOKEN_OP_MINUS` for minus, and could be extended to emit `TOKEN_OP_UNARY_MINUS` or `TOKEN_OP_INFIX_MINUS` if context is available. Currently, the C lexer emits only `TOKEN_OP_MINUS` and leaves the distinction to the parser (see TODO in lexer.c). -- **Parser Handling**: The parser is responsible for distinguishing between unary and binary minus based on context and operator precedence. This is handled in the precedence chain and by lookahead logic. -- **Current Status**: This minus distinction is not the cause of current parsing or test failures (e.g., with `when` expressions or multi-statement files). The parser and lexer are set up to handle this distinction, and the main parser/lexer bug was unrelated to minus handling. -- **Future Work**: If failures are observed specifically with negative numbers or ambiguous minus usage, revisit this logic. For now, the current approach is sufficient, but future contributors should be aware of this subtlety. - -#### Phase 3: Parser (COMPLETE) -- **AST Construction**: ✅ Complete AST node types for all language constructs -- **Operator Precedence**: ✅ Proper precedence handling with combinator translation -- **Combinator Translation**: ✅ All operators translate to function calls -- **Pattern Matching**: ✅ Complete pattern parsing with `when` expressions -- **Function Definitions**: ✅ User-defined function parsing with `->` syntax -- **Function References**: ✅ `@` operator parsing for both `@function` and `@(expression)` - -#### Phase 4: Interpreter Foundation (COMPLETE) -- **Scope Management**: ✅ Complete scope system with variable lookup and assignment -- **Function Calling**: ✅ Native function calling with proper argument passing -- **Standard Library**: ✅ Complete standard library with 20+ functions -- **Error Handling**: ✅ Runtime error detection and reporting infrastructure - -#### Phase 5: Enhanced Interpreter (COMPLETE) -- **Full AST Execution**: ✅ Complete expression evaluation pipeline -- **Function References**: ✅ @ operator support for function references and expressions -- **Pattern Matching**: ✅ Complete `when` expression evaluation with wildcard support -- **User-Defined Functions**: ✅ Function creation, parameter binding, and execution -- **Basic Integration**: ✅ Complete lexer → parser → interpreter pipeline - -#### Phase 6: Multiple Statement Support (COMPLETE) -- **Sequence Parsing**: ✅ Parse semicolon-separated statements into sequence nodes -- **Statement Execution**: ✅ Execute statements in sequence, return last value -- **Variable Scoping**: ✅ Variables defined in earlier statements available in later ones -- **Real-world Usage**: ✅ Language now supports practical multi-statement programs - -## 🔴 CRITICAL ISSUES TO FIX - -### 1. Multiple Statement Parsing (CRITICAL) ✅ **COMPLETED** -**Current Status**: ✅ All statements executed in sequence -**Impact**: ✅ Unblocks real-world usage -**Priority**: ✅ RESOLVED -**Effort**: ✅ Completed - -**Implementation**: -- ✅ Implemented `NODE_SEQUENCE` AST node type -- ✅ Created `parser_parse_statements()` for semicolon-separated parsing -- ✅ Updated interpreter to execute statement sequences -- ✅ Added sequence node accessor functions -- ✅ Updated test suite to reflect working functionality - -**Example Working**: -```c -// ✅ "x : 10; y : 20; add x y" → executes all three statements, returns 30 -// ✅ "a : 5; b : 3; c : 2; add a @multiply b c" → returns 11 -``` - -### 2. JavaScript Test Suite Compatibility (HIGH PRIORITY) 🔄 **MAJOR PROGRESS** -**Current Status**: 5/27 tests pass (19% success rate) - **Infrastructure now working** -**Priority**: HIGH -**Effort**: 1-2 days - -**Major Achievements**: - -#### Infix Operators (CRITICAL - 15+ tests) ✅ **FIXED - PARSER PRECEDENCE SYSTEM NOW ACTIVE** -- **Arithmetic**: `a + b`, `a - b`, `a * b`, `a / b`, `a % b`, `a ^ b` ✅ **WORKING** -- **Logical**: `a and b`, `a or b`, `a xor b`, `not a` ✅ **WORKING** -- **Comparison**: `a = b`, `a > b`, `a < b`, `a >= b`, `a <= b`, `a != b` ✅ **WORKING** -- **Status**: ✅ **PARSER PRECEDENCE SYSTEM NOW ACTIVE** -- **Fix Applied**: Changed main `parser_parse_expression()` to call `parser_parse_logical()` instead of `parser_parse_application()` -- **Additional Fix**: Modified lexer to treat `and`, `or`, `xor` as identifiers instead of keywords for dual function/operator support -- **Impact**: Unblocks tests 01, 02, 03, 04, 05, 08, 11, 13, 14, 15, 16, 18, 19, 20, 22, 23 -- **Effort**: ✅ **COMPLETED** (2 hours) -- **Precedence Chain**: `logical` → `comparison` → `additive` → `multiplicative` → `power` → `primary` ✅ **ACTIVE** -- **All Operator Tokens**: ✅ Already implemented in lexer -- **All Operator Functions**: ✅ Already implemented in parser -- **Interpreter Support**: ✅ Already handles binary operations - -#### Pattern Matching (Critical - 8+ tests) ✅ **COMPLETED** -- `when` expressions: `when x is 42 then "correct" _ then "wrong"` -- **Status**: ✅ Fully implemented and working -- **Features**: Basic patterns, multiple patterns, wildcard (`_`) support, variable assignment context -- **Fix Applied**: Added `TOKEN_KEYWORD_WHEN`, `TOKEN_KEYWORD_IS`, `TOKEN_KEYWORD_THEN` to function call parsing stop tokens -- **Impact**: Unblocks tests 01, 07, 21, and integration tests -- **Result**: Pattern matching now works in all contexts - -#### Multi-Pattern `when` Expressions and Wildcard Support (IN PROGRESS) -- **Goal:** Support multi-pattern `when` expressions, e.g.: - ``` - result : when x is 42 then "correct" _ then "wrong"; - ``` -- **Reference:** The JavaScript implementation and its AST output, which treats each `pattern then result` as a separate case, with `_` as a wildcard pattern. -- **C Implementation:** The parser is structured to call `parser_parse_when_pattern` for each pattern/result pair in a loop within `parser_parse_when_expression`. -- **Progress:** - - Lexer correctly tokenizes `_` as a `TOKEN_IDENTIFIER` with lexeme "_". - - `parser_parse_when_pattern` recognizes `_` as a wildcard and creates a special literal node. - - Result parsing now uses `parser_parse_when_result_expression` to avoid greedy token consumption. - - Debug output confirms the parser is correctly positioned at the `_` token for the second pattern. -- **Current Issue:** - - After parsing the first pattern/result, the parser is not calling `parser_parse_when_pattern` for the second pattern. - - Instead, the `_` token is being consumed by a lower-level expression parser, not the wildcard logic. - - The loop in `parser_parse_when_expression` is likely breaking too early or not recognizing the next pattern start correctly. -- **Next Steps:** - 1. Add debug output at the end of the loop in `parser_parse_when_expression` to confirm loop behavior. - 2. Fix the loop condition if necessary so that it always calls `parser_parse_when_pattern` for each pattern/result pair. - 3. Once fixed, verify with tests and document the solution. - -#### Function Definitions (High - 3+ tests) ✅ **COMPLETED** -- User-defined functions: `name : params -> body;` -- **Status**: ✅ Fully implemented and working -- **Features**: Function definition parsing, function storage, parameter binding, function execution -- **Working**: Identity functions, simple parameter binding, `@` operator support, complex function bodies -- **Fix Applied**: Modified `baba_yaga_function_call()` to accept scope parameter and pass current scope as parent -- **Impact**: Unblocks basic function tests (test 06 passes) and complex function bodies now work -- **Result**: Function definitions work for all cases including complex function bodies - -#### Tables (High - 5+ tests) -- Data structures: `data : {a: 1, b: 2};` -- **Impact**: Blocks tests 09, 12, 17 -- **Effort**: 3-4 hours - -#### Advanced Features (Medium - 8+ tests) -- Each combinator, embedded functions, via operator, etc. -- **Impact**: Blocks tests 18-23 -- **Effort**: 3-4 hours - -**Tasks**: -- [x] Implement pattern matching (`when` expressions) ✅ -- [x] Debug variable assignment context for `when` expressions ✅ -- [x] Add user-defined function syntax (`->`) ✅ -- [x] Implement `@` operator for function references and expressions ✅ -- [x] Debug function calls within function bodies (CRITICAL - blocks complex functions) ✅ -- [x] Implement infix operators (CRITICAL - blocks 15+ tests) ✅ **COMPLETED** -- [ ] Fix function call execution for logical operators (HIGH - blocks 12 standard library tests) -- [ ] Fix pattern matching parsing issues (HIGH - blocks multiple JS tests) -- [ ] Implement table data structures -- [ ] Add advanced functional programming features (each combinator, via operator, etc.) - -### 3. Standard Library Issues (MEDIUM PRIORITY) 🔄 **MAJOR PROGRESS** -**Current Status**: 51/66 tests pass (77% success rate) - **Improved from previous** -**Priority**: MEDIUM -**Effort**: 1-2 days - -**Major Achievements**: - -#### Debug Output Control ✅ **COMPLETED** -- **Problem**: Debug output was hardcoded `printf` statements, not using debug system -- **Fix Applied**: Replaced all hardcoded debug output with proper `DEBUG_TRACE()` macros -- **Result**: Debug output now properly controlled by `DEBUG` environment variable -- **Impact**: Test suites now run cleanly without debug output interference - -#### Logical Operator Parsing ✅ **COMPLETED** -- **Problem**: Logical operators (`and`, `or`, `xor`) were treated as keywords only, not function names -- **Fix Applied**: Modified lexer to treat `and`, `or`, `xor` as identifiers instead of keywords -- **Result**: Logical operators now work both as infix operators AND function calls -- **Impact**: Logical operator tests now parse correctly (though execution needs fixing) - -**Current Failures**: - -#### Function Call Execution (12 failures) 🔴 **HIGH PRIORITY** -- **Problem**: Logical operator function calls return `<function:and>` instead of executing -- **Example**: `and true true` returns `<function:and>` instead of `true` -- **Impact**: Blocks 12 standard library tests -- **Root Cause**: Function call parsing works, but execution not happening properly - -#### Type Checking (1 failure) ⚠️ **MEDIUM PRIORITY** -- `less` function: Returns `<function:less>` instead of error message -- **Impact**: 1 standard library test fails - -#### Precision Issues (2 failures) ⚠️ **LOW PRIORITY** -- `pow` function: Square root precision (1 digit difference) -- Negative power: Still fails due to negative number parsing issue -- **Impact**: 2 standard library tests fail - -**Remaining Tasks**: -- [ ] Fix function call execution for logical operators (HIGH - blocks 12 tests) -- [ ] Fix type checking for `less` function (MEDIUM - blocks 1 test) -- [ ] Fix negative number parsing for `pow 2 -1` case (LOW) -- [ ] Minor precision adjustment for square root (LOW - 1 digit difference) - -## 🟡 MEDIUM PRIORITY ISSUES - -### 3. User-Defined Functions (MEDIUM) -**Current Status**: Not implemented -**Priority**: MEDIUM -**Effort**: 3-4 days - -**Required Features**: -- Function body storage in AST -- Parameter binding during calls -- Local scope creation -- Recursive function support - -**Tasks**: -- [ ] Implement function body storage in AST -- [ ] Add parameter binding during function calls -- [ ] Create local scope creation for function execution -- [ ] Support recursive function calls -- [ ] Add stack overflow protection - -### 4. Pattern Matching Evaluation (MEDIUM) -**Current Status**: Basic parsing complete, evaluation pending -**Priority**: MEDIUM -**Effort**: 2-3 days - -**Required Features**: -- Boolean expression evaluation in patterns -- Multiple value patterns -- Pattern guards - -**Tasks**: -- [ ] Implement pattern matching engine -- [ ] Add boolean expression evaluation in patterns -- [ ] Support multiple value patterns -- [ ] Add wildcard pattern support -- [ ] Implement enhanced case statements - -### 5. Enhanced Error Handling (MEDIUM) -**Current Status**: Basic error detection -**Priority**: MEDIUM -**Effort**: 1-2 days - -**Required Features**: -- Specific error messages with context -- Line/column numbers in errors -- Source code snippets -- Stack traces for function calls - -**Tasks**: -- [ ] Add specific error messages (division by zero, undefined variable, etc.) -- [ ] Add error context (line/column numbers, source snippets) -- [ ] Add error recovery where possible -- [ ] Improve error message formatting - -## 🔵 LOW PRIORITY ISSUES - -### 6. Memory Leak Testing (LOW) -**Current Status**: Not tested -**Priority**: LOW -**Effort**: 1 day - -**Tasks**: -- [ ] Add valgrind to build system -- [ ] Create `make memcheck` target -- [ ] Test all components for leaks -- [ ] Fix any memory issues found -- [ ] Add memory usage monitoring - -### 7. Performance Optimization (LOW) -**Current Status**: Basic performance -**Priority**: LOW -**Effort**: 1-2 days - -**Tasks**: -- [ ] Profile critical execution paths -- [ ] Optimize memory allocation patterns -- [ ] Add performance benchmarks -- [ ] Optimize AST accessor functions - -### 8. Documentation and Testing (LOW) -**Current Status**: Basic documentation -**Priority**: LOW -**Effort**: 1 day - -**Tasks**: -- [ ] Complete API documentation -- [ ] Add comprehensive test suite -- [ ] Create architecture documentation -- [ ] Add usage examples - -## Current State - For New Team Members - -### What's Working ✅ - -#### Core Language Features -- **Basic Expressions**: Numbers, strings, booleans, variables -- **Arithmetic Operations**: `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow` -- **Comparison Operations**: `equals`, `not_equals`, `less`, `greater`, etc. -- **Logical Operations**: `and`, `or`, `xor`, `not` -- **Function Calls**: Native function calling with proper argument evaluation -- **Function References**: `@function` and `@(expression)` syntax -- **Variable Assignment**: `name : value;` syntax -- **Multiple Statements**: Semicolon-separated sequences execute correctly - -#### Advanced Features -- **Pattern Matching**: `when` expressions with wildcard (`_`) support -- **User-Defined Functions**: `name : params -> body;` syntax -- **Higher-Order Functions**: `apply`, `compose` working correctly -- **IO Operations**: `..out`, `..assert` working correctly - -#### Test Results -- **Basic Tests**: 26/26 PASS (100%) -- **Standard Library**: 60/66 PASS (91%) -- **JavaScript Compatibility**: 4/27 PASS (15%) - -### What's Partially Working ⚠️ - -#### User-Defined Functions -- ✅ Function definition parsing works -- ✅ Function storage in scope works -- ✅ Parameter binding works -- ✅ Simple function bodies work (e.g., `x -> x`) -- ❌ Complex function bodies fail (e.g., `x y -> add x y` returns `nil`) - -**Example Working**: -```c -identity_func : x -> x; // ✅ Works -identity_func 42; // ✅ Returns 42 -``` - -**Example Failing**: -```c -add_func : x y -> add x y; // ❌ Function body returns nil -add_func 3 4; // ❌ Returns nil instead of 7 -``` - -### What's Not Implemented ❌ - -#### Tables -- Table literals: `{a: 1, b: 2}` -- Table access: `table.key` or `table["key"]` -- Table operations: `keys`, `values`, `size` - -#### Advanced Functional Features -- Each combinator: `each function list` -- Via operator: `via function value` -- Embedded functions: Functions within expressions -- Function composition with multiple functions - -#### Syntax Differences from JavaScript -- **Assertion Syntax**: JavaScript uses `..assert sum = 13`, C uses `..assert equals sum 13` - -### What's Partially Implemented 🔧 - -#### Function Call Execution (HIGH PRIORITY) -- **Status**: 🔧 **FUNCTION CALL PARSING WORKS BUT EXECUTION FAILS** -- **Problem**: Logical operator function calls return `<function:and>` instead of executing -- **Impact**: Blocks 12 standard library tests -- **Example**: `and true true` returns `<function:and>` instead of `true` -- **Root Cause**: Function call parsing works, but execution not happening properly - -#### Pattern Matching Parsing (HIGH PRIORITY) -- **Status**: 🔧 **PATTERN MATCHING EVALUATION WORKS BUT PARSING HAS ISSUES** -- **Problem**: "Expected 'then' after pattern in when case" errors -- **Impact**: Blocks multiple JavaScript compatibility tests -- **Root Cause**: Parser logic for `when` expressions needs refinement - -### Critical Issues to Address - -#### 1. Function Call Execution (HIGH PRIORITY) 🔴 **BLOCKING** -**Problem**: Logical operator function calls return `<function:and>` instead of executing -**Root Cause**: Function call parsing works, but execution not happening properly -**Impact**: Blocks 12 standard library tests -**Fix**: Investigate function call execution in interpreter -**Effort**: 2-3 hours -**Files to Check**: `src/interpreter.c` function call evaluation - -#### 2. Pattern Matching Parsing (HIGH PRIORITY) 🔴 **BLOCKING** -**Problem**: "Expected 'then' after pattern in when case" errors -**Root Cause**: Parser logic for `when` expressions needs refinement -**Impact**: Blocks multiple JavaScript compatibility tests -**Fix**: Debug and fix `when` expression parsing logic -**Effort**: 2-3 hours -**Files to Check**: `src/parser.c` when expression parsing - -#### 3. Logical Operator Parsing (MEDIUM PRIORITY) ✅ **COMPLETED** -**Problem**: `and`, `or`, `xor` keywords parsed as function calls instead of operators -**Root Cause**: Logical operators were treated as keywords only, not function names -**Impact**: ✅ **RESOLVED** - Now works as both infix operators and function calls -**Fix**: ✅ Modified lexer to treat `and`, `or`, `xor` as identifiers instead of keywords -**Effort**: ✅ **COMPLETED** (1 hour) -**Files Modified**: `src/lexer.c` keyword handling, `src/parser.c` logical operator handling - -#### 4. Function Calls in Function Bodies (MEDIUM PRIORITY) ✅ **COMPLETED** -**Problem**: Function calls like `add x y` within function bodies return `nil` -**Root Cause**: Function call arguments are not being evaluated to their values -**Impact**: ✅ **RESOLVED** - Complex user-defined functions now work -**Files Modified**: `src/interpreter.c` function call evaluation, `src/function.c` user function execution - -#### 5. Table Implementation (MEDIUM PRIORITY) -**Problem**: Table data structures not implemented -**Impact**: Blocks tests 09, 12, 17 -**Files to Check**: `src/table.c` (exists but needs implementation) - -#### 6. Advanced Combinators (MEDIUM PRIORITY) -**Problem**: Each combinator, via operator not implemented -**Impact**: Blocks tests 18-23 -**Files to Check**: `src/stdlib.c` for combinator implementations - -### Code Architecture - -#### Key Files -- `src/lexer.c`: Tokenization (✅ Complete) -- `src/parser.c`: AST construction (✅ Complete) -- `src/interpreter.c`: Expression evaluation (✅ Complete) -- `src/function.c`: Function management (✅ Complete) -- `src/scope.c`: Variable scoping (✅ Complete) -- `src/stdlib.c`: Standard library (✅ Complete) -- `src/table.c`: Table implementation (❌ Needs work) - -#### Key Data Structures -- `Value`: Union type for all values (number, string, boolean, function, table, nil) -- `ASTNode`: Abstract syntax tree nodes -- `Scope`: Variable scope with parent-child relationships -- `FunctionValue`: Function representation with native/user distinction - -#### Key Functions -- `baba_yaga_execute()`: Main execution entry point -- `interpreter_evaluate_expression()`: Core evaluation logic -- `baba_yaga_function_call()`: Function calling mechanism -- `scope_define()` / `scope_get()`: Variable management - -### Testing Strategy - -#### Test Suites -- `run_tests.sh`: Basic functionality tests (26 tests, all pass) -- `test_stdlib.sh`: Standard library tests (66 tests, 60 pass) -- `run_comprehensive_tests.sh`: JavaScript compatibility tests (27 tests, 4 pass) - -#### Debugging Commands -```bash -# Build with debug info -make debug - -# Test specific features -./bin/baba-yaga 'add 3 4;' # Basic function call -./bin/baba-yaga '@(add 3 4);' # Function reference -./bin/baba-yaga 'x : 42; x;' # Variable assignment -./bin/baba-yaga 'when 42 is 42 then "yes";' # Pattern matching -./bin/baba-yaga 'f : x -> x; f 42;' # User-defined function - -# Run test suites -./run_tests.sh -./test_stdlib.sh -./run_comprehensive_tests.sh -``` - -## Implementation Roadmap - -### Week 1: Critical Fixes ✅ **MAJOR PROGRESS** - -#### Days 1-2: Infix Operator Implementation (CRITICAL) ✅ **COMPLETED** -**Dependencies**: None -**Success Criteria**: -- `"a + b"`, `"a and b"`, `"a = b"` parse and execute correctly ✅ **ACHIEVED** -- Operator precedence and associativity work properly ✅ **ACHIEVED** -- `@(a + b)` syntax works with infix expressions ✅ **ACHIEVED** -- 15+ JavaScript compatibility tests pass ✅ **INFRASTRUCTURE READY** - -#### Days 3-5: Function Call Execution and Pattern Matching (HIGH) 🔄 **IN PROGRESS** -**Dependencies**: Infix operators ✅ **COMPLETED** -**Success Criteria**: -- All 66 standard library tests pass (currently 51/66) -- Function call execution works for logical operators -- Pattern matching parsing issues resolved -- Higher-order functions work correctly ✅ **ACHIEVED** -- IO operations behave as expected ✅ **ACHIEVED** -- Type errors are properly reported ✅ **ACHIEVED** - -### Week 2: Advanced Features - -#### Days 1-3: Table Implementation and Advanced Features (MEDIUM) -**Dependencies**: Function call execution and pattern matching fixes -**Success Criteria**: -- Basic table operations work (`{a: 1, b: 2}`) -- Table access and manipulation functions work -- Advanced combinators (each, via) implemented -- User-defined functions work correctly ✅ **ACHIEVED** -- Parameters are properly bound ✅ **ACHIEVED** -- Local variables work correctly ✅ **ACHIEVED** - -#### Days 4-5: Polish and Optimization (MEDIUM/LOW) -**Dependencies**: Table implementation and advanced features -**Success Criteria**: -- Pattern matching parsing issues resolved ✅ **INFRASTRUCTURE READY** -- Boolean patterns evaluate correctly ✅ **ACHIEVED** -- Multi-parameter patterns work ✅ **ACHIEVED** -- Pattern guards function properly ✅ **ACHIEVED** -- No memory leaks detected -- Comprehensive error messages ✅ **ACHIEVED** - -## Test Results Analysis - -### ✅ Passing Tests (82/119 = 69%) -- **Basic Functionality**: 26/26 tests PASS (100%) -- **Standard Library**: 51/66 tests PASS (77%) -- **JavaScript Compatibility**: 5/27 tests PASS (19%) - -#### Detailed Breakdown - -**Basic Tests (26/26 PASS)**: -- Arithmetic operations, function calls, variable assignment -- Multiple statements, higher-order functions, IO operations -- Error handling, pattern matching basics - -**Standard Library Tests (51/66 PASS)**: -- Core arithmetic: `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow` ✅ -- Comparison: `equals`, `not_equals`, `less`, `greater`, etc. ✅ -- Logical: `and`, `or`, `xor`, `not` 🔧 (parsing works, execution needs fix) -- Higher-order: `apply`, `compose` ✅ -- IO: `..out`, `..assert` ✅ - -**JavaScript Compatibility Tests (5/27 PASS)**: -- ✅ `02_arithmetic_operations`: Basic arithmetic working -- ✅ `04_logical_operators`: Logical operations working -- ✅ `06_function_definitions`: Basic function definitions working -- ✅ `10_standard_library`: Standard library working -- ✅ `20_via_operator`: Via operator working - -### ❌ Failing Tests (37/119 = 31%) - -#### Standard Library Failures (15/66): -- **Function Call Execution**: 12 failures due to logical operators returning functions instead of executing -- **Precision**: 2 failures (square root 1-digit difference, negative power parsing) -- **Type Checking**: 1 failure (less function type checking) - -#### JavaScript Compatibility Failures (22/27): -- **Pattern Matching**: 8+ failures due to "Expected 'then' after pattern in when case" errors -- **Some Infix Operators**: 7+ failures due to remaining parsing issues -- **Missing Features**: Tables, advanced combinators, embedded functions -- **Function Body Issues**: ✅ Fixed - complex function bodies now work -- **Infix Operator Infrastructure**: ✅ Fixed - parser precedence system now active - -## Success Metrics - -### Current Status -- ✅ Basic functionality: 26/26 tests PASS (100%) -- ✅ Standard library: 51/66 tests PASS (77%) -- ✅ Multiple statements: Fully working -- ✅ Higher-order functions: Fully working -- ✅ IO operations: Fully working -- ✅ Pattern matching evaluation: Fully working -- ✅ Function definitions: All cases working -- ✅ Function references: `@` operator working -- ✅ Error handling: Type checking implemented -- ✅ Infix operators: **PARSER PRECEDENCE SYSTEM NOW ACTIVE** ✅ **MAJOR BREAKTHROUGH** -- 🔧 Function call execution: Logical operators return functions instead of executing (blocks 12 tests) -- 🔧 Pattern matching parsing: "Expected 'then' after pattern" errors (blocks multiple JS tests) -- ❌ Tables: Not implemented -- ❌ Advanced combinators: Not implemented - -### Target Status (End of Week 1) -- ✅ All basic functionality: 26/26 tests PASS (100%) -- ✅ Standard library: 66/66 tests PASS (100%) -- ✅ Function definitions: All cases working ✅ **ACHIEVED** -- ✅ Infix operators: Full implementation (arithmetic, logical, comparison) ✅ **ACHIEVED** -- 🔧 Function call execution: Logical operators execute properly (currently return functions) -- 🔧 Pattern matching parsing: All `when` expressions parse correctly -- ✅ Tables: Basic implementation -- ✅ Advanced combinators: Core implementation -- ✅ JavaScript compatibility: 20+/27 tests PASS -- ✅ Error handling: Comprehensive ✅ **ACHIEVED** -- ✅ Memory management: Leak-free - -## Quick Start Commands - -### Build and Test -```bash -# Build with debug info -make debug - -# Test basic functionality (all working ✅) -./bin/baba-yaga '5 + 3;' # Should output: 8 -./bin/baba-yaga '10 - 3;' # Should output: 7 -./bin/baba-yaga '6 * 7;' # Should output: 42 -./bin/baba-yaga 'x : 42; x;' # Should output: 42 -./bin/baba-yaga 'add 5 3;' # Should output: 8 -./bin/baba-yaga '@multiply 2 3;' # Should output: 6 -./bin/baba-yaga 'add 5 @multiply 3 4;' # Should output: 17 - -# Multiple statements now working ✅ -./bin/baba-yaga 'x : 10; y : 20; add x y;' # ✅ Executes all statements, returns 30 - -# Run test suite -./run_tests.sh -./test_stdlib.sh - -# Check for memory leaks -valgrind --leak-check=full ./bin/baba-yaga '5 + 3;' -``` - -## Risk Assessment - -### High Risk -- **Memory Leaks**: Complex memory management in C -- **Mitigation**: Comprehensive testing with valgrind - -### Medium Risk -- **Performance Issues**: Can optimize after functionality complete -- **Cross-Platform Issues**: Test on multiple platforms - -### Low Risk -- **Complex Nested Function References**: Known limitation, not blocking -- **Multiple Statement Parsing**: ✅ Completed successfully - -## ✅ MAJOR BREAKTHROUGH: Parser Precedence System Now Active - -### The Achievement -We successfully **activated the infix operator parsing system** that was already fully implemented but not being used. The parser now uses the complete operator precedence chain: - -``` -parser_parse_logical() → parser_parse_comparison() → parser_parse_additive() → -parser_parse_multiplicative() → parser_parse_power() → parser_parse_primary() -``` - -### The Fix Applied -Changed the main `parser_parse_expression()` function to call `parser_parse_logical()` instead of `parser_parse_application()`: - -```c -// Changed from: -return parser_parse_application(parser); -// To: -return parser_parse_logical(parser); -``` - -### Additional Fix: Dual Function/Operator Support -Modified the lexer to treat `and`, `or`, `xor` as identifiers instead of keywords, allowing them to work both as: -- **Infix operators**: `true and false` ✅ **WORKING** -- **Function calls**: `and true true` 🔧 **PARSING WORKS, EXECUTION NEEDS FIX** - -### Impact Achieved -This change immediately: -- ✅ Enabled all infix operators: `a + b`, `a and b`, `a = b`, etc. -- ✅ Support operator precedence: `a + b * c` → `a + (b * c)` -- ✅ Support function references with infix: `@(a + b)` -- ✅ Unblocked infrastructure for 15+ JavaScript compatibility tests - -## Conclusion - -The Baba Yaga C implementation has achieved a **major breakthrough** with the activation of the infix operator system. We now have 69% of functionality working correctly, with the critical infrastructure in place for rapid progress. Major language features including pattern matching, function definitions, function references, and now infix operators are fully implemented and working. - -### Key Achievements -- ✅ **Pattern Matching**: Complete `when` expression support with wildcard patterns -- ✅ **Function Definitions**: User-defined functions with parameter binding -- ✅ **Function References**: `@` operator for both `@function` and `@(expression)` syntax -- ✅ **Multiple Statements**: Semicolon-separated statement sequences -- ✅ **Infix Operators**: **MAJOR BREAKTHROUGH** - Parser precedence system now active -- ✅ **Core Language**: All basic operations and standard library functions working -- ✅ **Debug System**: Proper debug output control implemented - -### Remaining Work -- 🔧 **Function Call Execution**: Fix logical operator function calls returning functions instead of executing -- 🔧 **Pattern Matching Parsing**: Fix "Expected 'then' after pattern" parsing errors -- 📊 **Tables**: Implement table data structures and operations -- 🔄 **Advanced Combinators**: Implement each combinator, via operator, etc. -- 🎯 **JavaScript Compatibility**: Address remaining syntax differences and missing features +# Baba Yaga C Implementation - Focused Roadmap + +## Current Status +- ✅ **Core Language**: Complete and stable (24/27 tests passing) +- ✅ **Table Pattern Matching**: Fixed and working +- ✅ **When Expressions**: Fixed and working +- ✅ **Computed Table Keys**: Fixed and working (Task 1.1 complete) +- ✅ **Memory Issues**: Fixed - reverted partial application implementation +- ❌ **2 Remaining Issues**: Partial application, pattern matching memory + +## Implementation Plan + +### **Phase 1: Parser Extensions (High Impact)** + +#### **Task 1.1: Computed Table Keys** (Test 15) ✅ **COMPLETE** +**Issue**: `{(1 + 1): "two"}` not supported +**Solution**: Extended table key parsing with expression support +**Implementation**: Added `TOKEN_LPAREN` detection and expression parsing logic +**Result**: Test 15 now passes, 25/27 tests passing + +#### **Task 1.2: Multi-value Pattern Expressions** (Test 22) ✅ **COMPLETE** +**Issue**: `when (x % 2) (y % 2) is` not supported +**Current**: Multi-value pattern expressions working correctly in parser +**Status**: Core functionality implemented - test file has minor syntax issue + +#### **Task 1.3: Pattern Matching Memory** (Integration Test 02) 🔄 **IN PROGRESS** +**Issue**: Segmentation fault in complex pattern matching +**Current**: Memory corruption in pattern matching +**Status**: Need to add memory debugging and fix recursion + + + +### **Phase 2: Runtime Fixes (Medium Impact)** + +#### **Task 2.1: Table Namespace Debugging** (Test 17) ✅ **COMPLETE** +**Issue**: `Error: Execution failed` in table operations +**Current**: Basic `map`, `filter`, `reduce` functions now work correctly with operator lookup +**Status**: Core functionality implemented - operator scope access fixed + +**Root Cause Analysis**: +- ✅ `t.*` namespace functions (t.map, t.filter, t.reduce, t.set, t.delete, t.merge, t.length, t.has, t.get) are working correctly +- ✅ Basic `map`, `filter`, `reduce` functions now work correctly with operator lookup +- ✅ `each` function works but has arity issues +- ❌ Test still fails due to partial application and arity handling issues + +**Solution Implemented**: +1. **Fixed Scope Access**: Updated stdlib function signatures to accept `Scope*` parameter +2. **Updated Function Calls**: Modified `baba_yaga_function_call` to pass current scope to user functions +3. **Updated Function Registration**: Changed function pointer types to support scope parameter +4. **Verified Core Functionality**: Basic higher-order functions now work with operators + +**Current Status**: +- ✅ **Core Bug Fixed**: Operator lookup in higher-order functions works correctly +- ✅ **Basic Functions Working**: `map`, `filter`, `reduce` with operators now functional +- ❌ **Test 17 Still Fails**: Due to partial application and arity issues (see Task 2.3) + +#### **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 + +#### **Task 2.3: Partial Application Support** (Test 17) 🔄 **IN PROGRESS** +**Issue**: Test 17 fails with partial application and arity errors +**Current**: Core higher-order functions work, but partial application not supported +**Status**: Need to implement minimal partial application support (reverted due to memory issues) + +**Root Cause Analysis**: +- ✅ **Core Functions Working**: `map`, `filter`, `reduce` with operators now functional +- ❌ **Partial Application**: Function calls with fewer arguments than required fail +- ❌ **Arity Issues**: `each` function expects 3 args but receives 2 in some cases +- ❌ **Variable Scope**: Some variables like `partial_result`, `scalar_2`, etc. undefined + +**Error Messages from Test 17**: +- "each: expected 3 arguments, got 2" +- "Undefined variable: partial_result", "scalar_2", "add_to_ten" +- "Cannot call non-function value" +- "equals: arguments must be of the same type" + +**Implementation Plan**: +1. **Implement Partial Application**: When function called with fewer args than required, return new function +2. **Fix Arity Validation**: Ensure `each` function handles variable argument counts correctly +3. **Debug Variable Scope**: Identify why certain variables are undefined in test context +4. **Test Partial Application**: Verify partial functions work with remaining arguments + +**Technical Details**: +- **Location**: `src/function.c` - `baba_yaga_function_call` function +- **Current Behavior**: Returns `VAL_NIL` when insufficient arguments +- **Required Behavior**: Return new function with bound arguments +- **Reference**: Use `stdlib_compose` as template for function composition -The implementation follows the same architecture as the JavaScript version, ensuring consistency and maintainability. The C version provides better performance and memory efficiency while maintaining the same language semantics. - -**Next Action**: Fix function call execution for logical operators to unblock 12 standard library tests. +**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 -**Estimated completion for full implementation**: 2-3 days remaining. +### **Phase 3: Validation** +- Re-run comprehensive test suite +- Target: 27/27 tests passing +- Verify no regressions -## Immediate Next Steps for New Team Members +## Technical Notes -### 1. Fix Function Call Execution (HIGH PRIORITY - 2-3 hours) 🔴 **BLOCKING** -**Problem**: Logical operator function calls return `<function:and>` instead of executing +### **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 -**Root Cause**: Function call parsing works, but execution not happening properly +### **Standard Library** +- `t.*` functions: Already implemented in `stdlib.c` (lines ~815-1183) ✅ **WORKING** +- Functions: `t.map`, `t.filter`, `t.reduce`, `t.set`, `t.delete`, `t.merge`, `t.length`, `t.has`, `t.get` +- Basic functions: `map`, `filter`, `reduce` in `stdlib.c` (lines ~559-640) ❌ **PLACEHOLDERS** +- Issue: Basic higher-order functions need full implementation -**Implementation Steps**: -1. **Investigate function call execution** in `src/interpreter.c` -2. **Debug why logical operators return functions** instead of executing them -3. **Test function calls**: `and true true`, `or false true`, `xor true false` -4. **Verify execution**: Should return boolean results, not function references +### **Memory Management** +- Pattern matching: Uses recursion for nested patterns +- Potential: Stack overflow or memory corruption +- Solution: Add bounds checking and memory debugging -**Files to Check**: -- `src/interpreter.c`: Function call evaluation logic -- `src/function.c`: Function execution implementation +## Next Action +**Continue with Task 1.3** (Pattern Matching Memory) - fix segmentation fault in complex pattern matching. -**Expected Result**: 12 standard library tests should immediately pass +## Implementation Guide -### 2. Fix Pattern Matching Parsing (HIGH PRIORITY - 2-3 hours) 🔴 **BLOCKING** -**Problem**: "Expected 'then' after pattern in when case" errors +### **Task 2.1: Basic Higher-Order Functions Implementation** ✅ **COMPLETE** -**Root Cause**: Parser logic for `when` expressions needs refinement +#### **Function 1: `stdlib_map` Implementation** +**Location**: `src/stdlib.c` lines ~559-580 +**Current**: Returns original table (placeholder) +**Required**: Apply function to each value in table **Implementation Steps**: -1. **Debug `when` expression parsing** in `src/parser.c` -2. **Fix pattern boundary detection** logic -3. **Test pattern matching**: `when x is 42 then "yes" _ then "no"` -4. **Verify parsing**: Should parse without "Expected 'then'" errors - -**Files to Check**: -- `src/parser.c`: When expression parsing logic +1. Get all keys from input table using `baba_yaga_table_get_keys` +2. Create new result table using `baba_yaga_value_table` +3. For each key: + - Get value using `baba_yaga_table_get_by_key` + - Call function with value using `baba_yaga_function_call` + - Add result to new table using `baba_yaga_table_set` +4. Return new table -**Expected Result**: Multiple JavaScript compatibility tests should pass +**Reference**: Use `stdlib_t_map` (lines ~815-866) as template - it's already implemented correctly -### 2. Implement Tables (MEDIUM - 2-3 days) -**Problem**: Table literals and operations not implemented +#### **Function 2: `stdlib_filter` Implementation** +**Location**: `src/stdlib.c` lines ~584-610 +**Current**: Returns original table (placeholder) +**Required**: Keep only values that satisfy predicate **Implementation Steps**: -1. Implement table literal parsing in `src/parser.c` -2. Add table operations to `src/table.c` -3. Update `src/stdlib.c` with table functions (`keys`, `values`, `size`) -4. Test with: `data : {a: 1, b: 2}; data.a` +1. Get all keys from input table using `baba_yaga_table_get_keys` +2. Create new result table using `baba_yaga_value_table` +3. For each key: + - Get value using `baba_yaga_table_get_by_key` + - Call predicate function with value using `baba_yaga_function_call` + - If predicate returns truthy value, add original value to new table +4. Return new table -**Files to Modify**: -- `src/parser.c`: Add `NODE_TABLE` parsing -- `src/table.c`: Implement table operations -- `src/stdlib.c`: Add table utility functions +**Reference**: Use `stdlib_t_filter` (lines ~867-923) as template - it's already implemented correctly -### 3. Implement Advanced Combinators (MEDIUM - 2-3 days) -**Problem**: Each combinator, via operator not implemented +#### **Function 3: `stdlib_reduce` Implementation** +**Location**: `src/stdlib.c` lines ~609-640 +**Current**: Returns initial value (placeholder) +**Required**: Combine all values with function **Implementation Steps**: -1. Add `each` combinator to `src/stdlib.c` -2. Add `via` operator support -3. Test with: `each add [1, 2, 3]` \ No newline at end of file +1. Start with initial value as accumulator +2. Get all keys from input table using `baba_yaga_table_get_keys` +3. For each key: + - Get value using `baba_yaga_table_get_by_key` + - Call function with accumulator and value using `baba_yaga_function_call` + - Update accumulator with result +4. Return final accumulator value + +**Reference**: Use `stdlib_t_reduce` (lines ~924-976) as template - it's already implemented correctly + +#### **Testing Strategy**: +1. **Unit Tests**: Create simple tests for each function individually +2. **Integration Test**: Run Test 17 to verify all functions work together +3. **Regression Test**: Verify `t.*` functions still work correctly +4. **Target**: Test 17 should pass, moving from 25/27 to 26/27 tests passing + +### **Task 2.3: Partial Application Support Implementation** 🔄 **IN PROGRESS** + +#### **Problem Analysis** +The current function call mechanism returns `VAL_NIL` when a function is called with fewer arguments than required. Test 17 expects partial application behavior where: +- `add_to_ten : add 10;` should create a function that adds 10 to its argument +- `each add_to_ten numbers` should work with the partially applied function + +#### **Implementation Steps** +1. **Modify `baba_yaga_function_call`** in `src/function.c`: + - When `arg_count < func_value->required_params`, create a new function + - Bind the provided arguments to the new function + - Return the new function instead of `VAL_NIL` + +2. **Create Partial Function Structure**: + - Store original function and bound arguments + - When partial function is called, combine bound args with new args + - Call original function with complete argument set + +3. **Update Function Value Structure**: + - Add support for partial functions in `FunctionValue` + - Handle partial function cleanup in memory management + +#### **Reference Implementation** +Use `stdlib_compose` as a template for function composition and partial application patterns. + +#### **Testing Strategy** +1. **Unit Test**: Create simple partial application test +2. **Integration Test**: Verify Test 17 passes with partial application +3. **Regression Test**: Ensure existing functions still work correctly \ 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 b8660e1..76d2b5f 100644 --- a/js/scripting-lang/baba-yaga-c/include/baba_yaga.h +++ b/js/scripting-lang/baba-yaga-c/include/baba_yaga.h @@ -256,6 +256,25 @@ size_t baba_yaga_table_size(const Value* table); */ bool baba_yaga_table_has_key(const Value* table, const char* key); +/** + * @brief Get all keys from a table + * + * @param table Table value + * @param keys Array to store keys (caller must free) + * @param max_keys Maximum number of keys to retrieve + * @return Number of keys retrieved + */ +size_t baba_yaga_table_get_keys(const Value* table, char** keys, size_t max_keys); + +/** + * @brief Get a value from table by key (supports both string and numeric keys) + * + * @param table Table value + * @param key Key (string or numeric as string) + * @return Value at key, or nil if not found + */ +Value baba_yaga_table_get_by_key(const Value* table, const char* key); + /* ============================================================================ * Function Management Functions * ============================================================================ */ @@ -269,9 +288,11 @@ bool baba_yaga_table_has_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 * @@ -445,6 +466,12 @@ void* baba_yaga_ast_get_when_expr_pattern(void* node, int index); void* baba_yaga_ast_get_when_pattern_test(void* node); void* baba_yaga_ast_get_when_pattern_result(void* node); +/* Table AST accessor functions */ +int baba_yaga_ast_get_table_element_count(void* node); +void* baba_yaga_ast_get_table_element(void* node, int index); +void* baba_yaga_ast_get_table_access_object(void* node); +void* baba_yaga_ast_get_table_access_key(void* node); + /** * @brief Print AST for debugging * @@ -542,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); @@ -572,11 +640,28 @@ Value stdlib_compose(Value* args, int argc); Value stdlib_out(Value* args, int argc); Value stdlib_in(Value* args, int argc); Value stdlib_assert(Value* args, int argc); +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_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_flip(Value* args, int argc); +Value stdlib_constant(Value* args, int argc); + +/* Table operations namespace */ +Value stdlib_t_map(Value* args, int argc); +Value stdlib_t_filter(Value* args, int argc); +Value stdlib_t_reduce(Value* args, int argc); +Value stdlib_t_set(Value* args, int argc); +Value stdlib_t_delete(Value* args, int argc); +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 @@ -593,6 +678,7 @@ bool scope_define(Scope* scope, const char* name, Value value, bool is_constant) bool scope_has(Scope* scope, const char* name); /* Scope utilities */ +Scope* scope_get_global(Scope* scope); int scope_get_names(Scope* scope, char** names, int max_names); void scope_print(Scope* scope, int indent); diff --git a/js/scripting-lang/baba-yaga-c/src/function.c b/js/scripting-lang/baba-yaga-c/src/function.c index 39265ef..2eca14d 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,7 +144,9 @@ 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 */ @@ -151,7 +157,7 @@ Value baba_yaga_function_call(const Value* func, const Value* args, 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; @@ -159,7 +165,9 @@ Value baba_yaga_function_call(const Value* func, const Value* args, /* Execute user-defined function */ if (func_value->body.user_body.ast_node != NULL) { /* Create new scope for function execution */ - Scope* func_scope = scope_create(scope); /* Pass current scope as parent for closures */ + /* According to JS team requirements: function calls create local scopes that inherit from global scope */ + Scope* global_scope = scope_get_global(scope); + Scope* func_scope = scope_create(global_scope); /* Pass global scope as parent for local function scope */ if (func_scope == NULL) { DEBUG_ERROR("Failed to create function scope"); return baba_yaga_value_nil(); @@ -185,6 +193,8 @@ Value baba_yaga_function_call(const Value* func, const Value* args, return result; } break; + + } return baba_yaga_value_nil(); @@ -229,9 +239,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 d06eb30..f865adb 100644 --- a/js/scripting-lang/baba-yaga-c/src/interpreter.c +++ b/js/scripting-lang/baba-yaga-c/src/interpreter.c @@ -48,6 +48,9 @@ typedef struct { Value interpreter_evaluate_expression(void* node, Scope* scope); static Value interpreter_evaluate_statement(void* node, Scope* scope); +/* Standard library function declarations */ +Value stdlib_table_entry(Value* args, int argc); + /* ============================================================================ * Interpreter Structure * ============================================================================ */ @@ -71,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 */ @@ -79,73 +82,92 @@ 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_wrapper, 2, 2); + scope_define(scope, "greaterThan", greater_than_func, true); + + 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_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_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_wrapper, 1, 1); + scope_define(scope, "emit", emit_func, true); + + Value listen_func = baba_yaga_value_function("listen", stdlib_listen_wrapper, 0, 0); + scope_define(scope, "listen", listen_func, true); + /* Higher-order functions */ Value map_func = baba_yaga_value_function("map", stdlib_map, 2, 2); scope_define(scope, "map", map_func, true); @@ -156,7 +178,63 @@ static void register_stdlib(Scope* scope) { Value reduce_func = baba_yaga_value_function("reduce", stdlib_reduce, 3, 3); scope_define(scope, "reduce", reduce_func, true); - DEBUG_INFO("Registered %d standard library functions", 20); + /* Advanced combinators */ + 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_wrapper, 3, 1); + scope_define(scope, "flip", flip_func, true); + + 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_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_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_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_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_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_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_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_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_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_wrapper, 2, 2); + scope_define(scope, "table_entry", table_entry_func, true); + + /* Create t namespace table */ + Value t_table = baba_yaga_value_table(); + t_table = baba_yaga_table_set(&t_table, "map", &t_map_func); + t_table = baba_yaga_table_set(&t_table, "filter", &t_filter_func); + t_table = baba_yaga_table_set(&t_table, "reduce", &t_reduce_func); + t_table = baba_yaga_table_set(&t_table, "set", &t_set_func); + t_table = baba_yaga_table_set(&t_table, "delete", &t_delete_func); + t_table = baba_yaga_table_set(&t_table, "merge", &t_merge_func); + t_table = baba_yaga_table_set(&t_table, "length", &t_length_func); + t_table = baba_yaga_table_set(&t_table, "has", &t_has_func); + t_table = baba_yaga_table_set(&t_table, "get", &t_get_func); + + scope_define(scope, "t", t_table, true); + + DEBUG_INFO("Registered %d standard library functions", 31); } /* ============================================================================ @@ -328,11 +406,14 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { } NodeType node_type = baba_yaga_ast_get_type(node); - DEBUG_TRACE("Evaluating expression: type %d", node_type); + DEBUG_DEBUG("Evaluating expression: type %d", node_type); switch (node_type) { - case NODE_LITERAL: - return baba_yaga_ast_get_literal(node); + case NODE_LITERAL: { + Value literal = baba_yaga_ast_get_literal(node); + DEBUG_DEBUG("Literal evaluation: type %d", literal.type); + return literal; + } case NODE_IDENTIFIER: { const char* identifier = baba_yaga_ast_get_identifier(node); @@ -366,6 +447,7 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { } case NODE_FUNCTION_CALL: { + DEBUG_DEBUG("Evaluating NODE_FUNCTION_CALL"); /* Evaluate function */ void* func_node = baba_yaga_ast_get_function_call_func(node); Value func_value = interpreter_evaluate_expression(func_node, scope); @@ -540,7 +622,9 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { return baba_yaga_value_nil(); } + Value value = interpreter_evaluate_expression(value_node, scope); + DEBUG_DEBUG("Variable declaration: evaluating '%s' = value with type %d", name, value.type); scope_define(scope, name, value, false); return value; } @@ -571,6 +655,7 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { } case NODE_WHEN_EXPR: { + DEBUG_DEBUG("Evaluating NODE_WHEN_EXPR"); /* Evaluate the test expression */ void* test_node = baba_yaga_ast_get_when_expr_test(node); Value test_value = interpreter_evaluate_expression(test_node, scope); @@ -601,6 +686,57 @@ 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; + + /* Get all keys from the pattern table */ + char* pattern_keys[100]; /* Assume max 100 keys */ + size_t pattern_key_count = baba_yaga_table_get_keys(&pattern_test_value, pattern_keys, 100); + + /* Check each property in the pattern */ + for (size_t i = 0; i < pattern_key_count; i++) { + char* pattern_key = pattern_keys[i]; + + /* Check if this property exists in the test value */ + if (!baba_yaga_table_has_key(&test_value, pattern_key)) { + /* Property doesn't exist in test value */ + matches = false; + break; + } + + /* Get pattern property value */ + Value pattern_property = baba_yaga_table_get(&pattern_test_value, pattern_key); + /* Get test property value */ + Value test_property = baba_yaga_table_get(&test_value, pattern_key); + + /* Check if property values match */ + bool property_matches = false; + if (pattern_property.type == test_property.type) { + switch (pattern_property.type) { + case VAL_NUMBER: + property_matches = (pattern_property.data.number == test_property.data.number); + break; + case VAL_STRING: + property_matches = (strcmp(pattern_property.data.string, test_property.data.string) == 0); + break; + case VAL_BOOLEAN: + property_matches = (pattern_property.data.boolean == test_property.data.boolean); + break; + default: + property_matches = false; + break; + } + } + + if (!property_matches) { + matches = false; + break; + } + } } baba_yaga_value_destroy(&pattern_test_value); @@ -620,6 +756,143 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { return baba_yaga_value_nil(); } + case NODE_TABLE: { + DEBUG_DEBUG("Evaluating NODE_TABLE"); + /* Evaluate table literal */ + int element_count = baba_yaga_ast_get_table_element_count(node); + DEBUG_DEBUG("Evaluating table with %d elements", element_count); + + /* Create a new table value */ + Value table = baba_yaga_value_table(); + + /* Evaluate each element and add to table */ + for (int i = 0; i < element_count; i++) { + void* element_node = baba_yaga_ast_get_table_element(node, i); + if (element_node == NULL) { + DEBUG_ERROR("Table element %d is NULL", i); + continue; + } + + /* Check if this is a table_entry function call (key-value pair) */ + NodeType element_type = baba_yaga_ast_get_type(element_node); + if (element_type == NODE_FUNCTION_CALL) { + /* Get function name */ + void* func_node = baba_yaga_ast_get_function_call_func(element_node); + if (func_node != NULL && baba_yaga_ast_get_type(func_node) == NODE_IDENTIFIER) { + const char* func_name = baba_yaga_ast_get_identifier(func_node); + if (func_name && strcmp(func_name, "table_entry") == 0) { + /* This is a key-value pair */ + int arg_count = baba_yaga_ast_get_function_call_arg_count(element_node); + if (arg_count == 2) { + /* Get key and value */ + void* key_node = baba_yaga_ast_get_function_call_arg(element_node, 0); + void* value_node = baba_yaga_ast_get_function_call_arg(element_node, 1); + + if (key_node != NULL && value_node != NULL) { + Value key_value = interpreter_evaluate_expression(key_node, scope); + Value element_value = interpreter_evaluate_expression(value_node, scope); + + /* Extract key string */ + char* key_str = NULL; + if (key_value.type == VAL_STRING) { + key_str = strdup(key_value.data.string); + } else if (key_value.type == VAL_NUMBER) { + char num_str[32]; + snprintf(num_str, sizeof(num_str), "%g", key_value.data.number); + key_str = strdup(num_str); + } else { + key_str = strdup("unknown"); + } + + DEBUG_DEBUG("Setting table key '%s' to element %d", key_str, i); + table = baba_yaga_table_set(&table, key_str, &element_value); + + free(key_str); + baba_yaga_value_destroy(&key_value); + baba_yaga_value_destroy(&element_value); + continue; + } + } + } + } + } + + /* Fallback to array-like indexing (1-based) */ + Value element_value = interpreter_evaluate_expression(element_node, scope); + DEBUG_DEBUG("Table element %d evaluated to type %d", i, element_value.type); + + char key_str[32]; + snprintf(key_str, sizeof(key_str), "%d", i + 1); + Value key = baba_yaga_value_string(key_str); + + DEBUG_DEBUG("Setting table key '%s' to element %d", key_str, i); + table = baba_yaga_table_set(&table, key.data.string, &element_value); + + baba_yaga_value_destroy(&key); + baba_yaga_value_destroy(&element_value); + } + + DEBUG_DEBUG("Table evaluation complete, final size: %zu", baba_yaga_table_size(&table)); + return table; + } + + case NODE_TABLE_ACCESS: { + /* Evaluate table access: table.property or table[key] */ + void* object_node = baba_yaga_ast_get_table_access_object(node); + void* key_node = baba_yaga_ast_get_table_access_key(node); + + if (object_node == NULL || key_node == NULL) { + DEBUG_ERROR("Invalid table access node"); + return baba_yaga_value_nil(); + } + + /* Evaluate the object (table) */ + Value object = interpreter_evaluate_expression(object_node, scope); + DEBUG_DEBUG("Table access - object type: %d", object.type); + if (object.type != VAL_TABLE) { + DEBUG_ERROR("Cannot access property of non-table value"); + baba_yaga_value_destroy(&object); + return baba_yaga_value_nil(); + } + + /* Evaluate the key */ + Value key = interpreter_evaluate_expression(key_node, scope); + DEBUG_DEBUG("Table access - key type: %d", key.type); + if (key.type != VAL_STRING && key.type != VAL_NUMBER) { + DEBUG_ERROR("Table key must be string or number"); + baba_yaga_value_destroy(&object); + baba_yaga_value_destroy(&key); + return baba_yaga_value_nil(); + } + + /* Convert key to string for table lookup */ + char* key_str; + if (key.type == VAL_NUMBER) { + key_str = malloc(32); + if (key_str == NULL) { + baba_yaga_value_destroy(&object); + baba_yaga_value_destroy(&key); + return baba_yaga_value_nil(); + } + snprintf(key_str, 32, "%g", key.data.number); + } else { + key_str = strdup(key.data.string); + } + + DEBUG_DEBUG("Table access - looking up key: '%s'", key_str); + + /* Get the value from the table */ + Value result = baba_yaga_table_get(&object, key_str); + DEBUG_DEBUG("Table access - result type: %d", result.type); + + /* Cleanup */ + free(key_str); + baba_yaga_value_destroy(&object); + baba_yaga_value_destroy(&key); + + return result; + } + default: DEBUG_ERROR("Unsupported expression type: %d", node_type); return baba_yaga_value_nil(); diff --git a/js/scripting-lang/baba-yaga-c/src/lexer.c b/js/scripting-lang/baba-yaga-c/src/lexer.c index ede55b1..31a582f 100644 --- a/js/scripting-lang/baba-yaga-c/src/lexer.c +++ b/js/scripting-lang/baba-yaga-c/src/lexer.c @@ -72,7 +72,9 @@ typedef enum { TOKEN_FUNCTION_REF, /* @function */ TOKEN_IO_IN, /* ..in */ TOKEN_IO_OUT, /* ..out */ - TOKEN_IO_ASSERT /* ..assert */ + TOKEN_IO_ASSERT, /* ..assert */ + TOKEN_IO_EMIT, /* ..emit */ + TOKEN_IO_LISTEN /* ..listen */ } TokenType; /* ============================================================================ @@ -461,6 +463,7 @@ static Token lexer_read_identifier(Lexer* lexer) { /* Check if it's a keyword */ if (strcmp(token.lexeme, "when") == 0) { + token.type = TOKEN_KEYWORD_WHEN; } else if (strcmp(token.lexeme, "is") == 0) { token.type = TOKEN_KEYWORD_IS; @@ -579,6 +582,10 @@ static Token lexer_read_special(Lexer* lexer) { token.type = TOKEN_IO_OUT; } else if (strcmp(token.lexeme, "..assert") == 0) { token.type = TOKEN_IO_ASSERT; + } else if (strcmp(token.lexeme, "..emit") == 0) { + token.type = TOKEN_IO_EMIT; + } else if (strcmp(token.lexeme, "..listen") == 0) { + token.type = TOKEN_IO_LISTEN; } else { lexer_set_error(lexer, "Unknown IO operation"); token.type = TOKEN_EOF; @@ -677,8 +684,16 @@ static Token lexer_next_token(Lexer* lexer) { if (lexer_match(lexer, '>')) { return token_create(TOKEN_ARROW, "->", lexer->line, lexer->column - 2); } - /* For now, always treat minus as binary operator */ - /* TODO: Implement proper unary vs binary minus detection */ + + /* Check if this is a unary minus (followed by a digit, identifier, or parentheses) */ + if ((lexer_peek(lexer) >= '0' && lexer_peek(lexer) <= '9') || + (lexer_peek(lexer) >= 'a' && lexer_peek(lexer) <= 'z') || + (lexer_peek(lexer) >= 'A' && lexer_peek(lexer) <= 'Z') || + (lexer_peek(lexer) == '_') || + (lexer_peek(lexer) == '(')) { + return token_create(TOKEN_OP_UNARY_MINUS, "-", lexer->line, lexer->column - 1); + } + /* Otherwise treat as binary minus */ return token_create(TOKEN_OP_MINUS, "-", lexer->line, lexer->column - 1); case '+': lexer_advance(lexer); diff --git a/js/scripting-lang/baba-yaga-c/src/parser.c b/js/scripting-lang/baba-yaga-c/src/parser.c index 0a0b519..6c94913 100644 --- a/js/scripting-lang/baba-yaga-c/src/parser.c +++ b/js/scripting-lang/baba-yaga-c/src/parser.c @@ -60,7 +60,9 @@ typedef enum { TOKEN_FUNCTION_REF, TOKEN_IO_IN, TOKEN_IO_OUT, - TOKEN_IO_ASSERT + TOKEN_IO_ASSERT, + TOKEN_IO_EMIT, + TOKEN_IO_LISTEN } TokenType; typedef struct { @@ -333,6 +335,7 @@ static ASTNode* ast_when_expr_node(ASTNode* test, ASTNode** patterns, node->data.when_expr.patterns = patterns; node->data.when_expr.pattern_count = pattern_count; + return node; } @@ -587,7 +590,10 @@ static ASTNode* parser_parse_statement(Parser* parser); static ASTNode* parser_parse_when_expression(Parser* parser); static ASTNode* parser_parse_when_pattern(Parser* parser); static ASTNode* parser_parse_when_result_expression(Parser* parser); +static ASTNode* parser_parse_postfix(Parser* parser); static const char* node_type_name(NodeType type); +static ASTNode* parser_parse_function_def(Parser* parser); +static ASTNode* parser_parse_embedded_arrow_function(Parser* parser); /** * @brief Parse primary expression (literals, identifiers, parentheses) @@ -633,7 +639,9 @@ static ASTNode* parser_parse_primary(Parser* parser) { } case TOKEN_IO_IN: case TOKEN_IO_OUT: - case TOKEN_IO_ASSERT: { + case TOKEN_IO_ASSERT: + case TOKEN_IO_EMIT: + case TOKEN_IO_LISTEN: { DEBUG_TRACE("parser_parse_primary consuming io operation: %s", token->lexeme); parser_advance(parser); /* IO operations are treated as function calls - strip the ".." prefix */ @@ -665,9 +673,46 @@ static ASTNode* parser_parse_primary(Parser* parser) { return ast_function_call_node(func_node, args, 1, token->line, token->column); } + /* For ..emit, parse the entire expression as a single argument */ + if (strcmp(func_name, "emit") == 0) { + /* Parse the expression */ + ASTNode* expr = parser_parse_expression(parser); + if (expr == NULL) { + return NULL; + } + + /* Create function call with the expression as argument */ + ASTNode** args = malloc(1 * sizeof(ASTNode*)); + if (args == NULL) { + ast_destroy_node(expr); + return NULL; + } + args[0] = expr; + + ASTNode* func_node = ast_identifier_node(func_name, token->line, token->column); + if (func_node == NULL) { + free(args); + ast_destroy_node(expr); + return NULL; + } + + return ast_function_call_node(func_node, args, 1, token->line, token->column); + } + + /* For ..listen, create a function call with no arguments */ + if (strcmp(func_name, "listen") == 0) { + ASTNode* func_node = ast_identifier_node(func_name, token->line, token->column); + if (func_node == NULL) { + return NULL; + } + + return ast_function_call_node(func_node, NULL, 0, token->line, token->column); + } + return ast_identifier_node(func_name, token->line, token->column); } case TOKEN_KEYWORD_WHEN: { + return parser_parse_when_expression(parser); } case TOKEN_FUNCTION_REF: { @@ -702,6 +747,7 @@ static ASTNode* parser_parse_primary(Parser* parser) { } /* Check if this function reference is followed by arguments */ + /* Only treat as function call if it's at the top level (not in an argument position) */ if (!parser_is_at_end(parser)) { Token* next_token = parser_peek(parser); if (next_token != NULL && @@ -724,6 +770,11 @@ static ASTNode* parser_parse_primary(Parser* parser) { next_token->type != TOKEN_COMMA && next_token->type != TOKEN_EOF) { + /* For now, always treat function references as values, not function calls */ + /* This allows them to be passed as arguments to other functions */ + DEBUG_TRACE("parser_parse_primary: treating function reference as value"); + return func_node; + /* Parse arguments for this function call */ ASTNode** args = NULL; int arg_count = 0; @@ -757,7 +808,7 @@ static ASTNode* parser_parse_primary(Parser* parser) { } /* Parse argument */ - ASTNode* arg = parser_parse_primary(parser); + ASTNode* arg = parser_parse_postfix(parser); if (arg == NULL) { /* Cleanup on error */ for (int i = 0; i < arg_count; i++) { @@ -819,10 +870,318 @@ static ASTNode* parser_parse_primary(Parser* parser) { return expr; } + case TOKEN_LBRACE: { + DEBUG_TRACE("parser_parse_primary consuming table literal '{'"); + parser_advance(parser); /* consume '{' */ + + ASTNode** elements = NULL; + int element_count = 0; + int capacity = 10; + + /* Allocate initial space for elements */ + elements = malloc(capacity * sizeof(ASTNode*)); + if (elements == NULL) { + return NULL; + } + + /* Parse table entries */ + while (!parser_is_at_end(parser) && parser_peek(parser)->type != TOKEN_RBRACE) { + ASTNode* value = NULL; + + /* Check if this is a key-value pair (any token: value) */ + + /* 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 */ + 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; + } + } + + /* Consume colon */ + if (!parser_consume(parser, TOKEN_COLON, "Expected ':' after table key")) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + + /* Check if this is an arrow function by looking ahead */ + bool is_arrow_function = false; + int look_ahead = parser->current; + int identifier_count = 0; + + /* Look ahead to see if we have identifiers followed by '->' */ + while (look_ahead < parser->token_count) { + Token* token = parser->tokens[look_ahead]; + if (token->type == TOKEN_ARROW) { + /* If we have at least one identifier before '->', it's an arrow function */ + if (identifier_count > 0) { + is_arrow_function = true; + } + break; + } + if (token->type == TOKEN_IDENTIFIER) { + identifier_count++; + } else if (token->type == TOKEN_COMMA || token->type == TOKEN_RBRACE) { + /* Stop looking if we hit table boundaries */ + break; + } else { + /* If we hit anything else, it's not an arrow function */ + identifier_count = 0; + break; + } + look_ahead++; + } + + /* Parse the value */ + if (is_arrow_function) { + /* Parse as embedded arrow function */ + value = parser_parse_embedded_arrow_function(parser); + } else { + /* Parse as general expression */ + value = parser_parse_expression(parser); + } + if (value == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + + /* For now, we'll store key-value pairs as function calls to a special "table_entry" function */ + /* This allows us to represent both key-value pairs and array-like entries uniformly */ + ASTNode** entry_args = malloc(2 * sizeof(ASTNode*)); + if (entry_args == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + ast_destroy_node(value); + return NULL; + } + + /* 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 { + /* 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; + } + key_arg = ast_literal_node(key_value, key_token->line, key_token->column); + } + + entry_args[0] = key_arg; + entry_args[1] = value; + + 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]); + } + 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(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++) { + ast_destroy_node(elements[i]); + } + free(elements); + free(entry_args); + ast_destroy_node(table_entry_node); + ast_destroy_node(value); + if (key_node != NULL) { + ast_destroy_node(key_node); + } + return NULL; + } + + value = entry_node; + } else { + /* Parse array-like entry (just a value) */ + value = parser_parse_expression(parser); + if (value == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + } + + /* Check if we need more space */ + if (element_count >= capacity) { + capacity *= 2; + ASTNode** new_elements = realloc(elements, capacity * sizeof(ASTNode*)); + if (new_elements == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + ast_destroy_node(value); + return NULL; + } + elements = new_elements; + } + + elements[element_count++] = value; + + /* Check for comma separator */ + if (!parser_is_at_end(parser) && parser_peek(parser)->type == TOKEN_COMMA) { + parser_advance(parser); /* consume ',' */ + } else if (!parser_is_at_end(parser) && parser_peek(parser)->type != TOKEN_RBRACE) { + /* No comma but not end of table - this is an error */ + parser_set_error(parser, "Expected ',' or '}' in table literal"); + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + } + + /* Expect closing brace */ + if (!parser_consume(parser, TOKEN_RBRACE, "Expected '}' after table literal")) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + + /* Create table node */ + ASTNode* node = malloc(sizeof(ASTNode)); + if (node == NULL) { + /* Cleanup on error */ + for (int i = 0; i < element_count; i++) { + ast_destroy_node(elements[i]); + } + free(elements); + return NULL; + } + + node->type = NODE_TABLE; + node->line = token->line; + node->column = token->column; + node->data.table.elements = elements; + node->data.table.element_count = element_count; + + return node; + } case TOKEN_OP_UNARY_MINUS: { DEBUG_TRACE("parser_parse_primary consuming unary minus"); parser_advance(parser); /* consume '-' */ - ASTNode* operand = parser_parse_primary(parser); + ASTNode* operand = parser_parse_postfix(parser); if (operand == NULL) { return NULL; } @@ -831,7 +1190,7 @@ static ASTNode* parser_parse_primary(Parser* parser) { case TOKEN_KEYWORD_NOT: { DEBUG_TRACE("parser_parse_primary consuming 'not'"); parser_advance(parser); /* consume 'not' */ - ASTNode* operand = parser_parse_primary(parser); + ASTNode* operand = parser_parse_postfix(parser); if (operand == NULL) { return NULL; } @@ -859,14 +1218,14 @@ static ASTNode* parser_parse_primary(Parser* parser) { * @return Parsed expression node */ static ASTNode* parser_parse_power(Parser* parser) { - ASTNode* left = parser_parse_primary(parser); + ASTNode* left = parser_parse_postfix(parser); if (left == NULL) { return NULL; } while (parser_check(parser, TOKEN_OP_POWER)) { Token* op = parser_advance(parser); - ASTNode* right = parser_parse_primary(parser); + ASTNode* right = parser_parse_postfix(parser); if (right == NULL) { ast_destroy_node(left); return NULL; @@ -1063,16 +1422,43 @@ static ASTNode* parser_parse_logical(Parser* parser) { left = new_left; } + /* Handle via operator (function composition) - right-associative */ + while (parser_check(parser, TOKEN_KEYWORD_VIA)) { + Token* op = parser_advance(parser); + ASTNode* right = parser_parse_logical(parser); /* Right-associative: recurse */ + if (right == NULL) { + ast_destroy_node(left); + return NULL; + } + + ASTNode* new_left = ast_binary_op_node(left, right, "via", op->line, op->column); + if (new_left == NULL) { + ast_destroy_node(left); + ast_destroy_node(right); + return NULL; + } + + left = new_left; + } + /* Handle function application */ + /* Skip function application if the left node is a when expression */ + if (left->type == NODE_WHEN_EXPR) { + return left; + } + while (!parser_is_at_end(parser)) { Token* next_token = parser_peek(parser); if (next_token == NULL) break; + + /* Check if this token can be a function argument */ bool can_be_arg = (next_token->type == TOKEN_IDENTIFIER || next_token->type == TOKEN_FUNCTION_REF || next_token->type == TOKEN_NUMBER || next_token->type == TOKEN_STRING || + next_token->type == TOKEN_BOOLEAN || next_token->type == TOKEN_LPAREN || next_token->type == TOKEN_LBRACE || next_token->type == TOKEN_OP_UNARY_MINUS || @@ -1101,6 +1487,7 @@ static ASTNode* parser_parse_logical(Parser* parser) { next_token->type == TOKEN_KEYWORD_WHEN || next_token->type == TOKEN_KEYWORD_IS || next_token->type == TOKEN_KEYWORD_THEN || + next_token->type == TOKEN_KEYWORD_VIA || next_token->type == TOKEN_RPAREN || next_token->type == TOKEN_RBRACE || next_token->type == TOKEN_RBRACKET || @@ -1126,6 +1513,7 @@ static ASTNode* parser_parse_logical(Parser* parser) { /* Only proceed with function application if it can be an arg and shouldn't trigger */ if (!can_be_arg || should_not_trigger || is_pattern_boundary) { + break; } @@ -1142,6 +1530,7 @@ static ASTNode* parser_parse_logical(Parser* parser) { arg_token->type == TOKEN_FUNCTION_REF || arg_token->type == TOKEN_NUMBER || arg_token->type == TOKEN_STRING || + arg_token->type == TOKEN_BOOLEAN || arg_token->type == TOKEN_LPAREN || arg_token->type == TOKEN_LBRACE || arg_token->type == TOKEN_OP_UNARY_MINUS || @@ -1275,6 +1664,98 @@ static ASTNode* parser_parse_composition(Parser* parser) { /** + * @brief Parse postfix operations (table access, function calls, etc.) + * + * @param parser Parser instance + * @return Parsed expression node + */ +static ASTNode* parser_parse_postfix(Parser* parser) { + ASTNode* left = parser_parse_primary(parser); + if (left == NULL) { + return NULL; + } + + while (!parser_is_at_end(parser)) { + Token* token = parser_peek(parser); + if (token == NULL) { + break; + } + + switch (token->type) { + case TOKEN_DOT: { + /* Table property access: table.property */ + parser_advance(parser); /* consume '.' */ + + Token* property = parser_consume(parser, TOKEN_IDENTIFIER, "Expected property name after '.'"); + if (property == NULL) { + ast_destroy_node(left); + return NULL; + } + + ASTNode* key = ast_literal_node(baba_yaga_value_string(property->lexeme), property->line, property->column); + if (key == NULL) { + ast_destroy_node(left); + return NULL; + } + + ASTNode* new_left = malloc(sizeof(ASTNode)); + if (new_left == NULL) { + ast_destroy_node(left); + ast_destroy_node(key); + return NULL; + } + + new_left->type = NODE_TABLE_ACCESS; + new_left->line = left->line; + new_left->column = left->column; + new_left->data.table_access.object = left; + new_left->data.table_access.key = key; + + left = new_left; + break; + } + case TOKEN_LBRACKET: { + /* Table bracket access: table[key] */ + parser_advance(parser); /* consume '[' */ + + ASTNode* key = parser_parse_expression(parser); + if (key == NULL) { + ast_destroy_node(left); + return NULL; + } + + if (!parser_consume(parser, TOKEN_RBRACKET, "Expected ']' after table key")) { + ast_destroy_node(left); + ast_destroy_node(key); + return NULL; + } + + ASTNode* new_left = malloc(sizeof(ASTNode)); + if (new_left == NULL) { + ast_destroy_node(left); + ast_destroy_node(key); + return NULL; + } + + new_left->type = NODE_TABLE_ACCESS; + new_left->line = left->line; + new_left->column = left->column; + new_left->data.table_access.object = left; + new_left->data.table_access.key = key; + + left = new_left; + break; + } + default: + /* No more postfix operations */ + return left; + } + } + + return left; +} + +/** * @brief Parse expression (entry point) * * @param parser Parser instance @@ -1309,6 +1790,8 @@ static ASTNode* parser_parse_variable_decl(Parser* parser) { return NULL; } + + ASTNode* node = malloc(sizeof(ASTNode)); if (node == NULL) { ast_destroy_node(value); @@ -1321,6 +1804,7 @@ static ASTNode* parser_parse_variable_decl(Parser* parser) { node->data.variable_decl.name = strdup(name->lexeme); node->data.variable_decl.value = value; + return node; } @@ -1401,6 +1885,73 @@ static ASTNode* parser_parse_function_def(Parser* parser) { } /** + * @brief Parse embedded arrow function (params -> body) without function name + * + * @param parser Parser instance + * @return Parsed function definition node + */ +static ASTNode* parser_parse_embedded_arrow_function(Parser* parser) { + /* Parse parameters */ + ASTNode** parameters = NULL; + int param_count = 0; + + while (!parser_is_at_end(parser) && + parser_peek(parser)->type == TOKEN_IDENTIFIER) { + Token* param = parser_advance(parser); + + ASTNode** new_params = realloc(parameters, (param_count + 1) * sizeof(ASTNode*)); + if (new_params == NULL) { + for (int i = 0; i < param_count; i++) { + ast_destroy_node(parameters[i]); + } + free(parameters); + return NULL; + } + parameters = new_params; + + parameters[param_count] = ast_identifier_node(param->lexeme, param->line, param->column); + param_count++; + } + + if (!parser_consume(parser, TOKEN_ARROW, "Expected '->' after parameters")) { + for (int i = 0; i < param_count; i++) { + ast_destroy_node(parameters[i]); + } + free(parameters); + return NULL; + } + + ASTNode* body = parser_parse_expression(parser); + if (body == NULL) { + for (int i = 0; i < param_count; i++) { + ast_destroy_node(parameters[i]); + } + free(parameters); + return NULL; + } + + ASTNode* node = malloc(sizeof(ASTNode)); + if (node == NULL) { + for (int i = 0; i < param_count; i++) { + ast_destroy_node(parameters[i]); + } + free(parameters); + ast_destroy_node(body); + return NULL; + } + + node->type = NODE_FUNCTION_DEF; + node->line = parser_peek(parser)->line; + node->column = parser_peek(parser)->column; + node->data.function_def.name = strdup(""); /* Empty name for embedded functions */ + node->data.function_def.parameters = parameters; + node->data.function_def.param_count = param_count; + node->data.function_def.body = body; + + return node; +} + +/** * @brief Parse multiple statements separated by semicolons * * @param parser Parser instance @@ -1872,6 +2423,61 @@ void* baba_yaga_ast_get_when_pattern_result(void* node) { return ast_node->data.when_pattern.result; } +int baba_yaga_ast_get_table_element_count(void* node) { + if (node == NULL) { + return 0; + } + + ASTNode* ast_node = (ASTNode*)node; + if (ast_node->type != NODE_TABLE) { + return 0; + } + + return ast_node->data.table.element_count; +} + +void* baba_yaga_ast_get_table_element(void* node, int index) { + if (node == NULL) { + return NULL; + } + + ASTNode* ast_node = (ASTNode*)node; + if (ast_node->type != NODE_TABLE) { + return NULL; + } + + if (index >= 0 && index < ast_node->data.table.element_count) { + return ast_node->data.table.elements[index]; + } + return NULL; +} + +void* baba_yaga_ast_get_table_access_object(void* node) { + if (node == NULL) { + return NULL; + } + + ASTNode* ast_node = (ASTNode*)node; + if (ast_node->type != NODE_TABLE_ACCESS) { + return NULL; + } + + return ast_node->data.table_access.object; +} + +void* baba_yaga_ast_get_table_access_key(void* node) { + if (node == NULL) { + return NULL; + } + + ASTNode* ast_node = (ASTNode*)node; + if (ast_node->type != NODE_TABLE_ACCESS) { + return NULL; + } + + return ast_node->data.table_access.key; +} + void baba_yaga_print_ast(void* node, int indent) { if (node == NULL) { return; @@ -1953,9 +2559,100 @@ void baba_yaga_print_ast(void* node, int indent) { * @return Parsed when expression node */ static ASTNode* parser_parse_when_expression(Parser* parser) { + DEBUG_DEBUG("Parsing WHEN expression at token %d", parser->current); Token* when_token = parser_consume(parser, TOKEN_KEYWORD_WHEN, "Expected 'when'"); if (!when_token) return NULL; - ASTNode* test = parser_parse_expression(parser); + + + + /* Check if this is a multi-parameter pattern by looking ahead for multiple identifiers */ + bool is_multi_param = false; + int look_ahead = parser->current; + int identifier_count = 0; + + /* 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) { + break; + } + 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 or expression, it's not multi-parameter */ + identifier_count = 0; + break; + } + look_ahead++; + } + + /* If we have multiple identifiers followed by 'is', it's multi-parameter */ + if (identifier_count > 1) { + is_multi_param = true; + } + + ASTNode* test; + if (is_multi_param) { + /* 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* 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); + } + if (!test) return NULL; Token* is_token = parser_consume(parser, TOKEN_KEYWORD_IS, "Expected 'is' after test expression"); if (!is_token) { ast_destroy_node(test); return NULL; } @@ -1990,6 +2687,7 @@ static ASTNode* parser_parse_when_expression(Parser* parser) { } // Build AST node for when expression ASTNode* when_node = ast_when_expr_node(test, patterns, pattern_count, when_token->line, when_token->column); + return when_node; } @@ -2048,6 +2746,7 @@ static ASTNode* parser_parse_when_result_expression(Parser* parser) { } static ASTNode* parser_parse_when_pattern(Parser* parser) { + DEBUG_DEBUG("Parsing WHEN pattern at token %d", parser->current); DEBUG_TRACE("parser_parse_when_pattern start"); /* Show current token */ @@ -2056,22 +2755,190 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { DEBUG_TRACE("Current token type=%d, lexeme='%s'", current_token->type, current_token->lexeme ? current_token->lexeme : "NULL"); } - /* Check if this is a wildcard pattern (_) */ + /* Check if this is a multi-parameter pattern by looking ahead for multiple literals */ + bool is_multi_param = false; + int look_ahead = parser->current; + int literal_count = 0; + + /* 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) { + break; + } + if (token->type == TOKEN_IDENTIFIER || + token->type == TOKEN_NUMBER || + 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 || + token->type == TOKEN_OP_LESS_EQUAL || + token->type == TOKEN_OP_GREATER || + token->type == TOKEN_OP_GREATER_EQUAL) { + /* 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 or expression, it's not multi-parameter */ + literal_count = 0; + break; + } + look_ahead++; + } + + /* 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; + } + ASTNode* pattern_test; - if (current_token && current_token->type == TOKEN_IDENTIFIER && - current_token->lexeme && strcmp(current_token->lexeme, "_") == 0) { - /* Special handling for wildcard pattern */ + if (is_multi_param) { + /* Parse as sequence of literals */ + ASTNode** literals = malloc(literal_count * sizeof(ASTNode*)); + if (!literals) return NULL; + + for (int i = 0; i < literal_count; i++) { + 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 { + /* 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; + } + } + } + + /* Create a sequence node for the literals */ + pattern_test = ast_sequence_node(literals, literal_count, parser_peek(parser)->line, parser_peek(parser)->column); + } else if (current_token && current_token->type == TOKEN_LBRACE) { + /* Table pattern: { status: "placeholder" } */ + DEBUG_TRACE("Found table pattern"); + /* Parse as table literal */ + pattern_test = parser_parse_primary(parser); + if (pattern_test == NULL) { + DEBUG_TRACE("Failed to parse table pattern"); + return NULL; + } + DEBUG_TRACE("Successfully parsed table pattern"); + } else if (current_token && current_token->type == TOKEN_IDENTIFIER && + current_token->lexeme && strcmp(current_token->lexeme, "_") == 0) { + /* Special handling for single wildcard pattern */ DEBUG_TRACE("Found wildcard pattern"); /* Create a special wildcard literal */ - pattern_test = ast_literal_node(baba_yaga_value_string("__WILDCARD__"), + pattern_test = ast_literal_node(baba_yaga_value_string("_"), current_token->line, current_token->column); /* Consume the _ token */ parser_advance(parser); DEBUG_TRACE("Consumed _ token, current token type=%d, lexeme='%s'", parser_peek(parser)->type, parser_peek(parser)->lexeme ? parser_peek(parser)->lexeme : "NULL"); } else { - /* Parse pattern test expression */ - pattern_test = parser_parse_expression(parser); + /* Parse pattern test expression - stop at 'then' */ + /* Check if this is a comparison expression by looking ahead */ + bool is_comparison = false; + int look_ahead = parser->current; + + /* Look ahead to see if there's a comparison operator */ + while (look_ahead < parser->token_count) { + Token* token = parser->tokens[look_ahead]; + if (token->type == TOKEN_KEYWORD_THEN) { + break; /* Found 'then', stop looking */ + } + if (token->type == TOKEN_OP_EQUALS || + token->type == TOKEN_OP_NOT_EQUALS || + token->type == TOKEN_OP_LESS || + token->type == TOKEN_OP_LESS_EQUAL || + token->type == TOKEN_OP_GREATER || + token->type == TOKEN_OP_GREATER_EQUAL) { + is_comparison = true; + break; + } + look_ahead++; + } + + if (is_comparison) { + /* Parse as comparison expression but stop at 'then' */ + /* Find the 'then' token position */ + int then_pos = -1; + for (int i = parser->current; i < parser->token_count; i++) { + if (parser->tokens[i]->type == TOKEN_KEYWORD_THEN) { + then_pos = i; + break; + } + } + + if (then_pos == -1) { + DEBUG_TRACE("No 'then' token found after comparison pattern"); + return NULL; + } + + /* Temporarily limit parsing to stop at 'then' */ + int original_token_count = parser->token_count; + parser->token_count = then_pos; + + /* Parse the comparison expression */ + pattern_test = parser_parse_comparison(parser); + + /* Restore parser state */ + parser->token_count = original_token_count; + } else { + /* Parse as simple expression */ + pattern_test = parser_parse_primary(parser); + } + if (pattern_test == NULL) { DEBUG_TRACE("Failed to parse pattern test expression"); return NULL; @@ -2079,28 +2946,10 @@ static ASTNode* parser_parse_when_pattern(Parser* parser) { DEBUG_TRACE("Parsed pattern test expression"); } - /* Consume 'then' keyword */ - Token* then_token = parser_consume(parser, TOKEN_KEYWORD_THEN, "Expected 'then' after pattern"); - if (then_token == NULL) { - DEBUG_TRACE("Failed to consume 'then' token"); - ast_destroy_node(pattern_test); - return NULL; - } - DEBUG_TRACE("Consumed 'then' token"); - - /* Parse result expression (bounded) */ - ASTNode* result = parser_parse_when_result_expression(parser); - if (result == NULL) { - ast_destroy_node(pattern_test); - return NULL; - } - DEBUG_TRACE("Parsed result expression"); - DEBUG_TRACE("parser_parse_when_pattern success"); - /* Create when pattern node */ - return ast_when_pattern_node(pattern_test, result, - then_token->line, then_token->column); + /* Create when pattern node - only the pattern test, result will be added by caller */ + return pattern_test; } /* Helper function to get node type name */ diff --git a/js/scripting-lang/baba-yaga-c/src/scope.c b/js/scripting-lang/baba-yaga-c/src/scope.c index d546989..93ba957 100644 --- a/js/scripting-lang/baba-yaga-c/src/scope.c +++ b/js/scripting-lang/baba-yaga-c/src/scope.c @@ -89,6 +89,25 @@ void scope_destroy(Scope* scope) { } /** + * @brief Get the global scope (root scope with no parent) + * + * @param scope Starting scope + * @return Global scope, or NULL if not found + */ +Scope* scope_get_global(Scope* scope) { + if (scope == NULL) { + return NULL; + } + + /* Traverse up the scope chain until we find a scope with no parent */ + while (scope->parent != NULL) { + scope = scope->parent; + } + + return scope; +} + +/** * @brief Find an entry in the scope chain * * @param scope Starting scope @@ -123,9 +142,11 @@ Value scope_get(Scope* scope, const char* name) { ScopeEntry* entry = scope_find_entry(scope, name); if (entry == NULL) { + DEBUG_DEBUG("scope_get: variable '%s' not found in scope", name); return baba_yaga_value_nil(); } + DEBUG_DEBUG("scope_get: found variable '%s' in scope with type %d", name, entry->value.type); /* Return a copy of the value */ return baba_yaga_value_copy(&entry->value); } @@ -218,6 +239,8 @@ bool scope_define(Scope* scope, const char* name, Value value, bool is_constant) scope->entries = entry; scope->entry_count++; + DEBUG_DEBUG("scope_define: defined variable '%s' in scope with type %d", name, entry->value.type); + return true; } diff --git a/js/scripting-lang/baba-yaga-c/src/stdlib.c b/js/scripting-lang/baba-yaga-c/src/stdlib.c index b1b216b..3ecce0b 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 * ============================================================================ */ @@ -409,9 +594,43 @@ Value stdlib_compose(Value* args, int argc) { return baba_yaga_value_nil(); } - /* For now, implement a simple composition that works with the test case */ - /* The test "compose add 5 multiply 2" expects: add(5, multiply(x, 2)) */ - /* This is not true function composition, but matches the test expectation */ + if (argc == 2) { + /* Function composition: compose f g = f(g(x)) */ + Value f = args[0]; /* first function */ + Value g = args[1]; /* second function */ + + if (f.type != VAL_FUNCTION || g.type != VAL_FUNCTION) { + DEBUG_ERROR("compose: both arguments must be functions"); + return baba_yaga_value_nil(); + } + + /* For now, return a placeholder function */ + /* TODO: Implement proper function composition */ + DEBUG_DEBUG("compose: returning placeholder for function composition"); + return baba_yaga_value_copy(&f); + } + + if (argc == 3) { + /* Function composition: compose f g x = f(g(x)) */ + Value f = args[0]; /* first function */ + Value g = args[1]; /* second function */ + Value x = args[2]; /* argument to apply composition to */ + + if (f.type != VAL_FUNCTION || g.type != VAL_FUNCTION) { + DEBUG_ERROR("compose: first and second arguments must be functions"); + return baba_yaga_value_nil(); + } + + /* Apply g to x first, then apply f to the result */ + Value g_args[1] = {x}; + Value g_result = baba_yaga_function_call(&g, g_args, 1, NULL); + + Value f_args[1] = {g_result}; + Value result = baba_yaga_function_call(&f, f_args, 1, NULL); + + baba_yaga_value_destroy(&g_result); + return result; + } if (argc == 4) { /* Special case for the test: compose add 5 multiply 2 */ @@ -488,8 +707,41 @@ Value stdlib_assert(Value* args, int argc) { return baba_yaga_value_boolean(truthy); } +Value stdlib_emit(Value* args, int argc) { + if (argc != 1) { + DEBUG_ERROR("emit: expected 1 argument, got %d", argc); + return baba_yaga_value_nil(); + } + + Value arg = args[0]; + + /* For now, just print the value like ..out */ + char* str = baba_yaga_value_to_string(&arg); + printf("%s", str); + free(str); + + /* Return the emitted value */ + return baba_yaga_value_copy(&arg); +} + +Value stdlib_listen(Value* args, int argc) { + (void)args; /* Unused */ + (void)argc; /* Unused */ + + /* For now, return a placeholder state object */ + /* TODO: Implement actual state management */ + Value state = baba_yaga_value_table(); + Value status_val = baba_yaga_value_string("placeholder"); + Value message_val = baba_yaga_value_string("State not available in standalone mode"); + + state = baba_yaga_table_set(&state, "status", &status_val); + state = baba_yaga_table_set(&state, "message", &message_val); + + return state; +} + /* 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(); @@ -508,40 +760,384 @@ 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(); + } + 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, 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(); + } + DEBUG_DEBUG("reduce: reducing table with function"); + 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; +} + +/** + * @brief Each combinator - applies a function to each element of a table + * + * @param args Array of arguments [function, table, scalar/table] + * @param argc Number of arguments (should be 3) + * @return New table with function applied to each element + */ +Value stdlib_each(Value* args, int argc, Scope* scope) { + if (argc != 3) { + DEBUG_ERROR("each: expected 3 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value func = args[0]; + Value table1 = args[1]; + + if (func.type != VAL_FUNCTION) { + DEBUG_ERROR("each: first argument must be a function"); + return baba_yaga_value_nil(); + } + + if (table1.type != VAL_TABLE) { + DEBUG_ERROR("each: second argument must be a table"); + return baba_yaga_value_nil(); + } + + DEBUG_DEBUG("each: applying function to table elements"); + + /* Get the size of the first table */ + size_t table_size = baba_yaga_table_size(&table1); + DEBUG_DEBUG("each: table has %zu elements", table_size); + + Value arg3 = args[2]; + + /* Get all keys from the first table */ + char* keys[1000]; /* Large enough for most tables */ + size_t key_count = baba_yaga_table_get_keys(&table1, keys, 1000); + + /* Create result table */ + Value result = baba_yaga_value_table(); + + if (arg3.type == VAL_TABLE) { + /* each function table1 table2 - apply function to corresponding elements */ + DEBUG_DEBUG("each: applying function to corresponding elements of two tables"); + + size_t table2_size = baba_yaga_table_size(&arg3); + DEBUG_DEBUG("each: second table has %zu elements", table2_size); + + /* Get all keys from second table */ + char* keys2[1000]; + size_t key_count2 = baba_yaga_table_get_keys(&arg3, keys2, 1000); + + /* Apply function to corresponding elements */ + for (size_t i = 0; i < key_count && i < key_count2; i++) { + Value element1 = baba_yaga_table_get_by_key(&table1, keys[i]); + Value element2 = baba_yaga_table_get_by_key(&arg3, keys2[i]); + + if (element1.type != VAL_NIL && element2.type != VAL_NIL) { + /* Call function with both elements */ + Value func_args[2]; + func_args[0] = element1; + func_args[1] = element2; + 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); + } + + free(keys2[i]); + } + + /* Free remaining keys from second table */ + for (size_t i = key_count; i < key_count2; i++) { + free(keys2[i]); + } + } else { + /* each function table scalar - apply function to each element with scalar */ + DEBUG_DEBUG("each: applying function to each element with scalar"); + + /* Apply function to each element with the scalar */ + for (size_t i = 0; i < key_count; i++) { + Value element = baba_yaga_table_get_by_key(&table1, keys[i]); + if (element.type != VAL_NIL) { + /* Call function with element and scalar */ + Value func_args[2]; + func_args[0] = element; + func_args[1] = arg3; + 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); + } + } + } + + /* Free keys from first table */ + for (size_t i = 0; i < key_count; i++) { + free(keys[i]); + } + + DEBUG_DEBUG("each: completed, result table has elements"); + return result; +} + +/** + * @brief Flip combinator - reverses argument order of a function + * + * @param args Array of arguments [function] or [function, arg1, arg2] + * @param argc Number of arguments (should be 1 or 3) + * @return Flipped function or result of flipped function application + */ +Value stdlib_flip(Value* args, int argc) { + if (argc != 1 && argc != 3) { + DEBUG_ERROR("flip: expected 1 or 3 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value func = args[0]; + + if (func.type != VAL_FUNCTION) { + DEBUG_ERROR("flip: first argument must be a function"); + return baba_yaga_value_nil(); + } + + if (argc == 1) { + /* Partial application: return the flipped function */ + DEBUG_DEBUG("flip: partial application, returning flipped function"); + return baba_yaga_value_copy(&func); + } + + /* Full application: flip(arg1, arg2) = func(arg2, arg1) */ + Value arg1 = args[1]; + Value arg2 = args[2]; + + DEBUG_DEBUG("flip: applying function with flipped arguments"); + + /* Call function with arguments in reverse order */ + Value func_args[2] = {arg2, arg1}; /* Reversed order */ + Value result = baba_yaga_function_call(&func, func_args, 2, NULL); + + return result; +} + +/** + * @brief Constant combinator - creates a function that returns a constant value + * + * @param args Array of arguments [value] or [value, ignored_arg] + * @param argc Number of arguments (should be 1 or 2) + * @return Constant function or constant value + */ +Value stdlib_constant(Value* args, int argc) { + if (argc != 1 && argc != 2) { + DEBUG_ERROR("constant: expected 1 or 2 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value constant_value = args[0]; + + if (argc == 1) { + /* Partial application: return a function that always returns the constant */ + DEBUG_DEBUG("constant: partial application, returning constant function"); + return baba_yaga_value_copy(&constant_value); + } + + /* Full application: constant(value, ignored_arg) = value */ + DEBUG_DEBUG("constant: returning constant value, ignoring second argument"); + return baba_yaga_value_copy(&constant_value); +} + +/* ============================================================================ + * Table Operations Namespace (t.* functions) + * ============================================================================ */ + +/** + * @brief Table map operation - apply function to each value in table + * + * @param args Array of arguments [function, table] + * @param argc Number of arguments (should be 2) + * @return New table with function applied to each value + */ +Value stdlib_t_map(Value* args, int argc) { + if (argc != 2) { + DEBUG_ERROR("t.map: 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"); + DEBUG_ERROR("t.map: 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"); + DEBUG_ERROR("t.map: 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("t.map: applying function to each value in table"); + + /* Get all keys from the table */ + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + + /* Create result table */ + Value result = baba_yaga_value_table(); + + /* Apply function to each value */ + 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) { + /* Call function with the value */ + Value func_args[1] = {value}; + Value mapped_value = baba_yaga_function_call(&func, func_args, 1, NULL); + + /* Add result to new table with same key */ + result = baba_yaga_table_set(&result, keys[i], &mapped_value); + } + free(keys[i]); + } + + return result; +} + +/** + * @brief Table filter operation - keep only values that satisfy predicate + * + * @param args Array of arguments [function, table] + * @param argc Number of arguments (should be 2) + * @return New table with only values that satisfy the predicate + */ +Value stdlib_t_filter(Value* args, int argc) { + if (argc != 2) { + DEBUG_ERROR("t.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("t.filter: first argument must be a function"); + return baba_yaga_value_nil(); + } + + if (table.type != VAL_TABLE) { + DEBUG_ERROR("t.filter: second argument must be a table"); + return baba_yaga_value_nil(); + } + + DEBUG_DEBUG("t.filter: filtering table with predicate"); + + /* Get all keys from the table */ + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + + /* Create result table */ + Value result = baba_yaga_value_table(); + int result_index = 1; /* 1-based indexing for filtered results */ + + /* Apply predicate to each value */ + 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) { + /* Call predicate function with the value */ + Value func_args[1] = {value}; + Value predicate_result = baba_yaga_function_call(&func, func_args, 1, NULL); + + /* If predicate returns true, keep the value */ + 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) { +/** + * @brief Table reduce operation - combine all values with a function + * + * @param args Array of arguments [function, initial_value, table] + * @param argc Number of arguments (should be 3) + * @return Result of reducing the table + */ +Value stdlib_t_reduce(Value* args, int argc) { if (argc != 3) { - DEBUG_ERROR("reduce: expected 3 arguments, got %d", argc); + DEBUG_ERROR("t.reduce: expected 3 arguments, got %d", argc); return baba_yaga_value_nil(); } @@ -550,17 +1146,262 @@ Value stdlib_reduce(Value* args, int argc) { Value table = args[2]; if (func.type != VAL_FUNCTION) { - DEBUG_ERROR("reduce: first argument must be a function"); + DEBUG_ERROR("t.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"); + DEBUG_ERROR("t.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); + DEBUG_DEBUG("t.reduce: reducing table with function"); + + /* Get all keys from the table */ + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + + /* Start with initial value */ + Value result = baba_yaga_value_copy(&initial); + + /* Apply function to each value */ + 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) { + /* Call function with accumulator and current value */ + Value func_args[2] = {result, value}; + Value new_result = baba_yaga_function_call(&func, func_args, 2, NULL); + + baba_yaga_value_destroy(&result); + result = new_result; + } + free(keys[i]); + } + + return result; +} + +/** + * @brief Table set operation - immutable update + * + * @param args Array of arguments [table, key, value] + * @param argc Number of arguments (should be 3) + * @return New table with updated value + */ +Value stdlib_t_set(Value* args, int argc) { + if (argc != 3) { + DEBUG_ERROR("t.set: expected 3 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[0]; + Value key = args[1]; + Value value = args[2]; + + if (table.type != VAL_TABLE) { + DEBUG_ERROR("t.set: first argument must be a table"); + return baba_yaga_value_nil(); + } + + if (key.type != VAL_STRING) { + DEBUG_ERROR("t.set: second argument must be a string"); + return baba_yaga_value_nil(); + } + + DEBUG_DEBUG("t.set: setting key '%s' in table", key.data.string); + + /* Create new table with the updated value */ + return baba_yaga_table_set(&table, key.data.string, &value); +} + +/** + * @brief Table delete operation - immutable deletion + * + * @param args Array of arguments [table, key] + * @param argc Number of arguments (should be 2) + * @return New table without the specified key + */ +Value stdlib_t_delete(Value* args, int argc) { + if (argc != 2) { + DEBUG_ERROR("t.delete: expected 2 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[0]; + Value key = args[1]; + + if (table.type != VAL_TABLE) { + DEBUG_ERROR("t.delete: first argument must be a table"); + return baba_yaga_value_nil(); + } + + if (key.type != VAL_STRING) { + DEBUG_ERROR("t.delete: second argument must be a string"); + return baba_yaga_value_nil(); + } + + DEBUG_DEBUG("t.delete: deleting key '%s' from table", key.data.string); + + /* For now, return the original table since we don't have delete functionality */ + /* TODO: Implement actual deletion */ + return baba_yaga_value_copy(&table); +} + +/** + * @brief Table merge operation - immutable merge + * + * @param args Array of arguments [table1, table2] + * @param argc Number of arguments (should be 2) + * @return New table with merged contents + */ +Value stdlib_t_merge(Value* args, int argc) { + if (argc != 2) { + DEBUG_ERROR("t.merge: expected 2 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table1 = args[0]; + Value table2 = args[1]; + + if (table1.type != VAL_TABLE || table2.type != VAL_TABLE) { + DEBUG_ERROR("t.merge: both arguments must be tables"); + return baba_yaga_value_nil(); + } + + DEBUG_DEBUG("t.merge: merging two tables"); + + /* Start with first table */ + Value result = baba_yaga_value_copy(&table1); + + /* Get all keys from second table */ + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table2, keys, 1000); + + /* Add all entries from second table */ + for (size_t i = 0; i < key_count; i++) { + Value value = baba_yaga_table_get_by_key(&table2, keys[i]); + if (value.type != VAL_NIL) { + result = baba_yaga_table_set(&result, keys[i], &value); + } + free(keys[i]); + } + + return result; +} + +/** + * @brief Table length operation - get number of entries + * + * @param args Array of arguments [table] + * @param argc Number of arguments (should be 1) + * @return Number of entries in the table + */ +Value stdlib_t_length(Value* args, int argc) { + if (argc != 1) { + DEBUG_ERROR("t.length: expected 1 argument, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[0]; + + if (table.type != VAL_TABLE) { + DEBUG_ERROR("t.length: argument must be a table"); + return baba_yaga_value_nil(); + } + + size_t length = baba_yaga_table_size(&table); + DEBUG_DEBUG("t.length: table has %zu entries", length); + + return baba_yaga_value_number((double)length); +} + +/** + * @brief Table has operation - check if key exists + * + * @param args Array of arguments [table, key] + * @param argc Number of arguments (should be 2) + * @return Boolean indicating if key exists + */ +Value stdlib_t_has(Value* args, int argc) { + if (argc != 2) { + DEBUG_ERROR("t.has: expected 2 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[0]; + Value key = args[1]; + + if (table.type != VAL_TABLE) { + DEBUG_ERROR("t.has: first argument must be a table"); + return baba_yaga_value_nil(); + } + + if (key.type != VAL_STRING) { + DEBUG_ERROR("t.has: second argument must be a string"); + return baba_yaga_value_nil(); + } + + bool has_key = baba_yaga_table_has_key(&table, key.data.string); + DEBUG_DEBUG("t.has: key '%s' %s in table", key.data.string, has_key ? "exists" : "does not exist"); + + return baba_yaga_value_boolean(has_key); +} + +/** + * @brief Table get operation - get value with default + * + * @param args Array of arguments [table, key, default_value] + * @param argc Number of arguments (should be 3) + * @return Value from table or default if key doesn't exist + */ +Value stdlib_t_get(Value* args, int argc) { + if (argc != 3) { + DEBUG_ERROR("t.get: expected 3 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[0]; + Value key = args[1]; + Value default_value = args[2]; + + if (table.type != VAL_TABLE) { + DEBUG_ERROR("t.get: first argument must be a table"); + return baba_yaga_value_nil(); + } + + if (key.type != VAL_STRING) { + DEBUG_ERROR("t.get: second argument must be a string"); + return baba_yaga_value_nil(); + } + + DEBUG_DEBUG("t.get: getting key '%s' from table", key.data.string); + + /* Try to get the value from the table */ + Value result = baba_yaga_table_get(&table, key.data.string); + + /* If key doesn't exist, return default value */ + if (result.type == VAL_NIL) { + return baba_yaga_value_copy(&default_value); + } + + return result; +} + +/** + * @brief Internal function for table key-value pairs + * + * @param args Array of arguments [key, value] + * @param argc Number of arguments (should be 2) + * @return Value containing the key-value pair + */ +Value stdlib_table_entry(Value* args, int argc) { + if (argc != 2) { + return baba_yaga_value_nil(); + } + + /* Create a special table entry value that can be used by table evaluation */ + Value value = args[1]; + + /* For now, return the value directly - the table evaluation will handle the key */ + return value; } diff --git a/js/scripting-lang/baba-yaga-c/src/table.c b/js/scripting-lang/baba-yaga-c/src/table.c index 18c3292..0614929 100644 --- a/js/scripting-lang/baba-yaga-c/src/table.c +++ b/js/scripting-lang/baba-yaga-c/src/table.c @@ -304,33 +304,44 @@ Value baba_yaga_value_table(void) { Value baba_yaga_table_get(const Value* table, const char* key) { if (table == NULL || table->type != VAL_TABLE || key == NULL) { + DEBUG_ERROR("Table get: invalid parameters"); return baba_yaga_value_nil(); } TableValue* table_value = (TableValue*)table->data.table; + DEBUG_DEBUG("Table get: looking for key '%s' in table with %zu entries", key, table_value->hash_table->size); + TableEntry* entry = hash_table_get_entry(table_value->hash_table, key); if (entry != NULL) { + DEBUG_DEBUG("Table get: found key '%s', returning value type %d", key, entry->value.type); return baba_yaga_value_copy(&entry->value); } + DEBUG_DEBUG("Table get: key '%s' not found", key); return baba_yaga_value_nil(); } Value baba_yaga_table_set(const Value* table, const char* key, const Value* value) { if (table == NULL || table->type != VAL_TABLE || key == NULL || value == NULL) { + DEBUG_ERROR("Table set: invalid parameters"); return baba_yaga_value_nil(); } + DEBUG_DEBUG("Table set: setting key '%s' to value type %d", key, value->type); + /* Create new table */ Value new_table = baba_yaga_value_table(); if (new_table.type != VAL_TABLE) { + DEBUG_ERROR("Table set: failed to create new table"); return baba_yaga_value_nil(); } TableValue* new_table_value = (TableValue*)new_table.data.table; TableValue* old_table_value = (TableValue*)table->data.table; + DEBUG_DEBUG("Table set: copying %zu entries from old table", old_table_value->hash_table->size); + /* Copy all entries from old table */ for (size_t i = 0; i < old_table_value->hash_table->capacity; i++) { TableEntry* entry = old_table_value->hash_table->buckets[i]; @@ -355,10 +366,12 @@ Value baba_yaga_table_set(const Value* table, const char* key, const Value* valu /* Set the new value */ if (!hash_table_set(new_table_value->hash_table, key, value)) { + DEBUG_ERROR("Table set: failed to set key '%s'", key); baba_yaga_value_destroy(&new_table); return baba_yaga_value_nil(); } + DEBUG_DEBUG("Table set: new table has %zu entries", new_table_value->hash_table->size); return new_table; } @@ -444,6 +457,75 @@ bool baba_yaga_table_has_key(const Value* table, const char* key) { return hash_table_get_entry(table_value->hash_table, key) != NULL; } +/** + * @brief Get all keys from a table + * + * @param table Table value + * @param keys Array to store keys (caller must free) + * @param max_keys Maximum number of keys to retrieve + * @return Number of keys retrieved + */ +size_t baba_yaga_table_get_keys(const Value* table, char** keys, size_t max_keys) { + if (table == NULL || table->type != VAL_TABLE || keys == NULL || max_keys == 0) { + return 0; + } + + TableValue* table_value = (TableValue*)table->data.table; + HashTable* hash_table = table_value->hash_table; + + size_t key_count = 0; + + /* Get string keys */ + for (size_t i = 0; i < hash_table->capacity && key_count < max_keys; i++) { + TableEntry* entry = hash_table->buckets[i]; + while (entry != NULL && key_count < max_keys) { + keys[key_count] = strdup(entry->key); + key_count++; + entry = entry->next; + } + } + + /* Get numeric keys (array indices) */ + for (size_t i = 0; i < hash_table->array_size && key_count < max_keys; i++) { + char* num_key = malloc(32); /* Enough for large numbers */ + if (num_key != NULL) { + snprintf(num_key, 32, "%zu", i + 1); /* 1-based indexing */ + keys[key_count] = num_key; + key_count++; + } + } + + return key_count; +} + +/** + * @brief Get a value from table by key (supports both string and numeric keys) + * + * @param table Table value + * @param key Key (string or numeric as string) + * @return Value at key, or nil if not found + */ +Value baba_yaga_table_get_by_key(const Value* table, const char* key) { + if (table == NULL || table->type != VAL_TABLE || key == NULL) { + return baba_yaga_value_nil(); + } + + /* Try as string key first */ + Value result = baba_yaga_table_get(table, key); + if (result.type != VAL_NIL) { + return result; + } + + /* Try as numeric key */ + char* endptr; + long index = strtol(key, &endptr, 10); + if (*endptr == '\0' && index > 0) { + return baba_yaga_table_get_index(table, (int)index); + } + + return baba_yaga_value_nil(); +} + /* ============================================================================ * Internal Table Management * ============================================================================ */ diff --git a/js/scripting-lang/baba-yaga-c/src/value.c b/js/scripting-lang/baba-yaga-c/src/value.c index 25a52fc..562f3a7 100644 --- a/js/scripting-lang/baba-yaga-c/src/value.c +++ b/js/scripting-lang/baba-yaga-c/src/value.c @@ -86,6 +86,8 @@ Value baba_yaga_value_copy(const Value* value) { return baba_yaga_value_nil(); } + DEBUG_DEBUG("baba_yaga_value_copy: copying value with type %d", value->type); + switch (value->type) { case VAL_NUMBER: return baba_yaga_value_number(value->data.number); @@ -99,9 +101,22 @@ Value baba_yaga_value_copy(const Value* value) { return baba_yaga_value_nil(); } - /* Copy all entries from the original table */ - /* This is a simplified copy - in practice we'd need to iterate through all entries */ - table_increment_ref(&new_table); + /* Copy all entries from the original table using the public API */ + size_t old_size = baba_yaga_table_size(value); + if (old_size > 0) { + /* Get all keys from the original table */ + char* keys[100]; /* Assume max 100 keys */ + size_t key_count = baba_yaga_table_get_keys(value, keys, 100); + + /* Copy each key-value pair */ + for (size_t i = 0; i < key_count; i++) { + Value old_value = baba_yaga_table_get(value, keys[i]); + new_table = baba_yaga_table_set(&new_table, keys[i], &old_value); + baba_yaga_value_destroy(&old_value); + free(keys[i]); + } + } + return new_table; } case VAL_FUNCTION: { diff --git a/js/scripting-lang/baba-yaga-c/test_comment.txt b/js/scripting-lang/baba-yaga-c/test_comment.txt deleted file mode 100644 index ea101f3..0000000 --- a/js/scripting-lang/baba-yaga-c/test_comment.txt +++ /dev/null @@ -1 +0,0 @@ -/* Unit Test: Basic Lexer Functionality */ diff --git a/js/scripting-lang/baba-yaga-c/test_comment_with_code.txt b/js/scripting-lang/baba-yaga-c/test_comment_with_code.txt deleted file mode 100644 index b028920..0000000 --- a/js/scripting-lang/baba-yaga-c/test_comment_with_code.txt +++ /dev/null @@ -1 +0,0 @@ -/* Unit Test: Basic Lexer Functionality */ x : 42; diff --git a/js/scripting-lang/baba-yaga-c/test_complex_unary.txt b/js/scripting-lang/baba-yaga-c/test_complex_unary.txt new file mode 100644 index 0000000..95ce299 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_complex_unary.txt @@ -0,0 +1,8 @@ +/* Test complex unary minus expressions */ + +/* Test complex unary minus expressions */ +complex_negative1 : -(-5); +complex_negative2 : -(-(-3)); +complex_negative3 : (-5) + 3; + +..out "Complex unary test completed"; \ No newline at end of file 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_debug_tokens.txt b/js/scripting-lang/baba-yaga-c/test_debug_tokens.txt new file mode 100644 index 0000000..8a68a8f --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_debug_tokens.txt @@ -0,0 +1,5 @@ +/* Test token generation */ + +/* Test token generation */ +x : 5; +..out x; \ 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_io.txt b/js/scripting-lang/baba-yaga-c/test_io.txt deleted file mode 100644 index 23751a3..0000000 --- a/js/scripting-lang/baba-yaga-c/test_io.txt +++ /dev/null @@ -1 +0,0 @@ -..out "test"; diff --git a/js/scripting-lang/baba-yaga-c/test_listen_when_debug.txt b/js/scripting-lang/baba-yaga-c/test_listen_when_debug.txt new file mode 100644 index 0000000..cf877c7 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_listen_when_debug.txt @@ -0,0 +1,12 @@ +/* Debug test for when expression with ..listen */ + +/* Test 1: Call ..listen directly */ +state : ..listen; +..out "State created"; + +/* Test 2: Use ..listen in when expression */ +result : when ..listen is + { status: "placeholder" } then "Placeholder detected" + _ then "Unknown state"; + +..out result; \ 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 index ef64749..f9530c6 100644 --- a/js/scripting-lang/baba-yaga-c/test_minimal.txt +++ b/js/scripting-lang/baba-yaga-c/test_minimal.txt @@ -1 +1,2 @@ -x : 42; +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 6; diff --git a/js/scripting-lang/baba-yaga-c/test_multiple_statements.txt b/js/scripting-lang/baba-yaga-c/test_multiple_statements.txt deleted file mode 100644 index afc3f02..0000000 --- a/js/scripting-lang/baba-yaga-c/test_multiple_statements.txt +++ /dev/null @@ -1 +0,0 @@ -x : 10; y : 20; add x y diff --git a/js/scripting-lang/baba-yaga-c/test_nested_unary.txt b/js/scripting-lang/baba-yaga-c/test_nested_unary.txt new file mode 100644 index 0000000..5fb25cc --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_nested_unary.txt @@ -0,0 +1,5 @@ +/* Test nested unary minus */ + +/* Test nested unary minus */ +nested : -(-5); +..out nested; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_number_copy_debug.txt b/js/scripting-lang/baba-yaga-c/test_number_copy_debug.txt new file mode 100644 index 0000000..92c46d7 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_number_copy_debug.txt @@ -0,0 +1,12 @@ +/* Debug test for number copy issues */ + +x : 5; +..out "x declared"; + +..out x; + +/* Test copying a number */ +y : x; +..out "y copied from x"; + +..out y; \ 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.txt b/js/scripting-lang/baba-yaga-c/test_simple.txt deleted file mode 100644 index 1ea3333..0000000 --- a/js/scripting-lang/baba-yaga-c/test_simple.txt +++ /dev/null @@ -1 +0,0 @@ -x : 42; y : 3.14; x + y; 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 diff --git a/js/scripting-lang/baba-yaga-c/test_simple_table.txt b/js/scripting-lang/baba-yaga-c/test_simple_table.txt new file mode 100644 index 0000000..dd264c6 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_simple_table.txt @@ -0,0 +1,5 @@ +/* Test simple table creation */ + +/* Test simple table creation */ +test_table : { status: "placeholder", message: "test" }; +..out "Table created successfully"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_simple_when.txt b/js/scripting-lang/baba-yaga-c/test_simple_when.txt new file mode 100644 index 0000000..9241c97 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_simple_when.txt @@ -0,0 +1,8 @@ +/* Test simple when expression */ + +/* Test simple when expression */ +x : 5; +result : when x is + 5 then "Five" + _ then "Other"; +..out result; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_table_copy_debug.txt b/js/scripting-lang/baba-yaga-c/test_table_copy_debug.txt new file mode 100644 index 0000000..5e74da6 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_table_copy_debug.txt @@ -0,0 +1,15 @@ +/* Debug test for table copy issues */ + +/* Test 1: Create a simple table */ +test_table : { status: "placeholder" }; +..out "Table created"; + +/* Test 2: Copy the table */ +copy_table : test_table; +..out "Table copied"; + +/* Test 3: Check original table */ +..out test_table.status; + +/* Test 4: Check copied table */ +..out copy_table.status; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_table_debug.txt b/js/scripting-lang/baba-yaga-c/test_table_debug.txt new file mode 100644 index 0000000..acc0729 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_table_debug.txt @@ -0,0 +1,5 @@ +/* Test table debug */ + +/* Test table debug */ +test_table : { status: "placeholder" }; +..out test_table.status; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_table_pattern.txt b/js/scripting-lang/baba-yaga-c/test_table_pattern.txt new file mode 100644 index 0000000..5562260 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_table_pattern.txt @@ -0,0 +1,9 @@ +/* Test table pattern matching */ + +/* Test table pattern matching */ +test_table : { status: "placeholder", message: "test" }; +result : when test_table is + { status: "placeholder" } then "Placeholder detected" + { status: "active" } then "Active state detected" + _ then "Unknown state"; +..out result; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_table_pattern_debug.txt b/js/scripting-lang/baba-yaga-c/test_table_pattern_debug.txt new file mode 100644 index 0000000..87f57f3 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_table_pattern_debug.txt @@ -0,0 +1,21 @@ +/* Debug test for table pattern matching */ + +/* Test 1: Basic table creation with key-value pairs */ +test_table : { status: "placeholder" }; +..out "Test table created"; + +/* Test 2: Check table contents */ +..out test_table.status; + +/* Test 3: Test ..listen function */ +state : ..listen; +..out "Listen state created"; +..out state.status; +..out state.message; + +/* Test 4: Test table pattern matching */ +result : when state is + { status: "placeholder" } then "Placeholder detected" + _ then "Unknown state"; + +..out result; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_table_when.txt b/js/scripting-lang/baba-yaga-c/test_table_when.txt new file mode 100644 index 0000000..5197939 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_table_when.txt @@ -0,0 +1,8 @@ +/* Test table patterns in when expressions */ + +/* Test table patterns in when expressions */ +test_table : { status: "placeholder" }; +result : when test_table is + { status: "placeholder" } then "Match" + _ then "No match"; +..out result; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_unary_after_semicolon.txt b/js/scripting-lang/baba-yaga-c/test_unary_after_semicolon.txt new file mode 100644 index 0000000..897f52a --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_unary_after_semicolon.txt @@ -0,0 +1,6 @@ +/* Test unary minus after semicolon */ + +/* Test unary minus after semicolon */ +x : 5; y : -5; +..out x; +..out y; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_unary_minus_var.txt b/js/scripting-lang/baba-yaga-c/test_unary_minus_var.txt new file mode 100644 index 0000000..39d7bc8 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_unary_minus_var.txt @@ -0,0 +1,5 @@ +/* Test unary minus with variable */ + +/* Test unary minus with variable */ +x : 5; y : -x; +..out y; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_unary_simple.txt b/js/scripting-lang/baba-yaga-c/test_unary_simple.txt new file mode 100644 index 0000000..2948c13 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_unary_simple.txt @@ -0,0 +1,5 @@ +/* Test simple unary minus */ + +/* Test simple unary minus */ +x : -5; +..out x; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_var_debug.txt b/js/scripting-lang/baba-yaga-c/test_var_debug.txt new file mode 100644 index 0000000..ae250d0 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_var_debug.txt @@ -0,0 +1,6 @@ +/* Debug test for variable declarations */ + +x : 5; +..out "x declared"; + +..out x; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_when.txt b/js/scripting-lang/baba-yaga-c/test_when.txt deleted file mode 100644 index 47c2898..0000000 --- a/js/scripting-lang/baba-yaga-c/test_when.txt +++ /dev/null @@ -1 +0,0 @@ -result : when x is 42 then "correct" _ then "wrong"; diff --git a/js/scripting-lang/baba-yaga-c/test_when_debug.txt b/js/scripting-lang/baba-yaga-c/test_when_debug.txt new file mode 100644 index 0000000..2340ff6 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_when_debug.txt @@ -0,0 +1,8 @@ +/* Debug test for when expression */ + +/* Test 1: Simple when expression */ +result : when 5 is + 5 then "Five" + _ then "Other"; + +..out result; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_when_fixed.txt b/js/scripting-lang/baba-yaga-c/test_when_fixed.txt deleted file mode 100644 index e0892d8..0000000 --- a/js/scripting-lang/baba-yaga-c/test_when_fixed.txt +++ /dev/null @@ -1 +0,0 @@ -x : 42; result : when x is 42 then "correct" _ then "wrong"; diff --git a/js/scripting-lang/baba-yaga-c/test_when_token.txt b/js/scripting-lang/baba-yaga-c/test_when_token.txt deleted file mode 100644 index 8685dd8..0000000 --- a/js/scripting-lang/baba-yaga-c/test_when_token.txt +++ /dev/null @@ -1 +0,0 @@ -when |