diff options
Diffstat (limited to 'js')
49 files changed, 4796 insertions, 1074 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 e6744b2..e827ff3 100644 --- a/js/scripting-lang/baba-yaga-c/ROADMAP.md +++ b/js/scripting-lang/baba-yaga-c/ROADMAP.md @@ -1,708 +1,122 @@ -# Baba Yaga C Implementation - Complete Roadmap - -## Executive Summary - -**Current Status**: ✅ **Core Functionality Complete** - Major language features implemented -**Overall Progress**: ~90% Complete -**Test Results**: 26/26 basic tests PASS, 51/66 standard library tests PASS, 5/27 JavaScript tests PASS -**Critical Discovery**: 🔍 **Parser precedence system exists but not used** - infix operators blocked by 1-line fix -**Estimated Completion**: 1-2 days (after infix operator fix) - -## 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 - -#### 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) 🔄 **IN PROGRESS** -**Current Status**: 5/27 tests pass (19% success rate) -**Priority**: HIGH -**Effort**: 1-2 days - -**Major Missing Features**: - -#### Infix Operators (CRITICAL - 15+ tests) 🔴 **BLOCKING - DISCOVERED ROOT CAUSE** -- **Arithmetic**: `a + b`, `a - b`, `a * b`, `a / b`, `a % b`, `a ^ b` -- **Logical**: `a and b`, `a or b`, `a xor b`, `not a` -- **Comparison**: `a = b`, `a > b`, `a < b`, `a >= b`, `a <= b`, `a != b` -- **Status**: 🔧 **PARSER PRECEDENCE SYSTEM EXISTS BUT NOT USED** -- **Root Cause**: Main `parser_parse_expression()` calls `parser_parse_application()` instead of `parser_parse_logical()` -- **Impact**: Blocks tests 01, 02, 03, 04, 05, 08, 11, 13, 14, 15, 16, 18, 19, 20, 22, 23 -- **Effort**: 1-2 hours (simple function call change) -- **Solution**: Change main expression parser to use existing operator precedence chain: - ```c - // Current (prefix-only): - static ASTNode* parser_parse_expression(Parser* parser) { - return parser_parse_application(parser); - } - - // Fixed (infix support): - static ASTNode* parser_parse_expression(Parser* parser) { - return parser_parse_logical(parser); - } - ``` -- **Existing Precedence Chain**: `logical` → `comparison` → `additive` → `multiplicative` → `power` → `primary` -- **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 - -#### 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) ✅ -- [ ] Implement infix operators (CRITICAL - blocks 15+ tests) -- [ ] Implement table data structures -- [ ] Add advanced functional programming features (each combinator, via operator, etc.) - -### 3. Standard Library Issues (MEDIUM PRIORITY) ✅ **MOSTLY COMPLETED** -**Current Status**: 64/66 tests pass (97% success rate) -**Priority**: MEDIUM -**Effort**: 1-2 days - -**Specific Failures**: - -#### Type Checking (0 failures) ✅ **COMPLETED** -- `equals` function: ✅ Added type mismatch error for different argument types -- `and` function: ✅ Added type mismatch error for non-boolean arguments -- `not` function: ✅ Added type mismatch error for non-boolean arguments -- **Fix Applied**: Added proper type validation to all three functions -- **Result**: All type checking tests now pass - -#### Precision Issues (2 failures) ✅ **MOSTLY COMPLETED** -- `divide` function: ✅ Fixed precision for floating point division -- `pow` function: ✅ Fixed precision for square root (minor 1-digit difference) -- Negative power: Still fails due to negative number parsing issue -- **Fix Applied**: - - Fixed main function to use `baba_yaga_value_to_string()` instead of `printf("%g")` - - Updated value conversion to use `%.16g` format for natural precision -- **Result**: 4/6 precision tests now pass - -**Remaining Tasks**: -- [ ] Fix negative number parsing for `pow 2 -1` case -- [ ] Minor precision adjustment for square root (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 🔧 - -#### Infix Operators (CRITICAL DISCOVERY) -- **Status**: 🔧 **PARSER PRECEDENCE SYSTEM EXISTS BUT NOT USED** -- **Root Cause**: Main expression parser calls wrong function -- **Impact**: Blocks 15+ JavaScript compatibility tests -- **Fix**: Simple 1-line change in `parser_parse_expression()` -- **Expected Result**: Immediate unblocking of infix operator support - -### Critical Issues to Address - -#### 1. Infix Operator Parsing (CRITICAL PRIORITY) 🔴 **BLOCKING** -**Problem**: Infix operators (`x + y`, `3 < 5`) fail with "Unexpected token in expression" -**Root Cause**: Main `parser_parse_expression()` calls `parser_parse_application()` instead of `parser_parse_logical()` -**Impact**: Blocks 15+ JavaScript compatibility tests -**Fix**: Change one line in `src/parser.c` line 1342 -**Effort**: 1-2 hours -**Files to Check**: `src/parser.c` line 1342 - -#### 2. Logical Operator Parsing (HIGH PRIORITY) -**Problem**: `and`, `or`, `xor` keywords parsed as function calls instead of operators -**Root Cause**: Logical operators need special handling in parser -**Impact**: Blocks 6 standard library tests -**Fix**: Update logical operator parsing in `parser_parse_logical()` -**Effort**: 1 hour -**Files to Check**: `src/parser.c` logical operator handling - -#### 3. Function Calls in Function Bodies (MEDIUM PRIORITY) -**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**: Blocks complex user-defined functions -**Files to Check**: `src/interpreter.c` function call evaluation, `src/function.c` user function execution - -#### 4. 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) - -#### 5. 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 - -#### Days 1-2: Infix Operator Implementation (CRITICAL) -**Dependencies**: None -**Success Criteria**: -- `"a + b"`, `"a and b"`, `"a = b"` parse and execute correctly -- Operator precedence and associativity work properly -- `@(a + b)` syntax works with infix expressions -- 15+ JavaScript compatibility tests pass - -#### Days 3-5: Standard Library and Table Implementation (HIGH) -**Dependencies**: Infix operators -**Success Criteria**: -- All 66 standard library tests pass -- Basic table operations work (`{a: 1, b: 2}`) -- Higher-order functions work correctly -- IO operations behave as expected -- Type errors are properly reported - -### Week 2: Advanced Features - -#### Days 1-3: User-Defined Functions (MEDIUM) -**Dependencies**: Standard library fixes -**Success Criteria**: -- User-defined functions can be created and called -- Parameters are properly bound -- Local variables work correctly -- Recursive functions work - -#### Days 4-5: Pattern Matching and Polish (MEDIUM/LOW) -**Dependencies**: User-defined functions -**Success Criteria**: -- Complex when expressions work -- Boolean patterns evaluate correctly -- Multi-parameter patterns work -- Pattern guards function properly -- No memory leaks detected -- Comprehensive error messages - -## Test Results Analysis - -### ✅ Passing Tests (94/119 = 79%) -- **Basic Functionality**: 26/26 tests PASS (100%) -- **Standard Library**: 64/66 tests PASS (97%) -- **JavaScript Compatibility**: 4/27 tests PASS (15%) - -#### 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 (60/66 PASS)**: -- Core arithmetic: `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow` -- Comparison: `equals`, `not_equals`, `less`, `greater`, etc. -- Logical: `and`, `or`, `xor`, `not` -- Higher-order: `apply`, `compose` -- IO: `..out`, `..assert` - -**JavaScript Compatibility Tests (4/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 - -### ❌ Failing Tests (25/119 = 21%) - -#### Standard Library Failures (15/66): -- **Logical Operators**: 6 failures due to `and`, `or`, `xor` being parsed as function calls -- **Precision**: 2 failures (square root 1-digit difference, negative power parsing) -- **Type Checking**: 1 failure (less function type checking) - -#### JavaScript Compatibility Failures (22/27): -- **Infix Operators**: 15+ failures due to parser precedence system not being used (`a + b` vs `add a b`) -- **Missing Features**: Tables, advanced combinators, embedded functions -- **Function Body Issues**: ✅ Fixed - complex function bodies now work - -## 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: Fully working -- ✅ Function definitions: All cases working -- ✅ Function references: `@` operator working -- ✅ Error handling: Type checking implemented -- 🔧 Infix operators: **PARSER PRECEDENCE SYSTEM EXISTS BUT NOT USED** (blocks 15+ 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 -- ✅ Infix operators: Full implementation (arithmetic, logical, comparison) -- ✅ Tables: Basic implementation -- ✅ Advanced combinators: Core implementation -- ✅ JavaScript compatibility: 20+/27 tests PASS -- ✅ Error handling: Comprehensive -- ✅ 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 - -## 🔍 CRITICAL DISCOVERY: Parser Precedence System Exists But Not Used - -### The Real Problem -After deep analysis, we discovered that **the infix operator parsing system is already fully implemented** but not being used. The parser has a complete operator precedence chain: - -``` -parser_parse_logical() → parser_parse_comparison() → parser_parse_additive() → -parser_parse_multiplicative() → parser_parse_power() → parser_parse_primary() -``` - -### The Root Cause -The main `parser_parse_expression()` function calls `parser_parse_application()` instead of `parser_parse_logical()`. This means: - -- ✅ All operator tokens are already in the lexer -- ✅ All operator precedence functions are already implemented -- ✅ All binary operation evaluation is already in the interpreter -- ❌ **But the main expression parser never calls the precedence system** - -### The Fix -**One line change** in `src/parser.c` line 1342: -```c -// Change from: -return parser_parse_application(parser); -// To: -return parser_parse_logical(parser); -``` - -### Expected Impact -This simple change should immediately: -- ✅ Unblock 15+ JavaScript compatibility tests -- ✅ Enable 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)` - -## Conclusion - -The Baba Yaga C implementation is in excellent shape with 76% of functionality working correctly. Major language features including pattern matching, function definitions, and function references are fully implemented and working. The critical blocker of multiple statement parsing has been resolved, unlocking real-world usage. - -### 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 -- ✅ **Core Language**: All basic operations and standard library functions working - -### Remaining Work -- 🔧 **Function Body Debugging**: Fix function calls within function bodies returning `nil` -- 📊 **Tables**: Implement table data structures and operations -- 🔄 **Advanced Combinators**: Implement each combinator, via operator, etc. -- 🎯 **JavaScript Compatibility**: Address syntax differences and missing features - -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**: Implement infix operators (arithmetic, logical, comparison) to unblock 15+ JavaScript compatibility tests. - -**Estimated completion for full implementation**: 1 week remaining. - -## Immediate Next Steps for New Team Members - -### 1. Fix Infix Operator Parsing (CRITICAL - 1-2 hours) 🔴 **BLOCKING** -**Problem**: JavaScript tests use infix operators (`a + b`) but C implementation fails to parse them - -**Root Cause Discovered**: Parser precedence system exists but main expression parser calls wrong function - -**Implementation Steps**: -1. **Fix main expression parser** (1 line change): - ```c - // In src/parser.c line 1342, change: - return parser_parse_application(parser); - // To: - return parser_parse_logical(parser); - ``` -2. **Test infix operators**: `a + b`, `a and b`, `a = b`, `@(a + b)` -3. **Verify precedence**: `a + b * c` should parse as `a + (b * c)` - -**Files to Modify**: -- `src/parser.c`: Line 1342 (one line change) - -**Expected Result**: 15+ JavaScript compatibility tests should immediately pass - -### 2. Implement Tables (MEDIUM - 2-3 days) -**Problem**: Table literals and operations not implemented - -**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` - -**Files to Modify**: -- `src/parser.c`: Add `NODE_TABLE` parsing -- `src/table.c`: Implement table operations -- `src/stdlib.c`: Add table utility functions - -### 3. Implement Advanced Combinators (MEDIUM - 2-3 days) -**Problem**: Each combinator, via operator not implemented - -**Implementation Steps**: -1. Add `each` combinator to `src/stdlib.c` -2. Add `via` operator support -3. Test with: `each add [1, 2, 3]` and `via add 5` - -**Files to Modify**: -- `src/stdlib.c`: Add combinator implementations -- `src/parser.c`: Add combinator parsing if needed - -### 4. Fix Standard Library Issues (LOW - 1 day) -**Problem**: 6 standard library tests failing - -**Implementation Steps**: -1. Add type checking to `equals`, `and`, `not` functions -2. Fix floating point precision issues -3. Run `./test_stdlib.sh` to verify fixes - -**Files to Modify**: -- `src/stdlib.c`: Add type validation and fix precision - -### Getting Started Commands -```bash -# Build and test current state -make debug -./run_comprehensive_tests.sh - -# Debug function body issue -./bin/baba-yaga 'f : x -> add x 1; f 41;' - -# Test pattern matching (working) -./bin/baba-yaga 'when 42 is 42 then "yes" _ then "no";' - -# Test function references (working) -./bin/baba-yaga '@(add 3 4);' -``` \ No newline at end of file +# Baba Yaga C Implementation Roadmap + +## Current Status +- ✅ **Core Language**: Complete and stable (25/27 tests passing) +- ✅ **Table Pattern Matching**: Fixed and working +- ✅ **When Expressions**: Fixed and working +- ✅ **Computed Table Keys**: Fixed and working (Task 1.1 complete) +- ✅ **Multi-value Pattern Expressions**: Fixed and working (Task 1.2 complete) +- ✅ **Pattern Matching Memory**: Fixed and working (Task 1.3 complete) +- ✅ **Partial Application Support**: Fixed and working (Task 2.3 complete) +- ❌ **2 Remaining Issues**: Test 22 parser issue, Integration Test 02 file reading issue + +## Quick Reference +- **Test Command**: `./bin/baba-yaga tests/22_parser_limitations.txt` +- **Key Files**: `src/parser.c` (parser_parse_when_pattern), `tests/22_parser_limitations.txt` +- **Current Error**: `Parse error: Expected 'is' after test expression` +- **Working Test**: `echo "test_multi_expr : x y -> when (x % 2) (y % 2) is 0 0 then \"both even\";" | ./bin/baba-yaga` + +## Implementation Plan + +### **Phase 1: Core Language Features** ✅ **COMPLETE** +All core language features are now working correctly. + +### **Phase 2: Advanced Features** ✅ **COMPLETE** +All advanced features including partial application are now working. + +### **Phase 3: Final Polish** 🔄 **IN PROGRESS** + +#### **Task 3.1: Test 22 Parser Issue** (Test 22) 🔍 **INVESTIGATED** +**Issue**: `Parse error: Expected 'is' after test expression` +**Current**: Core multi-value pattern functionality works correctly +**Status**: Identified specific parser edge case - needs investigation + +**Investigation Findings**: +- ✅ **Individual functions work**: Multi-value patterns parse and execute correctly when tested individually +- ✅ **Isolated syntax works**: Same syntax works perfectly when tested via `echo` +- ❌ **File-specific issue**: The error only occurs when the complete test file is processed +- 🔍 **Parser edge case**: The issue appears to be in how the parser handles multiple patterns in sequence within a file context +- 📍 **Error location**: Parser fails to recognize the `is` keyword in multi-value pattern context when processing the full file + +**Root Cause Analysis**: +- The parser's `parser_parse_when_pattern` function may have an edge case when processing multiple patterns in sequence +- The error suggests the parser is not correctly transitioning between pattern parsing states +- This is likely a subtle parsing state management issue rather than a fundamental syntax problem + +#### **Task 3.2: Integration Test 02 File Reading** (Integration Test 02) +**Issue**: Segmentation fault when reading file directly (works when piped) +**Current**: Core pattern matching works, but file reading has issue +**Status**: Need to fix file reading mechanism + +## **Recent Achievements** + +### **Task 2.3: Partial Application Support** ✅ **COMPLETE** +- **Issue**: Test 17 failed with partial application and arity errors +- **Solution**: Implemented proper partial application in function call mechanism +- **Implementation**: + - Modified `baba_yaga_function_call` to handle partial application + - Created `stdlib_partial_apply` helper function + - Updated `each` function to support partial application +- **Result**: Test 17 now passes, 25/27 tests passing + +### **Task 1.2: Multi-value Pattern Expressions** ✅ **COMPLETE** +- **Issue**: `when (x % 2) (y % 2) is` not supported +- **Solution**: Enhanced parser to handle expressions in parentheses for multi-parameter patterns +- **Implementation**: Added detection for multi-parameter patterns with expressions +- **Result**: Multi-value pattern expressions now work correctly + +### **Task 1.3: Pattern Matching Memory** ✅ **COMPLETE** +- **Issue**: Segmentation fault in complex pattern matching +- **Solution**: Implemented sequence-to-sequence pattern matching for multi-parameter patterns +- **Implementation**: Added element-by-element comparison logic for multi-parameter patterns +- **Result**: Complex nested pattern matching now works correctly + +## **Next Priority** +**Task 3.1**: Fix Test 22 parser edge case to achieve 26/27 tests passing +**Task 3.2**: Fix Integration Test 02 file reading issue to achieve 27/27 tests passing + +## Technical Notes + +### **Partial Application Implementation** +- **Function Call Mechanism**: Modified `baba_yaga_function_call` to detect insufficient arguments +- **Partial Function Creation**: Creates new function with bound arguments stored in scope +- **Argument Combination**: `stdlib_partial_apply` combines bound and new arguments +- **Scope Management**: Uses temporary scope variables to store partial application data + +### **Pattern Matching Enhancements** +- **Multi-parameter Support**: Handles `when (expr1) (expr2) is` syntax +- **Sequence Comparison**: Element-by-element comparison for multi-value patterns +- **Wildcard Support**: `_` pattern matches any value in multi-parameter contexts + +### **Parser Investigation Results** +- **Multi-value patterns work correctly** in isolation and individual function definitions +- **File processing edge case** identified in `parser_parse_when_pattern` function +- **State management issue** suspected when processing multiple patterns in sequence +- **Error occurs specifically** when the complete test file is processed, not in isolated tests + +### **Memory Management** +- **Reference Counting**: Proper cleanup of function references +- **Scope Cleanup**: Automatic cleanup of temporary scope variables +- **Error Handling**: Graceful handling of memory allocation failures + +## Next Action +**Continue with Task 3.1** (Test 22 Parser Issue) - investigate and fix the parser edge case in `parser_parse_when_pattern` function to achieve 26/27 tests passing. + +## Implementation Guide + +### **For Task 3.1: Test 22 Parser Issue** +1. **Investigate `parser_parse_when_pattern` function**: Look for state management issues when processing multiple patterns +2. **Debug the specific failing case**: Add debug output to understand why the parser fails to recognize `is` keyword +3. **Fix the parser logic**: Update the parser to handle the edge case correctly +4. **Test the fix**: Verify that Test 22 now passes + +### **For Task 3.2: Integration Test 02 File Reading** +1. **Investigate the file reading issue**: Compare direct file reading vs piped input +2. **Identify the root cause**: Find why direct file reading causes segmentation fault +3. **Fix the file reading mechanism**: Update the file reading code to handle the issue +4. **Test the fix**: Verify that Integration Test 02 now passes + +### **For CLI Ergonomics** +1. **Simplify the REPL**: Make it more minimal and interactive +2. **Improve error messages**: Better error reporting and debugging +3. **Add helpful features**: Command history, line editing, etc. \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/include/baba_yaga.h b/js/scripting-lang/baba-yaga-c/include/baba_yaga.h index b8660e1..1e9eead 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,30 @@ 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_each_partial(Value* args, int argc, Scope* scope); +Value stdlib_partial_apply(Value* args, int argc, Scope* scope); +Value stdlib_flip(Value* args, int argc); +Value stdlib_constant(Value* args, int argc); + +/* 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 +680,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/run_comprehensive_tests.sh b/js/scripting-lang/baba-yaga-c/run_comprehensive_tests.sh index 8f59cfe..768bba2 100755 --- a/js/scripting-lang/baba-yaga-c/run_comprehensive_tests.sh +++ b/js/scripting-lang/baba-yaga-c/run_comprehensive_tests.sh @@ -14,7 +14,7 @@ NC='\033[0m' # No Color # Configuration BABA_YAGA_BIN="./bin/baba-yaga" -TESTS_DIR="../tests" +TESTS_DIR="./tests" TEMP_DIR="./temp_test_output" RESULTS_FILE="./test_results.txt" diff --git a/js/scripting-lang/baba-yaga-c/src/function.c b/js/scripting-lang/baba-yaga-c/src/function.c index 39265ef..bb5bedf 100644 --- a/js/scripting-lang/baba-yaga-c/src/function.c +++ b/js/scripting-lang/baba-yaga-c/src/function.c @@ -46,6 +46,8 @@ typedef struct { char* source; /**< Source code for debugging */ } FunctionBody; + + /** * @brief Function value structure */ @@ -56,7 +58,7 @@ typedef struct { int param_count; /**< Number of parameters */ int required_params; /**< Number of required parameters */ union { - Value (*native_func)(Value*, int); /**< Native function pointer */ + Value (*native_func)(Value*, int, Scope*); /**< Native function pointer */ FunctionBody user_body; /**< User function body */ } body; void* closure_scope; /**< Closure scope (placeholder) */ @@ -86,7 +88,9 @@ static void function_body_destroy(FunctionBody* body) { * Public Function API * ============================================================================ */ -Value baba_yaga_value_function(const char* name, Value (*body)(Value*, int), + + +Value baba_yaga_value_function(const char* name, Value (*body)(Value*, int, Scope*), int param_count, int required_param_count) { Value value; value.type = VAL_FUNCTION; @@ -140,18 +144,41 @@ Value baba_yaga_function_call(const Value* func, const Value* args, FunctionValue* func_value = (FunctionValue*)func->data.function; - /* Check if we have enough arguments */ + + + /* Check if we have enough arguments for partial application */ if (arg_count < func_value->required_params) { - /* TODO: Implement partial application */ - /* For now, return a new function with fewer required parameters */ - return baba_yaga_value_nil(); + /* Implement partial application */ + /* Create a new function with bound arguments */ + Value partial_func = baba_yaga_value_function("partial", stdlib_partial_apply, + func_value->required_params - arg_count, + func_value->required_params - arg_count); + + /* Store the original function and bound arguments in the scope */ + char temp_name[64]; + snprintf(temp_name, sizeof(temp_name), "_partial_func_%p", (void*)func); + scope_define(scope, temp_name, *func, true); + + /* Store bound arguments */ + for (int i = 0; i < arg_count; i++) { + char arg_name[64]; + snprintf(arg_name, sizeof(arg_name), "_partial_arg_%d_%p", i, (void*)func); + scope_define(scope, arg_name, args[i], true); + } + + /* Store the number of bound arguments */ + char count_name[64]; + snprintf(count_name, sizeof(count_name), "_partial_count_%p", (void*)func); + scope_define(scope, count_name, baba_yaga_value_number(arg_count), true); + + return partial_func; } /* Execute function based on type */ switch (func_value->type) { case FUNC_NATIVE: if (func_value->body.native_func != NULL) { - return func_value->body.native_func((Value*)args, arg_count); + return func_value->body.native_func((Value*)args, arg_count, scope); } break; @@ -159,7 +186,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 +214,8 @@ Value baba_yaga_function_call(const Value* func, const Value* args, return result; } break; + + } return baba_yaga_value_nil(); @@ -229,9 +260,9 @@ void function_decrement_ref(Value* func) { } /* Clean up function body */ - if (func_value->type == FUNC_USER) { - function_body_destroy(&func_value->body.user_body); - } + if (func_value->type == FUNC_USER) { + function_body_destroy(&func_value->body.user_body); + } /* TODO: Clean up closure scope */ diff --git a/js/scripting-lang/baba-yaga-c/src/interpreter.c b/js/scripting-lang/baba-yaga-c/src/interpreter.c index d06eb30..70d26f8 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,10 +655,14 @@ 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); + /* Check if test is a sequence (multi-parameter test) */ + bool is_multi_param_test = (baba_yaga_ast_get_type(test_node) == NODE_SEQUENCE); + /* Get patterns */ int pattern_count = baba_yaga_ast_get_when_expr_pattern_count(node); @@ -589,9 +677,69 @@ Value interpreter_evaluate_expression(void* node, Scope* scope) { void* pattern_test_node = baba_yaga_ast_get_when_pattern_test(pattern_node); Value pattern_test_value = interpreter_evaluate_expression(pattern_test_node, scope); + /* Check if pattern is a sequence (multi-parameter pattern) */ + bool is_multi_param_pattern = (baba_yaga_ast_get_type(pattern_test_node) == NODE_SEQUENCE); + /* Check if pattern matches */ bool matches = false; - if (pattern_test_value.type == VAL_NUMBER && test_value.type == VAL_NUMBER) { + + /* Handle multi-parameter pattern matching */ + if (is_multi_param_test && is_multi_param_pattern) { + /* Both test and pattern are sequences - compare element by element */ + int test_count = baba_yaga_ast_get_sequence_statement_count(test_node); + int pattern_count = baba_yaga_ast_get_sequence_statement_count(pattern_test_node); + + if (test_count == pattern_count) { + matches = true; + for (int j = 0; j < test_count; j++) { + void* test_elem_node = baba_yaga_ast_get_sequence_statement(test_node, j); + void* pattern_elem_node = baba_yaga_ast_get_sequence_statement(pattern_test_node, j); + + if (test_elem_node == NULL || pattern_elem_node == NULL) { + matches = false; + break; + } + + Value test_elem = interpreter_evaluate_expression(test_elem_node, scope); + Value pattern_elem = interpreter_evaluate_expression(pattern_elem_node, scope); + + /* Check if elements match */ + bool elem_matches = false; + if (pattern_elem.type == VAL_STRING && + strcmp(pattern_elem.data.string, "_") == 0) { + /* Wildcard element always matches */ + elem_matches = true; + } else if (pattern_elem.type == test_elem.type) { + switch (pattern_elem.type) { + case VAL_NUMBER: + elem_matches = (pattern_elem.data.number == test_elem.data.number); + break; + case VAL_STRING: + elem_matches = (strcmp(pattern_elem.data.string, test_elem.data.string) == 0); + break; + case VAL_BOOLEAN: + elem_matches = (pattern_elem.data.boolean == test_elem.data.boolean); + break; + default: + elem_matches = false; + break; + } + } + + if (!elem_matches) { + matches = false; + } + + /* Clean up element values */ + baba_yaga_value_destroy(&test_elem); + baba_yaga_value_destroy(&pattern_elem); + + if (!matches) { + break; + } + } + } + } else if (pattern_test_value.type == VAL_NUMBER && test_value.type == VAL_NUMBER) { matches = (pattern_test_value.data.number == test_value.data.number); } else if (pattern_test_value.type == VAL_STRING && test_value.type == VAL_STRING) { matches = (strcmp(pattern_test_value.data.string, test_value.data.string) == 0); @@ -601,6 +749,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 +819,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 cc15047..31a582f 100644 --- a/js/scripting-lang/baba-yaga-c/src/lexer.c +++ b/js/scripting-lang/baba-yaga-c/src/lexer.c @@ -73,9 +73,8 @@ typedef enum { TOKEN_IO_IN, /* ..in */ TOKEN_IO_OUT, /* ..out */ TOKEN_IO_ASSERT, /* ..assert */ - - /* Comments */ - TOKEN_COMMENT + TOKEN_IO_EMIT, /* ..emit */ + TOKEN_IO_LISTEN /* ..listen */ } TokenType; /* ============================================================================ @@ -464,15 +463,10 @@ 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; - } else if (strcmp(token.lexeme, "and") == 0) { - token.type = TOKEN_KEYWORD_AND; - } else if (strcmp(token.lexeme, "or") == 0) { - token.type = TOKEN_KEYWORD_OR; - } else if (strcmp(token.lexeme, "xor") == 0) { - token.type = TOKEN_KEYWORD_XOR; } else if (strcmp(token.lexeme, "then") == 0) { token.type = TOKEN_KEYWORD_THEN; } else if (strcmp(token.lexeme, "not") == 0) { @@ -588,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; @@ -686,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/main.c b/js/scripting-lang/baba-yaga-c/src/main.c index 84bbb56..c1bc9f8 100644 --- a/js/scripting-lang/baba-yaga-c/src/main.c +++ b/js/scripting-lang/baba-yaga-c/src/main.c @@ -105,7 +105,7 @@ int main(int argc, char* argv[]) { } else if (optind < argc) { /* Check if the argument looks like a file (not starting with -) */ char* arg = argv[optind]; - if (arg[0] != '-' && strchr(arg, ' ') == NULL && strchr(arg, ';') == NULL) { + if (arg[0] != '-' && access(arg, F_OK) == 0) { /* Treat as file */ run_file(interp, arg); } else { @@ -316,11 +316,21 @@ static void run_file(Interpreter* interp, const char* filename) { value = baba_yaga_execute(interp, source, strlen(source), &result); free(source); - if (result != EXEC_SUCCESS) { + if (result == EXEC_SUCCESS) { + /* Print result using value_to_string for consistent formatting */ + /* Don't print special IO return value */ + if (value.type != VAL_NUMBER || value.data.number != -999999) { + char* str = baba_yaga_value_to_string(&value); + printf("%s\n", str); + free(str); + } + } else { BabaYagaError* error = baba_yaga_get_error(interp); if (error != NULL) { fprintf(stderr, "Error: %s\n", error->message); baba_yaga_error_destroy(error); + } else { + fprintf(stderr, "Error: Execution failed\n"); } exit(EXIT_FAILURE); } diff --git a/js/scripting-lang/baba-yaga-c/src/parser.c b/js/scripting-lang/baba-yaga-c/src/parser.c index 8531b5a..6c94913 100644 --- a/js/scripting-lang/baba-yaga-c/src/parser.c +++ b/js/scripting-lang/baba-yaga-c/src/parser.c @@ -61,7 +61,8 @@ typedef enum { TOKEN_IO_IN, TOKEN_IO_OUT, TOKEN_IO_ASSERT, - TOKEN_COMMENT + TOKEN_IO_EMIT, + TOKEN_IO_LISTEN } TokenType; typedef struct { @@ -334,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 +589,11 @@ static ASTNode* parser_parse_logical(Parser* parser); 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) @@ -604,21 +610,25 @@ static ASTNode* parser_parse_primary(Parser* parser) { switch (token->type) { case TOKEN_NUMBER: { + DEBUG_TRACE("parser_parse_primary consuming number: %g", token->literal.number); parser_advance(parser); return ast_literal_node(baba_yaga_value_number(token->literal.number), token->line, token->column); } case TOKEN_STRING: { + DEBUG_TRACE("parser_parse_primary consuming string: %s", token->lexeme); parser_advance(parser); return ast_literal_node(baba_yaga_value_string(token->lexeme), token->line, token->column); } case TOKEN_BOOLEAN: { + DEBUG_TRACE("parser_parse_primary consuming boolean: %s", token->literal.boolean ? "true" : "false"); parser_advance(parser); return ast_literal_node(baba_yaga_value_boolean(token->literal.boolean), token->line, token->column); } case TOKEN_IDENTIFIER: { + DEBUG_TRACE("parser_parse_primary consuming identifier: %s", token->lexeme); parser_advance(parser); /* Special handling for wildcard pattern */ if (strcmp(token->lexeme, "_") == 0) { @@ -629,7 +639,10 @@ 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 */ const char* func_name = token->lexeme + 2; /* Skip ".." */ @@ -660,16 +673,55 @@ 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: { + DEBUG_TRACE("parser_parse_primary consuming function ref: %s", token->lexeme); parser_advance(parser); /* Check if this is @(expression) syntax */ if (!parser_is_at_end(parser) && parser_peek(parser)->type == TOKEN_LPAREN) { + DEBUG_TRACE("parser_parse_primary consuming '('"); parser_advance(parser); /* consume '(' */ /* Parse the expression inside parentheses */ @@ -695,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 && @@ -717,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; @@ -750,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++) { @@ -798,6 +856,7 @@ static ASTNode* parser_parse_primary(Parser* parser) { return func_node; } case TOKEN_LPAREN: { + DEBUG_TRACE("parser_parse_primary consuming '('"); parser_advance(parser); /* consume '(' */ ASTNode* expr = parser_parse_expression(parser); if (expr == NULL) { @@ -811,17 +870,327 @@ 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; } return ast_unary_op_node(operand, "negate", token->line, token->column); } 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; } @@ -849,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; @@ -1015,9 +1384,13 @@ static ASTNode* parser_parse_logical(Parser* parser) { } /* Handle logical operators */ - while (parser_check(parser, TOKEN_KEYWORD_AND) || - parser_check(parser, TOKEN_KEYWORD_OR) || - parser_check(parser, TOKEN_KEYWORD_XOR)) { + while ((parser_check(parser, TOKEN_KEYWORD_AND) || + parser_check(parser, TOKEN_KEYWORD_OR) || + parser_check(parser, TOKEN_KEYWORD_XOR)) || + (parser_check(parser, TOKEN_IDENTIFIER) && + (strcmp(parser_peek(parser)->lexeme, "and") == 0 || + strcmp(parser_peek(parser)->lexeme, "or") == 0 || + strcmp(parser_peek(parser)->lexeme, "xor") == 0))) { Token* op = parser_advance(parser); ASTNode* right = parser_parse_comparison(parser); if (right == NULL) { @@ -1026,11 +1399,17 @@ static ASTNode* parser_parse_logical(Parser* parser) { } const char* operator_name; - switch (op->type) { - case TOKEN_KEYWORD_AND: operator_name = "and"; break; - case TOKEN_KEYWORD_OR: operator_name = "or"; break; - case TOKEN_KEYWORD_XOR: operator_name = "xor"; break; - default: operator_name = "unknown"; break; + if (op->type == TOKEN_KEYWORD_AND || + (op->type == TOKEN_IDENTIFIER && strcmp(op->lexeme, "and") == 0)) { + operator_name = "and"; + } else if (op->type == TOKEN_KEYWORD_OR || + (op->type == TOKEN_IDENTIFIER && strcmp(op->lexeme, "or") == 0)) { + operator_name = "or"; + } else if (op->type == TOKEN_KEYWORD_XOR || + (op->type == TOKEN_IDENTIFIER && strcmp(op->lexeme, "xor") == 0)) { + operator_name = "xor"; + } else { + operator_name = "unknown"; } ASTNode* new_left = ast_binary_op_node(left, right, operator_name, op->line, op->column); @@ -1043,78 +1422,163 @@ 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 */ - while (!parser_is_at_end(parser) && - (parser_peek(parser)->type == TOKEN_IDENTIFIER || - parser_peek(parser)->type == TOKEN_FUNCTION_REF || - parser_peek(parser)->type == TOKEN_NUMBER || - parser_peek(parser)->type == TOKEN_STRING || - parser_peek(parser)->type == TOKEN_LPAREN || - parser_peek(parser)->type == TOKEN_LBRACE || - parser_peek(parser)->type == TOKEN_OP_UNARY_MINUS || - parser_peek(parser)->type == TOKEN_KEYWORD_NOT) && - parser_peek(parser)->type != TOKEN_OP_PLUS && - parser_peek(parser)->type != TOKEN_OP_MINUS && - parser_peek(parser)->type != TOKEN_OP_MULTIPLY && - parser_peek(parser)->type != TOKEN_OP_DIVIDE && - parser_peek(parser)->type != TOKEN_OP_MODULO && - parser_peek(parser)->type != TOKEN_OP_POWER && - parser_peek(parser)->type != TOKEN_OP_EQUALS && - parser_peek(parser)->type != TOKEN_OP_NOT_EQUALS && - parser_peek(parser)->type != TOKEN_OP_LESS && - parser_peek(parser)->type != TOKEN_OP_LESS_EQUAL && - parser_peek(parser)->type != TOKEN_OP_GREATER && - parser_peek(parser)->type != TOKEN_OP_GREATER_EQUAL && - parser_peek(parser)->type != TOKEN_KEYWORD_AND && - parser_peek(parser)->type != TOKEN_KEYWORD_OR && - parser_peek(parser)->type != TOKEN_KEYWORD_XOR && - parser_peek(parser)->type != TOKEN_KEYWORD_WHEN && - parser_peek(parser)->type != TOKEN_KEYWORD_IS && - parser_peek(parser)->type != TOKEN_KEYWORD_THEN && - parser_peek(parser)->type != TOKEN_RPAREN && - parser_peek(parser)->type != TOKEN_RBRACE && - parser_peek(parser)->type != TOKEN_RBRACKET && - parser_peek(parser)->type != TOKEN_SEMICOLON && - parser_peek(parser)->type != TOKEN_COMMA && - parser_peek(parser)->type != TOKEN_EOF) { + /* 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 || + next_token->type == TOKEN_KEYWORD_NOT); + + /* Check if this token should not trigger function application */ + bool should_not_trigger = (next_token->type == TOKEN_OP_PLUS || + next_token->type == TOKEN_OP_MINUS || + next_token->type == TOKEN_OP_MULTIPLY || + next_token->type == TOKEN_OP_DIVIDE || + next_token->type == TOKEN_OP_MODULO || + next_token->type == TOKEN_OP_POWER || + next_token->type == TOKEN_OP_EQUALS || + next_token->type == TOKEN_OP_NOT_EQUALS || + next_token->type == TOKEN_OP_LESS || + next_token->type == TOKEN_OP_LESS_EQUAL || + next_token->type == TOKEN_OP_GREATER || + next_token->type == TOKEN_OP_GREATER_EQUAL || + next_token->type == TOKEN_KEYWORD_AND || + next_token->type == TOKEN_KEYWORD_OR || + next_token->type == TOKEN_KEYWORD_XOR || + (next_token->type == TOKEN_IDENTIFIER && + (strcmp(next_token->lexeme, "and") == 0 || + strcmp(next_token->lexeme, "or") == 0 || + strcmp(next_token->lexeme, "xor") == 0)) || + 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 || + next_token->type == TOKEN_SEMICOLON || + next_token->type == TOKEN_COMMA || + next_token->type == TOKEN_EOF); + + /* Check if this is a pattern boundary (identifier followed by 'then') */ + bool is_pattern_boundary = false; + if (next_token->type == TOKEN_IDENTIFIER) { + /* Look ahead to see if the next token is 'then' */ + if (parser->current + 1 < parser->token_count) { + Token* next_next_token = parser->tokens[parser->current + 1]; + if (next_next_token && next_next_token->type == TOKEN_KEYWORD_THEN) { + is_pattern_boundary = true; + DEBUG_TRACE("Found pattern boundary: %s followed by 'then'", next_token->lexeme); + } + } + } + + DEBUG_TRACE("Function application check: can_be_arg=%d, should_not_trigger=%d, is_pattern_boundary=%d", + can_be_arg, should_not_trigger, is_pattern_boundary); + + /* 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; + } /* Collect all arguments for this function call */ ASTNode** args = NULL; int arg_count = 0; - while (!parser_is_at_end(parser) && - (parser_peek(parser)->type == TOKEN_IDENTIFIER || - parser_peek(parser)->type == TOKEN_FUNCTION_REF || - parser_peek(parser)->type == TOKEN_NUMBER || - parser_peek(parser)->type == TOKEN_STRING || - parser_peek(parser)->type == TOKEN_LPAREN || - parser_peek(parser)->type == TOKEN_LBRACE || - parser_peek(parser)->type == TOKEN_OP_UNARY_MINUS || - parser_peek(parser)->type == TOKEN_KEYWORD_NOT) && - parser_peek(parser)->type != TOKEN_OP_PLUS && - parser_peek(parser)->type != TOKEN_OP_MINUS && - parser_peek(parser)->type != TOKEN_OP_MULTIPLY && - parser_peek(parser)->type != TOKEN_OP_DIVIDE && - parser_peek(parser)->type != TOKEN_OP_MODULO && - parser_peek(parser)->type != TOKEN_OP_POWER && - parser_peek(parser)->type != TOKEN_OP_EQUALS && - parser_peek(parser)->type != TOKEN_OP_NOT_EQUALS && - parser_peek(parser)->type != TOKEN_OP_LESS && - parser_peek(parser)->type != TOKEN_OP_LESS_EQUAL && - parser_peek(parser)->type != TOKEN_OP_GREATER && - parser_peek(parser)->type != TOKEN_OP_GREATER_EQUAL && - parser_peek(parser)->type != TOKEN_KEYWORD_AND && - parser_peek(parser)->type != TOKEN_KEYWORD_OR && - parser_peek(parser)->type != TOKEN_KEYWORD_XOR && - parser_peek(parser)->type != TOKEN_KEYWORD_WHEN && - parser_peek(parser)->type != TOKEN_KEYWORD_IS && - parser_peek(parser)->type != TOKEN_KEYWORD_THEN && - parser_peek(parser)->type != TOKEN_RPAREN && - parser_peek(parser)->type != TOKEN_RBRACE && - parser_peek(parser)->type != TOKEN_RBRACKET && - parser_peek(parser)->type != TOKEN_SEMICOLON && - parser_peek(parser)->type != TOKEN_COMMA && - parser_peek(parser)->type != TOKEN_EOF) { + while (!parser_is_at_end(parser)) { + Token* arg_token = parser_peek(parser); + if (arg_token == NULL) break; + + /* Check if this token can be a function argument */ + bool can_be_arg = (arg_token->type == TOKEN_IDENTIFIER || + 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 || + arg_token->type == TOKEN_KEYWORD_NOT); + + /* Check if this token should not trigger function application */ + bool should_not_trigger = (arg_token->type == TOKEN_OP_PLUS || + arg_token->type == TOKEN_OP_MINUS || + arg_token->type == TOKEN_OP_MULTIPLY || + arg_token->type == TOKEN_OP_DIVIDE || + arg_token->type == TOKEN_OP_MODULO || + arg_token->type == TOKEN_OP_POWER || + arg_token->type == TOKEN_OP_EQUALS || + arg_token->type == TOKEN_OP_NOT_EQUALS || + arg_token->type == TOKEN_OP_LESS || + arg_token->type == TOKEN_OP_LESS_EQUAL || + arg_token->type == TOKEN_OP_GREATER || + arg_token->type == TOKEN_OP_GREATER_EQUAL || + arg_token->type == TOKEN_KEYWORD_AND || + arg_token->type == TOKEN_KEYWORD_OR || + arg_token->type == TOKEN_KEYWORD_XOR || + arg_token->type == TOKEN_KEYWORD_WHEN || + arg_token->type == TOKEN_KEYWORD_IS || + arg_token->type == TOKEN_KEYWORD_THEN || + arg_token->type == TOKEN_RPAREN || + arg_token->type == TOKEN_RBRACE || + arg_token->type == TOKEN_RBRACKET || + arg_token->type == TOKEN_SEMICOLON || + arg_token->type == TOKEN_COMMA || + arg_token->type == TOKEN_EOF); + + /* Check if this is a pattern boundary (identifier followed by 'then') */ + bool is_pattern_boundary = false; + if (arg_token->type == TOKEN_IDENTIFIER) { + /* Look ahead to see if the next token is 'then' */ + if (parser->current + 1 < parser->token_count) { + Token* next_next_token = parser->tokens[parser->current + 1]; + if (next_next_token && next_next_token->type == TOKEN_KEYWORD_THEN) { + is_pattern_boundary = true; + DEBUG_TRACE("Inner loop found pattern boundary: %s followed by 'then'", arg_token->lexeme); + } + } + } + + /* Stop if it can't be an arg, should not trigger, or is a pattern boundary */ + if (!can_be_arg || should_not_trigger || is_pattern_boundary) { + break; + } ASTNode* arg = parser_parse_comparison(parser); if (arg == NULL) { @@ -1197,137 +1661,95 @@ static ASTNode* parser_parse_composition(Parser* parser) { } */ + + /** - * @brief Parse function application (juxtaposition) - * - * @param parser Parser instance - * @return Parsed expression node - */ -/** - * @brief Parse function application (juxtaposition) + * @brief Parse postfix operations (table access, function calls, etc.) * * @param parser Parser instance * @return Parsed expression node */ -static ASTNode* parser_parse_application(Parser* parser) { - ASTNode* left = parser_parse_logical(parser); +static ASTNode* parser_parse_postfix(Parser* parser) { + ASTNode* left = parser_parse_primary(parser); if (left == NULL) { return NULL; } - /* Function application is left-associative */ - while (!parser_is_at_end(parser) && - (parser_peek(parser)->type == TOKEN_IDENTIFIER || - parser_peek(parser)->type == TOKEN_FUNCTION_REF || - parser_peek(parser)->type == TOKEN_NUMBER || - parser_peek(parser)->type == TOKEN_STRING || - parser_peek(parser)->type == TOKEN_LPAREN || - parser_peek(parser)->type == TOKEN_LBRACE || - parser_peek(parser)->type == TOKEN_OP_UNARY_MINUS || - parser_peek(parser)->type == TOKEN_KEYWORD_NOT) && - parser_peek(parser)->type != TOKEN_OP_PLUS && - parser_peek(parser)->type != TOKEN_OP_MINUS && - parser_peek(parser)->type != TOKEN_OP_MULTIPLY && - parser_peek(parser)->type != TOKEN_OP_DIVIDE && - parser_peek(parser)->type != TOKEN_OP_MODULO && - parser_peek(parser)->type != TOKEN_OP_POWER && - parser_peek(parser)->type != TOKEN_OP_EQUALS && - parser_peek(parser)->type != TOKEN_OP_NOT_EQUALS && - parser_peek(parser)->type != TOKEN_OP_LESS && - parser_peek(parser)->type != TOKEN_OP_LESS_EQUAL && - parser_peek(parser)->type != TOKEN_OP_GREATER && - parser_peek(parser)->type != TOKEN_OP_GREATER_EQUAL && - parser_peek(parser)->type != TOKEN_KEYWORD_AND && - parser_peek(parser)->type != TOKEN_KEYWORD_OR && - parser_peek(parser)->type != TOKEN_KEYWORD_XOR && - parser_peek(parser)->type != TOKEN_KEYWORD_WHEN && - parser_peek(parser)->type != TOKEN_KEYWORD_IS && - parser_peek(parser)->type != TOKEN_KEYWORD_THEN && - parser_peek(parser)->type != TOKEN_RPAREN && - parser_peek(parser)->type != TOKEN_RBRACE && - parser_peek(parser)->type != TOKEN_RBRACKET && - parser_peek(parser)->type != TOKEN_SEMICOLON && - parser_peek(parser)->type != TOKEN_COMMA && - parser_peek(parser)->type != TOKEN_EOF) { - - /* Collect all arguments for this function call */ - ASTNode** args = NULL; - int arg_count = 0; + while (!parser_is_at_end(parser)) { + Token* token = parser_peek(parser); + if (token == NULL) { + break; + } - while (!parser_is_at_end(parser) && - (parser_peek(parser)->type == TOKEN_IDENTIFIER || - parser_peek(parser)->type == TOKEN_FUNCTION_REF || - parser_peek(parser)->type == TOKEN_NUMBER || - parser_peek(parser)->type == TOKEN_STRING || - parser_peek(parser)->type == TOKEN_LPAREN || - parser_peek(parser)->type == TOKEN_LBRACE || - parser_peek(parser)->type == TOKEN_OP_UNARY_MINUS || - parser_peek(parser)->type == TOKEN_KEYWORD_NOT) && - parser_peek(parser)->type != TOKEN_OP_PLUS && - parser_peek(parser)->type != TOKEN_OP_MINUS && - parser_peek(parser)->type != TOKEN_OP_MULTIPLY && - parser_peek(parser)->type != TOKEN_OP_DIVIDE && - parser_peek(parser)->type != TOKEN_OP_MODULO && - parser_peek(parser)->type != TOKEN_OP_POWER && - parser_peek(parser)->type != TOKEN_OP_EQUALS && - parser_peek(parser)->type != TOKEN_OP_NOT_EQUALS && - parser_peek(parser)->type != TOKEN_OP_LESS && - parser_peek(parser)->type != TOKEN_OP_LESS_EQUAL && - parser_peek(parser)->type != TOKEN_OP_GREATER && - parser_peek(parser)->type != TOKEN_OP_GREATER_EQUAL && - parser_peek(parser)->type != TOKEN_KEYWORD_AND && - parser_peek(parser)->type != TOKEN_KEYWORD_OR && - parser_peek(parser)->type != TOKEN_KEYWORD_XOR && - parser_peek(parser)->type != TOKEN_KEYWORD_WHEN && - parser_peek(parser)->type != TOKEN_KEYWORD_IS && - parser_peek(parser)->type != TOKEN_KEYWORD_THEN && - parser_peek(parser)->type != TOKEN_RPAREN && - parser_peek(parser)->type != TOKEN_RBRACE && - parser_peek(parser)->type != TOKEN_RBRACKET && - parser_peek(parser)->type != TOKEN_SEMICOLON && - parser_peek(parser)->type != TOKEN_COMMA && - parser_peek(parser)->type != TOKEN_EOF) { + switch (token->type) { + case TOKEN_DOT: { + /* Table property access: table.property */ + parser_advance(parser); /* consume '.' */ - ASTNode* arg = parser_parse_logical(parser); - if (arg == NULL) { - /* Cleanup on error */ - for (int i = 0; i < arg_count; i++) { - ast_destroy_node(args[i]); - } - free(args); + Token* property = parser_consume(parser, TOKEN_IDENTIFIER, "Expected property name after '.'"); + if (property == NULL) { ast_destroy_node(left); return NULL; } - /* Add to arguments array */ - ASTNode** new_args = realloc(args, (arg_count + 1) * sizeof(ASTNode*)); - if (new_args == NULL) { - /* Cleanup on error */ - for (int i = 0; i < arg_count; i++) { - ast_destroy_node(args[i]); - } - free(args); - ast_destroy_node(arg); + ASTNode* key = ast_literal_node(baba_yaga_value_string(property->lexeme), property->line, property->column); + if (key == NULL) { ast_destroy_node(left); return NULL; } - args = new_args; - args[arg_count++] = arg; + + 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; } - - /* Create function call with all arguments */ - ASTNode* new_left = ast_function_call_node(left, args, arg_count, left->line, left->column); - if (new_left == NULL) { - /* Cleanup on error */ - for (int i = 0; i < arg_count; i++) { - ast_destroy_node(args[i]); + 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; } - free(args); - 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; } - - left = new_left; } return left; @@ -1340,7 +1762,7 @@ static ASTNode* parser_parse_application(Parser* parser) { * @return Parsed expression node */ static ASTNode* parser_parse_expression(Parser* parser) { - return parser_parse_application(parser); + return parser_parse_logical(parser); } /* ============================================================================ @@ -1368,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); @@ -1380,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; } @@ -1460,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 @@ -1506,11 +1998,8 @@ static ASTNode* parser_parse_statements(Parser* parser) { /* Consume semicolon */ parser_consume(parser, TOKEN_SEMICOLON, "Expected semicolon"); - /* Skip any whitespace/comments after semicolon */ - while (!parser_is_at_end(parser) && - (parser_peek(parser)->type == TOKEN_COMMENT)) { - parser->current++; /* Skip comment */ - } + /* Skip any whitespace after semicolon */ + /* Comments are already skipped by the lexer */ if (parser_is_at_end(parser)) { break; /* Trailing semicolon */ @@ -1934,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; @@ -2015,76 +2559,136 @@ void baba_yaga_print_ast(void* node, int indent) { * @return Parsed when expression node */ static ASTNode* parser_parse_when_expression(Parser* parser) { - /* Consume 'when' keyword */ + DEBUG_DEBUG("Parsing WHEN expression at token %d", parser->current); Token* when_token = parser_consume(parser, TOKEN_KEYWORD_WHEN, "Expected 'when'"); - if (when_token == NULL) { - return NULL; - } - - /* Parse test expression */ - ASTNode* test = parser_parse_expression(parser); - if (test == NULL) { - return NULL; - } + if (!when_token) return NULL; - /* Consume 'is' keyword */ - Token* is_token = parser_consume(parser, TOKEN_KEYWORD_IS, "Expected 'is' after test expression"); - if (is_token == NULL) { - ast_destroy_node(test); - return NULL; - } + - /* Parse patterns */ - ASTNode** patterns = NULL; - int pattern_count = 0; - int capacity = 5; /* Start with space for 5 patterns */ + /* 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; - patterns = malloc(capacity * sizeof(ASTNode*)); - if (patterns == NULL) { - ast_destroy_node(test); - return NULL; + /* 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++; } - /* Parse first pattern */ - ASTNode* pattern = parser_parse_when_pattern(parser); - if (pattern == NULL) { - free(patterns); - ast_destroy_node(test); - return NULL; + /* If we have multiple identifiers followed by 'is', it's multi-parameter */ + if (identifier_count > 1) { + is_multi_param = true; } - patterns[pattern_count++] = pattern; - - /* Parse additional patterns */ - while (!parser_is_at_end(parser)) { - /* Parse next pattern */ - ASTNode* next_pattern = parser_parse_when_pattern(parser); - if (next_pattern == NULL) { - break; /* Error parsing pattern, but continue with what we have */ - } + ASTNode* test; + if (is_multi_param) { + /* Parse as sequence of identifiers or expressions */ + ASTNode** identifiers = malloc(identifier_count * sizeof(ASTNode*)); + if (!identifiers) return NULL; - /* Expand array if needed */ - if (pattern_count >= capacity) { - capacity *= 2; - ASTNode** new_patterns = realloc(patterns, capacity * sizeof(ASTNode*)); - if (new_patterns == NULL) { - /* Cleanup and return what we have */ - for (int i = 0; i < pattern_count; i++) { - ast_destroy_node(patterns[i]); + 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; } - free(patterns); - ast_destroy_node(test); - 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); } - patterns = new_patterns; } - patterns[pattern_count++] = next_pattern; + /* 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); } - /* Create when expression node */ - return ast_when_expr_node(test, patterns, pattern_count, - when_token->line, when_token->column); + 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; } + + // Prepare flat array of NODE_WHEN_PATTERN nodes + ASTNode** patterns = NULL; + int pattern_count = 0, pattern_cap = 4; + patterns = malloc(pattern_cap * sizeof(ASTNode*)); + + while (!parser_is_at_end(parser) && parser_peek(parser)->type != TOKEN_SEMICOLON) { + // Parse pattern + ASTNode* pattern = parser_parse_when_pattern(parser); + if (!pattern) break; + // Expect 'then' + Token* then_token = parser_consume(parser, TOKEN_KEYWORD_THEN, "Expected 'then' after pattern in when case"); + if (!then_token) { ast_destroy_node(pattern); break; } + // Parse result (single expression) + ASTNode* result = parser_parse_when_result_expression(parser); + if (!result) { ast_destroy_node(pattern); break; } + // Create NODE_WHEN_PATTERN node + ASTNode* case_node = ast_when_pattern_node(pattern, result, when_token->line, when_token->column); + if (pattern_count >= pattern_cap) { + pattern_cap *= 2; + patterns = realloc(patterns, pattern_cap * sizeof(ASTNode*)); + } + patterns[pattern_count++] = case_node; + // If next token is a valid pattern start, continue loop; else break + Token* next = parser_peek(parser); + if (!next || next->type == TOKEN_SEMICOLON) break; + int is_wildcard = (next->type == TOKEN_IDENTIFIER && next->lexeme && strcmp(next->lexeme, "_") == 0); + if (!(is_wildcard || next->type == TOKEN_IDENTIFIER || next->type == TOKEN_NUMBER || next->type == TOKEN_STRING)) break; + } + // 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; } /** @@ -2093,30 +2697,259 @@ static ASTNode* parser_parse_when_expression(Parser* parser) { * @param parser Parser instance * @return Parsed when pattern node */ -static ASTNode* parser_parse_when_pattern(Parser* parser) { - /* Parse pattern test expression */ - ASTNode* pattern_test = parser_parse_expression(parser); - if (pattern_test == NULL) { - return NULL; +// Helper: look ahead to see if the next two tokens are a pattern start followed by 'then' +static bool parser_is_next_pattern(Parser* parser) { + if (parser_is_at_end(parser)) return false; + Token* t1 = parser_peek(parser); + if (!t1) return false; + if (t1->type != TOKEN_IDENTIFIER && t1->type != TOKEN_NUMBER && t1->type != TOKEN_STRING) return false; + // Look ahead one more + if (parser->current + 1 >= parser->token_count) return false; + Token* t2 = parser->tokens[parser->current + 1]; + return t2 && t2->type == TOKEN_KEYWORD_THEN; +} + +// Parse a result expression for a when pattern, stopping at pattern boundaries +static ASTNode* parser_parse_when_result_expression(Parser* parser) { + DEBUG_TRACE("parser_parse_when_result_expression start at token %d", parser->current); + + // Show current token before parsing + Token* before_token = parser_peek(parser); + if (before_token) { + DEBUG_TRACE("Before parsing result, token type=%d, lexeme='%s'", + before_token->type, before_token->lexeme ? before_token->lexeme : "NULL"); } - /* Consume 'then' keyword */ - Token* then_token = parser_consume(parser, TOKEN_KEYWORD_THEN, "Expected 'then' after pattern"); - if (then_token == NULL) { - ast_destroy_node(pattern_test); - return NULL; + // Check if the next token is a pattern start followed by 'then' + // If so, return an empty result expression + if (parser_is_next_pattern(parser)) { + DEBUG_TRACE("Detected next pattern, returning empty result"); + return ast_literal_node(baba_yaga_value_string(""), parser_peek(parser)->line, parser_peek(parser)->column); } - /* Parse result expression */ - ASTNode* result = parser_parse_expression(parser); + // Parse a single expression using a bounded parser + // Stop when we hit a pattern boundary or statement terminator + ASTNode* result = parser_parse_primary(parser); if (result == NULL) { - ast_destroy_node(pattern_test); return NULL; } - /* Create when pattern node */ - return ast_when_pattern_node(pattern_test, result, - then_token->line, then_token->column); + // Show current token after parsing + Token* after_token = parser_peek(parser); + if (after_token) { + DEBUG_TRACE("After parsing result, token type=%d, lexeme='%s'", + after_token->type, after_token->lexeme ? after_token->lexeme : "NULL"); + } + + DEBUG_TRACE("parser_parse_when_result_expression end at token %d", parser->current); + return result; +} + +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 */ + Token* current_token = parser_peek(parser); + if (current_token != NULL) { + DEBUG_TRACE("Current token type=%d, lexeme='%s'", current_token->type, current_token->lexeme ? current_token->lexeme : "NULL"); + } + + /* 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 (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("_"), + 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 - 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; + } + DEBUG_TRACE("Parsed pattern test expression"); + } + + DEBUG_TRACE("parser_parse_when_pattern success"); + + /* 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..d3ebdea 100644 --- a/js/scripting-lang/baba-yaga-c/src/stdlib.c +++ b/js/scripting-lang/baba-yaga-c/src/stdlib.c @@ -16,6 +16,191 @@ #include "baba_yaga.h" /* ============================================================================ + * Wrapper Functions for Basic Operations (to match function signature) + * ============================================================================ */ + +Value stdlib_add_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_add(args, argc); +} + +Value stdlib_subtract_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_subtract(args, argc); +} + +Value stdlib_multiply_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_multiply(args, argc); +} + +Value stdlib_divide_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_divide(args, argc); +} + +Value stdlib_modulo_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_modulo(args, argc); +} + +Value stdlib_pow_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_pow(args, argc); +} + +Value stdlib_negate_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_negate(args, argc); +} + +Value stdlib_equals_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_equals(args, argc); +} + +Value stdlib_not_equals_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_not_equals(args, argc); +} + +Value stdlib_less_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_less(args, argc); +} + +Value stdlib_less_equal_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_less_equal(args, argc); +} + +Value stdlib_greater_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_greater(args, argc); +} + +Value stdlib_greater_equal_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_greater_equal(args, argc); +} + +Value stdlib_and_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_and(args, argc); +} + +Value stdlib_or_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_or(args, argc); +} + +Value stdlib_xor_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_xor(args, argc); +} + +Value stdlib_not_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_not(args, argc); +} + +Value stdlib_compose_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_compose(args, argc); +} + +Value stdlib_out_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_out(args, argc); +} + +Value stdlib_in_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_in(args, argc); +} + +Value stdlib_assert_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_assert(args, argc); +} + +Value stdlib_emit_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_emit(args, argc); +} + +Value stdlib_listen_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_listen(args, argc); +} + +Value stdlib_flip_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_flip(args, argc); +} + +Value stdlib_constant_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_constant(args, argc); +} + +Value stdlib_apply_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_apply(args, argc); +} + +/* Table operation wrappers */ +Value stdlib_t_map_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_map(args, argc); +} + +Value stdlib_t_filter_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_filter(args, argc); +} + +Value stdlib_t_reduce_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_reduce(args, argc); +} + +Value stdlib_t_set_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_set(args, argc); +} + +Value stdlib_t_delete_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_delete(args, argc); +} + +Value stdlib_t_merge_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_merge(args, argc); +} + +Value stdlib_t_length_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_length(args, argc); +} + +Value stdlib_t_has_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_has(args, argc); +} + +Value stdlib_t_get_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_t_get(args, argc); +} + +Value stdlib_table_entry_wrapper(Value* args, int argc, Scope* scope) { + (void)scope; /* Unused */ + return stdlib_table_entry(args, argc); +} + +/* ============================================================================ * Standard Library Functions * ============================================================================ */ @@ -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,547 @@ 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 < 2 || argc > 3) { + DEBUG_ERROR("each: expected 2 or 3 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + /* Handle partial application: each function arg2 */ + if (argc == 2) { + Value func = args[0]; + Value arg2 = args[1]; + + if (func.type != VAL_FUNCTION) { + DEBUG_ERROR("each: first argument must be a function"); + return baba_yaga_value_nil(); + } + + /* Create a new function that applies the original function with the second argument */ + Value partial_func = baba_yaga_value_function("each_partial", stdlib_each_partial, 2, 2); + + /* Store the original function and second argument in the scope */ + char temp_name[32]; + snprintf(temp_name, sizeof(temp_name), "_each_func_%p", (void*)&func); + scope_define(scope, temp_name, func, true); + + char temp_name2[32]; + snprintf(temp_name2, sizeof(temp_name2), "_each_arg2_%p", (void*)&arg2); + scope_define(scope, temp_name2, arg2, true); + + return partial_func; + } + + Value func = args[0]; + Value table1 = args[1]; + + 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 Partial application helper for each function + * + * This function is called when a partial each function is applied with a table. + * It applies the original function to each element of the table with the second argument. + */ +/** + * @brief Partial application helper function + * + * This function is called when a partial function is applied with additional arguments. + * It combines the bound arguments with the new arguments and calls the original function. + */ +Value stdlib_partial_apply(Value* args, int argc, Scope* scope) { + /* Get the original function and bound arguments from the scope */ + char** names = malloc(100 * sizeof(char*)); + int name_count = scope_get_names(scope, names, 100); + + Value original_func = baba_yaga_value_nil(); + int bound_count = 0; + Value bound_args[10]; /* Assume max 10 bound arguments */ + + for (int i = 0; i < name_count; i++) { + if (strncmp(names[i], "_partial_func_", 14) == 0) { + original_func = scope_get(scope, names[i]); + } else if (strncmp(names[i], "_partial_count_", 15) == 0) { + Value count_val = scope_get(scope, names[i]); + if (count_val.type == VAL_NUMBER) { + bound_count = (int)count_val.data.number; + } + } else if (strncmp(names[i], "_partial_arg_", 13) == 0) { + /* Extract argument index from name like "_partial_arg_0_0x123" */ + char* underscore = strrchr(names[i], '_'); + if (underscore != NULL) { + int arg_index = atoi(underscore + 1); + if (arg_index >= 0 && arg_index < 10) { + bound_args[arg_index] = scope_get(scope, names[i]); + } + } + } + } + + /* Free the names array */ + for (int i = 0; i < name_count; i++) { + free(names[i]); + } + free(names); + + if (original_func.type != VAL_FUNCTION) { + DEBUG_ERROR("partial_apply: original function not found"); + return baba_yaga_value_nil(); + } + + /* Combine bound arguments with new arguments */ + Value combined_args[20]; /* Assume max 20 total arguments */ + int total_count = bound_count + argc; + + if (total_count > 20) { + DEBUG_ERROR("partial_apply: too many arguments"); + return baba_yaga_value_nil(); + } + + /* Copy bound arguments first */ + for (int i = 0; i < bound_count; i++) { + combined_args[i] = bound_args[i]; + } + + /* Copy new arguments */ + for (int i = 0; i < argc; i++) { + combined_args[bound_count + i] = args[i]; + } + + /* Call the original function with all arguments */ + return baba_yaga_function_call(&original_func, combined_args, total_count, scope); +} + +Value stdlib_each_partial(Value* args, int argc, Scope* scope) { + if (argc != 2) { + DEBUG_ERROR("each_partial: expected 2 arguments, got %d", argc); + return baba_yaga_value_nil(); + } + + Value table = args[1]; + if (table.type != VAL_TABLE) { + DEBUG_ERROR("each_partial: second argument must be a table"); + return baba_yaga_value_nil(); + } + + /* Get the original function and second argument from the scope */ + /* We need to find them by looking for the stored values */ + char** names = malloc(100 * sizeof(char*)); + int name_count = scope_get_names(scope, names, 100); + + Value original_func = baba_yaga_value_nil(); + Value arg2 = baba_yaga_value_nil(); + + for (int i = 0; i < name_count; i++) { + if (strncmp(names[i], "_each_func_", 11) == 0) { + original_func = scope_get(scope, names[i]); + } else if (strncmp(names[i], "_each_arg2_", 11) == 0) { + arg2 = scope_get(scope, names[i]); + } + } + + /* Free the names array */ + for (int i = 0; i < name_count; i++) { + free(names[i]); + } + free(names); + + if (original_func.type != VAL_FUNCTION) { + DEBUG_ERROR("each_partial: original function not found"); + return baba_yaga_value_nil(); + } + + /* Apply the original function to each element of the table with the second argument */ + char* keys[1000]; + size_t key_count = baba_yaga_table_get_keys(&table, keys, 1000); + + Value result = baba_yaga_value_table(); + + for (size_t i = 0; i < key_count; i++) { + Value element = baba_yaga_table_get_by_key(&table, keys[i]); + if (element.type != VAL_NIL) { + /* Call function with element and the second argument */ + Value func_args[2]; + func_args[0] = element; + func_args[1] = arg2; + Value element_result = baba_yaga_function_call(&original_func, func_args, 2, scope); + + /* Add result to new table */ + result = baba_yaga_table_set(&result, keys[i], &element_result); + } + free(keys[i]); + } + + return result; +} + +/** + * @brief Flip combinator - reverses argument order of a function + * + * @param args Array of arguments [function] or [function, arg1, arg2] + * @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 +1309,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_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_copy.txt b/js/scripting-lang/baba-yaga-c/test_copy.txt new file mode 100644 index 0000000..a67bf59 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_copy.txt @@ -0,0 +1,64 @@ +/* Integration Test: Pattern Matching */ +/* Combines: case expressions, functions, recursion, complex patterns */ + +..out "=== Integration Test: Pattern Matching ==="; + +/* Recursive factorial with case expressions */ +factorial : n -> + when n is + 0 then 1 + _ then n * (factorial (n - 1)); + +/* Pattern matching with multiple parameters */ +classify : x y -> + when x y is + 0 0 then "both zero" + 0 _ then "x is zero" + _ 0 then "y is zero" + _ _ then when x is + 0 then "x is zero (nested)" + _ then when y is + 0 then "y is zero (nested)" + _ then "neither zero"; + +/* Test factorial */ +fact5 : factorial 5; +fact3 : factorial 3; + +..assert fact5 = 120; +..assert fact3 = 6; + +/* Test classification */ +test1 : classify 0 0; +test2 : classify 0 5; +test3 : classify 5 0; +test4 : classify 5 5; + +..assert test1 = "both zero"; +..assert test2 = "x is zero"; +..assert test3 = "y is zero"; +..assert test4 = "neither zero"; + +/* Complex nested case expressions */ +analyze : x y z -> + when x y z is + 0 0 0 then "all zero" + 0 0 _ then "x and y zero" + 0 _ 0 then "x and z zero" + _ 0 0 then "y and z zero" + 0 _ _ then "only x zero" + _ 0 _ then "only y zero" + _ _ 0 then "only z zero" + _ _ _ then "none zero"; + +result1 : analyze 0 0 0; +result2 : analyze 0 1 1; +result3 : analyze 1 0 1; +result4 : analyze 1 1 1; + +..assert result1 = "all zero"; +..assert result2 = "only x zero"; +..assert result3 = "only y zero"; +..assert result4 = "none zero"; + +..out "Pattern matching integration test completed"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_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_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 new file mode 100644 index 0000000..1e8f5c0 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_minimal.txt @@ -0,0 +1 @@ +test_multi_expr : x y -> when (x % 2) (y % 2) is 0 0 then "both even" 0 1 then "x even, y odd" 1 0 then "x odd, y even" 1 1 then "both odd"; result4 : test_multi_expr 4 6; ..out result4; diff --git a/js/scripting-lang/baba-yaga-c/test_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_new.txt b/js/scripting-lang/baba-yaga-c/test_new.txt new file mode 100644 index 0000000..a67bf59 --- /dev/null +++ b/js/scripting-lang/baba-yaga-c/test_new.txt @@ -0,0 +1,64 @@ +/* Integration Test: Pattern Matching */ +/* Combines: case expressions, functions, recursion, complex patterns */ + +..out "=== Integration Test: Pattern Matching ==="; + +/* Recursive factorial with case expressions */ +factorial : n -> + when n is + 0 then 1 + _ then n * (factorial (n - 1)); + +/* Pattern matching with multiple parameters */ +classify : x y -> + when x y is + 0 0 then "both zero" + 0 _ then "x is zero" + _ 0 then "y is zero" + _ _ then when x is + 0 then "x is zero (nested)" + _ then when y is + 0 then "y is zero (nested)" + _ then "neither zero"; + +/* Test factorial */ +fact5 : factorial 5; +fact3 : factorial 3; + +..assert fact5 = 120; +..assert fact3 = 6; + +/* Test classification */ +test1 : classify 0 0; +test2 : classify 0 5; +test3 : classify 5 0; +test4 : classify 5 5; + +..assert test1 = "both zero"; +..assert test2 = "x is zero"; +..assert test3 = "y is zero"; +..assert test4 = "neither zero"; + +/* Complex nested case expressions */ +analyze : x y z -> + when x y z is + 0 0 0 then "all zero" + 0 0 _ then "x and y zero" + 0 _ 0 then "x and z zero" + _ 0 0 then "y and z zero" + 0 _ _ then "only x zero" + _ 0 _ then "only y zero" + _ _ 0 then "only z zero" + _ _ _ then "none zero"; + +result1 : analyze 0 0 0; +result2 : analyze 0 1 1; +result3 : analyze 1 0 1; +result4 : analyze 1 1 1; + +..assert result1 = "all zero"; +..assert result2 = "only x zero"; +..assert result3 = "only y zero"; +..assert result4 = "none zero"; + +..out "Pattern matching integration test completed"; \ No newline at end of file diff --git a/js/scripting-lang/baba-yaga-c/test_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_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_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/scratch_tests/test_ast_debug.txt b/js/scripting-lang/scratch_tests/test_ast_debug.txt deleted file mode 100644 index e8a764c..0000000 --- a/js/scripting-lang/scratch_tests/test_ast_debug.txt +++ /dev/null @@ -1,11 +0,0 @@ -/* Debug test for AST structure */ -is_even : n -> n % 2 = 0; - -test_debug : n -> - when n is - is_even n then "should not match" - 4 then "four" - _ then "other"; - -result : test_debug 4; -..out result; \ No newline at end of file diff --git a/js/scripting-lang/web/README-AST.md b/js/scripting-lang/web/README-AST.md new file mode 100644 index 0000000..194aeec --- /dev/null +++ b/js/scripting-lang/web/README-AST.md @@ -0,0 +1,67 @@ +# Baba Yaga AST Visualizer + +A web-based tool for visualizing the Abstract Syntax Tree (AST) of Baba Yaga code. + +## Features + +- **Real-time AST Generation**: Enter Baba Yaga code and see its AST instantly +- **Token Visualization**: View the tokenized representation of your code +- **Error Display**: Clear error messages for invalid syntax +- **Example Code**: Pre-loaded examples demonstrating different language features +- **Copy to Clipboard**: One-click copying of AST and tokens for easy sharing +- **Clean Interface**: Simple, focused design following the project's design patterns + +## Usage + +1. Open `ast-viewer.html` in your browser +2. Enter Baba Yaga code in the text area +3. Click "Generate AST" or use Ctrl+Enter +4. View the AST and tokens in the output sections below +5. Use the "Copy AST" or "Copy Tokens" buttons to copy the content to your clipboard + +## Examples Included + +- **Simple Assignment**: Basic variable assignment +- **When Expression**: Pattern matching with when/is/then +- **Function Definition**: Arrow function with pattern matching +- **Table Literal**: Creating and accessing table structures +- **Arithmetic Expression**: Mathematical operations and function composition +- **Complex When Expression**: Multi-pattern matching + +## Technical Details + +- Uses the same `lexer.js` and `parser.js` modules as the main language +- No modifications to core language files required +- Pure client-side JavaScript with ES6 modules +- Responsive design that works on desktop and mobile + +## File Structure + +``` +web/ +├── ast.html # Main AST visualization interface +├── src/ +│ └── ast.js # AST generation logic +├── style.css # Shared styling +└── README-AST.md # This file +``` + +## Browser Compatibility + +Requires a modern browser with ES6 module support: +- Chrome 61+ +- Firefox 60+ +- Safari 10.1+ +- Edge 16+ + +## Development + +To run locally: +```bash +cd web +python3 -m http.server 8000 +# or +npx serve . +``` + +Then open `http://localhost:8000/ast.html` \ No newline at end of file diff --git a/js/scripting-lang/web/ast-viewer.html b/js/scripting-lang/web/ast-viewer.html new file mode 100644 index 0000000..269504f --- /dev/null +++ b/js/scripting-lang/web/ast-viewer.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Baba Yaga AST Viewer</title> + <link rel="stylesheet" href="style.css"> + <style> + textarea { + width: 100%; + min-height: 200px; + padding: 0.6em; + font-size: 1em; + font-family: 'Courier New', monospace; + border: 2px solid var(--color-input-border); + border-radius: 0.2em; + margin-bottom: 1em; + box-sizing: border-box; + resize: vertical; + } + + .output { + white-space: pre-wrap; + font-family: 'Courier New', monospace; + font-size: 0.9em; + background: var(--color-code-bg); + padding: 1em; + border-radius: 0.2em; + border: 1px solid var(--color-result-border); + max-height: 400px; + overflow-y: auto; + resize: vertical; + min-height: 200px; + } + + .error { + color: var(--color-error); + background: #ffeef0; + border-left: 4px solid var(--color-error); + padding: 1em; + margin-bottom: 1em; + } + + .example-selector { + margin-bottom: 1em; + } + + .example-selector select { + padding: 0.6em; + font-size: 1em; + border: 2px solid var(--color-input-border); + border-radius: 0.2em; + background: white; + margin-left: 0.5em; + } + + .example-selector select:focus { + outline: none; + border-color: #007acc; + } + + .output-section { + margin-top: 1.5em; + } + + .output-section h3 { + margin-bottom: 0.5em; + color: var(--color-label); + text-transform: uppercase; + font-size: 0.9em; + } + + .output-container { + position: relative; + } + + .copy-btn { + position: absolute; + top: 0.5em; + right: 0.5em; + background: var(--color-button-bg); + color: var(--color-button-text); + border: none; + border-radius: 0.2em; + padding: 0.3em 0.6em; + font-size: 0.8em; + font-weight: bold; + cursor: pointer; + z-index: 10; + } + + .copy-btn:hover { + background: #005a9e; + } + + .copy-btn:active { + transform: translateY(1px); + } + </style> +</head> +<body> + <main> + <h1>Baba Yaga AST Visualizer</h1> + + <div class="example-selector"> + <label for="examples">Load Example:</label> + <select id="examples"> + <option value="">Choose an example...</option> + <option value="simple">Simple Assignment</option> + <option value="when">When Expression</option> + <option value="function">Function Definition</option> + <option value="table">Table Literal</option> + <option value="arithmetic">Arithmetic Expression</option> + <option value="complex">Complex When Expression</option> + </select> + </div> + + <label for="code-input">Code:</label> + <textarea + id="code-input" + placeholder="Enter Baba Yaga code here... +Example: +x : 42; +result : when x is 42 then "correct" _ then "wrong";" + ></textarea> + + <button id="generate-btn">Generate AST</button> + + <div class="output-section"> + <h3>AST Output:</h3> + <div class="output-container"> + <textarea id="ast-output" class="output" readonly placeholder="AST will appear here..."></textarea> + <button id="copy-ast-btn" class="copy-btn">Copy AST</button> + </div> + </div> + + <div class="output-section"> + <h3>Tokens:</h3> + <div class="output-container"> + <textarea id="tokens-output" class="output" readonly placeholder="Tokens will appear here..."></textarea> + <button id="copy-tokens-btn" class="copy-btn">Copy Tokens</button> + </div> + </div> + + <div id="error-output" class="error" style="display: none;"></div> + </main> + + <script type="module" src="src/ast.js"></script> +</body> +</html> \ No newline at end of file diff --git a/js/scripting-lang/web/simple.html b/js/scripting-lang/web/simple.html index 2aa5dac..9b8fd19 100644 --- a/js/scripting-lang/web/simple.html +++ b/js/scripting-lang/web/simple.html @@ -39,7 +39,7 @@ <body> <main> <h1>Baba Yaga</h1> - + <div class="result" id="result" style="display: none;"> <div class="output" id="output"></div> </div> diff --git a/js/scripting-lang/web/src/ast.js b/js/scripting-lang/web/src/ast.js new file mode 100644 index 0000000..522d026 --- /dev/null +++ b/js/scripting-lang/web/src/ast.js @@ -0,0 +1,161 @@ +// ast.js +// AST visualization tool for Baba Yaga language + +import { lexer, parser } from '../../lang.js'; + +const examples = { + simple: `x : 42;`, + + when: `result : when x is 42 then "correct" _ then "wrong";`, + + function: `factorial : n -> + when n is + 0 then 1 + _ then n * (factorial (n - 1));`, + + table: `person : {name: "Baba Yaga", age: 99, active: true}; +numbers : {1, 2, 3, 4, 5};`, + + arithmetic: `result : 5 + 3 * 2; +composed : compose @double @increment 5;`, + + complex: `classify : x y -> + when x y is + 0 0 then "both zero" + 0 _ then "x is zero" + _ 0 then "y is zero" + _ _ then "neither zero";` +}; + +// DOM elements - will be initialized when DOM is ready +let codeInput, generateBtn, examplesSelect, astOutput, tokensOutput, errorOutput, copyAstBtn, copyTokensBtn; + +// Initialize when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + // Initialize DOM elements + codeInput = document.getElementById('code-input'); + generateBtn = document.getElementById('generate-btn'); + examplesSelect = document.getElementById('examples'); + astOutput = document.getElementById('ast-output'); + tokensOutput = document.getElementById('tokens-output'); + errorOutput = document.getElementById('error-output'); + copyAstBtn = document.getElementById('copy-ast-btn'); + copyTokensBtn = document.getElementById('copy-tokens-btn'); + + // Example selector functionality + examplesSelect.addEventListener('change', () => { + const selectedExample = examplesSelect.value; + if (selectedExample && examples[selectedExample]) { + codeInput.value = examples[selectedExample]; + generateAST(); + } + }); + + // Generate button click handler + generateBtn.addEventListener('click', generateAST); + + // Copy button click handlers + copyAstBtn.addEventListener('click', () => copyToClipboard(astOutput, 'AST')); + copyTokensBtn.addEventListener('click', () => copyToClipboard(tokensOutput, 'Tokens')); + + // Auto-generate on Enter key (but not in textarea) + document.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && e.ctrlKey && document.activeElement !== codeInput) { + generateAST(); + } + }); + + // Initialize with a default example + codeInput.value = examples.when; + generateAST(); +}); + +// Generate AST from code +function generateAST() { + if (!codeInput) return; // DOM not ready yet + + const code = codeInput.value.trim(); + + if (!code) { + showError('Please enter some code to analyze.'); + return; + } + + try { + // Generate tokens + const tokens = lexer(code); + showTokens(tokens); + + // Generate AST + const ast = parser(tokens); + showAST(ast); + + // Clear any previous errors + showError(''); + + } catch (error) { + showError(`Parsing Error: ${error.message}`); + showAST(null); + showTokens(null); + } +} + +// Display AST in formatted JSON +function showAST(ast) { + if (!astOutput) return; // DOM not ready yet + + if (ast) { + astOutput.value = JSON.stringify(ast, null, 2); + } else { + astOutput.value = 'No AST available due to parsing error.'; + } +} + +// Display tokens in formatted JSON +function showTokens(tokens) { + if (!tokensOutput) return; // DOM not ready yet + + if (tokens) { + tokensOutput.value = JSON.stringify(tokens, null, 2); + } else { + tokensOutput.value = 'No tokens available due to parsing error.'; + } +} + +// Display error message +function showError(message) { + if (!errorOutput) return; // DOM not ready yet + + if (message) { + errorOutput.textContent = message; + errorOutput.style.display = 'block'; + } else { + errorOutput.style.display = 'none'; + } +} + +// Copy text to clipboard +async function copyToClipboard(textarea, label) { + if (!textarea || !textarea.value) { + showError(`No ${label} content to copy.`); + return; + } + + try { + await navigator.clipboard.writeText(textarea.value); + + // Show temporary success message + const originalText = errorOutput.textContent; + showError(`${label} copied to clipboard!`); + + // Clear success message after 2 seconds + setTimeout(() => { + if (errorOutput.textContent === `${label} copied to clipboard!`) { + showError(''); + } + }, 2000); + + } catch (error) { + showError(`Failed to copy ${label}: ${error.message}`); + } +} \ No newline at end of file diff --git a/js/scripting-lang/web/src/view.js b/js/scripting-lang/web/src/view.js index 6d591cf..ab64910 100644 --- a/js/scripting-lang/web/src/view.js +++ b/js/scripting-lang/web/src/view.js @@ -22,7 +22,9 @@ */ export function view(state) { return ` - <h1>Baba Yaga's PokéDex</h1> + <header class="app-header"> + <h1>Baba Yaga's PokéDex</h1> + </header> <container> <form id="search-form" autocomplete="off"> <label for="pokemon-query">Pokémon Name (or number)</label> diff --git a/js/scripting-lang/web/style.css b/js/scripting-lang/web/style.css index fea1820..4cd5c33 100644 --- a/js/scripting-lang/web/style.css +++ b/js/scripting-lang/web/style.css @@ -23,9 +23,43 @@ body { margin: 0; padding: 0; } +.app-header { + max-width: 800px; + margin: 2rem auto 1rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 1.5rem; +} + +.app-header h1 { + margin: 0; + font-size: 1.8rem; +} + +.app-nav { + display: flex; + gap: 1rem; +} + +.nav-link { + color: var(--color-text); + text-decoration: none; + padding: 0.5rem 1rem; + border: 2px solid var(--color-main-border); + border-radius: 6px; + font-weight: 600; + transition: all 0.2s; +} + +.nav-link:hover { + background: var(--color-main-border); + color: var(--color-button-text); +} + main { max-width: 800px; - margin: 3rem auto; + margin: 0 auto 3rem; background: var(--color-main-bg); border: 2px solid var(--color-main-border); border-radius: 8px; |