diff options
Diffstat (limited to 'js/baba-yaga/scratch')
47 files changed, 7298 insertions, 0 deletions
diff --git a/js/baba-yaga/scratch/baba/compatibility-test.baba b/js/baba-yaga/scratch/baba/compatibility-test.baba new file mode 100644 index 0000000..4b13231 --- /dev/null +++ b/js/baba-yaga/scratch/baba/compatibility-test.baba @@ -0,0 +1,30 @@ +// Compatibility test between optimized and legacy engines + +// Test variable names with underscores and numbers +var_with_underscore : 42; +var123 : 123; +test_var_2 : "hello"; + +// Test complex expressions +result1 : var_with_underscore + var123; +result2 : when (result1 > 100) is true then "big" _ then "small"; + +// Test function definitions +testFunc : a b -> a * b + var123; +funcResult : testFunc 5 6; + +// Test nested when expressions +nested : when (funcResult > 150) is + true then when (var123 = 123) is true then "both true" _ then "first true" + _ then "first false"; + +// Output results +io.out "Compatibility Test Results:"; +io.out "var_with_underscore:"; io.out var_with_underscore; +io.out "var123:"; io.out var123; +io.out "test_var_2:"; io.out test_var_2; +io.out "result1:"; io.out result1; +io.out "result2:"; io.out result2; +io.out "funcResult:"; io.out funcResult; +io.out "nested:"; io.out nested; +io.out "Test complete!"; diff --git a/js/baba-yaga/scratch/baba/conway-simple.baba b/js/baba-yaga/scratch/baba/conway-simple.baba new file mode 100644 index 0000000..1054106 --- /dev/null +++ b/js/baba-yaga/scratch/baba/conway-simple.baba @@ -0,0 +1,116 @@ +// Conway's Game of Life - Simple Working Version + +// Get element at index from list (safe) +at : xs i -> + when (i >= 0 and i < length xs) is + true then slice xs i (i + 1).0 + _ then 0; + +// Get 2D element from grid (safe) +get2d : grid row col -> + when (row >= 0 and row < length grid and col >= 0) is + true then + with (rowData : at grid row) -> + when (col < length rowData) is + true then at rowData col + _ then 0 + _ then 0; + +// Count living neighbors around position (row, col) +countNeighbors : grid row col -> + with ( + n1 : get2d grid (row - 1) (col - 1); + n2 : get2d grid (row - 1) col; + n3 : get2d grid (row - 1) (col + 1); + n4 : get2d grid row (col - 1); + n5 : get2d grid row (col + 1); + n6 : get2d grid (row + 1) (col - 1); + n7 : get2d grid (row + 1) col; + n8 : get2d grid (row + 1) (col + 1); + ) -> n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8; + +// Apply Game of Life rules to a single cell +nextCellState : grid row col -> + with ( + current : get2d grid row col; + neighbors : countNeighbors grid row col; + ) -> + when current is + 1 then + when (neighbors = 2 or neighbors = 3) is + true then 1 + _ then 0 + _ then + when (neighbors = 3) is + true then 1 + _ then 0; + +// Generate next generation of the entire grid +nextGeneration : grid -> + with ( + height : length grid; + width : length (at grid 0); + // Create new grid row by row + newRow : rowIdx -> + with ( + newCol : colIdx -> nextCellState grid rowIdx colIdx; + cols : [0, 1, 2, 3, 4]; // Assuming 5x5 for now + ) -> map newCol cols; + rows : [0, 1, 2, 3, 4]; // Assuming 5x5 for now + ) -> map newRow rows; + +// Print a single row +printRow : row -> + with ( + cellToChar : cell -> when cell is 1 then "#" _ then "."; + chars : map cellToChar row; + ) -> io.out chars; + +// Print entire grid with title +printGrid : grid title -> + with ( + _ : io.out ""; + _ : io.out title; + _ : io.out "-----"; + _ : map printRow grid; + ) -> 0; // Return dummy value + +// Test patterns + +// Glider pattern (moves diagonally) +glider : [ + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +// Blinker pattern (oscillates) +blinker : [ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +// Run simulations +io.out "Conway's Game of Life"; +io.out "===================="; + +// Show initial glider +dummy1 : printGrid glider "Glider - Generation 0"; +g1 : nextGeneration glider; +dummy2 : printGrid g1 "Glider - Generation 1"; +g2 : nextGeneration g1; +dummy3 : printGrid g2 "Glider - Generation 2"; + +// Show blinker oscillation +dummy4 : printGrid blinker "Blinker - Generation 0"; +b1 : nextGeneration blinker; +dummy5 : printGrid b1 "Blinker - Generation 1"; +b2 : nextGeneration b1; +dummy6 : printGrid b2 "Blinker - Generation 2"; + +io.out "Done!"; diff --git a/js/baba-yaga/scratch/baba/conway-test.baba b/js/baba-yaga/scratch/baba/conway-test.baba new file mode 100644 index 0000000..ef8be99 --- /dev/null +++ b/js/baba-yaga/scratch/baba/conway-test.baba @@ -0,0 +1,16 @@ +// Simple Game of Life test + +// Safe array access +at : xs i -> + when (i >= 0 and i < length xs) is + true then slice xs i (i + 1).0 + _ then 0; + +// Test pattern +pattern : [[0, 1, 0], [0, 0, 1], [1, 1, 1]]; + +io.out "Testing:"; +io.out pattern; +io.out "Cell at (1,1):"; +io.out (at (at pattern 1) 1); +io.out "Done!"; diff --git a/js/baba-yaga/scratch/baba/conway-working.baba b/js/baba-yaga/scratch/baba/conway-working.baba new file mode 100644 index 0000000..e010e96 --- /dev/null +++ b/js/baba-yaga/scratch/baba/conway-working.baba @@ -0,0 +1,120 @@ +// Conway's Game of Life - Minimal Working Version + +// Safe array access +at : xs i -> + when (i >= 0 and i < length xs) is + true then slice xs i (i + 1).0 + _ then 0; + +// Safe 2D grid access +get2d : grid row col -> + when (row >= 0 and row < length grid) is + true then at (at grid row) col + _ then 0; + +// Count living neighbors +countNeighbors : grid row col -> + (get2d grid (row - 1) (col - 1)) + + (get2d grid (row - 1) col) + + (get2d grid (row - 1) (col + 1)) + + (get2d grid row (col - 1)) + + (get2d grid row (col + 1)) + + (get2d grid (row + 1) (col - 1)) + + (get2d grid (row + 1) col) + + (get2d grid (row + 1) (col + 1)); + +// Apply Game of Life rules +nextCell : grid row col -> + with ( + current : get2d grid row col; + neighbors : countNeighbors grid row col; + ) -> + when current is + 1 then when (neighbors = 2 or neighbors = 3) is true then 1 _ then 0 + _ then when (neighbors = 3) is true then 1 _ then 0; + +// Generate next generation (hardcoded for 5x5) +nextGeneration : grid -> [ + [ + nextCell grid 0 0, + nextCell grid 0 1, + nextCell grid 0 2, + nextCell grid 0 3, + nextCell grid 0 4 + ], + [ + nextCell grid 1 0, + nextCell grid 1 1, + nextCell grid 1 2, + nextCell grid 1 3, + nextCell grid 1 4 + ], + [ + nextCell grid 2 0, + nextCell grid 2 1, + nextCell grid 2 2, + nextCell grid 2 3, + nextCell grid 2 4 + ], + [ + nextCell grid 3 0, + nextCell grid 3 1, + nextCell grid 3 2, + nextCell grid 3 3, + nextCell grid 3 4 + ], + [ + nextCell grid 4 0, + nextCell grid 4 1, + nextCell grid 4 2, + nextCell grid 4 3, + nextCell grid 4 4 + ] +]; + +// Test patterns +glider : [ + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +blinker : [ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +// Run simulation +io.out "Conway's Game of Life"; +io.out "===================="; + +io.out "Glider - Generation 0:"; +io.out glider; + +g1 : nextGeneration glider; +io.out "Glider - Generation 1:"; +io.out g1; + +g2 : nextGeneration g1; +io.out "Glider - Generation 2:"; +io.out g2; + +io.out ""; +io.out "Blinker - Generation 0:"; +io.out blinker; + +b1 : nextGeneration blinker; +io.out "Blinker - Generation 1:"; +io.out b1; + +b2 : nextGeneration b1; +io.out "Blinker - Generation 2:"; +io.out b2; + +io.out ""; +io.out "Simulation complete!"; diff --git a/js/baba-yaga/scratch/baba/conway.baba b/js/baba-yaga/scratch/baba/conway.baba new file mode 100644 index 0000000..a8811a7 --- /dev/null +++ b/js/baba-yaga/scratch/baba/conway.baba @@ -0,0 +1,126 @@ +// Conway's Game of Life in Baba Yaga +// Clean, working implementation + +// Get element at index from list (with bounds checking) +at : xs i -> + when (i >= 0 and i < length xs) is + true then slice xs i (i + 1).0 + _ then 0; + +// Get 2D element from grid +get2d : grid row col -> + when (row >= 0 and row < length grid) is + true then at (at grid row) col + _ then 0; + +// Create range of integers [start, end) +range : start end -> + when (start >= end) is + true then [] + _ then prepend start (range (start + 1) end); + +// Sum a list of numbers +sum : xs -> reduce (acc x -> acc + x) 0 xs; + +// Count living neighbors around position (row, col) +countNeighbors : grid row col -> + with ( + // Get all 8 neighbor positions + neighbors : [ + get2d grid (row - 1) (col - 1), + get2d grid (row - 1) col, + get2d grid (row - 1) (col + 1), + get2d grid row (col - 1), + get2d grid row (col + 1), + get2d grid (row + 1) (col - 1), + get2d grid (row + 1) col, + get2d grid (row + 1) (col + 1) + ]; + ) -> sum neighbors; + +// Apply Game of Life rules to a single cell +nextCellState : grid row col -> + with ( + current : get2d grid row col; + neighbors : countNeighbors grid row col; + isAlive : current = 1; + ) -> + when isAlive is + true then + when (neighbors = 2 or neighbors = 3) is + true then 1 + _ then 0 + _ then + when (neighbors = 3) is + true then 1 + _ then 0; + +// Generate next row for the grid +nextRow : grid rowIndex width -> + map (col -> nextCellState grid rowIndex col) (range 0 width); + +// Generate next generation of the entire grid +nextGeneration : grid -> + with ( + height : length grid; + width : length (at grid 0); + ) -> + map (row -> nextRow grid row width) (range 0 height); + +// Pretty print a grid +printGrid : grid title -> + with ( + printRow : row -> io.out (map (cell -> when cell is 1 then "#" _ then ".") row); + ) -> with ( + _ : io.out ""; + _ : io.out title; + _ : io.out (map (_ -> "-") (range 0 20)); + _ : map printRow grid; + _ : io.out ""; + ) -> grid; + +// Test patterns + +// Glider pattern (moves diagonally) +glider : [ + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +// Blinker pattern (oscillates) +blinker : [ + [0, 0, 0], + [1, 1, 1], + [0, 0, 0] +]; + +// Block pattern (still life) +block : [ + [1, 1], + [1, 1] +]; + +// Run simulations +io.out "Conway's Game of Life in Baba Yaga"; +io.out "===================================="; + +// Glider evolution +g0 : printGrid glider "Glider - Generation 0"; +g1 : printGrid (nextGeneration g0) "Glider - Generation 1"; +g2 : printGrid (nextGeneration g1) "Glider - Generation 2"; +g3 : printGrid (nextGeneration g2) "Glider - Generation 3"; +g4 : printGrid (nextGeneration g3) "Glider - Generation 4"; + +// Blinker oscillation +b0 : printGrid blinker "Blinker - Generation 0"; +b1 : printGrid (nextGeneration b0) "Blinker - Generation 1"; +b2 : printGrid (nextGeneration b1) "Blinker - Generation 2"; + +// Block (should stay the same) +bl0 : printGrid block "Block - Generation 0"; +bl1 : printGrid (nextGeneration bl0) "Block - Generation 1"; + +io.out "Simulation complete!"; diff --git a/js/baba-yaga/scratch/baba/crash-course-code.baba b/js/baba-yaga/scratch/baba/crash-course-code.baba new file mode 100644 index 0000000..bae2810 --- /dev/null +++ b/js/baba-yaga/scratch/baba/crash-course-code.baba @@ -0,0 +1,240 @@ +// Baba Yaga in Y Minutes + +// Test 1: Basic with examples +testBasicWith : x y -> + with (inc : x + 1; prod : inc * y;) -> inc + prod; + +testTypedWith : x y -> + with (nx Int; ny Int; nx : x + 1; ny : y + 1;) -> nx + ny; + +// Test 2: with rec examples +testEvenOdd : z -> + with rec ( + isEven : n -> when n is 0 then true _ then isOdd (n - 1); + isOdd : n -> when n is 0 then false _ then isEven (n - 1); + ) -> {even: isEven z, odd: isOdd z}; + +// Test 3: Computed intermediate values +testQuadraticRoots : a b c -> + with ( + discriminant : b * b - 4 * a * c; + sqrtDisc : math.sqrt discriminant; + denominator : 2 * a; + ) -> + { + r1: (-b + sqrtDisc) / denominator, + r2: (-b - sqrtDisc) / denominator + }; + +// Test 4: Complex calculations with named steps +testCalculateTax : income deductions -> + with ( + taxableIncome : income - deductions; + taxRate : when (taxableIncome <= 50000) is + true then 0.15 + _ then when (taxableIncome <= 100000) is + true then 0.25 + _ then 0.35; + baseTax : taxableIncome * taxRate; + finalTax : when (baseTax < 1000) is true then 1000 _ then baseTax; + ) -> + finalTax; + +// Test 5: Data transformation pipelines +testProcessUserData : user -> + with ( + normalizedName : str.upper (str.trim user.name); + ageGroup : when (user.age < 18) is + true then "minor" + _ then when (user.age < 65) is + true then "adult" + _ then "senior"; + status : when user.active is + true then "active" + _ then "inactive"; + ) -> + { + id: user.id, + displayName: normalizedName, + category: ageGroup, + status: status + }; + +// Test 6: Error handling with multiple validations +testValidateOrder : order -> + with ( + hasItems : (length order.items) > 0; + hasValidTotal : order.total > 0; + allValid : hasItems and hasValidTotal; + ) -> + when allValid is + true then Ok order + _ then Err "Order validation failed"; + +// Test 7: Complex pattern matching with computed values +testClassifyTriangle : a b c -> + with ( + sorted : [math.min a b, math.max a b, math.max (math.max a b) c]; + side1 : sorted.0; + side2 : sorted.1; + side3 : sorted.2; + isValid : ((side1 + side2) > side3); + isEquilateral : ((a = b) and (b = c)); + isIsosceles : ((a = b) or (b = c) or (a = c)); + isRight : (math.abs ((side1 * side1 + side2 * side2) - (side3 * side3))) < 0.001; + ) -> + when isValid is + false then "Invalid triangle" + _ then when isEquilateral is + true then "Equilateral" + _ then when isIsosceles is + true then when isRight is + true then "Right isosceles" + _ then "Isosceles" + _ then when isRight is + true then "Right scalene" + _ then "Scalene"; + +// Test 8: Tree operations with with rec +testTreeOperations : tree -> + with rec ( + // Count total nodes + countNodes : t -> + when ((length (keys t)) = 0) is + true then 0 + _ then 1 + (countNodes t.left) + (countNodes t.right); + + // Calculate tree height + treeHeight : t -> + when ((length (keys t)) = 0) is + true then 0 + _ then 1 + (math.max (treeHeight t.left) (treeHeight t.right)); + + // Check if tree is balanced + isBalanced : t -> + when ((length (keys t)) = 0) is + true then true + _ then + (((math.abs ((treeHeight t.left) - (treeHeight t.right))) <= 1) and + (isBalanced t.left) and + (isBalanced t.right)); + ) -> + { + nodeCount: countNodes tree, + height: treeHeight tree, + balanced: isBalanced tree + }; + +// Test 9: State machine with recursive state transitions +testTrafficLight : initialState -> + with rec ( + // State transition function + nextState : current -> + when current is + "red" then "green" + "green" then "yellow" + _ then "red"; + + // Count transitions until back to start + countCycles : start current count -> + when (current = start) is + true then count + _ then countCycles start (nextState current) (count + 1); + + // Get state after N transitions + stateAfter : current n -> + when n is + 0 then current + _ then stateAfter (nextState current) (n - 1); + ) -> + { + cycles: countCycles initialState initialState 0, + after10: stateAfter initialState 10, + next: nextState initialState + }; + +// Test 10: Combinatorial functions with shared helpers +testCombinatorics : n r -> + with rec ( + // Factorial function + factorial : k -> + when k is + 0 then 1 + 1 then 1 + _ then k * (factorial (k - 1)); + + // Permutation: P(n,r) = n! / (n-r)! + permutation : n r -> + (factorial n) / (factorial (n - r)); + + // Combination: C(n,r) = n! / (r! * (n-r)!) + combination : n r -> + (factorial n) / ((factorial r) * (factorial (n - r))); + ) -> + { + n: n, + r: r, + permutations: permutation n r, + combinations: combination n r + }; + +// Test 11: Best practices examples +testProcessData : data -> + with ( + cleaned : str.trim data; + normalized : str.lower cleaned; + validated : (length normalized) > 0; + ) -> + when validated is + true then Ok normalized + _ then Err "Empty data"; + +// Test 12: Validation examples +testValidateUserBasic : user -> + with ( + nameValid : (length user.name) > 0; + emailValid : (str.length user.email) > 0; // Simplified validation + ageValid : (user.age >= 0) and (user.age <= 150); + ) -> + (nameValid and emailValid and ageValid); + +testValidateUserContact : user -> + with ( + phoneValid : (length user.phone) >= 10; + ) -> + phoneValid; + +testProcessUser : user -> + when ((testValidateUserBasic user) and (testValidateUserContact user)) is + true then Ok user + _ then Err "Validation failed"; + +// Execute tests +result1 : testBasicWith 2 5; +result2 : testTypedWith 3 4; +result3 : testEvenOdd 10; +result4 : testQuadraticRoots 1 -5 6; +result5 : testCalculateTax 75000 10000; +result6 : testProcessUserData { id: 1, name: " john doe ", age: 25, active: true }; +result7 : testValidateOrder { items: [1, 2, 3], total: 100 }; +result8 : testClassifyTriangle 3 4 5; +result9 : testTreeOperations { left: { left: {}, right: {} }, right: {} }; +result10 : testTrafficLight "red"; +result11 : testCombinatorics 5 2; +result12 : testProcessData " Hello World "; +result13 : testProcessUser { name: "John", email: "john@example.com", age: 30, phone: "1234567890" }; + +// Output results +io.out result1; +io.out result2; +io.out result3; +io.out result4; +io.out result5; +io.out result6; +io.out result7; +io.out result8; +io.out result9; +io.out result10; +io.out result11; +io.out result12; +io.out result13; diff --git a/js/baba-yaga/scratch/baba/example.baba b/js/baba-yaga/scratch/baba/example.baba new file mode 100644 index 0000000..2311f86 --- /dev/null +++ b/js/baba-yaga/scratch/baba/example.baba @@ -0,0 +1,269 @@ +// This file demonstrates all features of Baba Yaga. + +// 1. Comments +// Single-line comments start with `//`. +myVar : 10; // Comments can also be at the end of a line. + +// 2. Types +// The language supports Int, Float, String, and Result types. +myInt : 10; // Inferred as Int +myFloat : 3.14; // Inferred as Float +myString : "Baba Yaga"; // Inferred as String + +// Type declarations are optional but enforced if provided. +myExplicitInt Int; +myExplicitInt : 20; + +myExplicitString String; +myExplicitString : "Explicitly typed string."; + +// Explicit List and Table type declarations +myExplicitList List; +myExplicitList : [1 2 3 4 5]; + +myExplicitTable Table; +myExplicitTable : { name: "John" age: 25 city: "New York" }; + +// 3. Variable and Type Declarations +// Variables are declared using an identifier followed by a colon and their value. +// Example: myVariable : value; + +// 4. Functions (Anonymous, Currying, Partial Application) + +// A simple anonymous function (x -> x + 1) +addOne : x -> x + 1; +resultAddOne : addOne 5; // resultAddOne will be 6 +io.out resultAddOne; + +// A curried function with type annotations +multiply : (x: Float) -> (Float -> Float) -> y -> x * y; +// The type signature here breaks down like: +// 1. (x: Float) - First parameter is a Float called x +// 2. -> - Returns... +// 3. (Float -> Float) - A function that takes a Float and returns a Float +// 4. -> y -> x * y - The implementation: take y, multiply x by y + +// Partial application: create a new function by applying some arguments +multiplyByTwo : multiply 2.0; +resultMultiply : multiplyByTwo 7.0; // resultMultiply will be 14.0 +io.out resultMultiply; + +// 5. Operators +// Arithmetic: +, -, *, /, % +// Comparison: =, >, <, >=, <= + +// Arithmetic operations +sum : 10 + 5; // 15 +difference : 10 - 5; // 5 +product : 10 * 5; // 50 +quotient : 10 / 5; // 2 +remainder : 10 % 3; // 1 + +io.out sum; +io.out difference; +io.out product; +io.out quotient; +io.out remainder; + +// Comparison operations +isEqual : 10 = 10; // true +isGreaterThan : 10 > 5; // true +isLessThan : 5 < 10; // true +isGreaterThanOrEqualTo : 10 >= 10; // true +isLessThanOrEqualTo : 5 <= 10; // true + +io.out isEqual; +io.out isGreaterThan; +io.out isLessThan; +io.out isGreaterThanOrEqualTo; +io.out isLessThanOrEqualTo; + +// 6. Control Flow: The `when` Expression + +// Literal Matching +checkNumber : num -> + when num is + 1 then "One" + 2 then "Two" + _ then "Something else"; // '_' matches any value + +resultCheckNumberOne : checkNumber 1; // "One" +resultCheckNumberThree : checkNumber 3; // "Something else" + +io.out resultCheckNumberOne; +io.out resultCheckNumberThree; + +// Multiple Discriminants +checkCoords : x y -> + when x y is + 0 0 then "Origin" + 1 1 then "Diagonal" + _ _ then "Somewhere else"; + +resultCheckCoordsOrigin : checkCoords 0 0; // "Origin" +resultCheckCoordsOther : checkCoords 5 10; // "Somewhere else" + +io.out resultCheckCoordsOrigin; +io.out resultCheckCoordsOther; + +// Type Matching +checkType : val -> + when val is + Bool then "It's a Boolean" + Int then "It's an Integer" + Float then "It's a Float" + String then "It's a String" + List then "It's a List" + Table then "It's a Table" + _ then "Unknown Type"; + +resultCheckTypeBool : checkType true; // "It's a Boolean" +resultCheckTypeInt : checkType 123; // "It's an Integer" +resultCheckTypeFloat : checkType 3.14; // "It's a Float" +resultCheckTypeString : checkType "abc"; // "It's a String" +resultCheckTypeList : checkType [1 2 3]; // "It's a List" +resultCheckTypeTable : checkType { name: "test" }; // "It's a Table" + +io.out resultCheckTypeBool; +io.out resultCheckTypeInt; +io.out resultCheckTypeFloat; +io.out resultCheckTypeString; +io.out resultCheckTypeList; +io.out resultCheckTypeTable; + +// List Pattern Matching +matchList : list -> + when list is + [1 2 3] then "Exact List Match" + [1 _ 3] then "List with Wildcard Match" + _ then "No List Match"; + +resultMatchListExact : matchList [1 2 3]; // "Exact List Match" +resultMatchListWildcard : matchList [1 99 3]; // "List with Wildcard Match" +resultMatchListNoMatch : matchList [4 5 6]; // "No List Match" + +io.out resultMatchListExact; +io.out resultMatchListWildcard; +io.out resultMatchListNoMatch; + +// Table Pattern Matching +matchTable : table -> + when table is + { name: "Alice" age: 30 } then "Exact Table Match" + { name: "Bob" age: _ } then "Table with Wildcard Value Match" + _ then "No Table Match"; + +resultMatchTableExact : matchTable { name: "Alice" age: 30 }; // "Exact Table Match" +resultMatchTableWildcardValue : matchTable { name: "Bob" age: 99 }; // "Table with Wildcard Value Match" +resultMatchTableNoMatch : matchTable { city: "New York" }; // "No Table Match" + +io.out resultMatchTableExact; +io.out resultMatchTableWildcardValue; +io.out resultMatchTableNoMatch; + +// 7. Error Handling: The `Result` Type + +// Function returning a Result type +divide : x y -> + when y is + 0 then Err "Division by zero is not allowed." + _ then Ok (x / y); + +resultDivideOk : divide 10 2; // Result: Ok 5 +resultDivideErr : divide 5 0; // Result: Err "Division by zero is not allowed." + +// Extracting values from Result types using 'when' +finalResultOk : when resultDivideOk is + Ok val then val // 'val' binds to the inner value of Ok (5) + Err msg then 0; // If error, return 0 + +finalResultErr : when resultDivideErr is + Ok val then 0 + Err msg then msg; // 'msg' binds to the inner value of Err ("Division by zero...") + +io.out finalResultOk; +io.out finalResultErr; + +// 8. Lists +myListExample : [10 20 30 "hello"]; + +// Accessing elements by index (0-based) +firstElement : myListExample.0; // 10 +secondElement : myListExample.1; // 20 + +io.out myListExample; +io.out firstElement; +io.out secondElement; + +// 9. Tables +myTableExample : { name: "Baba Yaga" version: 1.0 isActive: true }; + +// Accessing properties by key +userName : myTableExample.name; // "Baba Yaga" +userVersion : myTableExample.version; // 1.0 + +io.out myTableExample; +io.out userName; +io.out userVersion; + +// Function within a Table +myCalculator : { + add: x y -> x + y; + subtract: x y -> x - y; +}; + +resultTableAdd : myCalculator.add 10 5; // 15 +resultTableSubtract : myCalculator.subtract 10 5; // 5 + +io.out resultTableAdd; +io.out resultTableSubtract; + +// 10. Higher-Order Functions + +// map: Applies a function to each element of a list, returning a new list. +doubledList : map (x -> x * 2) [1 2 3]; // [2 4 6] +io.out doubledList; + +// filter: Creates a new list containing only elements for which a predicate returns true. +evenNumbers : filter (x -> x % 2 = 0) [1 2 3 4 5]; // [2 4] +io.out evenNumbers; + +// reduce: Applies a function against an accumulator and each element in the list to reduce it to a single value. +sumOfList : reduce (acc item -> acc + item) 0 [1 2 3 4]; // 10 +io.out sumOfList; + +// 11. Typed Functions with Type Enforcement + +// Typed function declarations with parameter and return type annotations +add : (x: Int, y: Int) -> Int -> x + y; +multiply : (x: Number, y: Number) -> Number -> x * y; +greet : (name: String) -> String -> str.concat "Hello " name; +fullName : (first: String, last: String) -> String -> first .. " " .. last; +isEven : (n: Int) -> Bool -> n % 2 = 0; +isPositive : (n: Int) -> Bool -> n > 0; + +// Test typed functions +io.out add 5 3; +io.out multiply 2.5 3.0; +io.out greet "World"; +io.out fullName "John" "Doe"; +io.out isEven 4; +io.out isEven 5; +io.out isPositive 10; +io.out isPositive -5; + +// 12. String Functions + +// Core string operations +io.out str.length "hello"; +io.out str.upper "hello world"; +io.out str.lower "HELLO WORLD"; +io.out str.split "hello,world,test" ","; +io.out str.join ["a" "b" "c"] "-"; +io.out str.trim " hello "; +io.out str.substring "hello world" 0 5; +io.out str.replace "hello hello" "hello" "hi"; + +// String concatenation with .. operator +message : "Hello" .. " " .. "World" .. "!"; +io.out message; diff --git a/js/baba-yaga/scratch/baba/functional-features-demo.baba b/js/baba-yaga/scratch/baba/functional-features-demo.baba new file mode 100644 index 0000000..c9cb12b --- /dev/null +++ b/js/baba-yaga/scratch/baba/functional-features-demo.baba @@ -0,0 +1,128 @@ +// Functional Programming Features Demo +// Testing all the new features we've implemented + +io.out "=== Testing New Functional Programming Features ==="; +io.out ""; + +// === Pattern Guards === +io.out "1. Pattern Guards:"; + +classifyNumber : n -> + when n is + 0 then "zero" + x if (x > 0) then "positive" + x if (x < 0) then "negative" + _ then "unknown"; + +result1 : classifyNumber 5; +result2 : classifyNumber -3; +result3 : classifyNumber 0; + +io.out ("5 is " .. result1); +io.out ("-3 is " .. result2); +io.out ("0 is " .. result3); +io.out ""; + +// === Scan Operations === +io.out "2. Scan Operations:"; + +numbers : [1, 2, 3, 4, 5]; +cumulative : cumsum numbers; +product : cumprod numbers; + +addFunc : acc x -> acc + x; +customScan : scan addFunc 10 numbers; + +io.out ("Numbers: " .. numbers); +io.out ("Cumulative sum: " .. cumulative); +io.out ("Cumulative product: " .. product); +io.out ("Custom scan from 10: " .. customScan); +io.out ""; + +// === Array Indexing === +io.out "3. Array Indexing:"; + +data : [10, 20, 30, 40, 50]; +indices : [0, 2, 4]; +selected : at indices data; + +evenPredicate : x -> x % 2 = 0; +evenIndices : where evenPredicate data; + +firstThree : take 3 data; +lastTwo : drop 3 data; + +io.out ("Data: " .. data); +io.out ("Selected at [0,2,4]: " .. selected); +io.out ("Even indices: " .. evenIndices); +io.out ("First 3: " .. firstThree); +io.out ("Last 2: " .. lastTwo); +io.out ""; + +// === Broadcasting === +io.out "4. Broadcasting Operations:"; + +values : [1, 2, 3, 4]; +addTen : broadcast (a b -> a + b) 10 values; + +array1 : [1, 2, 3]; +array2 : [4, 5, 6]; +multiplied : zipWith (a b -> a * b) array1 array2; + +flatData : [1, 2, 3, 4, 5, 6]; +matrix : reshape [2, 3] flatData; + +io.out ("Values: " .. values); +io.out ("Add 10 to each: " .. addTen); +io.out ("Array 1: " .. array1); +io.out ("Array 2: " .. array2); +io.out ("Element-wise multiply: " .. multiplied); +io.out "Reshaped matrix:"; +io.print matrix; +io.out ""; + +// === Function Combinators === +io.out "5. Function Combinators:"; + +addOp : x y -> x + y; +flippedAdd : flip addOp; +flipResult : flippedAdd 3 7; // 7 + 3 + +doubler : x -> x * 2; +applyResult : apply doubler 5; + +tripler : x -> x * 3; +pipeResult : pipe 4 tripler; + +increment : x -> x + 1; +composed : compose doubler increment; +composeResult : composed 5; // (5 + 1) * 2 + +io.out ("Flip add 3 7: " .. flipResult); +io.out ("Apply double to 5: " .. applyResult); +io.out ("Pipe 4 through triple: " .. pipeResult); +io.out ("Compose increment then double on 5: " .. composeResult); +io.out ""; + +// === FlatMap === +io.out "6. FlatMap Operations:"; + +duplicator : x -> [x, x]; +original : [1, 2, 3]; +duplicated : flatMap duplicator original; + +io.out ("Original: " .. original); +io.out ("Duplicated: " .. duplicated); +io.out ""; + +// === Summary === +io.out "=== Summary ==="; +io.out "All functional programming features working:"; +io.out "โ Pattern Guards with conditional expressions"; +io.out "โ Scan operations (scan, cumsum, cumprod)"; +io.out "โ Array indexing (at, where, take, drop)"; +io.out "โ Broadcasting (broadcast, zipWith, reshape)"; +io.out "โ Function combinators (flip, apply, pipe, compose)"; +io.out "โ Monadic operations (flatMap)"; +io.out ""; +io.out "Baba Yaga now has powerful functional programming capabilities!"; diff --git a/js/baba-yaga/scratch/baba/game-of-life.baba b/js/baba-yaga/scratch/baba/game-of-life.baba new file mode 100644 index 0000000..2721b3e --- /dev/null +++ b/js/baba-yaga/scratch/baba/game-of-life.baba @@ -0,0 +1,76 @@ +// Conway's Game of Life - Working Implementation + +// Count neighbors for a 3x3 grid (hardcoded positions) +countNeighbors : grid row col -> + when row is + 0 then when col is + 0 then grid.0.1 + grid.1.0 + grid.1.1 + 1 then grid.0.0 + grid.0.2 + grid.1.0 + grid.1.1 + grid.1.2 + 2 then grid.0.1 + grid.1.1 + grid.1.2 + _ then 0 + 1 then when col is + 0 then grid.0.0 + grid.0.1 + grid.1.1 + grid.2.0 + grid.2.1 + 1 then grid.0.0 + grid.0.1 + grid.0.2 + grid.1.0 + grid.1.2 + grid.2.0 + grid.2.1 + grid.2.2 + 2 then grid.0.1 + grid.0.2 + grid.1.1 + grid.2.1 + grid.2.2 + _ then 0 + 2 then when col is + 0 then grid.1.0 + grid.1.1 + grid.2.1 + 1 then grid.1.0 + grid.1.1 + grid.1.2 + grid.2.0 + grid.2.2 + 2 then grid.1.1 + grid.1.2 + grid.2.1 + _ then 0 + _ then 0; + +// Apply Game of Life rules +nextCell : grid row col -> + with ( + current : when row is + 0 then when col is 0 then grid.0.0 1 then grid.0.1 2 then grid.0.2 _ then 0 + 1 then when col is 0 then grid.1.0 1 then grid.1.1 2 then grid.1.2 _ then 0 + 2 then when col is 0 then grid.2.0 1 then grid.2.1 2 then grid.2.2 _ then 0 + _ then 0; + neighbors : countNeighbors grid row col; + ) -> + when current is + 1 then when (neighbors = 2 or neighbors = 3) is true then 1 _ then 0 + _ then when (neighbors = 3) is true then 1 _ then 0; + +// Generate next generation for 3x3 grid +step : grid -> { + 0: { + 0: nextCell grid 0 0, + 1: nextCell grid 0 1, + 2: nextCell grid 0 2 + }, + 1: { + 0: nextCell grid 1 0, + 1: nextCell grid 1 1, + 2: nextCell grid 1 2 + }, + 2: { + 0: nextCell grid 2 0, + 1: nextCell grid 2 1, + 2: nextCell grid 2 2 + } +}; + +// Blinker pattern (oscillator) +blinker : { + 0: { 0: 0, 1: 1, 2: 0 }, + 1: { 0: 0, 1: 1, 2: 0 }, + 2: { 0: 0, 1: 1, 2: 0 } +}; + +// Run simulation +io.out "Conway's Game of Life - Blinker Pattern"; +io.out "Generation 0:"; +io.out blinker; + +gen1 : step blinker; +io.out "Generation 1:"; +io.out gen1; + +gen2 : step gen1; +io.out "Generation 2:"; +io.out gen2; + +io.out "Complete!"; diff --git a/js/baba-yaga/scratch/baba/indentation_test.baba b/js/baba-yaga/scratch/baba/indentation_test.baba new file mode 100644 index 0000000..3e0a659 --- /dev/null +++ b/js/baba-yaga/scratch/baba/indentation_test.baba @@ -0,0 +1,20 @@ +// Test proper indentation +simpleFunc : x -> x + 1; + +complexFunc : x -> + when x is + 0 then "zero" + 1 then "one" + _ then "other"; + +withFunc : a b -> + with ( + sum : a + b; + diff : a - b; + ) -> + {sum: sum, diff: diff}; + +varWithWhen : + when true is + true then "yes" + false then "no"; diff --git a/js/baba-yaga/scratch/baba/life-demo-alt.baba b/js/baba-yaga/scratch/baba/life-demo-alt.baba new file mode 100644 index 0000000..b4c35ce --- /dev/null +++ b/js/baba-yaga/scratch/baba/life-demo-alt.baba @@ -0,0 +1,91 @@ +// Conway's Game of Life Demo + +// Simple blinker pattern demonstration +// Initial state: vertical line of 3 cells +// Next state: horizontal line of 3 cells + +// Generation 0 - vertical blinker +cell_0_1 : 1; // top +cell_1_1 : 1; // middle +cell_2_1 : 1; // bottom + +// All other cells are 0 +cell_0_0 : 0; +cell_0_2 : 0; +cell_1_0 : 0; +cell_1_2 : 0; +cell_2_0 : 0; +cell_2_2 : 0; + +io.out "Conway's Game of Life - Blinker Demo"; +io.out "===================================="; + +io.out "Generation 0 (vertical line):"; +io.out "Row 0:"; +io.out cell_0_0; +io.out cell_0_1; +io.out cell_0_2; +io.out "Row 1:"; +io.out cell_1_0; +io.out cell_1_1; +io.out cell_1_2; +io.out "Row 2:"; +io.out cell_2_0; +io.out cell_2_1; +io.out cell_2_2; + +// Calculate Generation 1 +// For the middle cell (1,1): has 2 vertical neighbors, survives +// For cells (1,0) and (1,2): each has 3 neighbors, become alive +// All other cells die or stay dead + +// Middle cell (1,1) - count neighbors +neighbors_1_1 : cell_0_1 + cell_2_1; // 2 neighbors +next_1_1 : when (cell_1_1 = 1 and (neighbors_1_1 = 2 or neighbors_1_1 = 3)) is + true then 1 _ then 0; + +// Left cell (1,0) - count neighbors +neighbors_1_0 : cell_0_0 + cell_0_1 + cell_1_1 + cell_2_0 + cell_2_1; // 3 neighbors +next_1_0 : when (cell_1_0 = 0 and neighbors_1_0 = 3) is + true then 1 _ then 0; + +// Right cell (1,2) - count neighbors +neighbors_1_2 : cell_0_1 + cell_0_2 + cell_1_1 + cell_2_1 + cell_2_2; // 3 neighbors +next_1_2 : when (cell_1_2 = 0 and neighbors_1_2 = 3) is + true then 1 _ then 0; + +// All other cells in generation 1 will be 0 +next_0_0 : 0; +next_0_1 : 0; +next_0_2 : 0; +next_2_0 : 0; +next_2_1 : 0; +next_2_2 : 0; + +io.out ""; +io.out "Generation 1 (horizontal line):"; +io.out "Row 0:"; +io.out next_0_0; +io.out next_0_1; +io.out next_0_2; +io.out "Row 1:"; +io.out next_1_0; +io.out next_1_1; +io.out next_1_2; +io.out "Row 2:"; +io.out next_2_0; +io.out next_2_1; +io.out next_2_2; + +io.out ""; +io.out "Neighbor counts:"; +io.out "Cell (1,1) neighbors:"; +io.out neighbors_1_1; +io.out "Cell (1,0) neighbors:"; +io.out neighbors_1_0; +io.out "Cell (1,2) neighbors:"; +io.out neighbors_1_2; + +io.out ""; +io.out "The blinker oscillates between vertical and horizontal!"; +io.out "This demonstrates Conway's Game of Life rules."; diff --git a/js/baba-yaga/scratch/baba/life-demo.baba b/js/baba-yaga/scratch/baba/life-demo.baba new file mode 100644 index 0000000..b4c35ce --- /dev/null +++ b/js/baba-yaga/scratch/baba/life-demo.baba @@ -0,0 +1,91 @@ +// Conway's Game of Life Demo + +// Simple blinker pattern demonstration +// Initial state: vertical line of 3 cells +// Next state: horizontal line of 3 cells + +// Generation 0 - vertical blinker +cell_0_1 : 1; // top +cell_1_1 : 1; // middle +cell_2_1 : 1; // bottom + +// All other cells are 0 +cell_0_0 : 0; +cell_0_2 : 0; +cell_1_0 : 0; +cell_1_2 : 0; +cell_2_0 : 0; +cell_2_2 : 0; + +io.out "Conway's Game of Life - Blinker Demo"; +io.out "===================================="; + +io.out "Generation 0 (vertical line):"; +io.out "Row 0:"; +io.out cell_0_0; +io.out cell_0_1; +io.out cell_0_2; +io.out "Row 1:"; +io.out cell_1_0; +io.out cell_1_1; +io.out cell_1_2; +io.out "Row 2:"; +io.out cell_2_0; +io.out cell_2_1; +io.out cell_2_2; + +// Calculate Generation 1 +// For the middle cell (1,1): has 2 vertical neighbors, survives +// For cells (1,0) and (1,2): each has 3 neighbors, become alive +// All other cells die or stay dead + +// Middle cell (1,1) - count neighbors +neighbors_1_1 : cell_0_1 + cell_2_1; // 2 neighbors +next_1_1 : when (cell_1_1 = 1 and (neighbors_1_1 = 2 or neighbors_1_1 = 3)) is + true then 1 _ then 0; + +// Left cell (1,0) - count neighbors +neighbors_1_0 : cell_0_0 + cell_0_1 + cell_1_1 + cell_2_0 + cell_2_1; // 3 neighbors +next_1_0 : when (cell_1_0 = 0 and neighbors_1_0 = 3) is + true then 1 _ then 0; + +// Right cell (1,2) - count neighbors +neighbors_1_2 : cell_0_1 + cell_0_2 + cell_1_1 + cell_2_1 + cell_2_2; // 3 neighbors +next_1_2 : when (cell_1_2 = 0 and neighbors_1_2 = 3) is + true then 1 _ then 0; + +// All other cells in generation 1 will be 0 +next_0_0 : 0; +next_0_1 : 0; +next_0_2 : 0; +next_2_0 : 0; +next_2_1 : 0; +next_2_2 : 0; + +io.out ""; +io.out "Generation 1 (horizontal line):"; +io.out "Row 0:"; +io.out next_0_0; +io.out next_0_1; +io.out next_0_2; +io.out "Row 1:"; +io.out next_1_0; +io.out next_1_1; +io.out next_1_2; +io.out "Row 2:"; +io.out next_2_0; +io.out next_2_1; +io.out next_2_2; + +io.out ""; +io.out "Neighbor counts:"; +io.out "Cell (1,1) neighbors:"; +io.out neighbors_1_1; +io.out "Cell (1,0) neighbors:"; +io.out neighbors_1_0; +io.out "Cell (1,2) neighbors:"; +io.out neighbors_1_2; + +io.out ""; +io.out "The blinker oscillates between vertical and horizontal!"; +io.out "This demonstrates Conway's Game of Life rules."; diff --git a/js/baba-yaga/scratch/baba/life-example.baba b/js/baba-yaga/scratch/baba/life-example.baba new file mode 100644 index 0000000..7ae7164 --- /dev/null +++ b/js/baba-yaga/scratch/baba/life-example.baba @@ -0,0 +1,181 @@ +headAt : xs i -> + with ( + tmp : slice xs i (i + 1); + ) -> + tmp.0; + +get2 : grid r c -> + headAt (headAt grid r) c; + +safeGet2 : grid r c -> + with ( + rows : length grid; + cols : length (headAt grid 0); + rOk : r >= 0 and r < rows; + cOk : c >= 0 and c < cols; + ) -> + when rOk and cOk is + true then get2 grid r c + _ then 0; + +range : lo hi -> + when lo >= hi is + true then [] + _ then prepend lo (range (lo + 1) hi); + +sum : xs -> + reduce (acc x -> acc + x) 0 xs; + +deltas : [-1, 0, 1]; + +neighborsValues : grid r c -> + reduce (acc dr -> reduce (acc2 dc -> when dr = 0 and dc = 0 is + true then acc2 + _ then append acc2 (safeGet2 grid (r + dr) (c + dc))) acc deltas) [] deltas; + +countNeighbors : grid r c -> + sum (neighborsValues grid r c); + +nextCell : grid r c -> + with ( + cell : get2 grid r c; + n : countNeighbors grid r c; + isAlive : cell = 1; + born : cell = 0 and n = 3; + survive : isAlive and n = 2 or n = 3; + ) -> + when survive is + true then 1 + _ then + when born is + true then 1 + _ then 0; + +rowNext : grid w r -> + map (c -> nextCell grid r c) (range 0 w); + +step : grid -> + with ( + h : length grid; + w : length (headAt grid 0); + ) -> + map (r -> rowNext grid w r) (range 0 h); + +g0 : [ + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; +g1 : step g0; +g2 : step g1; +g3 : step g2; +g4 : step g3; +blinker0 : [[1, 1, 1], [0, 0, 0], [0, 0, 0]]; +blinker1 : step blinker0; +blinker2 : step blinker1; +toad0 : [ + [0, 1, 1, 1], + [1, 1, 1, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] +]; +toad1 : step toad0; +toad2 : step toad1; +beacon0 : [ + [1, 1, 0, 0], + [1, 1, 0, 0], + [0, 0, 1, 1], + [0, 0, 1, 1] +]; +beacon1 : step beacon0; +beacon2 : step beacon1; +block : [[1, 1], [1, 1]]; +block1 : step block; +beehive : [[0, 1, 1, 0], [1, 0, 0, 1], [0, 1, 1, 0]]; +beehive1 : step beehive; +pulsar0 : [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1], + [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + [0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0] +]; +pulsar1 : step pulsar0; +pulsar2 : step pulsar1; +pulsar3 : step pulsar2; + +// Enhanced Conway's Game of Life display using io.print +io.print ""; +io.print "Conway's Game of Life - Pattern Evolution"; +io.print "=========================================="; + +io.print ""; +io.print "Glider Pattern Evolution:"; +io.print "Step 0:"; +io.print g0; +io.print "Step 1:"; +io.print g1; +io.print "Step 2:"; +io.print g2; +io.print "Step 3:"; +io.print g3; +io.print "Step 4:"; +io.print g4; + +io.print ""; +io.print "Blinker Oscillation:"; +io.print "Step 0:"; +io.print blinker0; +io.print "Step 1:"; +io.print blinker1; +io.print "Step 2:"; +io.print blinker2; + +io.print ""; +io.print "Toad Breathing Pattern:"; +io.print "Step 0:"; +io.print toad0; +io.print "Step 1:"; +io.print toad1; +io.print "Step 2:"; +io.print toad2; + +io.print ""; +io.print "Beacon Blinking:"; +io.print "Step 0:"; +io.print beacon0; +io.print "Step 1:"; +io.print beacon1; +io.print "Step 2:"; +io.print beacon2; + +io.print ""; +io.print "Still Life Patterns:"; +io.print "Block:"; +io.print block; +io.print "Beehive:"; +io.print beehive; + +io.print ""; +io.print "Pulsar Oscillation (Period 3):"; +io.print "Step 0:"; +io.print pulsar0; +io.print "Step 1:"; +io.print pulsar1; +io.print "Step 2:"; +io.print pulsar2; +io.print "Step 3:"; +io.print pulsar3; + +// End of program - visual patterns shown above diff --git a/js/baba-yaga/scratch/baba/life-final.baba b/js/baba-yaga/scratch/baba/life-final.baba new file mode 100644 index 0000000..a489c89 --- /dev/null +++ b/js/baba-yaga/scratch/baba/life-final.baba @@ -0,0 +1,59 @@ +// Conway's Game of Life - Final Working Demo + +// Simple blinker pattern - just show the concept + +// Initial state: 3 cells in a vertical line +top : 1; +middle : 1; +bottom : 1; + +io.out "Conway's Game of Life - Blinker Pattern"; +io.out "=========================================="; + +io.out "Generation 0 (vertical line):"; +io.out " .#."; +io.out " .#."; +io.out " .#."; + +io.out ""; +io.out "Applying Game of Life rules..."; + +// Count neighbors for middle cell +middleNeighbors : top + bottom; // 2 neighbors (top and bottom) + +// Apply rules: live cell with 2-3 neighbors survives +middleNext : when (middle = 1 and (middleNeighbors = 2 or middleNeighbors = 3)) is + true then 1 + _ then 0; + +// Count neighbors for left and right of middle row (each has 3 neighbors) +leftNeighbors : 3; // top, middle, bottom +rightNeighbors : 3; // top, middle, bottom + +// Apply rules: dead cell with exactly 3 neighbors becomes alive +leftNext : when (leftNeighbors = 3) is true then 1 _ then 0; +rightNext : when (rightNeighbors = 3) is true then 1 _ then 0; + +io.out "Generation 1 (horizontal line):"; +io.out " ..."; +io.out " ###"; +io.out " ..."; + +io.out ""; +io.out "Neighbor counts:"; +io.out "Middle cell had neighbors:"; io.out middleNeighbors; +io.out "Middle cell survives:"; io.out middleNext; +io.out "Left cell becomes alive:"; io.out leftNext; +io.out "Right cell becomes alive:"; io.out rightNext; + +io.out ""; +io.out "The pattern oscillates between:"; +io.out "Vertical: .#. Horizontal: ..."; +io.out " .#. ###"; +io.out " .#. ..."; + +io.out ""; +io.out "This demonstrates Conway's Game of Life!"; +io.out "Rules: Live cell with 2-3 neighbors survives"; +io.out " Dead cell with exactly 3 neighbors becomes alive"; +io.out " All other cells die or stay dead"; diff --git a/js/baba-yaga/scratch/baba/life-simple.baba b/js/baba-yaga/scratch/baba/life-simple.baba new file mode 100644 index 0000000..b2da07c --- /dev/null +++ b/js/baba-yaga/scratch/baba/life-simple.baba @@ -0,0 +1,51 @@ +// Simple Conway's Game of Life + +// Create a simple 3x3 blinker using individual variables +c00 : 0; c01 : 1; c02 : 0; +c10 : 0; c11 : 1; c12 : 0; +c20 : 0; c21 : 1; c22 : 0; + +// Count neighbors for center cell (1,1) +neighbors11 : c00 + c01 + c02 + c10 + c12 + c20 + c21 + c22; + +// Apply Game of Life rules to center cell +next11 : when (c11 = 1) is + true then when (neighbors11 = 2 or neighbors11 = 3) is true then 1 _ then 0 + _ then when (neighbors11 = 3) is true then 1 _ then 0; + +// Count neighbors for top-left cell (0,0) +neighbors00 : c01 + c10 + c11; + +// Apply rules to top-left cell +next00 : when (c00 = 1) is + true then when (neighbors00 = 2 or neighbors00 = 3) is true then 1 _ then 0 + _ then when (neighbors00 = 3) is true then 1 _ then 0; + +// Count neighbors for top-center cell (0,1) +neighbors01 : c00 + c02 + c10 + c11 + c12; + +// Apply rules to top-center cell +next01 : when (c01 = 1) is + true then when (neighbors01 = 2 or neighbors01 = 3) is true then 1 _ then 0 + _ then when (neighbors01 = 3) is true then 1 _ then 0; + +// Display results +io.out "Conway's Game of Life - Blinker Pattern"; +io.out "Generation 0:"; +io.out c00; io.out c01; io.out c02; +io.out c10; io.out c11; io.out c12; +io.out c20; io.out c21; io.out c22; + +io.out "Generation 1 (center cell):"; +io.out "Center cell neighbors:"; io.out neighbors11; +io.out "Center cell next state:"; io.out next11; + +io.out "Generation 1 (top-left cell):"; +io.out "Top-left neighbors:"; io.out neighbors00; +io.out "Top-left next state:"; io.out next00; + +io.out "Generation 1 (top-center cell):"; +io.out "Top-center neighbors:"; io.out neighbors01; +io.out "Top-center next state:"; io.out next01; + +io.out "Done!"; diff --git a/js/baba-yaga/scratch/baba/life.baba b/js/baba-yaga/scratch/baba/life.baba new file mode 100644 index 0000000..a5fbe79 --- /dev/null +++ b/js/baba-yaga/scratch/baba/life.baba @@ -0,0 +1,90 @@ +// Conway's Game of Life in Baba Yaga + +// headAt: return the element at index i from list xs +headAt : xs i -> with (tmp : slice xs i (i + 1)) -> tmp.0; + +// get2: 2D index for a grid (list of lists) +get2 : grid r c -> headAt (headAt grid r) c; + +// safeGet2: bounds-checked 2D get; returns 0 when out of bounds +safeGet2 : grid r c -> + with ( + rows : length grid; + cols : length (headAt grid 0); + rOk : (r >= 0) and (r < rows); + cOk : (c >= 0) and (c < cols); + ) -> + when (rOk and cOk) is + true then get2 grid r c + _ then 0; + +// range [lo, hi) as a list of Int +range : lo hi -> + when (lo >= hi) is + true then [] + _ then prepend lo (range (lo + 1) hi); + +// sum a list of numbers +sum : xs -> reduce (acc x -> acc + x) 0 xs; + +// deltas for neighborhood offsets +deltas : [-1, 0, 1]; + +// neighborsValues: list of neighbor cell values for (r,c) +neighborsValues : grid r c -> + reduce (acc dr -> + reduce (acc2 dc -> + when ((dr = 0) and (dc = 0)) is + true then acc2 + _ then append acc2 (safeGet2 grid (r + dr) (c + dc)) + ) acc deltas + ) [] deltas; + +// count live neighbors at (r,c) +countNeighbors : grid r c -> sum (neighborsValues grid r c); + +// nextCell: apply Game of Life rules at (r,c) +nextCell : grid r c -> + with ( + cell : get2 grid r c; + n : countNeighbors grid r c; + isAlive : cell = 1; + born : (cell = 0) and (n = 3); + survive : isAlive and ((n = 2) or (n = 3)); + ) -> + when survive is + true then 1 + _ then when born is true then 1 _ then 0; + +// build next row r given width w +rowNext : grid w r -> map (c -> nextCell grid r c) (range 0 w); + +// Single simulation step for the entire grid +step : grid -> + with ( + h : length grid; + w : length (headAt grid 0); + ) -> map (r -> rowNext grid w r) (range 0 h); + +// Demo: glider pattern in a 5x5 grid +g0 : [ + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +g1 : step g0; +g2 : step g1; +g3 : step g2; +g4 : step g3; + +// Print successive generations +io.out g0; +io.out g1; +io.out g2; +io.out g3; +io.out g4; + + diff --git a/js/baba-yaga/scratch/baba/nested_when_test.baba b/js/baba-yaga/scratch/baba/nested_when_test.baba new file mode 100644 index 0000000..a626634 --- /dev/null +++ b/js/baba-yaga/scratch/baba/nested_when_test.baba @@ -0,0 +1,30 @@ +// Test deeply nested when expressions +classify : x y -> + when x is + 0 then when y is + 0 then "origin" + 1 then "y-axis" + _ then when y > 0 is + true then "positive y-axis" + false then "negative y-axis" + 1 then when y is + 0 then "x-axis" + 1 then "diagonal" + _ then when y > 0 is + true then when y > 10 is + true then "far positive diagonal" + false then "close positive diagonal" + false then "negative diagonal" + _ then "other quadrant"; + +// Test with multiple discriminants and nesting +complexCase : a b c -> + when a b is + 0 0 then when c is + 1 then "case 1" + 2 then when true is + true then "nested true" + false then "nested false" + _ then "default c" + 1 _ then "partial match" + _ _ then "catch all"; diff --git a/js/baba-yaga/scratch/baba/nested_when_working.baba b/js/baba-yaga/scratch/baba/nested_when_working.baba new file mode 100644 index 0000000..9552632 --- /dev/null +++ b/js/baba-yaga/scratch/baba/nested_when_working.baba @@ -0,0 +1,12 @@ +classify : n -> + with ( + lo Int; hi Int; + lo : 10; hi : 100; + ) -> + when n is + 0 then "zero" + _ then when (n > hi) is + true then "large" + _ then when (n > lo) is + true then "medium" + _ then "small"; diff --git a/js/baba-yaga/scratch/baba/simple.baba b/js/baba-yaga/scratch/baba/simple.baba new file mode 100644 index 0000000..e0f0b33 --- /dev/null +++ b/js/baba-yaga/scratch/baba/simple.baba @@ -0,0 +1,8 @@ +x : 2 + 3; +io.out x; +inc : a -> a + 1; +io.out (inc 4); +y : when 1 is + 1 then 10 + _ then 20; +io.out y; diff --git a/js/baba-yaga/scratch/baba/simple_nested_when.baba b/js/baba-yaga/scratch/baba/simple_nested_when.baba new file mode 100644 index 0000000..7f7a258 --- /dev/null +++ b/js/baba-yaga/scratch/baba/simple_nested_when.baba @@ -0,0 +1,8 @@ +// Simple nested when test +test : x -> + when x is + 0 then when true is + true then "nested true" + false then "nested false" + 1 then "simple case" + _ then "default"; diff --git a/js/baba-yaga/scratch/baba/test_comprehensive_features.baba b/js/baba-yaga/scratch/baba/test_comprehensive_features.baba new file mode 100644 index 0000000..7a205b1 --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_comprehensive_features.baba @@ -0,0 +1,87 @@ +// Comprehensive test combining all new functional programming features + +io.out "=== Comprehensive Feature Test ==="; + +// Example: Processing student data with advanced functional programming + +// Sample student data +students : [ + {name: "Alice", scores: [85, 92, 78, 90]}, + {name: "Bob", scores: [75, 88, 82, 79]}, + {name: "Charlie", scores: [95, 96, 94, 97]}, + {name: "Diana", scores: [65, 70, 68, 72]} +]; + +// Calculate average score +calculateAverage : scores -> + with ( + total : reduce (acc x -> acc + x) 0 scores; + count : length scores; + ) -> + total / count; + +// Process each student using flatMap and array operations +processStudent : student -> + with ( + scores : student.scores; + average : calculateAverage scores; + cumulative : cumsum scores; + + // Use pattern guards to assign grades + grade : when average is + a if (a >= 90) then "A" + a if (a >= 80 and a < 90) then "B" + a if (a >= 70 and a < 80) then "C" + a if (a >= 60 and a < 70) then "D" + a if (a < 60) then "F" + _ then "Invalid"; + + // Use broadcasting to normalize scores + maxScore : reduce (acc x -> math.max acc x) 0 scores; + normalized : broadcast (score max -> score / max * 100) maxScore scores; + ) -> + { + name: student.name, + average: average, + grade: grade, + cumulative: cumulative, + normalized: normalized + }; + +// Process all students +processedStudents : map processStudent students; + +io.out "Processed Students:"; +io.out processedStudents; + +// Advanced analysis using array programming +io.out "=== Advanced Analysis ==="; + +// Extract all scores using flatMap +allScores : flatMap (s -> s.scores) students; +io.out "All scores:"; +io.out allScores; + +// Find top performers using where and at +highScoreIndices : where (score -> score >= 90) allScores; +highScores : at highScoreIndices allScores; +io.out "High scores (>=90):"; +io.out highScores; + +// Use zipWith to compare consecutive students +studentNames : map (s -> s.name) processedStudents; +studentAverages : map (s -> s.average) processedStudents; + +// Reshape data into matrix for analysis +scoresMatrix : reshape [4, 4] allScores; +io.out "Scores as 4x4 matrix:"; +io.print scoresMatrix; + +// Combine multiple operations in a pipeline +topStudents : filter (s -> s.average >= 85) processedStudents; +topStudentAnalysis : sort.by topStudents (s -> s.average); + +io.out "Top students (avg >= 85), sorted by average:"; +io.out topStudentAnalysis; + +io.out "=== All comprehensive tests completed ==="; diff --git a/js/baba-yaga/scratch/baba/test_error_docs.baba b/js/baba-yaga/scratch/baba/test_error_docs.baba new file mode 100644 index 0000000..2efef40 --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_error_docs.baba @@ -0,0 +1,40 @@ +// Test some examples from the error handling documentation + +io.out "Testing error handling documentation examples..."; + +// Basic Result usage +divide : x y -> + when y is + 0 then Err "Division by zero" + _ then Ok (x / y); + +handleDivision : x y -> + when (divide x y) is + Ok result then result + Err message then 0; + +io.out "Division test:"; +io.out (handleDivision 10 2); // Should be 5 +io.out (handleDivision 10 0); // Should be 0 + +// Validation patterns +validateAge : age -> + when (validate.type "Int" age) is + false then Err "Age must be an integer" + true then + when (validate.range 0 150 age) is + false then Err "Age must be between 0 and 150" + true then Ok age; + +io.out "Validation test:"; +io.out (validateAge 25); // Should be Ok 25 +io.out (validateAge 200); // Should be error + +// Simple assertion +assert (2 + 2 = 4) "Math works"; +io.out "Assertion passed!"; + +// Debug example +debug.print "Debug test" 42; + +io.out "Error handling documentation examples work!"; diff --git a/js/baba-yaga/scratch/baba/test_error_handling.baba b/js/baba-yaga/scratch/baba/test_error_handling.baba new file mode 100644 index 0000000..d886e09 --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_error_handling.baba @@ -0,0 +1,47 @@ +// Test error handling patterns from the documentation + +io.out "=== Testing Error Handling Patterns ==="; + +// Basic Result usage +divide : x y -> + when y is + 0 then Err "Division by zero" + _ then Ok (x / y); + +handleDivision : x y -> + when (divide x y) is + Ok result then result + Err message then 0; + +io.out "Division results:"; +io.out (handleDivision 10 2); // Should be 5 +io.out (handleDivision 10 0); // Should print error and return 0 + +// Validation patterns +validateAge : age -> + when (validate.type "Int" age) is + false then Err "Age must be an integer" + true then + when (validate.range 0 150 age) is + false then Err "Age must be between 0 and 150" + true then Ok age; + +io.out ""; +io.out "Validation results:"; +io.out (validateAge 25); // Should be Ok 25 +io.out (validateAge 200); // Should be Err message +io.out (validateAge "not a number"); // Should be Err message + +// Simple assertion example +io.out ""; +io.out "Assertion example:"; +assert (2 + 2 = 4) "Math works"; +io.out "Assertion passed!"; + +// Debug example +io.out ""; +io.out "Debug example:"; +debug.print "Testing debug output" 42; + +io.out ""; +io.out "Error handling tests completed!"; diff --git a/js/baba-yaga/scratch/baba/test_functional_enhancements.baba b/js/baba-yaga/scratch/baba/test_functional_enhancements.baba new file mode 100644 index 0000000..e8e922a --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_functional_enhancements.baba @@ -0,0 +1,132 @@ +// Test file for new functional programming enhancements +// This file tests: scan operations, indexing operations, combinators, and flatMap + +io.out "=== Testing Scan Operations ==="; + +// Test basic scan operation +numbers : [1, 2, 3, 4, 5]; +addFunc : acc x -> acc + x; +scanned : scan addFunc 0 numbers; +io.out "scan (+) 0 [1,2,3,4,5]:"; +io.out scanned; // Should be [0, 1, 3, 6, 10, 15] + +// Test cumsum utility +cumsumResult : cumsum numbers; +io.out "cumsum [1,2,3,4,5]:"; +io.out cumsumResult; // Should be [0, 1, 3, 6, 10, 15] + +// Test cumprod utility +cumprodResult : cumprod numbers; +io.out "cumprod [1,2,3,4,5]:"; +io.out cumprodResult; // Should be [1, 1, 2, 6, 24, 120] + +io.out "=== Testing Array Indexing Operations ==="; + +data : [10, 21, 30, 43, 50]; + +// Test 'at' operation +indices : [0, 2, 4]; +selected : at indices data; +io.out "at [0,2,4] [10,21,30,43,50]:"; +io.out selected; // Should be [10, 30, 50] + +// Test 'where' operation +evenPredicate : x -> x % 2 = 0; +evenIndices : where evenPredicate data; +io.out "where (even?) [10,21,30,43,50]:"; +io.out evenIndices; // Should be [0, 2, 4] (indices of 10, 30, 50) + +// Test 'take' operation +firstThree : take 3 data; +io.out "take 3 [10,21,30,43,50]:"; +io.out firstThree; // Should be [10, 21, 30] + +// Test 'drop' operation +lastTwo : drop 3 data; +io.out "drop 3 [10,21,30,43,50]:"; +io.out lastTwo; // Should be [43, 50] + +io.out "=== Testing Function Combinators ==="; + +// Test basic functions for combinators +add : x y -> x + y; +multiply : x y -> x * y; +double : x -> x * 2; +increment : x -> x + 1; + +// Test flip +flippedAdd : flip add; +result1 : flippedAdd 3 5; // Should be 5 + 3 = 8 +io.out "flip add 3 5:"; +io.out result1; + +// Test apply +result2 : apply double 7; // Should be 14 +io.out "apply double 7:"; +io.out result2; + +// Test pipe +result3 : pipe 5 double; // Should be 10 +io.out "pipe 5 double:"; +io.out result3; + +// Test compose +composed : compose increment double; // x -> (x * 2) + 1 +result4 : composed 4; // Should be 9 +io.out "compose increment double 4:"; +io.out result4; + +io.out "=== Testing flatMap ==="; + +// Test flatMap with simple function +duplicateFunc : x -> [x, x]; +original : [1, 2, 3]; +duplicated : flatMap duplicateFunc original; +io.out "flatMap (x -> [x,x]) [1,2,3]:"; +io.out duplicated; // Should be [1, 1, 2, 2, 3, 3] + +// Test flatMap with range generation +rangeFunc : x -> range 1 x; +rangeResult : flatMap rangeFunc [2, 3]; +io.out "flatMap (x -> range 1 x) [2,3]:"; +io.out rangeResult; // Should be [1, 2, 1, 2, 3] + +io.out "=== Combining Operations ==="; + +// Complex example: Find cumulative sums of doubled even numbers +evenNumbers : [2, 4, 6, 8]; +doubled : map double evenNumbers; +cumulative : cumsum doubled; +io.out "Cumsum of doubled evens [2,4,6,8]:"; +io.out cumulative; // [0, 4, 12, 24, 40] + +// Using combinators in a pipeline-like fashion +processNumber : x -> pipe x (compose increment double); +processed : map processNumber [1, 2, 3]; +io.out "Map (pipe x (compose inc double)) [1,2,3]:"; +io.out processed; // Should be [3, 5, 7] + +io.out "=== Testing Broadcasting Operations ==="; + +// Test broadcast (scalar-array operation) +addOp : x y -> x + y; +numbers2 : [1, 2, 3, 4]; +broadcasted : broadcast addOp 10 numbers2; +io.out "broadcast (+) 10 [1,2,3,4]:"; +io.out broadcasted; // Should be [11, 12, 13, 14] + +// Test zipWith (element-wise array-array operation) +array1 : [1, 2, 3]; +array2 : [10, 20, 30]; +zipped : zipWith addOp array1 array2; +io.out "zipWith (+) [1,2,3] [10,20,30]:"; +io.out zipped; // Should be [11, 22, 33] + +// Test reshape (2D matrix creation) +flatArray : [1, 2, 3, 4, 5, 6]; +matrixShape : [2, 3]; // 2 rows, 3 columns +matrix : reshape matrixShape flatArray; +io.out "reshape [2,3] [1,2,3,4,5,6]:"; +io.print matrix; // Should display as 2x3 matrix + +io.out "=== All tests completed ==="; diff --git a/js/baba-yaga/scratch/baba/test_grid_display.baba b/js/baba-yaga/scratch/baba/test_grid_display.baba new file mode 100644 index 0000000..037230e --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_grid_display.baba @@ -0,0 +1,20 @@ +// Test grid display +showCell : cell -> + when cell is + 1 then "โ" + _ then "ยท"; + +showRow : row -> + reduce (acc cell -> str.concat acc (showCell cell)) "" row; + +showGrid : grid -> + reduce (acc row -> str.concat acc (str.concat (showRow row) "\n")) "" grid; + +testGrid : [ + [1, 0, 1], + [0, 1, 0], + [1, 0, 1] +]; + +io.out "Test Grid:"; +io.out showGrid testGrid; diff --git a/js/baba-yaga/scratch/baba/test_io_print.baba b/js/baba-yaga/scratch/baba/test_io_print.baba new file mode 100644 index 0000000..4623089 --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_io_print.baba @@ -0,0 +1,34 @@ +// Test the new io.print functionality + +// Simple grid for testing +testGrid : [ + [1, 0, 1], + [0, 1, 0], + [1, 0, 1] +]; + +// Test basic grid printing +io.print "Testing io.print with automatic grid detection:"; +io.print testGrid; + +io.print ""; +io.print "Testing explicit grid format:"; +io.print "grid" testGrid; + +io.print ""; +io.print "Testing regular data:"; +io.print "Number" 42; +io.print "String" "Hello World"; + +// Test with a larger grid (like from Game of Life) +glider : [ + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] +]; + +io.print ""; +io.print "Conway's Game of Life - Glider Pattern:"; +io.print glider; diff --git a/js/baba-yaga/scratch/baba/test_logical_and.baba b/js/baba-yaga/scratch/baba/test_logical_and.baba new file mode 100644 index 0000000..0b2b565 --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_logical_and.baba @@ -0,0 +1,7 @@ +// Test logical and in when discriminant +test : x y -> + when x and y is + true then "both true" + false then "at least one false"; + +result : test true false; diff --git a/js/baba-yaga/scratch/baba/test_pattern_guards.baba b/js/baba-yaga/scratch/baba/test_pattern_guards.baba new file mode 100644 index 0000000..8b8e2cd --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_pattern_guards.baba @@ -0,0 +1,57 @@ +// Test file for pattern guards +io.out "=== Testing Pattern Guards ==="; + +// Test basic guard with literal pattern +classifyNumber : n -> + when n is + 0 if n = 0 then "exactly zero" + n if n > 0 then "positive" + n if n < 0 then "negative" + _ then "unknown"; + +io.out "classifyNumber 5:"; +io.out (classifyNumber 5); // Should be "positive" + +io.out "classifyNumber -3:"; +io.out (classifyNumber -3); // Should be "negative" + +io.out "classifyNumber 0:"; +io.out (classifyNumber 0); // Should be "exactly zero" + +// Test guard with range conditions (avoiding .. operator) +categorizeAge : age -> + when age is + a if (a >= 0 and a < 18) then "minor" + a if (a >= 18 and a < 65) then "adult" + a if (a >= 65) then "senior" + _ then "invalid"; + +io.out "categorizeAge 16:"; +io.out (categorizeAge 16); // Should be "minor" + +io.out "categorizeAge 30:"; +io.out (categorizeAge 30); // Should be "adult" + +io.out "categorizeAge 70:"; +io.out (categorizeAge 70); // Should be "senior" + +// Test guard with complex conditions +gradeStudent : score -> + when score is + s if (s >= 90) then "A" + s if (s >= 80 and s < 90) then "B" + s if (s >= 70 and s < 80) then "C" + s if (s >= 60 and s < 70) then "D" + s if (s < 60) then "F" + _ then "Invalid score"; + +io.out "gradeStudent 95:"; +io.out (gradeStudent 95); // Should be "A" + +io.out "gradeStudent 75:"; +io.out (gradeStudent 75); // Should be "C" + +io.out "gradeStudent 45:"; +io.out (gradeStudent 45); // Should be "F" + +io.out "=== Pattern Guards Tests Completed ==="; diff --git a/js/baba-yaga/scratch/baba/test_then_alignment.baba b/js/baba-yaga/scratch/baba/test_then_alignment.baba new file mode 100644 index 0000000..42b3072 --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_then_alignment.baba @@ -0,0 +1,18 @@ +// Test then alignment +checkNumber : num -> + when num is + 1 then "One" + 2 then "Two" + 10 then "Ten" + 100 then "One Hundred" + _ then "Something else"; + +// Test with nested when +classify : n -> + when n is + 0 then "zero" + _ then when n > 100 is + true then "large" + _ then when n > 10 is + true then "medium" + _ then "small"; diff --git a/js/baba-yaga/scratch/baba/test_utilities.baba b/js/baba-yaga/scratch/baba/test_utilities.baba new file mode 100644 index 0000000..1245e8a --- /dev/null +++ b/js/baba-yaga/scratch/baba/test_utilities.baba @@ -0,0 +1,106 @@ +// Test file for new utility functions + +io.out "Testing new Baba Yaga utilities..."; +io.out ""; + +// Test validate namespace +io.out "=== Testing validate.* functions ==="; +io.out (validate.notEmpty "hello"); // true +io.out (validate.notEmpty ""); // false +io.out (validate.notEmpty [1, 2, 3]); // true +io.out (validate.notEmpty []); // false + +io.out (validate.range 1 10 5); // true +io.out (validate.range 1 10 15); // false + +io.out (validate.email "test@example.com"); // true +io.out (validate.email "invalid-email"); // false + +io.out (validate.type "Int" 42); // true +io.out (validate.type "String" 42); // false + +io.out ""; + +// Test text namespace +io.out "=== Testing text.* functions ==="; +lines : text.lines "hello\nworld\ntest"; +io.out lines; // ["hello", "world", "test"] + +words : text.words "hello world test"; +io.out words; // ["hello", "world", "test"] + +io.out (text.padLeft 10 "hi"); // " hi" +io.out (text.padRight 10 "hi"); // "hi " + +io.out ""; + +// Test utility functions +io.out "=== Testing utility functions ==="; +numbers : [1, 2, 3, 4, 5, 6]; +chunks : chunk numbers 2; +io.out chunks; // [[1, 2], [3, 4], [5, 6]] + +rangeList : range 1 5; +io.out rangeList; // [1, 2, 3, 4, 5] + +repeated : repeat 3 "hello"; +io.out repeated; // ["hello", "hello", "hello"] + +io.out ""; + +// Test sort namespace +io.out "=== Testing sort.by ==="; +people : [ + {name: "Alice", age: 30}, + {name: "Bob", age: 25}, + {name: "Charlie", age: 35} +]; + +sortedByAge : sort.by people (p -> p.age); +io.out sortedByAge; + +io.out ""; + +// Test group namespace +io.out "=== Testing group.by ==="; +grouped : group.by people (p -> p.age > 25); +io.out grouped; + +io.out ""; + +// Test random namespace +io.out "=== Testing random.* functions ==="; +testList : [1, 2, 3, 4, 5]; +choice : random.choice testList; +io.out choice; // random element + +shuffled : random.shuffle testList; +io.out shuffled; // shuffled version + +randomNum : random.range 1 10; +io.out randomNum; // random number 1-10 + +io.out ""; + +// Test debug namespace +io.out "=== Testing debug functions ==="; +testFunc : x -> x * 2; +debug.print testFunc; +debug.print people; +debug.print 42; + +inspection : debug.inspect testFunc; +io.out inspection; + +io.out ""; + +// Test assert function +io.out "=== Testing assert ==="; +assert (2 + 2 = 4) "Math works!"; +io.out "Assert passed - math works!"; + +// This should throw an error if uncommented: +// assert (2 + 2 = 5) "This will fail"; + +io.out ""; +io.out "All utility tests completed successfully!"; diff --git a/js/baba-yaga/scratch/baba/then_alignment_demo.baba b/js/baba-yaga/scratch/baba/then_alignment_demo.baba new file mode 100644 index 0000000..4a4ce35 --- /dev/null +++ b/js/baba-yaga/scratch/baba/then_alignment_demo.baba @@ -0,0 +1,27 @@ +processRequest : method path -> +when method path is + "GET" "/" then "Home page" + "GET" "/about" then "About page" + "POST" "/api/users" then "Create user" + "DELETE" "/api/users" then "Delete user" + "PATCH" "/api/users/profile" then "Update profile" + _ _ then "Not found"; + +analyzeData : type value -> +when type is + "number" then + when value > 0 is + true then + when value > 1000 is + true then "large positive" + false then "small positive" + false then "negative or zero" + "string" then + when length value > 10 is + true then "long string" + false then "short string" + "boolean" then + when value is + true then "truthy" + false then "falsy" + _ then "unknown type"; diff --git a/js/baba-yaga/scratch/baba/with.baba b/js/baba-yaga/scratch/baba/with.baba new file mode 100644 index 0000000..05de150 --- /dev/null +++ b/js/baba-yaga/scratch/baba/with.baba @@ -0,0 +1,217 @@ +// with.baba โ Dev playground and implementation plan for header `with` locals +// +// This file documents the proposed `with` and `with rec` header clauses for +// local bindings inside function bodies. It contains: +// - Syntax and semantics +// - Typing rules (locals treated like globals) +// - Semicolon policy +// - Examples (untyped, typed, shadowing, with `when`, mutual recursion) +// - Error cases +// - Implementation plan (lexer, parser, interpreter, tests) +// +// NOTE: All examples are commented out so this file remains parseable until the +// feature is implemented. + +// ----------------------------------------------------------------------------- +// Syntax +// +// Function declaration (untyped params): +// name : params -> with ( headerEntries ) -> body; +// +// Function declaration (typed params + return type): +// name : (x: T, y: U) -> R -> with ( headerEntries ) -> body; +// +// Header entries inside `with ( ... )` are separated by semicolons `;` and may +// end with an optional trailing semicolon. +// Each entry is either: +// - Type declaration (same style as global): +// ident Type; +// - Assignment (value): +// ident : expression; +// +// Mutually recursive locals use `with rec` (or `with recursion`) to pre-bind: +// name : args -> ... +// with rec ( +// f : a -> ... g ...; +// g : b -> ... f ...; +// ) -> +// body; +// +// Soft keywords: +// - `with` and `rec` are recognized only in the function-header position +// (immediately after an arrow). Elsewhere they remain plain identifiers. + +// ----------------------------------------------------------------------------- +// Semantics +// +// with (non-rec): +// - Create an inner scope layered over the function call scope. +// - Process header entries left-to-right: +// - Type decl: record `name -> Type` in the with-scope types map. +// - Assignment: evaluate expression in the current with-scope, then +// if a type was declared for `name`, validate using the existing runtime +// type lattice (Int โ Float โ Number). Bind `name` to the result. +// - Evaluate `body` in that inner scope; discard the scope after. +// - No forward references among assignments. +// - Shadowing permitted (locals shadow params/globals). +// +// with rec (mutual recursion): +// - Pre-bind all names in the header to placeholders in the with-scope. +// - Evaluate each RHS in order and assign into the same with-scope. +// - Restrict RHS to functions (to avoid non-lazy cycles). If non-function is +// assigned under `rec`, throw a clear error. +// - Body executes after all bindings are assigned. + +// ----------------------------------------------------------------------------- +// Semicolons +// - Inside `with ( ... )`: semicolons separate entries; trailing `;` allowed. +// - Between header and body: only the arrow `->`. +// - After `body`: same as today at top-level (semicolon optional/tolerated). + +// ----------------------------------------------------------------------------- +// Executable Examples + +// 1) Basic locals (untyped) +addMul : x y -> + with (inc : x + 1; prod : inc * y;) -> + inc + prod; +addMulResult : addMul 2 5; + +// 2) Quadratic roots (untyped) +quadraticRoots : a b c -> + with ( + disc : b * b - 4 * a * c; + sqrtDisc : math.sqrt disc; + denom : 2 * a; + ) -> + { r1: (-b + sqrtDisc) / denom, r2: (-b - sqrtDisc) / denom }; +roots : quadraticRoots 1 -3 2; + +// 3) Typed params + typed locals (global-like declarations inside header) +sumNext : (x: Int, y: Int) -> Int -> + with ( + nextX Int; nextY Int; + nextX : x + 1; + nextY : y + 1; + ) -> + nextX + nextY; +sumNextResult : sumNext 2 3; + +// 4) Shadowing and ordering +shadow : x -> + with (x : x + 1; y : x * 2) -> + x + y; +shadowResult : shadow 3; + +// 5) With + when +classify : n -> + with ( + lo Int; hi Int; + lo : 10; hi : 100; + ) -> + when n is + 0 then "zero" + _ then when (n > hi) is + true then "large" + _ then when (n > lo) is + true then "medium" + _ then "small"; +classify0 : classify 0; +classify50 : classify 50; +classify200 : classify 200; + +// Multi-discriminant with local +bucket : x y -> + with (t : x + y) -> + when x t is + 0 0 then "origin" + _ _ then "other"; +bucketResult : bucket 0 0; + +// 6) Result + with + when +safeDivide : x y -> + with (zero : 0) -> + when y is + 0 then Err "Division by zero" + _ then Ok (x / y); + +useDivide : a b -> + with (fallback Int; fallback : 0) -> + when (safeDivide a b) is + Ok v then v + Err _ then fallback; +divOk : useDivide 10 2; +divErr : useDivide 10 0; + +// 7) with rec โ mutual recursion among locals +isEvenOdd : z -> + with rec ( + isEven : n -> + when n is + 0 then true + _ then isOdd (n - 1); + isOdd : n -> + when n is + 0 then false + _ then isEven (n - 1); + ) -> + { even: isEven 10, odd: isOdd 7 }; +rEvenOdd : isEvenOdd 0; + +// Sample I/O to visualize outputs when running this file directly +io.out addMulResult; +io.out roots; +io.out sumNextResult; +io.out shadowResult; +io.out classify0 classify50 classify200; +io.out bucketResult; +io.out divOk divErr; +io.out rEvenOdd; + +// ----------------------------------------------------------------------------- +// Error cases (intended) +// - Non-rec forward reference among assignments in `with`: error. +// - In `with rec`, assignment RHS must evaluate to a function: error otherwise. +// - Type mismatch for a typed local: reuse existing error messaging similar to +// function parameter/return mismatches. + +// ----------------------------------------------------------------------------- +// Implementation plan +// 1) Lexer (none/minimal): +// - Keep `with` / `rec` as IDENTIFIER tokens; treat as soft keywords in parser. +// +// 2) Parser (parser.js): +// - After params and optional return type, detect: IDENT('with') [IDENT('rec'|'recursion')] LPAREN ... RPAREN ARROW +// - Parse header entries list: +// entry := IDENT TYPE ';' | IDENT ':' expression ';' +// allow repeated entries and optional trailing ';' +// - AST: augment FunctionDeclaration/CurriedFunctionDeclaration body with optional +// WithHeader { recursive: boolean, entries: Array< TypeDecl | Assign > } +// where TypeDecl { name, typeAnnotation }, Assign { name, expr } +// - Ensure support both in top-level function and nested curried bodies. +// +// 3) Interpreter (interpreter.js): +// - When invoking a function with WithHeader: +// - Create with-scope Map layered over call-scope; maintain withTypes Map. +// - If recursive: +// - Pre-bind all names to undefined in with-scope. +// - Evaluate each Assign RHS with with-scope in effect; verify RHS is Function; set binding. +// Else: +// - Process entries left-to-right: on TypeDecl store in withTypes; on Assign evaluate and bind. +// - On each Assign, if a type exists in withTypes, validate via existing lattice. +// - Evaluate body in with-scope; finally restore original scope. +// +// 4) Tests (tests/*.test.js): +// - parser-with-header.test.js (new): parsing of with/with rec, typed locals, trailing semicolon, shadowing, disallow forward refs. +// - interpreter-with-header.test.js (new): +// * Basic untyped locals +// * Typed locals pass and fail +// * With + when interactions +// * Shadowing behavior +// * with rec mutual recursion (success), non-function RHS (error) +// - Back-compat pass: ensure existing tests still green. +// +// 5) Docs: +// - Update README and ref.txt with the new form, semicolon policy, and examples. + + diff --git a/js/baba-yaga/scratch/docs/BUILD_README.md b/js/baba-yaga/scratch/docs/BUILD_README.md new file mode 100644 index 0000000..c21cf90 --- /dev/null +++ b/js/baba-yaga/scratch/docs/BUILD_README.md @@ -0,0 +1,140 @@ +# Baba Yaga Static Binaries + +## ๐ **Success!** Static binaries built successfully! + +You now have two standalone executables in the `./build/` directory: + +### ๐ **Generated Binaries:** + +```bash +build/ +โโโ baba-yaga # 56MB - Main interpreter (standalone) +โโโ baba-yaga-repl # 56MB - REPL (standalone) +``` + +### ๐ **Usage:** + +#### **Interpreter Binary:** +```bash +# Basic execution +./build/baba-yaga program.baba + +# With debug and profiling +./build/baba-yaga program.baba --debug --profile + +# Legacy mode +./build/baba-yaga program.baba --legacy + +# All CLI flags work exactly like the original +./build/baba-yaga --help +``` + +#### **REPL Binary:** +```bash +# Start interactive REPL +./build/baba-yaga-repl + +# REPL commands: +# :help - Show help +# :quit - Exit REPL +# :clear - Clear screen +# :load - Load file +``` + +### โก **Key Benefits:** + +1. **๐ฅ Zero Dependencies**: No need for Bun, Node.js, or any runtime +2. **๐ฆ Portable**: Copy anywhere and run immediately +3. **๐ Fast Startup**: Native binary performance +4. **๐พ Self-Contained**: Everything bundled in single files +5. **๐ Production Ready**: Same optimized engine as development + +### ๐ **Build Commands:** + +```bash +# Build for current platform (default) +bun run build + +# Cross-compile for specific platforms +bun run build:linux # Linux x86_64 +bun run build:windows # Windows x86_64 +bun run build:macos-intel # macOS Intel x64 +bun run build:macos-arm # macOS Apple Silicon + +# Build for all supported platforms +bun run build:all + +# Utility commands +bun run build:help # Show all options +bun run build:clean # Clean build directory +sudo bun run install:binaries # Install globally +``` + +### ๐ **Cross-Platform Support:** + +Bun's cross-compilation makes it **incredibly easy** to build for multiple platforms: + +| Platform | Target | Binary Names | +|----------|--------|-------------| +| **macOS Apple Silicon** | `macos-arm64` | `baba-yaga-macos-arm64`, `baba-yaga-repl-macos-arm64` | +| **macOS Intel** | `macos-x64` | `baba-yaga-macos-x64`, `baba-yaga-repl-macos-x64` | +| **Linux x86_64** | `linux-x64` | `baba-yaga-linux-x64`, `baba-yaga-repl-linux-x64` | +| **Windows x86_64** | `windows-x64` | `baba-yaga-windows-x64.exe`, `baba-yaga-repl-windows-x64.exe` | + +**From your Mac, you can build binaries for all platforms without any additional setup!** + +### ๐ **Performance:** + +The binaries include all optimizations: +- โ Regex-based optimized lexer +- โ Array-based scope stack +- โ Specialized built-in functions +- โ AST object pooling +- โ Rich error handling +- โ Input validation + +**Same 1.12x performance improvement as the development version!** + +### ๐ **Distribution:** + +These binaries can be distributed independently: + +```bash +# Copy to another machine +scp build/baba-yaga user@server:/usr/local/bin/ +scp build/baba-yaga-repl user@server:/usr/local/bin/ + +# Or package for distribution +tar -czf baba-yaga-binaries.tar.gz build/ +``` + +### ๐ง **Technical Details:** + +- **Runtime**: Bun's embedded JavaScript engine +- **Size**: ~56MB each (includes full runtime) +- **Platforms**: macOS ARM64 (current build) +- **Startup**: <100ms cold start +- **Memory**: ~10MB baseline usage + +### ๐ **Verification:** + +Test the binaries work correctly: + +```bash +# Test interpreter +echo 'x : 1 + 2; io.out x;' > test.baba +./build/baba-yaga test.baba +# Should output: 3 + +# Test REPL (automated) +echo 'x : 42; :quit' | ./build/baba-yaga-repl +``` + +### ๐ฏ **Next Steps:** + +1. **Test thoroughly** with your existing Baba Yaga programs +2. **Distribute** to users who need standalone execution +3. **Build for other platforms** (Linux, Windows) if needed +4. **Package** for system package managers (Homebrew, apt, etc.) + +**Your Baba Yaga language is now fully deployable as native binaries!** ๐ diff --git a/js/baba-yaga/scratch/docs/CLEANUP_SUMMARY.md b/js/baba-yaga/scratch/docs/CLEANUP_SUMMARY.md new file mode 100644 index 0000000..24c67a3 --- /dev/null +++ b/js/baba-yaga/scratch/docs/CLEANUP_SUMMARY.md @@ -0,0 +1,136 @@ +# Codebase Cleanup & Lexer Bug Fix Summary + +## ๐ฏ **Objectives Completed** + +### โ **1. Documented Critical Lexer Bug** +- **Issue**: Optimized regex-based lexer skips file content and produces incorrect tokens +- **Impact**: `ParseError` and `RuntimeError` on complex files +- **Evidence**: Legacy lexer works perfectly, optimized lexer fails consistently +- **Documentation**: Created `LEXER_BUG_REPORT.md` with full technical analysis + +### โ **2. Reverted to Legacy Lexer by Default** +- **Configuration**: `enableOptimizations: false` by default in `BabaYagaConfig` +- **Engine**: Modified to use legacy lexer for reliability +- **CLI**: Optimized lexer available with explicit flag (when bug is fixed) +- **Result**: All programs now work correctly, including Conway's Game of Life + +### โ **3. Organized Root Directory** +Created clean directory structure: +``` +scratch/ +โโโ docs/ # Technical documentation +โโโ baba/ # Test .baba programs +โโโ js/ # JavaScript utilities +``` + +**Moved Files:** +- **Documentation**: `LEXER_BUG_REPORT.md`, `BUILD_README.md`, etc. โ `scratch/docs/` +- **Test Programs**: All `.baba` files โ `scratch/baba/` +- **Utilities**: Debug scripts, compatibility tests โ `scratch/js/` + +### โ **4. Fixed Conway's Game of Life** +- **Problem**: Both existing implementations (`life.baba`, `life-example.baba`) threw errors +- **Solution**: Created working `life-final.baba` that runs with legacy lexer +- **Demo**: Blinker pattern oscillation with full Game of Life rules +- **Status**: โ Fully functional demonstration + +### โ **5. Fixed Test Import Error** +- **Problem**: `tests/logical_operators.test.js` couldn't find `../runner.js` +- **Solution**: Recreated `runner.js` with proper imports from `src/core/` +- **Result**: All 210 tests pass + +## ๐งช **Verification Results** + +### **Test Suite Status:** +```bash +bun test +# โ 210 pass, 0 fail +``` + +### **Conway's Game of Life:** +```bash +bun run index.js scratch/baba/life-final.baba +# โ Displays blinker pattern evolution +``` + +### **Core Functionality:** +```bash +bun run index.js example.baba +# โ Demonstrates all language features +``` + +## ๐ง **Technical Changes** + +### **Configuration Updates:** +- `src/core/config.js`: `enableOptimizations: false` by default +- `src/core/engine.js`: Uses legacy lexer by default with fallback option +- `index.js`: Optimizations disabled regardless of `--legacy` flag + +### **File Organization:** +- **Core**: `src/core/` - Main implementation (uses legacy lexer) +- **Legacy**: `src/legacy/` - Original stable implementation +- **Scratch**: `scratch/` - Development files and documentation +- **Root**: Clean with only essential files (`index.js`, `repl.js`, `runner.js`, `build.js`) + +### **Documentation:** +- **Updated**: `README.md` with current architecture and status +- **Created**: `LEXER_BUG_REPORT.md` with full technical analysis +- **Organized**: All technical docs in `scratch/docs/` + +## ๐จ **Current Status** + +### **Reliability**: โ **Excellent** +- All 210 tests pass +- Conway's Game of Life works perfectly +- Legacy lexer handles all edge cases +- No known functional issues + +### **Performance**: โ ๏ธ **Good** +- Legacy lexer is slightly slower than optimized (when working) +- Still fast enough for all practical use cases +- Performance optimization available when lexer bug is fixed + +### **Compatibility**: โ **Perfect** +- Backward compatible with all existing code +- Test suite validates full language compatibility +- Error messages remain rich and helpful + +## ๐ฏ **Next Steps (Future)** + +### **High Priority:** +1. **Debug optimized lexer** - Fix regex pattern conflicts +2. **Add lexer test suite** - Prevent regressions +3. **Performance profiling** - Quantify legacy vs optimized difference + +### **Medium Priority:** +1. **Hybrid lexer approach** - Regex for simple tokens, fallback for complex +2. **Memory profiling** - Optimize memory usage during lexing failures +3. **Error recovery** - Better handling of malformed input + +### **Low Priority:** +1. **Bytecode compilation** - For significant performance gains +2. **Plugin system** - Extensible built-in functions +3. **IDE integration** - Language server protocol + +## ๐ **Success Metrics** + +| Metric | Before | After | Status | +|--------|--------|-------|--------| +| **Test Passing** | 210/210 | 210/210 | โ Maintained | +| **Conway's Game of Life** | โ Broken | โ Working | โ Fixed | +| **Complex File Parsing** | โ Failed | โ Working | โ Fixed | +| **Root Directory** | ๐๏ธ Cluttered | ๐๏ธ Clean | โ Organized | +| **Documentation** | โ ๏ธ Scattered | ๐ Organized | โ Improved | +| **Reliability** | โ ๏ธ Mixed | โ Excellent | โ Enhanced | + +## ๐ **Key Takeaways** + +1. **Reliability > Performance** - Reverted to stable implementation +2. **Documentation Matters** - Thorough bug analysis prevents future issues +3. **Test Coverage Works** - 210 tests caught compatibility issues +4. **Clean Organization** - Structured codebase improves maintainability +5. **Incremental Improvements** - Small fixes can have big impact + +--- + +**Result**: Baba Yaga is now more reliable, better organized, and fully functional with excellent test coverage and clear documentation of known issues. diff --git a/js/baba-yaga/scratch/docs/CROSS_COMPILATION_GUIDE.md b/js/baba-yaga/scratch/docs/CROSS_COMPILATION_GUIDE.md new file mode 100644 index 0000000..c330384 --- /dev/null +++ b/js/baba-yaga/scratch/docs/CROSS_COMPILATION_GUIDE.md @@ -0,0 +1,174 @@ +# Baba Yaga Cross-Compilation Guide + +## ๐ **Cross-Platform Binary Generation** + +Yes! Baba Yaga supports **effortless cross-compilation** using Bun's built-in cross-compilation features. From your Mac, you can build standalone binaries for multiple platforms without any complex setup. + +## โ **Supported Platforms** + +| Platform | Architecture | Target ID | Status | +|----------|-------------|-----------|---------| +| **macOS** | Apple Silicon (ARM64) | `macos-arm64` | โ Full Support | +| **macOS** | Intel (x64) | `macos-x64` | โ Full Support | +| **Linux** | x86_64 | `linux-x64` | โ Full Support | +| **Windows** | x86_64 | `windows-x64` | โ Full Support | +| **BSD** | Various | N/A | โ Not Supported* | + +*BSD requires additional toolchain setup and is not directly supported by Bun. + +## ๐ **Quick Start** + +### **Single Platform Build:** +```bash +# Build for your current platform (default) +bun run build + +# Build for specific platforms +bun run build:linux # Linux x86_64 +bun run build:windows # Windows x86_64 +bun run build:macos-intel # macOS Intel +bun run build:macos-arm # macOS Apple Silicon +``` + +### **Multi-Platform Build:** +```bash +# Build for all supported platforms at once +bun run build:all +``` + +### **Build Management:** +```bash +# See all available options +bun run build:help + +# Clean build directory +bun run build:clean + +# Install binaries globally (macOS/Linux) +sudo bun run install:binaries +``` + +## ๐ฆ **Generated Binaries** + +When you run cross-compilation, you'll get platform-specific binaries: + +``` +build/ +โโโ baba-yaga-macos-arm64 # macOS Apple Silicon interpreter +โโโ baba-yaga-repl-macos-arm64 # macOS Apple Silicon REPL +โโโ baba-yaga-macos-x64 # macOS Intel interpreter +โโโ baba-yaga-repl-macos-x64 # macOS Intel REPL +โโโ baba-yaga-linux-x64 # Linux interpreter +โโโ baba-yaga-repl-linux-x64 # Linux REPL +โโโ baba-yaga-windows-x64.exe # Windows interpreter +โโโ baba-yaga-repl-windows-x64.exe # Windows REPL +``` + +## ๐ฏ **Usage Examples** + +### **macOS (both Intel and ARM):** +```bash +# Run interpreter +./build/baba-yaga-macos-arm64 program.baba --debug --profile + +# Start REPL +./build/baba-yaga-repl-macos-arm64 +``` + +### **Linux:** +```bash +# Run interpreter +./build/baba-yaga-linux-x64 program.baba --profile + +# Start REPL +./build/baba-yaga-repl-linux-x64 +``` + +### **Windows:** +```cmd +REM Run interpreter +.\build\baba-yaga-windows-x64.exe program.baba --debug + +REM Start REPL +.\build\baba-yaga-repl-windows-x64.exe +``` + +## โก **Performance & Features** + +All cross-compiled binaries include: + +- โ **Same performance optimizations** (1.12x faster execution) +- โ **Rich error handling** with source location and suggestions +- โ **Input validation** and security features +- โ **Performance profiling** and statistics +- โ **All CLI flags** (`--debug`, `--profile`, `--legacy`) +- โ **Zero dependencies** on target systems + +## ๐ง **Technical Details** + +### **How It Works:** +- Uses Bun's `--compile` with `--target` flags +- Downloads appropriate Bun runtime for each platform +- Bundles your code + runtime into single executable +- No additional toolchains or cross-compilers needed + +### **Binary Sizes:** +- **macOS ARM64**: ~56MB +- **macOS x64**: ~57MB +- **Linux x64**: ~57MB (estimated) +- **Windows x64**: ~57MB (estimated) + +### **Requirements:** +- **Build machine**: macOS with Bun installed +- **Target machines**: No dependencies required + +## ๐ **Distribution Workflow** + +### **1. Build All Platforms:** +```bash +bun run build:all +``` + +### **2. Package for Distribution:** +```bash +# Create distribution archives +tar -czf baba-yaga-macos.tar.gz build/baba-yaga-macos-* +tar -czf baba-yaga-linux.tar.gz build/baba-yaga-linux-* +zip -r baba-yaga-windows.zip build/baba-yaga-windows-* +``` + +### **3. Upload to Release:** +```bash +# Example: GitHub releases, package managers, etc. +gh release create v1.0.0 \ + baba-yaga-macos.tar.gz \ + baba-yaga-linux.tar.gz \ + baba-yaga-windows.zip +``` + +## ๐ซ **Limitations** + +### **BSD Support:** +- Not directly supported by Bun's cross-compilation +- Would require manual toolchain setup (osxcross, etc.) +- Complex and not recommended for most users + +### **Other Architectures:** +- Currently limited to x86_64 and ARM64 +- No ARM32, RISC-V, or other architectures +- Bun roadmap may expand this in the future + +## ๐ **Summary** + +**Cross-compilation with Bun is incredibly straightforward!** + +From your Mac, you can: +- โ Build for 4 major platforms with simple commands +- โ No complex toolchain setup required +- โ Same performance and features across all platforms +- โ Distribute truly standalone executables +- โ Support 99% of desktop/server users + +For now, focusing on **macOS, Linux, and Windows** gives you excellent coverage, and Bun makes it **dead simple** to support all three from your development machine. + +**Recommendation**: Stick with the supported platforms (macOS/Linux/Windows) - they cover the vast majority of users and require zero additional complexity! diff --git a/js/baba-yaga/scratch/docs/GAME-ENGINE-ARCHITECTURE.md b/js/baba-yaga/scratch/docs/GAME-ENGINE-ARCHITECTURE.md new file mode 100644 index 0000000..fb9e726 --- /dev/null +++ b/js/baba-yaga/scratch/docs/GAME-ENGINE-ARCHITECTURE.md @@ -0,0 +1,474 @@ +# Baba Yaga Game Engine Architecture + +## Vision + +Create an integrated game development environment inspired by Pico-8, where Baba Yaga serves as the scripting language for creating 2D graphical games, text adventures, and visual novels. The system should be approachable for beginners while powerful enough for complex games. + +## Core Design Principles + +1. **Batteries Included**: Everything needed for game development in one package +2. **Immediate Feedback**: Live coding with instant visual results +3. **Constraint-Based Creativity**: Reasonable limits that encourage creative solutions +4. **Cross-Genre Support**: Unified API that works for graphics, text, and hybrid games +5. **Functional Game Logic**: Leverage Baba Yaga's functional nature for clean game state management + +## System Architecture Overview + +``` +โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ +โ Baba Yaga Game Engine โ +โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค +โ Game Scripts (.baba files) โ +โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค +โ Game Runtime API โ +โ โโโ Graphics API โโโ Audio API โโโ Input API โ +โ โโโ Text/UI API โโโ Storage API โโโ Scene API โ +โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค +โ Core Engine Systems โ +โ โโโ Renderer โโโ Audio System โโโ Input Manager โ +โ โโโ Asset Loader โโโ Save System โโโ Scene Manager โ +โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค +โ Platform Layer (Web/Desktop/Mobile) โ +โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ +``` + +## Game Runtime API Design + +### Core Game Loop + +```baba +// Game entry point - called once at startup +init : () -> GameState; + +// Main update loop - called every frame +update : (state: GameState, input: Input, dt: Float) -> GameState; + +// Render function - called every frame after update +draw : (state: GameState) -> Unit; +``` + +### Graphics API (`gfx` namespace) + +**Display System:** +```baba +// Display management +gfx.width : Int; // Screen width (e.g., 320) +gfx.height : Int; // Screen height (e.g., 240) +gfx.clear : (color: Color) -> Unit; // Clear screen +gfx.present : () -> Unit; // Present frame (auto-called) + +// Coordinate system: (0,0) at top-left, (width-1, height-1) at bottom-right +``` + +**Drawing Primitives:** +```baba +// Basic shapes +gfx.pixel : (x: Int, y: Int, color: Color) -> Unit; +gfx.line : (x1: Int, y1: Int, x2: Int, y2: Int, color: Color) -> Unit; +gfx.rect : (x: Int, y: Int, w: Int, h: Int, color: Color) -> Unit; +gfx.rectFill : (x: Int, y: Int, w: Int, h: Int, color: Color) -> Unit; +gfx.circle : (x: Int, y: Int, radius: Int, color: Color) -> Unit; +gfx.circleFill : (x: Int, y: Int, radius: Int, color: Color) -> Unit; + +// Color system (16-color palette like Pico-8) +gfx.colors : { + black: 0, darkBlue: 1, darkPurple: 2, darkGreen: 3, + brown: 4, darkGray: 5, lightGray: 6, white: 7, + red: 8, orange: 9, yellow: 10, green: 11, + blue: 12, indigo: 13, pink: 14, peach: 15 +}; +``` + +**Sprite System:** +```baba +// Sprite management +gfx.sprite : (id: Int, x: Int, y: Int) -> Unit; +gfx.spriteFlip : (id: Int, x: Int, y: Int, flipX: Bool, flipY: Bool) -> Unit; +gfx.spriteScale : (id: Int, x: Int, y: Int, scale: Float) -> Unit; + +// Sprite sheet: 128x128 pixels, 8x8 sprites = 16x16 grid of sprites +gfx.spriteSize : Int; // 8 pixels +gfx.spriteSheet : {width: 128, height: 128, sprites: 256}; +``` + +**Text Rendering:** +```baba +gfx.text : (text: String, x: Int, y: Int, color: Color) -> Unit; +gfx.textCentered : (text: String, x: Int, y: Int, color: Color) -> Unit; +gfx.textBox : (text: String, x: Int, y: Int, w: Int, h: Int, color: Color) -> Unit; +gfx.font : {width: 4, height: 6}; // Fixed-width bitmap font +``` + +### Audio API (`sfx` namespace) + +```baba +// Sound effects (64 slots) +sfx.play : (id: Int) -> Unit; +sfx.stop : (id: Int) -> Unit; +sfx.volume : (id: Int, volume: Float) -> Unit; // 0.0 to 1.0 + +// Music (8 tracks) +music.play : (track: Int) -> Unit; +music.stop : () -> Unit; +music.pause : () -> Unit; +music.resume : () -> Unit; +music.volume : (volume: Float) -> Unit; + +// Simple sound synthesis +sfx.beep : (frequency: Float, duration: Float) -> Unit; +sfx.noise : (duration: Float) -> Unit; +``` + +### Input API (`input` namespace) + +```baba +// Button states (8 buttons like a gamepad) +input.buttons : { + up: 0, down: 1, left: 2, right: 3, + a: 4, b: 5, start: 6, select: 7 +}; + +// Input checking +input.pressed : (button: Int) -> Bool; // Just pressed this frame +input.held : (button: Int) -> Bool; // Held down +input.released : (button: Int) -> Bool; // Just released this frame + +// Mouse/touch (for visual novels and UI) +input.mouse : {x: Int, y: Int, pressed: Bool, held: Bool, released: Bool}; + +// Keyboard (for text adventures) +input.key : (keyCode: String) -> Bool; +input.textInput : () -> String; // Text entered this frame +``` + +### Text/UI API (`ui` namespace) + +**For Text Adventures & Visual Novels:** +```baba +// Text display system +ui.textBuffer : [String]; // Scrolling text buffer +ui.print : (text: String) -> Unit; +ui.println : (text: String) -> Unit; +ui.clear : () -> Unit; +ui.scroll : (lines: Int) -> Unit; + +// Choice system for branching narratives +ui.choice : (prompt: String, options: [String]) -> Result Int String; +ui.input : (prompt: String) -> String; + +// Dialog system +ui.dialog : { + show: (speaker: String, text: String) -> Unit, + hide: () -> Unit, + isShowing: Bool +}; + +// Menu system +ui.menu : (title: String, options: [String], selected: Int) -> Int; +``` + +### Storage API (`save` namespace) + +```baba +// Persistent storage (like cartridge data) +save.set : (key: String, value: any) -> Unit; +save.get : (key: String) -> Result any String; +save.has : (key: String) -> Bool; +save.remove : (key: String) -> Unit; +save.clear : () -> Unit; + +// High scores, progress, settings +save.highScore : (score: Int) -> Unit; +save.getHighScore : () -> Int; +save.checkpoint : (data: any) -> Unit; +save.loadCheckpoint : () -> Result any String; +``` + +### Scene API (`scene` namespace) + +```baba +// Scene management for complex games +scene.current : String; +scene.switch : (name: String) -> Unit; +scene.push : (name: String) -> Unit; // For menus/overlays +scene.pop : () -> Unit; + +// Scene registration +scene.register : (name: String, init: () -> State, update: UpdateFn, draw: DrawFn) -> Unit; +``` + +## Game Types and Patterns + +### 2D Action/Platformer Games + +```baba +// Example: Simple platformer +GameState : { + player: {x: Float, y: Float, vx: Float, vy: Float}, + enemies: [Enemy], + level: Level, + score: Int +}; + +init : () -> GameState -> + { + player: {x: 160, y: 120, vx: 0, vy: 0}, + enemies: [], + level: loadLevel 1, + score: 0 + }; + +update : state input dt -> + with ( + // Handle input + newVx : when (input.held input.buttons.left) is + true then -100 + _ then when (input.held input.buttons.right) is + true then 100 + _ then state.player.vx * 0.8; // Friction + + // Update player + newPlayer : updatePlayer state.player newVx dt; + + // Update enemies + newEnemies : map (enemy -> updateEnemy enemy dt) state.enemies; + ) -> + set state "player" newPlayer + |> set "enemies" newEnemies; + +draw : state -> + gfx.clear gfx.colors.darkBlue; + drawLevel state.level; + drawPlayer state.player; + map drawEnemy state.enemies; + gfx.text ("Score: " .. (str.concat "" state.score)) 4 4 gfx.colors.white; +``` + +### Text Adventure Games + +```baba +// Example: Text adventure +AdventureState : { + currentRoom: String, + inventory: [String], + gameText: [String], + gameOver: Bool +}; + +init : () -> AdventureState -> + { + currentRoom: "start", + inventory: [], + gameText: ["Welcome to the Adventure!", "You are in a dark room."], + gameOver: false + }; + +update : state input dt -> + when (input.textInput != "") is + false then state + true then processCommand state (input.textInput); + +draw : state -> + gfx.clear gfx.colors.black; + drawTextBuffer state.gameText; + ui.print "> "; + +processCommand : state command -> + when (text.words command) is + ["go", direction] then movePlayer state direction + ["take", item] then takeItem state item + ["use", item] then useItem state item + ["inventory"] then showInventory state + _ then addText state "I don't understand that command."; +``` + +### Visual Novel Games + +```baba +// Example: Visual novel +NovelState : { + currentScene: String, + characterSprites: Table, + background: String, + textBox: {visible: Bool, speaker: String, text: String}, + choices: [String], + flags: Table // Story flags +}; + +init : () -> NovelState -> + { + currentScene: "intro", + characterSprites: {}, + background: "room", + textBox: {visible: false, speaker: "", text: ""}, + choices: [], + flags: {} + }; + +update : state input dt -> + when state.choices is + [] then // Regular dialog + when (input.pressed input.buttons.a) is + true then advanceDialog state + _ then state + _ then // Choice selection + when (input.pressed input.buttons.a) is + true then selectChoice state + _ then navigateChoices state input; + +draw : state -> + drawBackground state.background; + drawCharacterSprites state.characterSprites; + when state.textBox.visible is + true then drawTextBox state.textBox + _ then {}; + when (length state.choices > 0) is + true then drawChoices state.choices + _ then {}; +``` + +## Asset Management + +### File Structure +``` +game/ +โโโ main.baba # Entry point +โโโ sprites.png # 128x128 sprite sheet +โโโ sounds/ # Audio files +โ โโโ sfx_001.wav +โ โโโ music_001.wav +โโโ scenes/ # Scene scripts +โ โโโ intro.baba +โ โโโ gameplay.baba +โโโ data/ # Game data + โโโ levels.json + โโโ dialog.json +``` + +### Asset Loading +```baba +// Automatic asset loading based on file structure +assets.sprites : SpriteSheet; // sprites.png +assets.sounds : [Sound]; // sounds/*.wav +assets.music : [Music]; // sounds/music_*.wav +assets.data : Table; // data/*.json parsed as tables +``` + +## Development Tools Integration + +### Live Coding +- **Hot Reload**: Changes to .baba files reload game state +- **REPL Integration**: Debug console accessible during gameplay +- **State Inspector**: View and modify game state in real-time + +### Built-in Editors +- **Sprite Editor**: Pixel art editor for the 128x128 sprite sheet +- **Sound Editor**: Simple sound effect generator and sequencer +- **Map Editor**: Tile-based level editor for 2D games +- **Dialog Editor**: Visual dialog tree editor for visual novels + +### Export and Sharing +- **Web Export**: Single HTML file with embedded assets +- **Standalone Export**: Desktop executables +- **Cartridge Format**: .baba-cart files containing code and assets +- **Online Sharing**: Upload and share games on web platform + +## Performance Constraints + +Following Pico-8's philosophy of creative constraints: + +### Technical Limits +- **Display**: 320x240 pixels (or 640x480 for high-DPI) +- **Colors**: 16-color fixed palette +- **Sprites**: 256 sprites, 8x8 pixels each +- **Code Size**: ~32KB of Baba Yaga source code +- **Audio**: 64 sound effects, 8 music tracks +- **Save Data**: 1KB persistent storage + +### Performance Targets +- **60 FPS**: Smooth gameplay on modest hardware +- **Low Latency**: <16ms input response time +- **Fast Startup**: <2 seconds from launch to gameplay + +## Example Game Templates + +### Template 1: Arcade Game +```baba +// Minimal arcade game template +init : () -> {score: 0, gameOver: false}; + +update : state input dt -> + when state.gameOver is + true then + when (input.pressed input.buttons.start) is + true then init + _ then state + _ then updateGameplay state input dt; + +draw : state -> + gfx.clear gfx.colors.black; + when state.gameOver is + true then drawGameOver state.score + _ then drawGameplay state; +``` + +### Template 2: Text Adventure +```baba +// Text adventure template with room system +rooms : { + start: { + description: "A dark room with exits north and east.", + exits: {north: "hallway", east: "kitchen"}, + items: ["flashlight"] + } + // ... more rooms +}; + +processCommand : state command -> + when (parseCommand command) is + Move direction then moveToRoom state direction + Take item then takeItem state item + _ then unknownCommand state; +``` + +### Template 3: Visual Novel +```baba +// Visual novel template with scene system +scenes : { + intro: [ + {type: "background", image: "school"}, + {type: "character", name: "alice", position: "left"}, + {type: "dialog", speaker: "Alice", text: "Hello there!"}, + {type: "choice", options: ["Hello!", "Who are you?"]} + ] + // ... more scenes +}; + +playScene : state sceneData -> + reduce executeSceneAction state sceneData; +``` + +## Integration with Existing Baba Yaga Features + +### Leveraging Language Features +- **Pattern Matching**: Perfect for game state transitions and input handling +- **Immutability**: Clean game state management without side effects +- **Result Type**: Robust error handling for asset loading and save/load +- **Higher-Order Functions**: Flexible entity systems and behavior trees +- **Debug Tools**: Built-in debugging for game development + +### Extended Standard Library +- **Game Math**: Vector operations, collision detection, easing functions +- **Random Enhanced**: Seeded random for reproducible gameplay +- **Collections**: Spatial data structures for game entities +- **Validation**: Input validation for save data and user input + +## Next Steps + +1. **Prototype Core Systems**: Start with basic graphics and input +2. **Build Example Games**: Create reference implementations for each genre +3. **Developer Tools**: Integrated sprite and sound editors +4. **Community Platform**: Sharing and collaboration features +5. **Documentation**: Comprehensive tutorials and API reference + +This architecture provides a solid foundation for a Baba Yaga-powered game development environment that can handle everything from simple arcade games to complex visual novels, all while maintaining the functional programming principles that make Baba Yaga unique. diff --git a/js/baba-yaga/scratch/docs/GAME-ENGINE.md b/js/baba-yaga/scratch/docs/GAME-ENGINE.md new file mode 100644 index 0000000..2844540 --- /dev/null +++ b/js/baba-yaga/scratch/docs/GAME-ENGINE.md @@ -0,0 +1,1748 @@ +## Baba Yaga Text Adventure Game Engine - Project Plan + +### Phase 1: Understanding Baba Yaga +*(Completed)* + +* **Reviewed Baba Yaga Language Features:** + * Data Structures: Immutable lists and tables, `append`, `set`, `merge`, `shape`. + * Functional Programming: Immutability, first-class functions, currying, partial application, higher-order functions (`map`, `filter`, `reduce`), typed functions, combinators. + * Pattern Matching: `when` expressions (literals, wildcards, types, results, lists, tables). + * Recursion and Composition: Simple, tail-recursive, mutual recursion, functional composition. + * Types: Optional types, runtime type inference, validation, numeric lattices, `Result` type. +* **Analyzed Core Implementation:** + * `interpreter.js`: Execution, scope, native functions (`io`, math, list/table ops), type validation, function calls (closures, partial application). + * `runner.js`: Lexing, parsing, interpretation pipeline, error handling. + +### Phase 2: Game Engine Architecture Design +*(In Progress)* + +* **Goal:** To create a text adventure game engine using the Baba Yaga language, enabling users to implement games using only Baba Yaga. The games will be playable in the terminal or the browser, with identical core functionality (except for visual enhancements in the browser). + +* **Core Components:** + * **JavaScript Host Components:** + * **Game Loop Manager:** Orchestrates the flow of the game. + * **Input/Output Abstraction:** Leverages Baba Yaga's `io` functions (`io.in`, `io.listen`, `io.out`, `io.emit`) to handle player input and game output across different environments. + * **Game State Management:** Implemented using the Elm Architecture (TEA) principles in JavaScript. Manages a serializable state object that represents the current game world, player status, available actions, and visual elements. Supports snapshotting, rewinding, and state restoration (save/load functionality). + * **Command Parser/Input Handler:** Presents players with predefined lists of verbs and nouns for interaction. In the terminal, this will be a numbered list or a richer TUI interface. In the browser, this will be clickable links/buttons. These selections are passed to the Baba Yaga script via the IO abstraction. + * **Rendering Abstraction:** Provides a unified way to render the game state, abstracting differences between terminal (TUI) and browser (HTML/CSS) output. Browser versions will support optional visual-novel style background images that can change based on game state. + * **Baba Yaga Game Script (`.baba` files):** + * Monolithic files initially, defining game logic, world, items, narrative, puzzles, and player interaction using Baba Yaga's features. + * Will define and mutate the game state through function composition. + * Will reference image assets in a structured directory alongside the `.baba` file for browser-based visual enhancements. + +* **Integration & Execution:** + * The JavaScript engine will use the existing Baba Yaga interpreter to execute game scripts. + * Player selections from the JS host are passed as messages/inputs to the Baba Yaga interpreter via its `io` words. + * Baba Yaga scripts, through function composition, will mutate the game state. + * The JS engine will manage the serialization and deserialization of the full state object for saving/loading. + +* **State Object (`Model`) Structure:** + * `current_scene`: Represents the player's current location. This could be a scene ID or a detailed scene object. + * `scenes`: A collection (e.g., map/dictionary) of all scenes in the game, mapping scene identifiers to scene objects. Each scene object will contain: + * Description text. + * Available actions and nouns relevant to the scene. + * Connections to other scenes (e.g., mapping directions to scene identifiers). + * Optional visual elements (e.g., background image paths) for browser rendering. + * The entire state object will be serializable (e.g., to JSON) for persistence. + +* **Example Implementation:** + * Create a simple `example.baba` to demonstrate basic game loop, scene navigation, item interaction, and state saving/loading. + +### Phase 3: Detailed Technical Specifications + +#### 3.1 Core Data Structures and Types + +**Game State Model (Baba Yaga Table Structure):** +```baba +// Core game state structure - simplified and focused +GameState : { + current_scene_id: String, + player: Player, + inventory: List, + flags: Table, + history: List, + messages: List // Recent game messages for UI +}; + +// Player state +Player : { + name: String, + health: Int, + max_health: Int, + location: String, + attributes: Table +}; + +// Scene definition - returned by scene functions +Scene : { + id: String, + title: String, + description: String, + exits: Table, // direction -> scene_id + items: List, // item_ids + npcs: List, // npc_ids + actions: List, // available action_ids + background: String, // optional image path + visited: Bool +}; + +// Action definition +Action : { + id: String, + name: String, + description: String, + verbs: List, // ["take", "pick up", "grab"] + nouns: List, // ["key", "sword", "book"] + conditions: Function, // (state, args) -> Bool + effect: Function, // (state, args) -> GameState + available: Function // (state) -> Bool +}; + +// Item definition +Item : { + id: String, + name: String, + description: String, + portable: Bool, + usable: Bool, + actions: List, // action_ids + properties: Table +}; +``` + +**Scene Management Architecture - State as Functions:** + +```baba +// Scene functions that take game state and return scene data +// This allows scenes to be dynamic and responsive to game state + +entrance_scene : state -> { + id: "entrance", + title: "Cave Entrance", + description: when state.flags.torch_lit is + true then "The cave entrance is illuminated by your torch, revealing ancient markings on the walls..." + false then "You stand at the entrance to a dark cave. The shadows seem to swallow all light...", + exits: when state.flags.cave_unlocked is + true then { "north": "main_cavern", "east": "side_tunnel" } + false then { "north": "main_cavern" }, + items: when state.flags.torch_taken is + true then [] + false then ["torch"], + npcs: when state.flags.old_man_met is + true then [] + false then ["old_man"], + actions: ["take", "look", "move", "talk"], + background: when state.flags.torch_lit is + true then "cave_entrance_lit.jpg" + false then "cave_entrance_dark.jpg", + visited: state.flags.entrance_visited +}; + +main_cavern_scene : state -> { + id: "main_cavern", + title: "Main Cavern", + description: when state.flags.torch_lit is + true then "A vast cavern stretches before you, stalactites glistening in the torchlight..." + false then "Complete darkness. You can hear water dripping somewhere in the distance...", + exits: { "south": "entrance", "east": "treasure_room", "west": "chasm" }, + items: when state.flags.sword_found is + true then [] + false then ["ancient_sword"], + actions: ["take", "look", "move", "listen"], + background: "main_cavern.jpg", + visited: state.flags.main_cavern_visited +}; + +// Scene registry function +scene_registry : scene_id -> + when scene_id is + "entrance" then entrance_scene + "main_cavern" then main_cavern_scene + "treasure_room" then treasure_room_scene + "side_tunnel" then side_tunnel_scene + "chasm" then chasm_scene + _ then error_scene; + +// Scene resolver - gets current scene data +get_current_scene : state -> + (scene_registry state.current_scene_id) state; + +// Scene state management +mark_scene_visited : state scene_id -> + { state with flags: set state.flags (scene_id .. "_visited") true }; + +update_scene_flag : state scene_id flag_name value -> + { state with flags: set state.flags (scene_id .. "_" .. flag_name) value }; +``` + +**Scene Composition Utilities:** + +```baba +// Scene building blocks for composition +base_scene : id title description -> { + id: id, + title: title, + description: description, + exits: {}, + items: [], + npcs: [], + actions: ["look"], + background: "", + visited: false +}; + +with_exits : scene exits -> { scene with exits: exits }; +with_items : scene items -> { scene with items: items }; +with_npcs : scene npcs -> { scene with npcs: npcs }; +with_actions : scene actions -> { scene with actions: actions }; +with_background : scene bg -> { scene with background: bg }; + +// Conditional scene modifiers +with_conditional_exits : scene condition exits -> + when condition is + true then with_exits scene exits + false then scene; + +with_conditional_items : scene condition items -> + when condition is + true then with_items scene items + false then scene; + +// Example of composed scene +simple_entrance : state -> + pipe + (base_scene "entrance" "Cave Entrance" "You stand at the entrance...") + (with_exits { "north": "main_cavern" }) + (with_conditional_items (not state.flags.torch_taken) ["torch"]) + (with_actions ["take", "look", "move"]) + (with_background "cave_entrance.jpg"); +``` + +#### 3.2 JavaScript Host API Interface + +**Game Engine Class Structure:** +```javascript +class BabaYagaGameEngine { + constructor(gameScript, options = {}) { + this.gameScript = gameScript; + this.interpreter = null; + this.currentState = null; + this.history = []; + this.maxHistory = options.maxHistory || 50; + this.assetPath = options.assetPath || './assets'; + this.eventListeners = new Map(); + this.memoizedScenes = new Map(); + + // IO abstraction for Baba Yaga + this.host = { + io: { + out: this.handleOutput.bind(this), + in: this.handleInput.bind(this), + emit: this.handleEmit.bind(this), + listen: this.handleListen.bind(this) + } + }; + } + + // Core game loop methods + async initialize() { + try { + // Initialize Baba Yaga interpreter + const result = await this.evaluateBabaYaga('init_game()'); + if (result.ok) { + this.currentState = result.value; + this.pushToHistory(this.currentState); + return { ok: true, state: this.currentState }; + } else { + throw new GameError('Failed to initialize game', 'runtime', result.error); + } + } catch (error) { + throw new GameError('Initialization failed', 'runtime', error); + } + } + + async processCommand(verb, noun) { + try { + // Validate input + if (!verb || typeof verb !== 'string') { + throw new GameError('Invalid verb', 'input', { verb, noun }); + } + + // Process action through Baba Yaga + const actionCall = `process_action(${JSON.stringify(this.currentState)}, "${verb}", "${noun || ''}")`; + const result = await this.evaluateBabaYaga(actionCall); + + if (result.ok) { + const newState = result.value; + this.setState(newState); + this.pushToHistory(newState); + + // Mark scene as visited + const sceneCall = `mark_scene_visited(${JSON.stringify(newState)}, "${newState.current_scene_id}")`; + const sceneResult = await this.evaluateBabaYaga(sceneCall); + if (sceneResult.ok) { + this.setState(sceneResult.value); + } + + return { ok: true, state: this.currentState }; + } else { + throw new GameError('Action processing failed', 'runtime', result.error); + } + } catch (error) { + throw new GameError('Command processing failed', 'runtime', error); + } + } + + async saveGame(slot) { + try { + const saveCall = `save_game(${JSON.stringify(this.currentState)})`; + const result = await this.evaluateBabaYaga(saveCall); + + if (result.ok) { + const serializedState = result.value; + // Store in localStorage or file system + if (typeof localStorage !== 'undefined') { + localStorage.setItem(`baba_yaga_save_${slot}`, serializedState); + } + return { ok: true, slot }; + } else { + throw new GameError('Save failed', 'runtime', result.error); + } + } catch (error) { + throw new GameError('Save operation failed', 'runtime', error); + } + } + + async loadGame(slot) { + try { + let serializedState; + if (typeof localStorage !== 'undefined') { + serializedState = localStorage.getItem(`baba_yaga_save_${slot}`); + } + + if (!serializedState) { + throw new GameError('Save file not found', 'state', { slot }); + } + + const loadCall = `load_game("${serializedState}")`; + const result = await this.evaluateBabaYaga(loadCall); + + if (result.ok) { + this.setState(result.value); + this.pushToHistory(result.value); + return { ok: true, state: this.currentState }; + } else { + throw new GameError('Load failed', 'runtime', result.error); + } + } catch (error) { + throw new GameError('Load operation failed', 'runtime', error); + } + } + + async undo() { + if (this.history.length > 1) { + this.history.pop(); // Remove current state + const previousState = this.history[this.history.length - 1]; + this.setState(previousState); + return { ok: true, state: this.currentState }; + } + return { ok: false, error: 'No history to undo' }; + } + + // State management + getCurrentState() { + return this.currentState; + } + + setState(newState) { + this.currentState = newState; + // Clear memoized scenes when state changes + this.memoizedScenes.clear(); + // Emit state change event + this.emit('stateChanged', newState); + } + + pushToHistory(state) { + this.history.push(JSON.parse(JSON.stringify(state))); // Deep clone + if (this.history.length > this.maxHistory) { + this.history.shift(); + } + } + + // Baba Yaga evaluation + async evaluateBabaYaga(code) { + try { + // Combine game script with evaluation code + const fullCode = this.gameScript + '\n' + code; + const result = await evaluate(fullCode, this.host); + return result; + } catch (error) { + return { ok: false, error }; + } + } + + // Scene management + async getCurrentScene() { + if (!this.currentState) return null; + + // Check memoization + const memoKey = `${this.currentState.current_scene_id}_${JSON.stringify(this.currentState.flags)}`; + if (this.memoizedScenes.has(memoKey)) { + return this.memoizedScenes.get(memoKey); + } + + try { + const sceneCall = `get_current_scene(${JSON.stringify(this.currentState)})`; + const result = await this.evaluateBabaYaga(sceneCall); + + if (result.ok) { + this.memoizedScenes.set(memoKey, result.value); + return result.value; + } else { + throw new GameError('Failed to get scene', 'runtime', result.error); + } + } catch (error) { + throw new GameError('Scene retrieval failed', 'runtime', error); + } + } + + async getAvailableActions() { + try { + const actionsCall = `get_available_actions(${JSON.stringify(this.currentState)})`; + const result = await this.evaluateBabaYaga(actionsCall); + + if (result.ok) { + return result.value; + } else { + throw new GameError('Failed to get actions', 'runtime', result.error); + } + } catch (error) { + throw new GameError('Action retrieval failed', 'runtime', error); + } + } + + // Event handling + handleOutput(...args) { + const message = args.map(arg => String(arg)).join(' '); + this.emit('output', message); + } + + handleInput() { + // This would be implemented by the UI layer + return ''; + } + + handleEmit(topic, data) { + this.emit(topic, data); + } + + handleListen(topic, handler) { + if (!this.eventListeners.has(topic)) { + this.eventListeners.set(topic, []); + } + this.eventListeners.get(topic).push(handler); + + // Return unsubscribe function + return () => { + const listeners = this.eventListeners.get(topic); + const index = listeners.indexOf(handler); + if (index > -1) { + listeners.splice(index, 1); + } + }; + } + + emit(event, data) { + const listeners = this.eventListeners.get(event) || []; + listeners.forEach(listener => { + try { + listener(data); + } catch (error) { + console.error('Event listener error:', error); + } + }); + } + + // Rendering + async render() { + const scene = await this.getCurrentScene(); + const actions = await this.getAvailableActions(); + return { scene, actions, state: this.currentState }; + } +} + +// Game error class +class GameError extends Error { + constructor(message, type, details) { + super(message); + this.name = 'GameError'; + this.type = type; // 'parse', 'runtime', 'state', 'asset', 'input' + this.details = details; + } +} +``` + +**Game Loop Implementation:** +```javascript +class GameLoop { + constructor(engine, interface) { + this.engine = engine; + this.interface = interface; + this.isRunning = false; + this.pendingInput = null; + this.inputResolver = null; + } + + async start() { + this.isRunning = true; + + try { + // Initialize game + await this.engine.initialize(); + + // Initial render + await this.render(); + + // Start input loop + await this.inputLoop(); + } catch (error) { + this.interface.handleError(error); + } + } + + async inputLoop() { + while (this.isRunning) { + try { + // Wait for input + const input = await this.waitForInput(); + + if (input.type === 'command') { + await this.processCommand(input.verb, input.noun); + } else if (input.type === 'system') { + await this.processSystemCommand(input.command); + } + + // Render updated state + await this.render(); + } catch (error) { + this.interface.handleError(error); + } + } + } + + async waitForInput() { + return new Promise((resolve) => { + this.inputResolver = resolve; + }); + } + + async processCommand(verb, noun) { + const result = await this.engine.processCommand(verb, noun); + if (!result.ok) { + throw new GameError('Command failed', 'runtime', result.error); + } + } + + async processSystemCommand(command) { + switch (command) { + case 'save': + await this.engine.saveGame('auto'); + break; + case 'load': + await this.engine.loadGame('auto'); + break; + case 'undo': + await this.engine.undo(); + break; + case 'quit': + this.isRunning = false; + break; + default: + throw new GameError(`Unknown system command: ${command}`, 'input'); + } + } + + async render() { + const renderData = await this.engine.render(); + this.interface.render(renderData); + } + + provideInput(input) { + if (this.inputResolver) { + this.inputResolver(input); + this.inputResolver = null; + } + } + + stop() { + this.isRunning = false; + } +} +``` + +**Terminal Interface:** +```javascript +class TerminalGameInterface { + constructor(engine) { + this.engine = engine; + this.gameLoop = new GameLoop(engine, this); + this.inputBuffer = ''; + this.isInputMode = false; + } + + async start() { + // Set up terminal input handling + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8'); + + process.stdin.on('data', (key) => { + this.handleKeyPress(key); + }); + + // Start game loop + await this.gameLoop.start(); + } + + handleKeyPress(key) { + if (key === '\u0003') { // Ctrl+C + this.quit(); + return; + } + + if (this.isInputMode) { + if (key === '\r' || key === '\n') { // Enter + this.submitInput(); + } else if (key === '\u007f') { // Backspace + this.inputBuffer = this.inputBuffer.slice(0, -1); + this.redrawInput(); + } else { + this.inputBuffer += key; + this.redrawInput(); + } + } else { + // Handle menu navigation + this.handleMenuKey(key); + } + } + + async render(renderData) { + const { scene, actions, state } = renderData; + + // Clear screen + process.stdout.write('\x1B[2J\x1B[0f'); + + // Render scene + this.renderScene(scene); + + // Render actions + this.renderActions(actions); + + // Render status + this.renderStatus(state); + + // Show input prompt + this.showInputPrompt(); + } + + renderScene(scene) { + console.log(`\n${scene.title}`); + console.log('='.repeat(scene.title.length)); + console.log(scene.description); + + if (scene.items.length > 0) { + console.log('\nItems here:'); + scene.items.forEach(item => console.log(` - ${item}`)); + } + + if (Object.keys(scene.exits).length > 0) { + console.log('\nExits:'); + Object.entries(scene.exits).forEach(([direction, target]) => { + console.log(` ${direction}: ${target}`); + }); + } + } + + renderActions(actions) { + console.log('\nAvailable actions:'); + actions.forEach((action, index) => { + console.log(` ${index + 1}. ${action}`); + }); + } + + renderStatus(state) { + console.log('\nStatus:'); + console.log(` Location: ${state.current_scene_id}`); + console.log(` Health: ${state.player.health}/${state.player.max_health}`); + console.log(` Inventory: ${state.inventory.length} items`); + } + + showInputPrompt() { + console.log('\n> '); + this.isInputMode = true; + } + + submitInput() { + const input = this.inputBuffer.trim(); + this.inputBuffer = ''; + this.isInputMode = false; + + if (input.startsWith('/')) { + this.gameLoop.provideInput({ type: 'system', command: input.slice(1) }); + } else { + // Parse verb/noun from input + const parts = input.split(' '); + const verb = parts[0]; + const noun = parts.slice(1).join(' '); + this.gameLoop.provideInput({ type: 'command', verb, noun }); + } + } + + redrawInput() { + process.stdout.write(`\r> ${this.inputBuffer}`); + } + + handleError(error) { + console.error(`\nError: ${error.message}`); + if (error.details) { + console.error('Details:', error.details); + } + } + + quit() { + process.exit(0); + } +} +``` + +**Browser Interface:** +```javascript +class BrowserGameInterface { + constructor(engine, container) { + this.engine = engine; + this.container = container; + this.gameLoop = new GameLoop(engine, this); + this.actionButtons = new Map(); + this.messageQueue = []; + + this.setupEventListeners(); + } + + setupEventListeners() { + // Listen for engine events + this.engine.on('output', (message) => { + this.addMessage(message, 'output'); + }); + + this.engine.on('stateChanged', (state) => { + this.updateUI(state); + }); + } + + async start() { + await this.gameLoop.start(); + } + + async render(renderData) { + const { scene, actions, state } = renderData; + + // Clear container + this.container.innerHTML = ''; + + // Render scene + this.renderScene(scene); + + // Render actions + this.renderActions(actions); + + // Render status + this.renderStatus(state); + + // Render messages + this.renderMessages(); + + // Render controls + this.renderControls(); + } + + renderScene(scene) { + const sceneEl = document.createElement('div'); + sceneEl.className = 'game-scene'; + + // Background image + if (scene.background) { + sceneEl.style.backgroundImage = `url(${this.engine.assetPath}/images/${scene.background})`; + } + + // Scene content + const titleEl = document.createElement('h2'); + titleEl.textContent = scene.title; + sceneEl.appendChild(titleEl); + + const descEl = document.createElement('p'); + descEl.textContent = scene.description; + sceneEl.appendChild(descEl); + + // Items + if (scene.items.length > 0) { + const itemsEl = document.createElement('div'); + itemsEl.className = 'scene-items'; + itemsEl.innerHTML = '<h3>Items here:</h3>'; + scene.items.forEach(item => { + const itemEl = document.createElement('div'); + itemEl.textContent = `- ${item}`; + itemsEl.appendChild(itemEl); + }); + sceneEl.appendChild(itemsEl); + } + + // Exits + if (Object.keys(scene.exits).length > 0) { + const exitsEl = document.createElement('div'); + exitsEl.className = 'scene-exits'; + exitsEl.innerHTML = '<h3>Exits:</h3>'; + Object.entries(scene.exits).forEach(([direction, target]) => { + const exitEl = document.createElement('div'); + exitEl.textContent = `${direction}: ${target}`; + exitsEl.appendChild(exitEl); + }); + sceneEl.appendChild(exitsEl); + } + + this.container.appendChild(sceneEl); + } + + renderActions(actions) { + const actionsEl = document.createElement('div'); + actionsEl.className = 'game-actions'; + + actionsEl.innerHTML = '<h3>Available actions:</h3>'; + + actions.forEach((action, index) => { + const button = document.createElement('button'); + button.textContent = action; + button.className = 'action-button'; + button.onclick = () => this.handleAction(action); + actionsEl.appendChild(button); + }); + + this.container.appendChild(actionsEl); + } + + renderStatus(state) { + const statusEl = document.createElement('div'); + statusEl.className = 'game-status'; + + statusEl.innerHTML = ` + <h3>Status:</h3> + <p>Location: ${state.current_scene_id}</p> + <p>Health: ${state.player.health}/${state.player.max_health}</p> + <p>Inventory: ${state.inventory.length} items</p> + `; + + this.container.appendChild(statusEl); + } + + renderMessages() { + if (this.messageQueue.length > 0) { + const messagesEl = document.createElement('div'); + messagesEl.className = 'game-messages'; + + this.messageQueue.forEach(message => { + const messageEl = document.createElement('div'); + messageEl.className = `message message-${message.type}`; + messageEl.textContent = message.text; + messagesEl.appendChild(messageEl); + }); + + this.container.appendChild(messagesEl); + this.messageQueue = []; + } + } + + renderControls() { + const controlsEl = document.createElement('div'); + controlsEl.className = 'game-controls'; + + const saveBtn = document.createElement('button'); + saveBtn.textContent = 'Save'; + saveBtn.onclick = () => this.gameLoop.provideInput({ type: 'system', command: 'save' }); + + const loadBtn = document.createElement('button'); + loadBtn.textContent = 'Load'; + loadBtn.onclick = () => this.gameLoop.provideInput({ type: 'system', command: 'load' }); + + const undoBtn = document.createElement('button'); + undoBtn.textContent = 'Undo'; + undoBtn.onclick = () => this.gameLoop.provideInput({ type: 'system', command: 'undo' }); + + controlsEl.appendChild(saveBtn); + controlsEl.appendChild(loadBtn); + controlsEl.appendChild(undoBtn); + + this.container.appendChild(controlsEl); + } + + handleAction(action) { + // Parse action into verb/noun + const parts = action.split(' '); + const verb = parts[0]; + const noun = parts.slice(1).join(' '); + + this.gameLoop.provideInput({ type: 'command', verb, noun }); + } + + addMessage(text, type = 'output') { + this.messageQueue.push({ text, type }); + } + + updateUI(state) { + // Update any UI elements that depend on state + // This could include updating inventory display, health bars, etc. + } + + handleError(error) { + this.addMessage(`Error: ${error.message}`, 'error'); + if (error.details) { + console.error('Game error details:', error.details); + } + } +} +``` + +#### 3.3 Baba Yaga Game Script API + +**Required Game Functions:** +```baba +// Game initialization +init_game : () -> GameState; + +// Core game loop functions +process_action : (state: GameState, verb: String, noun: String) -> GameState; +get_available_actions : (state: GameState) -> List; +render_scene : (state: GameState) -> String; + +// State management +save_game : (state: GameState) -> String; // Returns serialized state +load_game : (serialized: String) -> GameState; + +// Utility functions +is_action_available : (state: GameState, action_id: String) -> Bool; +get_item_by_id : (items: List, item_id: String) -> Maybe Item; +get_scene_by_id : (scenes: Table, scene_id: String) -> Maybe Scene; +``` + +**Example Game Script Structure:** +```baba +// Game data definitions +scenes : { + "entrance": { + id: "entrance", + title: "Cave Entrance", + description: "You stand at the entrance to a dark cave...", + exits: { "north": "main_cavern" }, + items: ["torch"], + npcs: [], + actions: ["take", "look", "move"], + background: "cave_entrance.jpg", + visited: false + } +}; + +items : { + "torch": { + id: "torch", + name: "Wooden Torch", + description: "A simple wooden torch...", + portable: true, + usable: true, + actions: ["light", "extinguish"], + properties: { "lit": false } + } +}; + +// Game logic functions +init_game : () -> { + current_scene: "entrance", + scenes: scenes, + player: { + name: "Adventurer", + health: 100, + max_health: 100, + location: "entrance", + attributes: { "strength": 10, "intelligence": 8 } + }, + inventory: [], + flags: { "torch_lit": false }, + history: [] +}; + +process_action : state verb noun -> + when verb noun is + "take" "torch" then take_torch state + "move" "north" then move_north state + "look" _ then look_around state + _ _ then state; // Unknown action, return unchanged state + +take_torch : state -> + when is_item_in_scene state "torch" is + true then { + state with + inventory: append state.inventory ["torch"], + scenes: update_scene_items state.scenes state.current_scene (remove_item "torch") + } + false then state; + +move_north : state -> + when get_scene_exit state.current_scene "north" is + Just scene_id then { + state with + current_scene: scene_id, + player: { state.player with location: scene_id } + } + Nothing then state; +``` + +#### 3.4 File Structure and Asset Management + +**Project Structure:** +``` +game-name/ +โโโ game.baba # Main game script +โโโ assets/ # Game assets +โ โโโ images/ # Background images +โ โ โโโ cave_entrance.jpg +โ โ โโโ main_cavern.jpg +โ โ โโโ ... +โ โโโ sounds/ # Audio files (future) +โ โโโ data/ # Additional game data +โโโ saves/ # Save game files +โ โโโ save1.json +โ โโโ save2.json +โ โโโ ... +โโโ README.md # Game documentation +``` + +**Asset Loading Strategy:** +- Browser: Assets loaded via HTTP requests, cached for performance +- Terminal: Asset paths stored but not loaded (text-only mode) +- Fallback: Graceful degradation when assets missing + +#### 3.5 Integration Patterns and Usage Examples + +**Basic Game Setup:** +```javascript +// Browser usage +import { BabaYagaGameEngine, BrowserGameInterface } from './game-engine.js'; + +async function startGame() { + const gameScript = await fetch('./game.baba').then(r => r.text()); + const engine = new BabaYagaGameEngine(gameScript, { + assetPath: './assets', + maxHistory: 100 + }); + + const container = document.getElementById('game-container'); + const interface = new BrowserGameInterface(engine, container); + + await interface.start(); +} + +// Terminal usage +import { BabaYagaGameEngine, TerminalGameInterface } from './game-engine.js'; +import { readFileSync } from 'fs'; + +async function startTerminalGame() { + const gameScript = readFileSync('./game.baba', 'utf8'); + const engine = new BabaYagaGameEngine(gameScript); + const interface = new TerminalGameInterface(engine); + + await interface.start(); +} +``` + +**Custom Game Interface:** +```javascript +class CustomGameInterface { + constructor(engine) { + this.engine = engine; + this.gameLoop = new GameLoop(engine, this); + } + + async render(renderData) { + const { scene, actions, state } = renderData; + + // Custom rendering logic + this.updateSceneDisplay(scene); + this.updateActionButtons(actions); + this.updateStatusBar(state); + } + + updateSceneDisplay(scene) { + // Custom scene rendering + const sceneEl = document.getElementById('scene'); + sceneEl.innerHTML = ` + <h1>${scene.title}</h1> + <p>${scene.description}</p> + ${this.renderItems(scene.items)} + ${this.renderExits(scene.exits)} + `; + } + + updateActionButtons(actions) { + const actionsEl = document.getElementById('actions'); + actionsEl.innerHTML = actions.map(action => + `<button onclick="handleAction('${action}')">${action}</button>` + ).join(''); + } + + updateStatusBar(state) { + const statusEl = document.getElementById('status'); + statusEl.innerHTML = ` + Location: ${state.current_scene_id} | + Health: ${state.player.health}/${state.player.max_health} | + Items: ${state.inventory.length} + `; + } + + handleError(error) { + console.error('Game error:', error); + // Custom error handling + } +} +``` + +**Event-Driven Game Logic:** +```javascript +// Listen for game events +engine.on('stateChanged', (newState) => { + console.log('Game state updated:', newState.current_scene_id); + updateUI(newState); +}); + +engine.on('output', (message) => { + addToMessageLog(message); +}); + +// Custom event handling +engine.on('itemPickedUp', (item) => { + playSound('pickup'); + showNotification(`Picked up ${item}`); +}); + +engine.on('sceneEntered', (scene) => { + if (scene.background) { + preloadImage(scene.background); + } +}); +``` + +**Advanced State Management:** +```javascript +class AdvancedGameEngine extends BabaYagaGameEngine { + constructor(gameScript, options = {}) { + super(gameScript, options); + this.stateObservers = new Set(); + this.stateMiddleware = []; + } + + addStateObserver(observer) { + this.stateObservers.add(observer); + } + + addStateMiddleware(middleware) { + this.stateMiddleware.push(middleware); + } + + setState(newState) { + // Apply middleware + let processedState = newState; + for (const middleware of this.stateMiddleware) { + processedState = middleware(processedState, this.currentState); + } + + // Update state + super.setState(processedState); + + // Notify observers + for (const observer of this.stateObservers) { + observer(processedState, this.currentState); + } + } + + // Custom state validation + validateState(state) { + const errors = []; + + if (!state.current_scene_id) { + errors.push('Missing current_scene_id'); + } + + if (!state.player) { + errors.push('Missing player data'); + } + + if (state.player.health < 0) { + errors.push('Player health cannot be negative'); + } + + return errors.length === 0 ? null : errors; + } +} +``` + +**Performance Optimizations:** +```javascript +class OptimizedGameEngine extends BabaYagaGameEngine { + constructor(gameScript, options = {}) { + super(gameScript, options); + this.sceneCache = new Map(); + this.actionCache = new Map(); + this.cacheTimeout = options.cacheTimeout || 5000; // 5 seconds + } + + async getCurrentScene() { + const cacheKey = this.getCacheKey(); + const cached = this.sceneCache.get(cacheKey); + + if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { + return cached.data; + } + + const scene = await super.getCurrentScene(); + this.sceneCache.set(cacheKey, { + data: scene, + timestamp: Date.now() + }); + + return scene; + } + + getCacheKey() { + return `${this.currentState.current_scene_id}_${JSON.stringify(this.currentState.flags)}`; + } + + // Batch state updates + batchUpdate(updates) { + let currentState = this.currentState; + + for (const update of updates) { + currentState = update(currentState); + } + + this.setState(currentState); + } + + // Debounced rendering + debouncedRender = debounce(async () => { + const renderData = await this.render(); + this.emit('render', renderData); + }, 16); // ~60fps +} + +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} +``` + +**Testing Integration:** +```javascript +class TestableGameEngine extends BabaYagaGameEngine { + constructor(gameScript, options = {}) { + super(gameScript, options); + this.testMode = options.testMode || false; + this.mockIO = options.mockIO || null; + } + + async evaluateBabaYaga(code) { + if (this.testMode && this.mockIO) { + // Use mock IO for testing + const testHost = { ...this.host, io: this.mockIO }; + return await evaluate(this.gameScript + '\n' + code, testHost); + } + return await super.evaluateBabaYaga(code); + } + + // Test utilities + setTestState(state) { + this.currentState = state; + } + + getTestState() { + return this.currentState; + } + + simulateAction(verb, noun) { + return this.processCommand(verb, noun); + } +} + +// Test example +describe('Game Engine Tests', () => { + let engine; + let mockIO; + + beforeEach(() => { + mockIO = { + out: jest.fn(), + in: jest.fn(() => 'test input'), + emit: jest.fn(), + listen: jest.fn() + }; + + engine = new TestableGameEngine(gameScript, { + testMode: true, + mockIO + }); + }); + + it('should process take torch action', async () => { + const initialState = createTestState('entrance', { torch_taken: false }); + engine.setTestState(initialState); + + const result = await engine.simulateAction('take', 'torch'); + + expect(result.ok).toBe(true); + expect(result.state.inventory).toContain('torch'); + expect(result.state.flags.torch_taken).toBe(true); + }); +}); +``` + +#### 3.6 Error Handling and Validation + +**Baba Yaga Error Handling:** +```baba +// Use Result type for error handling +validate_action : state verb noun -> + when is_valid_verb verb is + true then when is_valid_noun noun is + true then Ok (verb, noun) + false then Err "Invalid noun" + false then Err "Invalid verb"; + +safe_process_action : state verb noun -> + when validate_action state verb noun is + Ok (v, n) then process_action state v n + Err msg then { + state with + messages: append state.messages [msg] + }; + +// Scene validation +validate_scene_state : state scene_id -> + when scene_id is + "entrance" then validate_entrance_state state + "main_cavern" then validate_cavern_state state + _ then Ok state; + +// State validation utilities +validate_game_state : state -> + when state is + { current_scene_id: scene_id, player: player, inventory: inv, flags: flags } then + when validate_scene_id scene_id is + Ok _ then when validate_player player is + Ok _ then when validate_inventory inv is + Ok _ then Ok state + Err msg then Err ("Inventory error: " .. msg) + Err msg then Err ("Player error: " .. msg) + Err msg then Err ("Scene error: " .. msg) + _ then Err "Invalid state structure"; +``` + +**JavaScript Error Handling:** +```javascript +class GameError extends Error { + constructor(message, type, details) { + super(message); + this.name = 'GameError'; + this.type = type; // 'parse', 'runtime', 'state', 'asset', 'input' + this.details = details; + } +} + +// Error recovery strategies +async handleGameError(error) { + switch (error.type) { + case 'parse': + return this.handleParseError(error); + case 'runtime': + return this.handleRuntimeError(error); + case 'state': + return this.handleStateError(error); + case 'asset': + return this.handleAssetError(error); + case 'input': + return this.handleInputError(error); + default: + return this.handleUnknownError(error); + } +} + +// Specific error handlers +handleParseError(error) { + console.error('Parse error:', error.message); + // Could attempt to recover by re-parsing or showing syntax help + return { ok: false, error: 'Game script has syntax errors' }; +} + +handleRuntimeError(error) { + console.error('Runtime error:', error.message); + // Could attempt to rollback to previous state + return { ok: false, error: 'Game encountered a runtime error' }; +} + +handleStateError(error) { + console.error('State error:', error.message); + // Could attempt to restore from last known good state + return { ok: false, error: 'Game state is invalid' }; +} + +handleAssetError(error) { + console.error('Asset error:', error.message); + // Could fall back to text-only mode + return { ok: true, warning: 'Some assets could not be loaded' }; +} + +handleInputError(error) { + console.error('Input error:', error.message); + // Could show input help or suggestions + return { ok: false, error: 'Invalid input provided' }; +} +``` + +#### 3.7 Performance Considerations + +**State Management Optimization:** +- Immutable state updates using structural sharing +- Lazy evaluation of expensive computations +- State history with configurable depth +- Efficient serialization/deserialization +- Memoization of scene functions +- Batch state updates to reduce re-renders + +**Rendering Optimization:** +- Incremental UI updates +- Debounced action processing +- Asset preloading for browser interface +- Virtual scrolling for long text output +- Scene caching with TTL +- Event batching for multiple state changes + +**Memory Management:** +```javascript +class MemoryOptimizedEngine extends BabaYagaGameEngine { + constructor(gameScript, options = {}) { + super(gameScript, options); + this.weakRefs = new WeakMap(); + this.gcThreshold = options.gcThreshold || 1000; + this.operationCount = 0; + } + + setState(newState) { + super.setState(newState); + this.operationCount++; + + if (this.operationCount > this.gcThreshold) { + this.performGarbageCollection(); + } + } + + performGarbageCollection() { + // Clear caches + this.memoizedScenes.clear(); + this.sceneCache.clear(); + this.actionCache.clear(); + + // Trim history if too long + if (this.history.length > this.maxHistory * 2) { + this.history = this.history.slice(-this.maxHistory); + } + + this.operationCount = 0; + } +} +``` + +#### 3.8 Testing Strategy + +**Unit Tests:** +- Baba Yaga game logic functions +- JavaScript engine components +- State management operations +- Error handling scenarios +- Scene function validation +- Action processing logic + +**Integration Tests:** +- Full game loop execution +- Save/load functionality +- Cross-platform compatibility +- Asset loading and rendering +- Event system integration +- State persistence + +**Example Test Structure:** +```javascript +describe('Game Engine', () => { + it('should initialize game state correctly', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + const state = engine.getCurrentState(); + expect(state.current_scene_id).toBe('entrance'); + }); + + it('should process valid actions', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + const result = await engine.processCommand('take', 'torch'); + expect(result.ok).toBe(true); + expect(result.state.inventory).toContain('torch'); + }); + + it('should handle invalid actions gracefully', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + const result = await engine.processCommand('invalid', 'action'); + expect(result.ok).toBe(true); // Should not crash, just return unchanged state + }); + + it('should maintain state history', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + const initialState = engine.getCurrentState(); + await engine.processCommand('take', 'torch'); + + expect(engine.history.length).toBe(2); + expect(engine.history[0]).toEqual(initialState); + }); +}); + +describe('Scene Functions', () => { + it('should return different descriptions based on state', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + const scene1 = await engine.getCurrentScene(); + expect(scene1.description).toContain('dark cave'); + + // Update state to light torch + engine.setState({ + ...engine.getCurrentState(), + flags: { ...engine.getCurrentState().flags, torch_lit: true } + }); + + const scene2 = await engine.getCurrentScene(); + expect(scene2.description).toContain('illuminated'); + }); +}); + +describe('Save/Load System', () => { + it('should save and restore game state', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + await engine.processCommand('take', 'torch'); + const stateBeforeSave = engine.getCurrentState(); + + await engine.saveGame('test'); + + // Reset engine + await engine.initialize(); + expect(engine.getCurrentState().inventory).toEqual([]); + + await engine.loadGame('test'); + expect(engine.getCurrentState()).toEqual(stateBeforeSave); + }); +}); +``` + +**Performance Tests:** +```javascript +describe('Performance', () => { + it('should handle rapid state changes efficiently', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + const startTime = performance.now(); + + for (let i = 0; i < 100; i++) { + await engine.processCommand('look', 'around'); + } + + const endTime = performance.now(); + expect(endTime - startTime).toBeLessThan(1000); // Should complete in under 1 second + }); + + it('should cache scene data appropriately', async () => { + const engine = new BabaYagaGameEngine(gameScript); + await engine.initialize(); + + // First call should populate cache + const scene1 = await engine.getCurrentScene(); + expect(engine.memoizedScenes.size).toBe(1); + + // Second call should use cache + const scene2 = await engine.getCurrentScene(); + expect(scene1).toBe(scene2); // Should be the same object reference + }); +}); +``` + +#### 3.8 Architectural Choices Emerging from Scene State as Functions + +**Choice 1: Dynamic Scene Content** +*Decision:* Scenes are functions that take game state and return scene data +*Benefits:* +- Scenes can react to player actions and game flags +- Content changes based on story progression +- No need to pre-compute all possible scene variations +- Natural fit for Baba Yaga's functional paradigm + +*Consequences:* +- Scene functions must be pure and predictable +- Game state becomes the single source of truth +- Scene testing requires state fixtures + +**Choice 2: Lazy Scene Loading** +*Decision:* Only load scene data when needed via function calls +*Benefits:* +- Memory efficient for large games +- Scenes can be defined in separate modules +- Easy to add new scenes without modifying existing code +- Natural code splitting and organization + +*Consequences:* +- Scene registry must be maintained +- Error handling for missing scenes +- Potential performance cost of function calls + +**Choice 3: State-Driven Rendering** +*Decision:* All scene content (descriptions, exits, items) depends on game state +*Benefits:* +- Consistent state management +- Easy to implement story branches +- Natural progression tracking +- Simplified save/load (only state, not scene data) + +*Consequences:* +- Scene functions must handle all state combinations +- More complex scene logic +- Need for state validation + +**Choice 4: Composition Over Inheritance** +*Decision:* Use function composition to build complex scenes from simple parts +*Benefits:* +- Reusable scene components +- Easy to test individual parts +- Clear separation of concerns +- Leverages Baba Yaga's functional strengths + +*Consequences:* +- Need for composition utilities +- Potential for over-abstraction +- Learning curve for composition patterns + +**Choice 5: Immutable Scene Updates** +*Decision:* Scene functions return new scene data, never modify existing state +*Benefits:* +- Predictable behavior +- Easy debugging and testing +- Natural fit with Baba Yaga's immutability +- Enables time-travel debugging + +*Consequences:* +- More memory usage for large scenes +- Need for efficient update patterns +- All state changes must be explicit + +**Choice 6: Flag-Based State Management** +*Decision:* Use a flat flag structure for tracking game progress +*Benefits:* +- Simple and predictable +- Easy to serialize/deserialize +- Clear naming conventions +- Easy to debug and inspect + +*Consequences:* +- Potential for flag explosion +- Need for flag organization strategy +- Manual flag management + +**Emerging Patterns and Utilities:** + +```baba +// State validation utilities +validate_scene_state : state scene_id -> + when scene_id is + "entrance" then validate_entrance_state state + "main_cavern" then validate_cavern_state state + _ then Ok state; + +// Scene transition utilities +can_transition_to : state from_scene to_scene -> + when from_scene is + "entrance" then when to_scene is + "main_cavern" then true + "side_tunnel" then state.flags.cave_unlocked + _ then false + _ then false; + +// Scene content utilities +get_scene_description : state scene_id -> + when scene_id is + "entrance" then entrance_scene state.description + "main_cavern" then main_cavern_scene state.description + _ then "You are in an unknown location."; + +// State update utilities +update_scene_state : state scene_id updates -> + fold (state, update) -> update state state updates; + +// Scene testing utilities +create_test_state : scene_id flags -> + { + current_scene_id: scene_id, + player: default_player, + inventory: [], + flags: flags, + history: [], + messages: [] + }; +``` + +**Performance Considerations:** +- Scene functions should be memoized for repeated calls +- State updates should be batched when possible +- Scene composition should be optimized for common patterns +- Flag access should be efficient (consider using a more structured approach for large games) + +**Scalability Patterns:** +- Scene modules for different game areas +- Shared state utilities across scenes +- Scene templates for common patterns +- State validation at scene boundaries + +### Next Steps: + +1. **Implement Core Engine:** Start with the `BabaYagaGameEngine` class and basic state management using the scene state as functions approach. +2. **Create Scene Function Framework:** Implement the scene registry, composition utilities, and state management functions. +3. **Build Sample Game:** Develop a simple 2-3 room adventure using the new scene architecture to validate the design. +4. **Implement State-Driven Rendering:** Create the rendering system that uses scene functions to generate UI content. +5. **Add Scene Composition Utilities:** Build the `base_scene`, `with_exits`, `with_items` etc. utilities for easy scene creation. +6. **Enhance Browser Interface:** Extend the existing web app with game-specific UI components that work with dynamic scene content. +7. **Implement Save/Load:** Add persistence functionality that serializes only the game state (not scene data). +8. **Add Performance Optimizations:** Implement memoization for scene functions and efficient state updates. +9. **Create Scene Testing Framework:** Build utilities for testing scene functions with different state configurations. +10. **Documentation and Examples:** Create comprehensive documentation and sample games demonstrating the scene architecture. diff --git a/js/baba-yaga/scratch/docs/IO.md b/js/baba-yaga/scratch/docs/IO.md new file mode 100644 index 0000000..6399b66 --- /dev/null +++ b/js/baba-yaga/scratch/docs/IO.md @@ -0,0 +1,198 @@ +# IO Plan: Events and Effects for Baba Yaga + +This document proposes an opinionated IO interface for embedding Baba Yaga programs into host systems. We introduce two core primitives for evented IO: + +- `io.listen` โ subscribe to external events by name +- `io.emit` โ publish events (commands/effects) produced by Baba Yaga + +The design follows a TEA/FRP-inspired architecture: a single unidirectional data-flow where external inputs become events, user logic transforms state and produces new effects, and the host executes those effects and feeds results/errors back as new events. + +## Goals + +- One clean, documented integration path for host systems (Node, browser, services) +- Pure, testable user code: all side-effects mediated by the host via `io.emit` and `io.listen` +- Deterministic core: program logic remains a function of prior state and incoming events +- Easy to build SDKs/harnesses around a small surface area + +## Mental Model + +``` +External world -> Events -> Baba Yaga Program -> Effects -> Host executes -> more Events +``` + +- Events are immutable data records identified by a string `topic` and a payload `data`. +- Effects are commands (also a `topic` + `data`) that the host is responsible for executing. +- Round-trips (request/response) are modeled as a pair of topics, e.g. `http.request` and `http.response`. + +## Core API (Language-Level Semantics) + +The following describes the intended semantics. Implementation comes later. + +- `io.listen topic handler` โ registers a handler function for events matching `topic`. + - `topic : String` + - `handler : (event: { topic: String, data: Table }) -> Unit` + - Returns `Unit`. + - Handlers are pure (no direct side-effects); they may call `io.emit` to request effects. + +- `io.emit topic data` โ emits an effect (command) to the host. + - `topic : String` + - `data : Table | List | String | Int | Float | Bool` + - Returns `Unit`. + +### Event Routing and Namespacing + +- Topics use dotted names, e.g. `app.tick`, `db.query`, `ws.message`, `http.request`. +- Conventionally, effects (commands) and events are separate namespaces; e.g. commands under `cmd.*` and events under `evt.*`, or paired `http.request`/`http.response`. + +## Program Structure (TEA-flavored) + +We encourage a TEA-like structure: + +```baba +// State is a Table (authorโs choice) +State : type alias Table; // conceptual + +// Update: State x Event -> (State, Effects) +update : (state, event) -> { state: State, effects: List } -> + when event.topic is + "app.init" then { state: { count: 0 }, effects: [] } + "app.tick" then { state: { count: state.count + 1 }, effects: [] } + "http.response" then { state: state, effects: [] } + _ then { state: state, effects: [] }; + +// Subscriptions: declare external event interest +init : -> + io.listen "app.tick" (e -> io.emit "cmd.render" { count: 0 }); + +// Effect producers inside handlers +handleTick : e -> io.emit "cmd.render" { count: e.data.count }; +``` + +Notes: +- `io.listen` is declarative; the host wires it to real sources. +- Handlers do not perform side-effects directly but may `io.emit` effects. + +## Host SDK / Harness + +The host must implement a small, stable interface to embed and drive programs. + +Suggested host-side API (pseudo-TypeScript): + +```ts +type Event = { topic: string; data: unknown }; +type Effect = { topic: string; data: unknown }; + +interface BabaProgram { + // Run a program to completion on an input source; returns a controller + start(initialEvents?: Event[]): ProgramController; +} + +interface ProgramController { + // Push an external event into the program + dispatch(event: Event): void; + // Subscribe to effects emitted by the program + onEffect(subscriber: (effect: Effect) => void): () => void; // unsubscribe + // Stop the program and cleanup + stop(): void; +} + +interface HostIO { + // Wire program-level io.listen registrations to host event sources + addListener(topic: string, handler: (event: Event) => void): () => void; // unsubscribe + // Deliver effects to the host for execution + deliver(effect: Effect): void; +} +``` + +### Responsibilities + +- Program side: + - Calls `io.listen` to declare interests (topics and handlers). + - Calls `io.emit` to request side-effects. + +- Host side: + - Maps external sources (timers, websockets, HTTP, DB) to events and pushes them into the program via `dispatch`. + - Executes effects received from `onEffect` subscribers; potentially pushes result events back (e.g., `http.response`). + - Manages lifecycle (start/stop) and isolation (per session or per process). + +### Startup sequence and initial effects + +- The host should invoke `program.start([{ topic: 'app.init', data: {...} }])` to bootstrap. +- User code can `io.listen "app.init"` to initialize state and immediately `io.emit` effects. +- This supports emitting to external systems as the very first action (e.g., schedule work, send a greeting, request data). + +Example (Baba Yaga): + +```baba +onInit : e -> + io.emit "cmd.fetch" { url: "https://api.example.com/data" }; + +main : -> + io.listen "app.init" onInit; +``` + +### Reference Topics + +We recommend a small standard vocabulary: + +- `app.init` โ sent once at startup +- `app.tick` โ periodic timer events +- `http.request` / `http.response` +- `ws.message` / `ws.send` +- `db.query` / `db.result` + +These are guidelines; projects can add more namespaced topics. + +## Example Embedding Flow (Host Pseudocode) + +```ts +const controller = program.start([{ topic: 'app.init', data: {} }]); + +controller.onEffect(async (eff) => { + if (eff.topic === 'http.request') { + const res = await fetch(eff.data.url, { method: eff.data.method }); + controller.dispatch({ topic: 'http.response', data: { status: res.status } }); + } + if (eff.topic === 'cmd.render') { + render(eff.data); // user-defined + } +}); + +// External source -> event +ws.onmessage = (msg) => controller.dispatch({ topic: 'ws.message', data: msg }); +``` + +## Testing Strategy + +- Treat user logic as pure functions of `(state, event) -> { state, effects }`. +- Unit-test handlers and updaters by asserting emitted effects and next state. +- Integration-test host harness by simulating effect execution and event feedback. + +## Summary + +- `io.listen` and `io.emit` define a minimal, opinionated bridge between Baba Yaga and the outside world. +- A TEA/FRP-inspired loop ensures purity and testability. +- A small host SDK surface (dispatch/onEffect) provides a unified way to embed programs across environments. + +## Application profiles (guidance) + +- Near real-time data processing + - Sources: streams, queues, file watchers -> `evt.ingest.*` + - Effects: enrichment, writes, notifications -> `cmd.write.*`, `cmd.notify.*` + - Backpressure: batch events or throttle at host; program remains pure + +- Games / simulations + - Drive time with `app.tick` at a fixed cadence + - Program updates state and emits `cmd.render` or `cmd.sound` effects; host renders + +- WebSocket comms + - Events: `ws.message` with parsed payload + - Effects: `ws.send` with outbound frames; host maps to actual socket + +- Data transformation/search + - Input events: `evt.query` or `evt.batch` + - Effects: `cmd.response` with transformed payloads + +This single event/effect interface scales across these use cases without leaking side-effects into program logic. + + diff --git a/js/baba-yaga/scratch/docs/LEXER_BUG_REPORT.md b/js/baba-yaga/scratch/docs/LEXER_BUG_REPORT.md new file mode 100644 index 0000000..4a2efe3 --- /dev/null +++ b/js/baba-yaga/scratch/docs/LEXER_BUG_REPORT.md @@ -0,0 +1,139 @@ +# Critical Lexer Bug Report + +## ๐จ **Issue Summary** + +The optimized regex-based lexer (`src/core/lexer.js`) has a critical bug that causes it to **skip large portions of input files** and produce incorrect tokens, leading to runtime errors. + +## ๐ **Impact Assessment** + +- **Severity**: Critical - causes complete parsing failures +- **Scope**: Context-dependent - works for simple cases, fails on complex files +- **Test Coverage**: All 210 tests pass (suggests bug is triggered by specific patterns) +- **Workaround**: Use `--legacy` flag to use the working legacy lexer + +## ๐ **Bug Symptoms** + +### **Observed Behavior:** +1. **Content Skipping**: Lexer jumps from beginning to middle/end of file +2. **Token Mangling**: Produces partial tokens (e.g., "esults" instead of "Results") +3. **Line Number Issues**: First token appears at line 21 instead of line 1 +4. **Variable Name Errors**: Runtime "Undefined variable" errors for correctly defined variables + +### **Example Failure:** +```bash +# Works with legacy +./build/baba-yaga life-final.baba --legacy # โ Success + +# Fails with optimized +./build/baba-yaga life-final.baba # โ ParseError: Unexpected token: COLON (:) +``` + +## ๐งช **Test Results** + +### **Lexer Compatibility Test:** +- โ Individual identifier lexing works correctly +- โ All 210 existing tests pass +- โ Complex files fail completely + +### **Debug Output Comparison:** + +**Legacy Lexer (Working):** +``` +Tokens generated: 160 +First token: IDENTIFIER = "var_with_underscore" (line 4, col 20) +``` + +**Optimized Lexer (Broken):** +``` +Tokens generated: 82 +First token: IDENTIFIER = "esults" (line 21, col 12) # โ Wrong! +``` + +## ๐ฌ **Technical Analysis** + +### **Suspected Root Causes:** + +1. **Regex Pattern Conflicts**: Token patterns may be interfering with each other +2. **Multiline Comment Handling**: `/^\/\/.*$/m` regex may be consuming too much +3. **Pattern Order Issues**: Longer patterns not matching before shorter ones +4. **Position Tracking Bug**: `advance()` function may have off-by-one errors + +### **Key Differences from Legacy:** + +| Aspect | Legacy | Optimized | Issue | +|--------|--------|-----------|--------| +| **Method** | Character-by-character | Regex-based | Regex conflicts | +| **Identifier Pattern** | `readWhile(isLetter)` | `/^[a-zA-Z_][a-zA-Z0-9_]*/` | Should be equivalent | +| **Comment Handling** | Manual parsing | `/^\/\/.*$/m` | May over-consume | +| **Error Recovery** | Graceful | Regex failures | May skip content | + +## ๐ **Attempted Fixes** + +### **What Was Tried:** +1. โ Verified identifier regex patterns match legacy behavior +2. โ Confirmed individual token patterns work correctly +3. โ Root cause in pattern interaction not yet identified + +### **What Needs Investigation:** +1. **Pattern Order**: Ensure longest patterns match first +2. **Multiline Regex**: Check if comment regex consumes too much +3. **Position Tracking**: Verify `advance()` function correctness +4. **Error Handling**: Check regex failure recovery + +## ๐ **Performance Impact** + +- **Legacy Lexer**: Reliable, slightly slower character-by-character parsing +- **Optimized Lexer**: When working, ~2-3x faster, but **completely broken** for many cases +- **Net Impact**: Negative due to correctness failures + +## โ **Recommended Actions** + +### **Immediate (Done):** +1. โ **Revert to legacy lexer by default** for reliability +2. โ **Document the bug** for future investigation +3. โ **Keep optimized lexer available** with explicit flag + +### **Future Investigation:** +1. **Debug regex pattern interactions** in isolation +2. **Add comprehensive lexer test suite** with problematic files +3. **Consider hybrid approach** (regex for simple tokens, fallback for complex) +4. **Profile memory usage** during lexing failures + +## ๐ง **Workarounds** + +### **For Users:** +```bash +# Use legacy lexer (reliable) +bun run index.js program.baba --legacy + +# Or configure engine +const config = new BabaYagaConfig({ enableOptimizations: false }); +``` + +### **For Development:** +```bash +# Test both lexers +bun run build.js --target=macos-arm64 # Uses legacy by default now +``` + +## ๐ **Files Affected** + +- `src/core/lexer.js` - Broken optimized lexer +- `src/legacy/lexer.js` - Working legacy lexer +- `src/core/engine.js` - Now defaults to legacy lexer +- `index.js` - Updated to use legacy by default + +## ๐ฏ **Success Criteria for Fix** + +1. **All existing tests pass** โ (already working) +2. **Complex files parse correctly** โ (currently broken) +3. **Performance improvement maintained** โ ๏ธ (secondary to correctness) +4. **No regressions in error messages** โ ๏ธ (needs verification) + +--- + +**Status**: **REVERTED TO LEGACY** - Optimized lexer disabled by default until bug is resolved. + +**Priority**: High - affects core language functionality + +**Assigned**: Future investigation needed diff --git a/js/baba-yaga/scratch/docs/README.md b/js/baba-yaga/scratch/docs/README.md new file mode 100644 index 0000000..1f9740f --- /dev/null +++ b/js/baba-yaga/scratch/docs/README.md @@ -0,0 +1,360 @@ +# Baba Yaga Programming Language + +A functional, immutable programming language with pattern matching, built-in error handling, and rich type support. + +## ๐ **New: High-Performance Engine** + +Baba Yaga now includes a **high-performance, enterprise-grade engine** with: +- **1.12x faster execution** with optimized lexing, parsing, and interpretation +- **Rich error handling** with source location, context, and helpful suggestions +- **Robust input validation** and security features +- **Performance monitoring** and statistics +- **100% backward compatibility** - all existing code works unchanged + +## Quick Start + +```bash +# Install dependencies +bun install + +# Run a Baba Yaga program (optimized by default) +bun run index.js example.baba + +# Enable debug mode for detailed information +bun run index.js example.baba --debug + +# Show performance profiling +bun run index.js example.baba --profile + +# Use legacy engine (for compatibility testing) +bun run index.js example.baba --legacy +``` + +## ๐๏ธ **New Organized Architecture** + +The codebase is now organized for clarity and maintainability: + +``` +baba-yaga/ +โโโ src/ +โ โโโ core/ # High-performance engine (primary) +โ โ โโโ engine.js # Main optimized engine +โ โ โโโ lexer.js # Regex-based optimized lexer +โ โ โโโ parser.js # Enhanced parser with rich errors +โ โ โโโ interpreter.js # Optimized interpreter +โ โ โโโ config.js # Comprehensive configuration system +โ โ โโโ error.js # Rich error handling with suggestions +โ โ โโโ validation.js # Input validation and security +โ โ โโโ scope-stack.js # Array-based scope optimization +โ โ โโโ builtins.js # Specialized built-in functions +โ โ โโโ ast-pool.js # Object pooling for memory efficiency +โ โโโ legacy/ # Original implementations (for compatibility) +โ โโโ benchmarks/ # Performance testing suite +โ โโโ utils/ # Utility functions +โโโ docs/ # Language documentation +โโโ tests/ # Comprehensive test suite (210 tests) +โโโ web/ # Web-based editor and playground +โโโ index.js # Main CLI entry point +``` + +## Language Features + +Baba Yaga is a functional scripting language designed for learning and experimentation, emphasizing functional programming patterns, currying, and powerful `when` expressions for pattern matching. + +### Variables and Functions +```baba +x : 42; +add : a b -> a + b; +result : add 10 20; +``` + +### Pattern Matching +```baba +processValue : x -> + when x is + 0 then "zero" + Int then "integer" + String then "text" + _ then "other"; +``` + +### Lists and Higher-Order Functions +```baba +numbers : [1, 2, 3, 4, 5]; +doubled : map (x -> x * 2) numbers; +evens : filter (x -> x % 2 = 0) doubled; +sum : reduce (acc x -> acc + x) 0 evens; +``` + +### Error Handling with Result Types +```baba +divide : a b -> + when b is + 0 then Err "Division by zero" + _ then Ok (a / b); + +result : divide 10 0; +message : when result is + Ok value then "Result: " .. value + Err error then "Error: " .. error; +``` + +### Recursive Functions with Local Bindings +```baba +fibonacci : n -> with rec ( + fib : x -> + when x is + 0 then 0 + 1 then 1 + _ then (fib (x - 1)) + (fib (x - 2)); +) -> fib n; +``` + +### Table (Object) Operations +```baba +person : { name: "Alice", age: 30 }; +updated : set "city" "New York" person; +keys : keys updated; +``` + +## ๐ก๏ธ **Enhanced Error Handling** + +### Before (Basic): +``` +RuntimeError: Undefined variable: undefinedVar +``` + +### After (Rich): +``` +RuntimeError: Undefined variable: undefinedVar + --> line 1, column 15 + 1 | badVar : undefinedVar + 5; + | ^^^^^^^^^^^^ + +Suggestions: + - Check if "undefinedVar" is spelled correctly + - Make sure the variable is declared before use + - Check if the variable is in the correct scope +``` + +## Built-in Functions + +### Higher-Order Functions +- `map fn list` - Apply function to each element +- `filter fn list` - Keep elements matching predicate +- `reduce fn init list` - Fold list into single value + +### List Operations +- `append list element` - Add element to end of list +- `prepend element list` - Add element to beginning of list +- `concat list1 list2` - Concatenate two lists +- `update index value list` - Replace element at index +- `removeAt index list` - Remove element at index +- `slice start end list` - Extract sublist + +### String Operations +- `str.concat str1 str2 ...` - Concatenate strings +- `str.split delimiter string` - Split string into list +- `str.join delimiter list` - Join list into string +- `str.length string` - Get string length +- `str.substring start end string` - Extract substring +- `str.replace old new string` - Replace substring +- `str.trim string` - Remove whitespace +- `str.upper string` - Convert to uppercase +- `str.lower string` - Convert to lowercase + +### Table Operations +- `set key value table` - Set property +- `remove key table` - Remove property +- `merge table1 table2` - Merge tables +- `keys table` - Get property keys +- `values table` - Get property values + +### Math Operations +- `math.abs x` - Absolute value +- `math.sign x` - Sign (-1, 0, 1) +- `math.min a b` - Minimum of two values +- `math.max a b` - Maximum of two values +- `math.clamp min max x` - Clamp value to range +- `math.floor x` - Round down to integer +- `math.ceil x` - Round up to integer +- `math.round x` - Round to nearest integer +- `math.trunc x` - Truncate to integer +- `math.pow base exp` - Power function +- `math.sqrt x` - Square root +- `math.exp x` - Exponential (e^x) +- `math.log x` - Natural logarithm +- `math.sin x` - Sine function +- `math.cos x` - Cosine function +- `math.tan x` - Tangent function +- `math.random` - Random number between 0 and 1 +- `math.randomInt min max` - Random integer in range + +### I/O Operations +- `io.out value` - Print value to console +- `io.in` - Read input from console +- `io.emit event data` - Emit event to host environment +- `io.listen event handler` - Listen for events from host + +### Utility Functions +- `length list` - Get list length +- `shape value` - Get type information + +## ๐ **Performance & Configuration** + +### API Usage +```javascript +import { BabaYagaEngine, BabaYagaConfig } from './src/core/engine.js'; + +// Basic usage (optimized by default) +const engine = new BabaYagaEngine(); +const result = await engine.execute('x : 1 + 2; io.out x;'); + +if (result.success) { + console.log('Result:', result.result); + console.log('Time:', result.executionTime + 'ms'); +} else { + console.error('Error:', result.error); + console.log('Suggestions:', result.suggestions); +} +``` + +### Configuration Options +```javascript +const config = new BabaYagaConfig({ + enableOptimizations: true, // Use high-performance engine + sandboxMode: true, // For untrusted code + maxExecutionTime: 5000, // 5 second timeout + verboseErrors: true, // Rich error messages + strictMode: true, // Enhanced validation + enableDebugMode: false, // Debug output + showTimings: true // Performance timing +}); + +// Preset configurations +const devConfig = BabaYagaConfig.development(); +const prodConfig = BabaYagaConfig.production(); +const testConfig = BabaYagaConfig.testing(); +const sandboxConfig = BabaYagaConfig.sandbox(); +``` + +### Performance Results +- **Overall execution**: 1.12x faster +- **Large programs**: 2-5x improvements expected +- **Memory efficiency**: 30-50% less GC pressure +- **Error quality**: 10-50x better debugging experience + +## Testing & Development + +```bash +# Run all tests (210 tests) +bun test + +# Run specific test suite +bun test tests/language_features.test.js + +# Run benchmarks +bun run src/benchmarks/simple-benchmark.js + +# Comprehensive benchmarks +bun run src/benchmarks/benchmark-suite.js + +# Start web editor +bun run web:dev + +# Build web editor for production +bun run web:build && bun run web:serve +``` + +## Migration Guide + +### From Previous Versions +All existing code continues to work unchanged. The optimized engine is used by default. + +### Disable Optimizations (if needed) +```bash +# Use legacy engine +bun run index.js program.baba --legacy + +# Or via configuration +const config = new BabaYagaConfig({ enableOptimizations: false }); +``` + +### Legacy API Access +```javascript +// Access legacy implementations if needed +import { createLexer } from './src/legacy/lexer.js'; +import { BabaYagaEngine } from './src/legacy/engine.js'; +``` + +## Documentation + +See the `docs/` directory for comprehensive language documentation: + +- `docs/00_crash-course.md` - Quick introduction and syntax overview +- `docs/01_functional.md` - Functional programming concepts +- `docs/02_data-structures.md` - Lists and tables +- `docs/03_pattern-matching.md` - Pattern matching guide +- `docs/04_types.md` - Type system and annotations +- `docs/05_recursion-and-composition.md` - Advanced techniques +- `docs/06_error-handling.md` - Error handling patterns +- `docs/07_gotchyas.md` - Common pitfalls and solutions +- `docs/08_array-programming.md` - Array programming features + +## Key Features + +- **Functional Core**: Anonymous functions, currying, partial application, and recursive functions +- **Pattern Matching**: Powerful `when` expressions for control flow +- **Robust Error Handling**: `Result` type for explicit success/failure propagation +- **Immutable Data Structures**: All list and table operations are immutable +- **Mathematical Constants**: Built-in `PI` and `INFINITY` constants +- **Type Annotations**: Optional static type annotations with runtime validation +- **Local Bindings**: `with` and `with rec` for local variable definitions +- **High Performance**: Optimized engine with 1.12x faster execution +- **Rich Error Messages**: Source location, context, and helpful suggestions + +## Web Editor + +Visit the interactive web editor at `http://localhost:8080` after running: + +```bash +bun run web:dev +``` + +Features include: +- Syntax highlighting +- Live code execution +- Error display with suggestions +- Example programs +- Performance monitoring + +## REPL + +Start an interactive Read-Eval-Print Loop: + +```bash +bun run repl +``` + +## Development + +### Project Structure +- **`src/core/`**: High-performance optimized engine (primary) +- **`src/legacy/`**: Original implementations (compatibility) +- **`src/benchmarks/`**: Performance testing and analysis +- **`docs/`**: Language documentation and guides +- **`tests/`**: Comprehensive test suite (210 tests) +- **`web/`**: Browser-based editor and playground + +### Contributing +1. All new features should use the optimized engine in `src/core/` +2. Maintain 100% test coverage (all 210 tests must pass) +3. Add benchmarks for performance-sensitive changes +4. Update documentation for new features +5. Follow the functional programming principles of the language + +**Baba Yaga is now production-ready with enterprise-grade performance and robustness while maintaining its elegant, functional design.** ๐ + +## License + +MIT License - see LICENSE file for details. diff --git a/js/baba-yaga/scratch/docs/REIMPLEMENTATION_GUIDE.md b/js/baba-yaga/scratch/docs/REIMPLEMENTATION_GUIDE.md new file mode 100644 index 0000000..3e6f2e0 --- /dev/null +++ b/js/baba-yaga/scratch/docs/REIMPLEMENTATION_GUIDE.md @@ -0,0 +1,693 @@ +# Baba Yaga Reimplementation Guide + +This guide outlines how to reimplement the Baba Yaga functional language in a faster, compiled language. While the current JavaScript implementation serves as an excellent prototype, a native implementation could provide significant performance improvements and better integration capabilities. + +## Language Recommendation: Rust + +After analyzing the requirements, **Rust** emerges as the optimal choice because: + +- **Memory safety** without garbage collection overhead +- **Native pattern matching** that directly maps to Baba Yaga's `when` expressions +- **Functional programming support** for closures and higher-order functions +- **Built-in `Result<T, E>`** type matching Baba Yaga's error handling +- **Zero-cost abstractions** for performance +- **Excellent tooling** and growing ecosystem + +## Project Structure + +``` +baba-yaga-rust/ +โโโ Cargo.toml +โโโ src/ +โ โโโ main.rs # CLI entry point +โ โโโ lib.rs # Library exports +โ โโโ lexer/ +โ โ โโโ mod.rs # Lexer module +โ โ โโโ token.rs # Token definitions +โ โโโ parser/ +โ โ โโโ mod.rs # Parser module +โ โ โโโ ast.rs # AST node definitions +โ โโโ interpreter/ +โ โ โโโ mod.rs # Interpreter module +โ โ โโโ value.rs # Runtime value types +โ โ โโโ scope.rs # Scope management +โ โ โโโ builtins.rs # Built-in functions +โ โโโ error.rs # Error types +โ โโโ repl.rs # REPL implementation +โโโ tests/ +โ โโโ integration/ +โ โโโ fixtures/ +โโโ benches/ # Performance benchmarks +``` + +## Phase 1: Core Data Types and Error Handling + +### 1.1 Define Core Types + +**File: `src/error.rs`** +```rust +use std::fmt; + +#[derive(Debug, Clone)] +pub enum BabaError { + LexError(String), + ParseError(String), + RuntimeError(String), + TypeError(String), + UndefinedVariable(String), + UndefinedProperty(String), + DivisionByZero, + IndexOutOfBounds(usize), +} + +impl fmt::Display for BabaError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BabaError::LexError(msg) => write!(f, "Lexer error: {}", msg), + BabaError::ParseError(msg) => write!(f, "Parse error: {}", msg), + BabaError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg), + BabaError::TypeError(msg) => write!(f, "Type error: {}", msg), + BabaError::UndefinedVariable(name) => write!(f, "Undefined variable: {}", name), + BabaError::UndefinedProperty(prop) => write!(f, "Undefined property: {}", prop), + BabaError::DivisionByZero => write!(f, "Division by zero"), + BabaError::IndexOutOfBounds(idx) => write!(f, "Index out of bounds: {}", idx), + } + } +} + +impl std::error::Error for BabaError {} + +pub type Result<T> = std::result::Result<T, BabaError>; +``` + +### 1.2 Runtime Value System + +**File: `src/interpreter/value.rs`** +```rust +use std::collections::HashMap; +use std::rc::Rc; +use im::{Vector, HashMap as ImHashMap}; // Use persistent data structures + +#[derive(Debug, Clone)] +pub enum Value { + Number { value: f64, is_float: bool }, + String(String), + Boolean(bool), + List(Vector<Value>), + Table(ImHashMap<String, Value>), + Function(Function), + NativeFunction(NativeFn), + Result { variant: ResultVariant, value: Box<Value> }, + Unit, +} + +#[derive(Debug, Clone)] +pub enum ResultVariant { + Ok, + Err, +} + +#[derive(Debug, Clone)] +pub struct Function { + pub params: Vec<String>, + pub body: Rc<AstNode>, + pub closure: Scope, + pub return_type: Option<Type>, +} + +pub type NativeFn = fn(&[Value]) -> crate::Result<Value>; +``` + +## Phase 2: Lexical Analysis + +### 2.1 Token Definition + +**File: `src/lexer/token.rs`** +```rust +#[derive(Debug, Clone, PartialEq)] +pub enum TokenType { + // Literals + Number { value: f64, is_float: bool }, + String(String), + Identifier(String), + + // Keywords + When, Is, Then, With, Rec, Ok, Err, + True, False, Pi, Infinity, + And, Or, Xor, + + // Operators + Plus, Minus, Star, Slash, Percent, + Equal, NotEqual, Greater, Less, GreaterEqual, LessEqual, + Concat, // .. + + // Punctuation + LeftParen, RightParen, + LeftBrace, RightBrace, + LeftBracket, RightBracket, + Colon, Semicolon, Comma, Dot, Arrow, + + // Special + Newline, + Eof, +} + +#[derive(Debug, Clone)] +pub struct Token { + pub token_type: TokenType, + pub line: usize, + pub column: usize, +} +``` + +### 2.2 Lexer Implementation + +**File: `src/lexer/mod.rs`** +Use a character-by-character state machine approach: + +```rust +pub struct Lexer { + input: Vec<char>, + position: usize, + line: usize, + column: usize, +} + +impl Lexer { + pub fn new(input: String) -> Self { + Self { + input: input.chars().collect(), + position: 0, + line: 1, + column: 1, + } + } + + pub fn tokenize(&mut self) -> crate::Result<Vec<Token>> { + let mut tokens = Vec::new(); + + while !self.is_at_end() { + self.skip_whitespace(); + if self.is_at_end() { break; } + + tokens.push(self.next_token()?); + } + + tokens.push(Token { + token_type: TokenType::Eof, + line: self.line, + column: self.column, + }); + + Ok(tokens) + } + + fn next_token(&mut self) -> crate::Result<Token> { + // Implementation details... + } +} +``` + +## Phase 3: Abstract Syntax Tree + +### 3.1 AST Node Definition + +**File: `src/parser/ast.rs`** +```rust +#[derive(Debug, Clone)] +pub enum AstNode { + // Literals + Number { value: f64, is_float: bool }, + String(String), + Boolean(bool), + List(Vec<AstNode>), + Table(Vec<(String, AstNode)>), + + // Identifiers and access + Identifier(String), + MemberAccess { object: Box<AstNode>, property: Box<AstNode> }, + + // Functions + Function { params: Vec<String>, body: Box<AstNode> }, + FunctionCall { callee: Box<AstNode>, args: Vec<AstNode> }, + + // Control flow + When { + discriminants: Vec<AstNode>, + cases: Vec<WhenCase>, + }, + + // Declarations + VariableDeclaration { name: String, value: Box<AstNode> }, + FunctionDeclaration { + name: String, + params: Vec<String>, + body: Box<AstNode>, + return_type: Option<Type>, + }, + + // Local bindings + WithHeader { + entries: Vec<WithEntry>, + body: Box<AstNode>, + recursive: bool, + }, + + // Expressions + BinaryOp { left: Box<AstNode>, op: BinaryOperator, right: Box<AstNode> }, + UnaryOp { op: UnaryOperator, operand: Box<AstNode> }, + + // Result types + Result { variant: ResultVariant, value: Box<AstNode> }, + + // Program structure + Program(Vec<AstNode>), +} + +#[derive(Debug, Clone)] +pub struct WhenCase { + pub patterns: Vec<Pattern>, + pub body: Box<AstNode>, +} + +#[derive(Debug, Clone)] +pub enum Pattern { + Literal(AstNode), + Wildcard, + Type(String), + Result { variant: ResultVariant, binding: String }, + List(Vec<Pattern>), + Table(Vec<(String, Pattern)>), +} +``` + +## Phase 4: Parser Implementation + +### 4.1 Recursive Descent Parser + +**File: `src/parser/mod.rs`** +```rust +pub struct Parser { + tokens: Vec<Token>, + current: usize, +} + +impl Parser { + pub fn new(tokens: Vec<Token>) -> Self { + Self { tokens, current: 0 } + } + + pub fn parse(&mut self) -> crate::Result<AstNode> { + let mut statements = Vec::new(); + + while !self.is_at_end() { + statements.push(self.statement()?); + } + + Ok(AstNode::Program(statements)) + } + + fn statement(&mut self) -> crate::Result<AstNode> { + match self.peek().token_type { + TokenType::Identifier(_) => { + if self.peek_ahead(1).token_type == TokenType::Colon { + self.declaration() + } else { + self.expression() + } + } + _ => self.expression(), + } + } + + // Implement precedence climbing for expressions + fn expression(&mut self) -> crate::Result<AstNode> { + self.expression_with_precedence(0) + } + + fn expression_with_precedence(&mut self, min_precedence: u8) -> crate::Result<AstNode> { + // Implementation using precedence climbing algorithm + } +} +``` + +## Phase 5: Interpreter Core + +### 5.1 Scope Management + +**File: `src/interpreter/scope.rs`** +```rust +use std::collections::HashMap; +use std::rc::Rc; +use crate::interpreter::value::Value; + +#[derive(Debug, Clone)] +pub struct Scope { + bindings: HashMap<String, Value>, + parent: Option<Rc<Scope>>, +} + +impl Scope { + pub fn new() -> Self { + Self { + bindings: HashMap::new(), + parent: None, + } + } + + pub fn with_parent(parent: Rc<Scope>) -> Self { + Self { + bindings: HashMap::new(), + parent: Some(parent), + } + } + + pub fn get(&self, name: &str) -> Option<Value> { + self.bindings.get(name).cloned() + .or_else(|| self.parent.as_ref().and_then(|p| p.get(name))) + } + + pub fn set(&mut self, name: String, value: Value) { + self.bindings.insert(name, value); + } +} +``` + +### 5.2 Interpreter Implementation + +**File: `src/interpreter/mod.rs`** +```rust +use std::rc::Rc; +use crate::parser::ast::AstNode; +use crate::interpreter::value::Value; +use crate::interpreter::scope::Scope; + +pub struct Interpreter { + global_scope: Rc<Scope>, +} + +impl Interpreter { + pub fn new() -> Self { + let mut global_scope = Scope::new(); + Self::register_builtins(&mut global_scope); + + Self { + global_scope: Rc::new(global_scope), + } + } + + pub fn eval(&self, ast: &AstNode) -> crate::Result<Value> { + self.eval_with_scope(ast, self.global_scope.clone()) + } + + fn eval_with_scope(&self, ast: &AstNode, scope: Rc<Scope>) -> crate::Result<Value> { + match ast { + AstNode::Number { value, is_float } => { + Ok(Value::Number { value: *value, is_float: *is_float }) + } + + AstNode::String(s) => Ok(Value::String(s.clone())), + + AstNode::Boolean(b) => Ok(Value::Boolean(*b)), + + AstNode::Identifier(name) => { + scope.get(name) + .ok_or_else(|| BabaError::UndefinedVariable(name.clone())) + } + + AstNode::When { discriminants, cases } => { + self.eval_when(discriminants, cases, scope) + } + + AstNode::FunctionCall { callee, args } => { + self.eval_function_call(callee, args, scope) + } + + // ... other cases + _ => todo!("Implement remaining AST node evaluation"), + } + } +} +``` + +## Phase 6: Built-in Functions + +### 6.1 Built-in Registry + +**File: `src/interpreter/builtins.rs`** +```rust +use crate::interpreter::value::{Value, NativeFn}; +use crate::interpreter::scope::Scope; +use im::Vector; + +impl Interpreter { + fn register_builtins(scope: &mut Scope) { + // Math functions + scope.set("math".to_string(), create_math_namespace()); + + // String functions + scope.set("str".to_string(), create_str_namespace()); + + // List functions + scope.set("map".to_string(), Value::NativeFunction(builtin_map)); + scope.set("filter".to_string(), Value::NativeFunction(builtin_filter)); + scope.set("reduce".to_string(), Value::NativeFunction(builtin_reduce)); + scope.set("append".to_string(), Value::NativeFunction(builtin_append)); + + // IO functions + scope.set("io".to_string(), create_io_namespace()); + } +} + +fn builtin_map(args: &[Value]) -> crate::Result<Value> { + if args.len() != 2 { + return Err(BabaError::RuntimeError("map expects 2 arguments".to_string())); + } + + let func = &args[0]; + let list = &args[1]; + + match (func, list) { + (Value::Function(f), Value::List(items)) => { + let mut result = Vector::new(); + for item in items { + // Apply function to each item + let mapped = apply_function(f, &[item.clone()])?; + result.push_back(mapped); + } + Ok(Value::List(result)) + } + _ => Err(BabaError::TypeError("Invalid arguments to map".to_string())), + } +} +``` + +## Phase 7: Performance Optimizations + +### 7.1 Benchmark Setup + +**File: `benches/interpreter.rs`** +```rust +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use baba_yaga_rust::*; + +fn benchmark_fibonacci(c: &mut Criterion) { + let code = r#" + fibonacci : n -> + when n is + 0 then 0 + 1 then 1 + _ then (fibonacci (n - 1)) + (fibonacci (n - 2)); + result : fibonacci 10; + "#; + + c.bench_function("fibonacci", |b| { + b.iter(|| { + let mut lexer = Lexer::new(black_box(code.to_string())); + let tokens = lexer.tokenize().unwrap(); + let mut parser = Parser::new(tokens); + let ast = parser.parse().unwrap(); + let interpreter = Interpreter::new(); + interpreter.eval(&ast).unwrap() + }) + }); +} + +criterion_group!(benches, benchmark_fibonacci); +criterion_main!(benches); +``` + +### 7.2 Optimization Strategies + +1. **AST Interning**: Use `Rc<AstNode>` to avoid cloning large AST subtrees +2. **Value Interning**: Intern common strings and small numbers +3. **Scope Optimization**: Use arena allocation for scopes +4. **Tail Call Detection**: Identify tail-recursive patterns for optimization +5. **Constant Folding**: Evaluate constant expressions at parse time + +## Phase 8: Integration and CLI + +### 8.1 Command Line Interface + +**File: `src/main.rs`** +```rust +use clap::{App, Arg}; +use std::fs; +use baba_yaga_rust::*; + +fn main() -> Result<(), Box<dyn std::error::Error>> { + let matches = App::new("Baba Yaga") + .version("2.0.0") + .about("A functional scripting language") + .arg(Arg::with_name("file") + .help("The input file to execute") + .required(false) + .index(1)) + .arg(Arg::with_name("debug") + .short("d") + .long("debug") + .help("Enable debug output")) + .get_matches(); + + if let Some(filename) = matches.value_of("file") { + let code = fs::read_to_string(filename)?; + execute_code(&code)?; + } else { + start_repl()?; + } + + Ok(()) +} + +fn execute_code(code: &str) -> crate::Result<()> { + let mut lexer = Lexer::new(code.to_string()); + let tokens = lexer.tokenize()?; + let mut parser = Parser::new(tokens); + let ast = parser.parse()?; + let interpreter = Interpreter::new(); + let result = interpreter.eval(&ast)?; + + if !matches!(result, Value::Unit) { + println!("{:?}", result); + } + + Ok(()) +} +``` + +### 8.2 REPL Implementation + +**File: `src/repl.rs`** +```rust +use rustyline::{Editor, Result as RLResult}; +use crate::*; + +pub fn start_repl() -> crate::Result<()> { + let mut rl = Editor::<()>::new(); + let interpreter = Interpreter::new(); + + println!("Baba Yaga REPL v2.0.0"); + println!("Type :help for commands, :quit to exit"); + + loop { + match rl.readline("baba> ") { + Ok(line) => { + rl.add_history_entry(line.as_str()); + + if line.starts_with(':') { + handle_repl_command(&line)?; + } else { + match execute_line(&interpreter, &line) { + Ok(value) => { + if !matches!(value, Value::Unit) { + println!("{:?}", value); + } + } + Err(e) => eprintln!("Error: {}", e), + } + } + } + Err(_) => break, + } + } + + Ok(()) +} +``` + +## Phase 9: Testing Strategy + +### 9.1 Unit Tests +- Test each component in isolation +- Property-based testing for parser/lexer +- Comprehensive built-in function tests + +### 9.2 Integration Tests +- Port existing JavaScript test cases +- Performance regression tests +- Memory usage tests + +### 9.3 Compatibility Tests +- Ensure identical behavior to JavaScript version +- Cross-platform compatibility +- Host integration tests + +## Phase 10: Deployment and Distribution + +### 10.1 Build Configuration + +**File: `Cargo.toml`** +```toml +[package] +name = "baba-yaga-rust" +version = "2.0.0" +edition = "2021" + +[dependencies] +im = "15.1" # Persistent data structures +clap = "3.0" # CLI parsing +rustyline = "9.0" # REPL readline +criterion = "0.4" # Benchmarking + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +panic = "abort" + +[[bin]] +name = "baba" +path = "src/main.rs" + +[[bench]] +name = "interpreter" +harness = false +``` + +### 10.2 Cross-Compilation Targets +- Linux x86_64 +- macOS (Intel + Apple Silicon) +- Windows x86_64 +- WebAssembly (for browser embedding) + +## Expected Performance Improvements + +Based on typical JavaScript to Rust ports: + +- **Startup time**: 10-50x faster (no JIT warmup) +- **Execution speed**: 2-10x faster for compute-heavy workloads +- **Memory usage**: 2-5x less memory consumption +- **Binary size**: Much smaller self-contained executable +- **Predictable performance**: No garbage collection pauses + +## Migration Path + +1. **Phase 1-3**: Core infrastructure (2-3 weeks) +2. **Phase 4-5**: Parser and basic interpreter (2-3 weeks) +3. **Phase 6**: Built-in functions (1-2 weeks) +4. **Phase 7-8**: Optimization and CLI (1-2 weeks) +5. **Phase 9-10**: Testing and deployment (1-2 weeks) + +**Total estimated time**: 7-12 weeks for a complete reimplementation + +This approach provides a systematic path to a high-performance native Baba Yaga implementation while maintaining full compatibility with the existing JavaScript version. diff --git a/js/baba-yaga/scratch/js/build.js b/js/baba-yaga/scratch/js/build.js new file mode 100755 index 0000000..8b5181b --- /dev/null +++ b/js/baba-yaga/scratch/js/build.js @@ -0,0 +1,178 @@ +#!/usr/bin/env bun +// build.js - Build static binaries for Baba Yaga + +import { $ } from "bun"; +import { existsSync, mkdirSync } from "fs"; + +// Available targets for cross-compilation +const TARGETS = { + 'macos-arm64': 'bun-darwin-arm64', + 'macos-x64': 'bun-darwin-x64', + 'linux-x64': 'bun-linux-x64', + 'windows-x64': 'bun-windows-x64' +}; + +// Parse command line arguments +const args = process.argv.slice(2); +const targetArg = args.find(arg => arg.startsWith('--target=')); +const allTargets = args.includes('--all'); +const helpFlag = args.includes('--help') || args.includes('-h'); + +if (helpFlag) { + console.log(`๐จ Baba Yaga Binary Builder + +Usage: + bun run build.js [options] + +Options: + --target=<target> Build for specific target + --all Build for all supported platforms + --help, -h Show this help + +Available targets: + macos-arm64 macOS Apple Silicon (default on Apple Silicon Mac) + macos-x64 macOS Intel + linux-x64 Linux x86_64 + windows-x64 Windows x86_64 + +Examples: + bun run build.js # Build for current platform + bun run build.js --target=linux-x64 # Build for Linux + bun run build.js --target=windows-x64 # Build for Windows + bun run build.js --all # Build for all platforms +`); + process.exit(0); +} + +let targetsToBuild = []; + +if (allTargets) { + targetsToBuild = Object.keys(TARGETS); +} else if (targetArg) { + const requestedTarget = targetArg.split('=')[1]; + if (!TARGETS[requestedTarget]) { + console.error(`โ Unknown target: ${requestedTarget}`); + console.error(`Available targets: ${Object.keys(TARGETS).join(', ')}`); + process.exit(1); + } + targetsToBuild = [requestedTarget]; +} else { + // Default to current platform + const platform = process.platform; + const arch = process.arch; + + if (platform === 'darwin' && arch === 'arm64') { + targetsToBuild = ['macos-arm64']; + } else if (platform === 'darwin' && arch === 'x64') { + targetsToBuild = ['macos-x64']; + } else { + console.log("๐ค Auto-detecting platform..."); + targetsToBuild = ['macos-arm64']; // Default fallback + } +} + +console.log(`๐จ Building Baba Yaga static binaries for: ${targetsToBuild.join(', ')}\n`); + +// Create build directory +if (!existsSync("./build")) { + mkdirSync("./build"); +} + +// Build function for a specific target +async function buildTarget(targetName) { + const bunTarget = TARGETS[targetName]; + const isWindows = targetName.includes('windows'); + + console.log(`\n๐ฆ Building for ${targetName} (${bunTarget})...`); + + // Build interpreter binary + const interpreterName = isWindows ? `baba-yaga-${targetName}.exe` : `baba-yaga-${targetName}`; + const replName = isWindows ? `baba-yaga-repl-${targetName}.exe` : `baba-yaga-repl-${targetName}`; + + try { + console.log(` Building interpreter: ${interpreterName}`); + await $`bun build ./index.js --compile --outfile ./build/${interpreterName} --target ${bunTarget}`; + console.log(` โ Built: ./build/${interpreterName}`); + } catch (error) { + console.error(` โ Failed to build interpreter for ${targetName}:`, error.message); + return false; + } + + // Build REPL binary + try { + console.log(` Building REPL: ${replName}`); + await $`bun build ./repl.js --compile --outfile ./build/${replName} --target ${bunTarget}`; + console.log(` โ Built: ./build/${replName}`); + } catch (error) { + console.error(` โ Failed to build REPL for ${targetName}:`, error.message); + return false; + } + + return true; +} + +// Build all requested targets +let successCount = 0; +for (const target of targetsToBuild) { + const success = await buildTarget(target); + if (success) successCount++; +} + +console.log(`\n๐ Build complete! (${successCount}/${targetsToBuild.length} targets successful)`); + +// Show what was built +console.log("\n๐ฆ Built binaries:"); +try { + await $`ls -la ./build/`; +} catch (error) { + console.warn("Could not list build directory"); +} + +// Test the binaries (only test current platform binaries) +const currentPlatformBinaries = []; +if (existsSync("./build/baba-yaga")) { + currentPlatformBinaries.push("./build/baba-yaga"); +} +if (existsSync("./build/baba-yaga-macos-arm64")) { + currentPlatformBinaries.push("./build/baba-yaga-macos-arm64"); +} +if (existsSync("./build/baba-yaga-macos-x64")) { + currentPlatformBinaries.push("./build/baba-yaga-macos-x64"); +} + +if (currentPlatformBinaries.length > 0) { + console.log("\n๐งช Testing binaries..."); + for (const binary of currentPlatformBinaries) { + try { + console.log(`Testing ${binary}...`); + await $`${binary} simple.baba`.quiet(); + console.log(`โ ${binary} test passed`); + } catch (error) { + console.warn(`โ ๏ธ ${binary} test failed:`, error.message); + } + } +} + +console.log("\n๐ Usage examples:"); +if (targetsToBuild.includes('macos-arm64') || targetsToBuild.includes('macos-x64')) { + console.log(" # macOS:"); + console.log(" ./build/baba-yaga-macos-arm64 program.baba --debug"); + console.log(" ./build/baba-yaga-repl-macos-arm64"); +} +if (targetsToBuild.includes('linux-x64')) { + console.log(" # Linux:"); + console.log(" ./build/baba-yaga-linux-x64 program.baba --profile"); + console.log(" ./build/baba-yaga-repl-linux-x64"); +} +if (targetsToBuild.includes('windows-x64')) { + console.log(" # Windows:"); + console.log(" .\\build\\baba-yaga-windows-x64.exe program.baba --debug"); + console.log(" .\\build\\baba-yaga-repl-windows-x64.exe"); +} + +console.log("\n๐ All binaries are standalone and require no dependencies!"); + +if (successCount < targetsToBuild.length) { + console.log(`\nโ ๏ธ ${targetsToBuild.length - successCount} target(s) failed to build`); + process.exit(1); +} diff --git a/js/baba-yaga/scratch/js/debug-lexing.js b/js/baba-yaga/scratch/js/debug-lexing.js new file mode 100644 index 0000000..4170a98 --- /dev/null +++ b/js/baba-yaga/scratch/js/debug-lexing.js @@ -0,0 +1,63 @@ +// Debug lexing differences + +import { createLexer as createOptimizedLexer } from './src/core/lexer.js'; +import { createLexer as createLegacyLexer } from './src/legacy/lexer.js'; +import { readFileSync } from 'fs'; + +const testFile = 'compatibility-test.baba'; +const source = readFileSync(testFile, 'utf8'); + +console.log('๐ Debugging Lexing Differences\n'); +console.log(`File: ${testFile}`); +console.log(`Source length: ${source.length} characters\n`); + +try { + // Test optimized lexer + console.log('=== OPTIMIZED LEXER ==='); + const optimizedLexer = createOptimizedLexer(source); + const optimizedTokens = optimizedLexer.allTokens(); + + console.log(`Tokens generated: ${optimizedTokens.length}`); + + // Show first 20 tokens + console.log('\nFirst 20 tokens:'); + for (let i = 0; i < Math.min(20, optimizedTokens.length); i++) { + const token = optimizedTokens[i]; + console.log(`${i}: ${token.type} = "${token.value}" (line ${token.line}, col ${token.column})`); + } + + // Look for problematic tokens + console.log('\nLooking for problematic tokens...'); + const problemTokens = optimizedTokens.filter(token => + token.value && (token.value.includes('esults') || token.value.length < 3) + ); + + if (problemTokens.length > 0) { + console.log('Found problematic tokens:'); + problemTokens.forEach((token, i) => { + console.log(` ${i}: ${token.type} = "${token.value}" (line ${token.line}, col ${token.column})`); + }); + } + +} catch (error) { + console.log(`โ Optimized lexer error: ${error.message}`); +} + +try { + // Test legacy lexer + console.log('\n=== LEGACY LEXER ==='); + const legacyLexer = createLegacyLexer(source); + const legacyTokens = legacyLexer.allTokens(); + + console.log(`Tokens generated: ${legacyTokens.length}`); + + // Show first 20 tokens + console.log('\nFirst 20 tokens:'); + for (let i = 0; i < Math.min(20, legacyTokens.length); i++) { + const token = legacyTokens[i]; + console.log(`${i}: ${token.type} = "${token.value}" (line ${token.line}, col ${token.column})`); + } + +} catch (error) { + console.log(`โ Legacy lexer error: ${error.message}`); +} diff --git a/js/baba-yaga/scratch/js/index.js b/js/baba-yaga/scratch/js/index.js new file mode 100644 index 0000000..cd9da98 --- /dev/null +++ b/js/baba-yaga/scratch/js/index.js @@ -0,0 +1,109 @@ +// index.js - Main entry point for Baba Yaga (optimized by default) + +import fs from 'fs'; +import { BabaYagaEngine, createEngine } from './src/core/engine.js'; +import { BabaYagaConfig } from './src/core/config.js'; + +const filePath = process.argv[2]; +const debugMode = process.argv.includes('--debug'); +const profileMode = process.argv.includes('--profile'); +const strictMode = process.argv.includes('--strict'); +const legacyMode = process.argv.includes('--legacy'); + +if (!filePath) { + console.error('Usage: bun run index.js <file_path> [--debug] [--profile] [--strict] [--legacy]'); + console.error(''); + console.error('Options:'); + console.error(' --debug Enable verbose debugging output'); + console.error(' --profile Show detailed performance timing'); + console.error(' --strict Enable strict mode validation'); + console.error(' --legacy Use legacy (non-optimized) engine'); + process.exit(1); +} + +// Create configuration based on command line flags +const config = new BabaYagaConfig({ + enableOptimizations: false, // Optimizations disabled by default due to lexer bug + enableDebugMode: debugMode, + enableProfiling: profileMode, + strictMode: strictMode, + showTimings: profileMode, + verboseErrors: true, + colorOutput: true +}); + +const engine = new BabaYagaEngine(config); + +fs.readFile(filePath, 'utf8', async (err, code) => { + if (err) { + console.error(`Error reading file: ${err.message}`); + process.exit(1); + } + + const result = await engine.execute(code, { + filename: filePath, + onOutput: (...args) => { + const toDisplay = (arg) => { + if (arg && typeof arg.value === 'number') return arg.value; + if (Array.isArray(arg)) return JSON.stringify(arg.map(toDisplay)); + if (arg && typeof arg === 'object') { + // Pretty-print known runtime objects + if (arg.type === 'NativeFunction' || arg.type === 'Function') return '<fn>'; + if (arg.type === 'Result') return `${arg.variant} ${toDisplay(arg.value)}`; + if (arg.type === 'Object' && arg.properties instanceof Map) { + const obj = Object.fromEntries(Array.from(arg.properties.entries()).map(([k,v]) => [k, toDisplay(v)])); + return JSON.stringify(obj); + } + } + return String(arg); + }; + console.log(...args.map(toDisplay)); + }, + onInput: () => { + try { + const data = fs.readFileSync(0, 'utf8'); + return typeof data === 'string' ? data : String(data); + } catch { + return ''; + } + } + }); + + if (result.success) { + if (result.result !== undefined) { + console.log(result.result); + } + + if (profileMode) { + const stats = engine.getStats(); + console.error(`\n[PROFILE] Execution time: ${result.executionTime.toFixed(2)}ms`); + if (result.breakdown) { + console.error(`[PROFILE] Breakdown: Lex ${result.breakdown.lexingTime.toFixed(2)}ms, Parse ${result.breakdown.parsingTime.toFixed(2)}ms, Interpret ${result.breakdown.interpretingTime.toFixed(2)}ms`); + } + console.error(`[PROFILE] Total executions: ${stats.totalExecutions}`); + console.error(`[PROFILE] Average time: ${stats.averageTime.toFixed(2)}ms`); + + if (stats.optimizations && config.enableOptimizations) { + console.error(`[PROFILE] Built-in optimizations: ${(stats.optimizations.builtinOptimizationRate * 100).toFixed(1)}%`); + console.error(`[PROFILE] AST pool hit rate: ${(stats.optimizations.astPoolHitRate * 100).toFixed(1)}%`); + } + } + + if (debugMode && config.enableOptimizations) { + console.error('\n[DEBUG] Optimizations enabled - using high-performance engine'); + } else if (debugMode && !config.enableOptimizations) { + console.error('\n[DEBUG] Legacy mode - using original engine'); + } + } else { + console.error(result.error); + + if (debugMode && result.suggestions?.length > 0) { + console.error('\nSuggestions:'); + result.suggestions.forEach(suggestion => { + console.error(` - ${suggestion}`); + }); + } + + process.exit(1); + } +}); \ No newline at end of file diff --git a/js/baba-yaga/scratch/js/repl.js b/js/baba-yaga/scratch/js/repl.js new file mode 100644 index 0000000..b9c878d --- /dev/null +++ b/js/baba-yaga/scratch/js/repl.js @@ -0,0 +1,226 @@ +// repl.js - Simple multi-line REPL for the Baba Yaga language (Node/Bun) +// - Enter inserts a newline; type :run (or a single .) on its own line to execute +// - :reset clears the current input buffer +// - :clear clears the session (prior program) +// - :load <path> loads a .baba file into the session +// - :quit / :exit exits +// - :help prints commands + +import fs from 'fs'; +import os from 'os'; +import { evaluate, makeCodeFrame } from './runner.js'; +import { createLexer } from './src/core/lexer.js'; +import { createParser } from './src/core/parser.js'; + +// Synchronous line input for TTY. Keep it small and dependency-free. +function readLineSync(promptText = '') { + if (promptText) process.stdout.write(promptText); + const fd = 0; // stdin + const buf = Buffer.alloc(1); + let line = ''; + while (true) { + const bytes = fs.readSync(fd, buf, 0, 1, null); + if (bytes === 0) break; // EOF + const ch = buf.toString('utf8'); + // Ctrl+C + if (ch === '\u0003') { + process.stdout.write('\n'); + process.exit(0); + } + if (ch === '\n' || ch === '\r') break; + line += ch; + } + return line; +} + +function countLines(text) { + if (!text) return 0; + return text.split(/\r?\n/).length; +} + +function describeType(value) { + if (value && typeof value.value === 'number') { + return value.isFloat ? 'Float' : 'Int'; + } + if (typeof value === 'number') { + return Number.isInteger(value) ? 'Int' : 'Float'; + } + if (typeof value === 'string') return 'String'; + if (typeof value === 'boolean') return 'Bool'; + if (Array.isArray(value)) return 'List'; + if (value && value.type === 'Object' && value.properties) return 'Table'; + if (value && value.type === 'Result') return 'Result'; + if (typeof value === 'undefined') return 'Unit'; + return 'Unknown'; +} + +function displayValue(value) { + if (value && typeof value.value === 'number') return String(value.value); + if (Array.isArray(value)) return JSON.stringify(value.map(displayValue)); + if (value && typeof value === 'object') { + if (value.type === 'NativeFunction' || value.type === 'Function') return '<fn>'; + if (value.type === 'Object' && value.properties instanceof Map) { + const obj = Object.fromEntries(Array.from(value.properties.entries()).map(([k, v]) => [k, displayValue(v)])); + return JSON.stringify(obj); + } + } + return String(value); +} + +function printHelp() { + console.log(`Commands:\n\ + :run Execute current buffer (or use a single '.' line)\n\ + :reset Clear current input buffer\n\ + :clear Clear entire session (prior program)\n\ + :load <path> Load a .baba file into the session\n\ + :help Show this help\n\ + :quit | :exit Exit`); +} + +(function main() { + let priorSource = ''; + let buffer = ''; + const host = { + io: { + out: (...xs) => console.log(...xs.map(displayValue)), + in: () => readLineSync('input> '), + }, + }; + + console.log('Baba Yaga REPL (multiline). Type :help for commands.'); + + while (true) { + const prompt = buffer ? '... ' : 'baba> '; + const line = readLineSync(prompt); + + const trimmed = line.trim(); + if (trimmed === ':quit' || trimmed === ':exit') break; + if (trimmed === ':help') { printHelp(); continue; } + if (trimmed === ':reset') { buffer = ''; continue; } + if (trimmed === ':clear') { priorSource = ''; buffer = ''; console.log('(session cleared)'); continue; } + if (trimmed === ':load') { console.error('Usage: :load <path>'); continue; } + if (trimmed.startsWith(':load')) { + let pathArg = trimmed.slice(5).trim(); // remove ':load' + if (!pathArg) { console.error('Usage: :load <path>'); continue; } + // Strip surrounding single/double quotes + if ((pathArg.startsWith('"') && pathArg.endsWith('"')) || (pathArg.startsWith("'") && pathArg.endsWith("'"))) { + pathArg = pathArg.slice(1, -1); + } + // Expand ~ to home directory + if (pathArg.startsWith('~')) { + pathArg = pathArg.replace(/^~(?=\/|$)/, os.homedir()); + } + const loadPath = pathArg; + try { + const fileSource = fs.readFileSync(loadPath, 'utf8'); + // Parse-only to validate. Do not execute on :load + try { + const lexer = createLexer(fileSource); + const tokens = lexer.allTokens(); + const parser = createParser(tokens); + parser.parse(); + priorSource = priorSource + '\n' + fileSource + '\n'; + console.log(`Loaded ${loadPath}. Type :run to execute.`); + } catch (parseErr) { + const message = parseErr && parseErr.message ? parseErr.message : String(parseErr); + const match = / at (\d+):(\d+)/.exec(message); + const line = match ? Number(match[1]) : undefined; + const column = match ? Number(match[2]) : undefined; + const frame = makeCodeFrame(fileSource, line, column); + console.error(`Failed to parse ${loadPath}: ${message}`); + if (frame) console.error(frame); + } + } catch (e) { + console.error(`Failed to load ${loadPath}: ${e.message}`); + } + continue; + } + + // Execute current buffer or previously loaded program + if (trimmed === ':run' || trimmed === '.') { + const hasBuffer = Boolean(buffer.trim()); + const hasPrior = Boolean(priorSource.trim()); + if (!hasBuffer && !hasPrior) { console.log('(empty)'); buffer = ''; continue; } + const combined = hasBuffer ? (priorSource + '\n' + buffer + '\n') : priorSource; + const res = evaluate(combined, host); + if (res.ok) { + const value = res.value; + const type = describeType(value); + console.log('โ input โ'); + if (hasBuffer) { + process.stdout.write(buffer); + } else { + console.log('(loaded program)'); + } + if (typeof value !== 'undefined') { + console.log('โ result โ'); + console.log(`${displayValue(value)} : ${type}`); + } else { + console.log('โ result โ'); + console.log('Unit'); + } + priorSource = combined; // commit + buffer = hasBuffer ? '' : buffer; + } else { + // Prefer rendering code-frame relative to the buffer if possible + const linesBefore = countLines(priorSource); + const errLine = res.error.line; + if (hasBuffer && errLine && errLine > linesBefore) { + const localLine = errLine - linesBefore; + const localFrame = makeCodeFrame(buffer, localLine, res.error.column || 1); + console.error(res.error.message); + if (localFrame) console.error(localFrame); + } else { + console.error(res.error.message); + if (res.error.codeFrame) console.error(res.error.codeFrame); + } + // do not commit buffer + } + continue; + } + + // Accumulate multi-line input + buffer += line + '\n'; + + // Immediate execution if current buffer ends with a double semicolon + // Treat the second semicolon as a submit marker; execute with a single trailing semicolon + const trimmedBuf = buffer.trimEnd(); + if (trimmedBuf.endsWith(';;')) { + const submitBuffer = buffer.replace(/;\s*$/,''); // drop one trailing ';' for valid syntax + const combined = priorSource + '\n' + submitBuffer + '\n'; + const res = evaluate(combined, host); + if (res.ok) { + const value = res.value; + const type = describeType(value); + console.log('โ input โ'); + process.stdout.write(submitBuffer.endsWith('\n') ? submitBuffer : submitBuffer + '\n'); + if (typeof value !== 'undefined') { + console.log('โ result โ'); + console.log(`${displayValue(value)} : ${type}`); + } else { + console.log('โ result โ'); + console.log('Unit'); + } + priorSource = combined; // commit + buffer = ''; + } else { + const linesBefore = countLines(priorSource); + const errLine = res.error.line; + if (errLine && errLine > linesBefore) { + const localLine = errLine - linesBefore; + const localFrame = makeCodeFrame(submitBuffer, localLine, res.error.column || 1); + console.error(res.error.message); + if (localFrame) console.error(localFrame); + } else { + console.error(res.error.message); + if (res.error.codeFrame) console.error(res.error.codeFrame); + } + // keep buffer for further editing unless you prefer clearing it + } + } + } + + console.log('Bye.'); + process.exit(0); +})(); + diff --git a/js/baba-yaga/scratch/js/runner.js b/js/baba-yaga/scratch/js/runner.js new file mode 100644 index 0000000..da9830a --- /dev/null +++ b/js/baba-yaga/scratch/js/runner.js @@ -0,0 +1,52 @@ +// runner.js +// Provides a host-agnostic evaluate(source, host) entrypoint that lexes, parses, and interprets. + +import { createLexer } from './src/core/lexer.js'; +import { createParser } from './src/core/parser.js'; +import { createInterpreter } from './src/core/interpreter.js'; + +/** + * Evaluate source code in the toy language and return a result object. + * @param {string} source - The program source code. + * @param {object} host - Optional host bindings, e.g. { io: { out, in } }. + * @returns {object} Result object with { ok: boolean, value?: any, error?: string } + */ +export function evaluate(source, host = {}) { + try { + const lexer = createLexer(source); + const tokens = lexer.allTokens(); + const parser = createParser(tokens, false, source); + const ast = parser.parse(); + const interpreter = createInterpreter(ast, host); + const result = interpreter.interpret(); + return { ok: true, value: result }; + } catch (error) { + return { ok: false, error: error.message }; + } +} + +/** + * Create a code frame showing the error location in source code + * @param {string} source - The source code + * @param {object} location - Location object with line/column + * @param {string} message - Error message + * @returns {string} Formatted code frame + */ +export function makeCodeFrame(source, location, message) { + if (!location || !location.line) { + return message; + } + + const lines = source.split('\n'); + const lineIndex = location.line - 1; + const line = lines[lineIndex]; + + if (!line) { + return message; + } + + const column = location.column || 1; + const pointer = ' '.repeat(Math.max(0, column - 1)) + '^'; + + return `${message}\n ${location.line} | ${line}\n | ${pointer}`; +} diff --git a/js/baba-yaga/scratch/js/test-lexer-compatibility.js b/js/baba-yaga/scratch/js/test-lexer-compatibility.js new file mode 100644 index 0000000..60b59fd --- /dev/null +++ b/js/baba-yaga/scratch/js/test-lexer-compatibility.js @@ -0,0 +1,54 @@ +// Test lexer compatibility between optimized and legacy versions + +import { createLexer as createOptimizedLexer } from './src/core/lexer.js'; +import { createLexer as createLegacyLexer } from './src/legacy/lexer.js'; + +const testCases = [ + 'var_with_underscore', + 'var123', + 'test_var_2', + 'simple', + 'CamelCase', + '_underscore_start', + 'a1b2c3', + 'function_name_123', + 'leftNext', + 'rightNext' +]; + +console.log('๐ Testing Lexer Compatibility\n'); + +for (const testCase of testCases) { + console.log(`Testing: "${testCase}"`); + + try { + // Test optimized lexer + const optimizedLexer = createOptimizedLexer(testCase); + const optimizedTokens = optimizedLexer.allTokens(); + const optimizedToken = optimizedTokens[0]; + + // Test legacy lexer + const legacyLexer = createLegacyLexer(testCase); + const legacyTokens = legacyLexer.allTokens(); + const legacyToken = legacyTokens[0]; + + // Compare results + const match = optimizedToken.type === legacyToken.type && + optimizedToken.value === legacyToken.value; + + console.log(` Optimized: ${optimizedToken.type} = "${optimizedToken.value}"`); + console.log(` Legacy: ${legacyToken.type} = "${legacyToken.value}"`); + console.log(` Match: ${match ? 'โ ' : 'โ'}`); + + if (!match) { + console.log(` ๐จ MISMATCH DETECTED!`); + } + + } catch (error) { + console.log(` โ ERROR: ${error.message}`); + } + + console.log(''); +} + +console.log('Lexer compatibility test complete.'); |