diff options
Diffstat (limited to 'js/scripting-lang/docs/repl/baba-yaga/0.0.1/repl.js.html')
-rw-r--r-- | js/scripting-lang/docs/repl/baba-yaga/0.0.1/repl.js.html | 2482 |
1 files changed, 0 insertions, 2482 deletions
diff --git a/js/scripting-lang/docs/repl/baba-yaga/0.0.1/repl.js.html b/js/scripting-lang/docs/repl/baba-yaga/0.0.1/repl.js.html deleted file mode 100644 index 8088165..0000000 --- a/js/scripting-lang/docs/repl/baba-yaga/0.0.1/repl.js.html +++ /dev/null @@ -1,2482 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>JSDoc: Source: repl.js</title> - - <script src="scripts/prettify/prettify.js"> </script> - <script src="scripts/prettify/lang-css.js"> </script> - <!--[if lt IE 9]> - <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> - <![endif]--> - <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> - <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> -</head> - -<body> - -<div id="main"> - - <h1 class="page-title">Source: repl.js</h1> - - - - - - - <section> - <article> - <pre class="prettyprint source linenums"><code>#!/usr/bin/env node - -/** - * Baba Yaga REPL - Interactive Language Playground & Harness Integration Demo - * - * This REPL serves two primary purposes: - * 1. **Language Playground**: Interactive exploration of the Baba Yaga functional language - * 2. **Harness Demo**: Demonstration of scripting harness integration patterns - * - * ## Architecture Overview - * - * The REPL integrates with a TEA-inspired functional harness: - * - * ```javascript - * // Model: Current state (currentState) - * // Update: Pure function (harness.update) → { model, commands, version } - * // Commands: Side effects processed by adapters - * - * // Example flow: - * const result = await harness.update(currentState); - * // result = { model: newState, commands: [...], version: 1 } - * - * for (const command of result.commands) { - * await adapter.process(command); - * } - * ``` - * - * ## Key Integration Patterns - * - * ### 1. Harness Integration - * The FunctionalHarness manages script execution and state versioning: - * - Scripts are executed in a controlled environment - * - State changes are tracked with version history - * - Commands are extracted for adapter processing - * - * ### 2. Adapter Pattern - * Adapters handle side effects (I/O, network, etc.): - * - Console Adapter: Output and logging - * - File Adapter: File read/write operations - * - Network Adapter: HTTP requests - * - * ### 3. State Management - * - Automatic version tracking - * - History with rollback capabilities - * - Basic branching support - * - * ## Usage Examples - * - * ### Basic Script Execution - * ```javascript - * // User types: "result : add 5 3;" - * // REPL executes: harness.update(currentState) with script content - * // Result: { model: { result: 8 }, commands: [], version: 1 } - * ``` - * - * ### Adapter Command Processing - * ```javascript - * // User types: "..emit { action: 'http_request', url: 'https://api.example.com' };" - * // REPL extracts command and routes to Network Adapter - * // Network Adapter makes actual HTTP request - * ``` - * - * ### State Versioning - * ```javascript - * // Each script execution creates a new version - * // Users can rollback: ":rollback 2" - * // Users can create branches: ":branch 3 experimental" - * ``` - * - * ## Integration Guide for Developers - * - * To integrate Baba Yaga and the harness into your own application: - * - * 1. **Import the harness**: `import { FunctionalHarness } from './scripting-harness/core/harness.js'` - * 2. **Create adapters**: Define your own adapter objects with `process()` methods - * 3. **Initialize harness**: `await harness.initialize()` - * 4. **Execute scripts**: `const result = await harness.update(currentState)` - * 5. **Process commands**: Route `result.commands` to appropriate adapters - * - * See the constructor and adapter definitions below for working examples. - */ - -import { FunctionalHarness } from '../scripting-harness/core/harness.js'; -import { createInterface } from 'readline'; -import { promises as fs } from 'fs'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -/** - * Baba Yaga REPL Class - * - * This class demonstrates integration of the Baba Yaga language - * with the functional harness architecture. It serves as both a language - * playground and a reference for harness integration patterns. - * - * ## Architecture Principles Demonstrated - * - * 1. **Separation of Concerns**: Script execution vs. side effects - * 2. **Adapter Pattern**: Pluggable side-effect handlers - * 3. **State Management**: Versioned state with history and rollback - * 4. **Command Processing**: Structured communication between pure and impure code - * - * ## Key Methods for Integration Reference - * - * - `init()`: Harness initialization and setup - * - `executeScript()`: Core script execution with harness integration - * - `processAdapterCommand()`: Adapter routing and command processing - * - `handleInput()`: Input parsing and command routing - * - * ## State Flow - * - * ``` - * User Input → handleInput() → executeScript() → harness.update() - * ↓ - * { model, commands, version } - * ↓ - * processAdapterCommand() - * ↓ - * adapter.process(command) - * ``` - */ -class REPL { - /** - * Initialize the REPL with harness integration - * - * This constructor sets up the core components needed for both - * language playground functionality and harness integration demonstration. - * - * ## Key Components - * - * ### 1. Readline Interface - * Handles user input with multi-line support and history management. - * - * ### 2. Harness Instance - * The FunctionalHarness that manages script execution and state. - * - * ### 3. Adapter Registry - * Side-effect handlers that demonstrate the adapter pattern. - * - * ## Integration Pattern - * - * This constructor demonstrates how to set up harness integration: - * - * ```javascript - * // 1. Create harness instance - * this.harness = new FunctionalHarness(scriptPath, config); - * - * // 2. Define adapters for side effects - * this.adapters = { - * console: { process: async (command) => { // handle console output } }, - * file: { process: async (command) => { // handle file operations } }, - * network: { process: async (command) => { // handle HTTP requests } } - * }; - * - * // 3. Initialize state tracking - * this.currentState = {}; - * this.currentVersion = 0; - * ``` - * - * ## Adapter Pattern Explanation - * - * Adapters are the bridge between script execution and side effects. - * Each adapter handles a specific type of side effect: - * - * - **Console Adapter**: Handles output and logging - * - **File Adapter**: Handles file system operations - * - **Network Adapter**: Handles HTTP requests - * - * This pattern allows the harness to focus on script execution while - * enabling real-world functionality through structured command processing. - */ - constructor() { - // Readline interface for user interaction - this.rl = null; - - // Command history management - this.history = []; - this.historyFile = join(__dirname, '.repl_history'); - - // Multi-line input support - this.isMultiLine = false; - this.multiLineBuffer = ''; - - // Harness integration - Core of the architecture - this.harness = null; - this.currentState = {}; - this.currentVersion = 0; - - /** - * Adapter Registry - Side Effect Handlers - * - * This registry demonstrates the adapter pattern, where each adapter - * handles a specific type of side effect. This allows the harness - * to remain pure while enabling real-world functionality. - * - * ## Adapter Structure - * - * Each adapter has: - * - `name`: Human-readable identifier - * - `description`: Purpose and capabilities - * - `process(command)`: Async function that handles commands - * - * ## Command Format - * - * Commands are structured objects with: - * - `type`: Usually 'emit' for side effects - * - `value`: Action-specific data (e.g., { action: 'http_request', url: '...' }) - * - * ## Integration Example - * - * ```javascript - * // Script generates command - * ..emit { action: 'save_file', filename: 'data.json', data: { x: 1 } }; - * - * // Harness extracts command - * const result = await harness.update({ script: userCode }); - * // result.commands = [{ type: 'emit', value: { action: 'save_file', ... } }] - * - * // REPL routes to appropriate adapter - * await this.processAdapterCommand(result.commands[0]); - * // Routes to file adapter's process() method - * ``` - */ - this.adapters = { - // Console Adapter - Output and Logging - // Handles console output commands from scripts. This adapter - // demonstrates how to process simple output commands. - // - // Usage in Scripts: - // ..emit "Hello, World!"; - // ..emit { message: "Debug info", level: "info" }; - console: { - name: 'Console Adapter', - description: 'Handles console output and logging', - process: async (command) => { - if (command.type === 'emit') { - console.log('\x1b[36m[Console Adapter]\x1b[0m', command.value); - } - } - }, - - // File Adapter - File System Operations - // Handles file read and write operations. This adapter demonstrates - // how to process structured file commands with error handling. - // - // Supported Actions: - // - save_file: ..emit { action: 'save_file', filename: 'data.json', data: {...} }; - // - read_file: ..emit { action: 'read_file', filename: 'config.json' }; - file: { - name: 'File Adapter', - description: 'Handles file operations (read and write)', - process: async (command) => { - if (command.type === 'emit' && command.value.action === 'save_file') { - try { - await fs.writeFile(command.value.filename, JSON.stringify(command.value.data, null, 2)); - console.log(`\x1b[32m[File Adapter]\x1b[0m ✅ Saved to ${command.value.filename}`); - } catch (error) { - console.log(`\x1b[31m[File Adapter]\x1b[0m ❌ Error: ${error.message}`); - } - } else if (command.type === 'emit' && command.value.action === 'read_file') { - try { - const content = await fs.readFile(command.value.filename, 'utf8'); - console.log(`\x1b[32m[File Adapter]\x1b[0m ✅ Read from ${command.value.filename}`); - console.log(`\x1b[36m[File Adapter]\x1b[0m Content length: ${content.length} characters`); - - // Store the content for script processing - command.value.content = content; - console.log(`\x1b[33m[File Adapter]\x1b[0m 💡 File content available for script processing`); - } catch (error) { - console.log(`\x1b[31m[File Adapter]\x1b[0m ❌ Error reading ${command.value.filename}: ${error.message}`); - } - } - } - }, - - // Network Adapter - HTTP Requests - // Handles HTTP requests with real network calls. This adapter - // demonstrates how to process network commands with proper - // request configuration and error handling. - // - // Supported Actions: - // - http_request: ..emit { action: 'http_request', method: 'GET', url: 'https://api.example.com' }; - network: { - name: 'Network Adapter', - description: 'Handles HTTP requests with real network calls', - process: async (command) => { - if (command.type === 'emit' && command.value.action === 'http_request') { - const { method = 'GET', url, headers = {}, body, timeout = 5000 } = command.value; - - console.log(`\x1b[33m[Network Adapter]\x1b[0m Making ${method} request to ${url}`); - - try { - // Prepare request options - const options = { - method: method.toUpperCase(), - headers: { - 'User-Agent': 'Baba-Yaga-REPL/1.0', - 'Accept': 'application/json', - ...headers - }, - timeout: timeout - }; - - // Add body for POST/PUT requests - if (body && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) { - options.body = typeof body === 'string' ? body : JSON.stringify(body); - if (typeof body === 'object') { - options.headers['Content-Type'] = 'application/json'; - } - } - - // Make the actual HTTP request - const response = await fetch(url, options); - - // Process response - const responseText = await response.text(); - let responseData; - - try { - responseData = JSON.parse(responseText); - } catch { - responseData = responseText; - } - - // Display results - console.log(`\x1b[32m[Network Adapter]\x1b[0m ✅ ${method} ${url} - Status: ${response.status}`); - console.log(`\x1b[36m[Network Adapter]\x1b[0m Response Headers:`, Object.fromEntries(response.headers.entries())); - - if (typeof responseData === 'object') { - console.log(`\x1b[36m[Network Adapter]\x1b[0m Response Data:`, JSON.stringify(responseData, null, 2)); - } else { - console.log(`\x1b[36m[Network Adapter]\x1b[0m Response Data:`, responseData); - } - - // Emit response data for further processing - console.log(`\x1b[33m[Network Adapter]\x1b[0m 💡 Response data available for script processing`); - - } catch (error) { - console.log(`\x1b[31m[Network Adapter]\x1b[0m ❌ Error making ${method} request to ${url}:`); - console.log(`\x1b[31m[Network Adapter]\x1b[0m ${error.message}`); - - if (error.name === 'TypeError' && error.message.includes('fetch')) { - console.log(`\x1b[33m[Network Adapter]\x1b[0m 💡 Note: Node.js fetch requires Node 18+ or a polyfill`); - } - } - } - } - } - }; - - // Built-in harness examples - this.examples = { - 'basic': { - title: 'Basic State Management', - description: 'Simple state processing with basic operations', - code: `/* Basic state management example */ -/* Create and process state */ -x : 5; -y : 10; -sum : x + y; -result : { x, y, sum }; -/* Return processed state */ -result` - }, - 'counter': { - title: 'Counter with State', - description: 'Counter that maintains state across executions', - code: `/* Counter example with state persistence */ -/* Simple counter logic */ -count : 0; -new_count : count + 1; -result : { count: new_count, name: "Counter" }; -/* Return updated state */ -result` - }, - 'data-pipeline': { - title: 'Data Processing Pipeline', - description: 'Simple data transformation', - code: `/* Data processing pipeline */ -/* Process simple data */ -numbers : {1: 10, 2: 3, 3: 8}; -doubled : map @(multiply 2) numbers; -result : { original: numbers, processed: doubled }; -/* Return processed result */ -result` - }, - 'user-management': { - title: 'User Management System', - description: 'User state management with validation', - code: `/* User management system */ -/* Simple user validation */ -name : "Alice"; -age : 25; -status : when age >= 18 then "valid" _ then "underage"; -user : { name, age, status }; -/* Return validated state */ -user` - }, - 'error-handling': { - title: 'Error Handling', - description: 'Demonstrates error handling in harness', - code: `/* Error handling example */ -/* Safe operations through harness */ -data : 10; -safe_operation : when data > 0 then data / 2 _ then 0; -result : { operation: "safe_division", result: safe_operation }; -/* Return safe result */ -result` - }, - 'recursive': { - title: 'Recursive Functions', - description: 'Factorial and Fibonacci using recursion', - code: `/* Recursive functions example */ -/* Factorial function */ -factorial : n -> - when n is - 0 then 1 - _ then n * (factorial (n - 1)); - -/* Fibonacci function */ -fibonacci : n -> - when n is - 0 then 0 - 1 then 1 - _ then (fibonacci (n - 1)) + (fibonacci (n - 2)); - -/* Test factorial */ -fact_5 : factorial 5; -fact_0 : factorial 0; - -/* Test fibonacci */ -fib_6 : fibonacci 6; -fib_10 : fibonacci 10; - -/* Return results */ -{ factorial, fibonacci, fact_5, fact_0, fib_6, fib_10 }` - }, - 'network': { - title: 'Network API Integration', - description: 'Fetch Pokémon data using PokéAPI', - code: `/* Network API integration example */ -/* Using PokéAPI to fetch Pokémon data */ - -/* Get current state to see if we have a Pokémon name */ -state : ..listen; - -/* Determine which Pokémon to fetch */ -pokemon_name : when state is - { pokemon: name } then name - _ then "ditto"; /* Default to ditto */ - -/* Emit network request to PokéAPI */ -..emit { - action: "http_request", - method: "GET", - url: "https://pokeapi.co/api/v2/pokemon/" + pokemon_name -}; - -/* Also fetch a list of Pokémon */ -..emit { - action: "http_request", - method: "GET", - url: "https://pokeapi.co/api/v2/pokemon?limit=5" -}; - -/* Return the request configuration */ -{ - pokemon_name, - requests: [ - { method: "GET", url: "https://pokeapi.co/api/v2/pokemon/" + pokemon_name }, - { method: "GET", url: "https://pokeapi.co/api/v2/pokemon?limit=5" } - ] -}` - }, - 'http-get': { - title: 'HTTP GET Request', - description: 'Simple GET request to a public API', - code: `/* HTTP GET request example */ -/* Simple GET request to JSONPlaceholder API */ - -/* Make a GET request to fetch a post */ -..emit { - action: "http_request", - method: "GET", - url: "https://jsonplaceholder.typicode.com/posts/1", - headers: { - "Accept": "application/json" - } -}; - -/* Return request info */ -{ - request_type: "GET", - url: "https://jsonplaceholder.typicode.com/posts/1", - description: "Fetching a sample post from JSONPlaceholder" -}` - }, - 'http-post': { - title: 'HTTP POST Request', - description: 'POST request with JSON body', - code: `/* HTTP POST request example */ -/* Creating a new post via JSONPlaceholder API */ - -/* Prepare post data */ -post_data : { - title: "Baba Yaga REPL Test", - body: "This is a test post from the Baba Yaga REPL", - userId: 1 -}; - -/* Make POST request */ -..emit { - action: "http_request", - method: "POST", - url: "https://jsonplaceholder.typicode.com/posts", - headers: { - "Content-Type": "application/json" - }, - body: post_data -}; - -/* Return request info */ -{ - request_type: "POST", - url: "https://jsonplaceholder.typicode.com/posts", - data: post_data, - description: "Creating a new post" -}` - }, - 'http-weather': { - title: 'Weather API Request', - description: 'Fetch weather data from OpenWeatherMap', - code: `/* Weather API request example */ -/* Using OpenWeatherMap API (free tier) */ - -/* Get current state for city */ -state : ..listen; - -/* Determine city to fetch weather for */ -city : when state is - { city: name } then name - _ then "London"; /* Default city */ - -/* Make weather request */ -..emit { - action: "http_request", - method: "GET", - url: "https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=YOUR_API_KEY&units=metric", - headers: { - "Accept": "application/json" - } -}; - -/* Return request info */ -{ - city: city, - request_type: "GET", - url: "https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=YOUR_API_KEY&units=metric", - note: "Replace YOUR_API_KEY with actual OpenWeatherMap API key" -}` - }, - 'file-operations': { - title: 'File Operations with Adapters', - description: 'Demonstrates file adapter usage for read/write operations', - code: `/* File operations example */ -/* Demonstrates file adapter integration */ - -/* Get current state */ -state : ..listen; - -/* Read a file using file adapter */ -..emit { - action: "read_file", - filename: "tests/09_tables.txt" -}; - -/* Save current state to file */ -..emit { - action: "save_file", - filename: "current_state.json", - data: state -}; - -/* Return operation info */ -{ - operations: [ - { action: "read_file", filename: "tests/09_tables.txt" }, - { action: "save_file", filename: "current_state.json", data: state } - ], - note: "File operations processed through file adapter" -}` - }, - 'state-driven-adapters': { - title: 'State-Driven Adapter Usage', - description: 'Demonstrates conditional adapter usage based on state', - code: `/* State-driven adapter usage */ -/* Shows how state determines which adapters to use */ - -/* Get current state */ -state : ..listen; - -/* Process state and emit appropriate commands */ -when state.action is - "save_data" then ..emit { - action: "save_file", - filename: state.filename, - data: state.data - } - "fetch_data" then ..emit { - action: "http_request", - method: "GET", - url: state.url - } - "log_info" then ..emit { - action: "console_log", - message: state.message - } - _ then ..emit { - action: "console_log", - message: "Unknown action: " + state.action - }; - -/* Return processed state */ -{ - action: state.action, - processed: true, - timestamp: Date.now() -}` - }, - 'harness-features': { - title: 'Harness Features Demo', - description: 'Demonstrates versioning, branching, and state management', - code: `/* Harness features demonstration */ -/* Shows versioning, state management, and adapter integration */ - -/* Get current state */ -state : ..listen; - -/* Process state with version tracking */ -processed_state : when state is - { action: "initialize" } then { - version: 1, - status: "initialized", - timestamp: Date.now(), - data: {} - } - { action: "update", data: newData } then { - version: state.version + 1, - status: "updated", - timestamp: Date.now(), - data: newData - } - { action: "save" } then { - version: state.version, - status: "saved", - timestamp: Date.now(), - data: state.data - } - _ then { - version: state.version || 1, - status: "unknown", - timestamp: Date.now(), - data: state.data || {} - }; - -/* Save state to file for persistence */ -..emit { - action: "save_file", - filename: "harness_state_v" + processed_state.version + ".json", - data: processed_state -}; - -/* Log the operation */ -..emit { - action: "console_log", - message: "State processed: " + processed_state.status + " (v" + processed_state.version + ")" -}; - -/* Return processed state */ -processed_state` - }, - 'branching-demo': { - title: 'Branching and State Management', - description: 'Demonstrates branching, state diffing, and version control', - code: `/* Branching and state management demonstration */ -/* Shows advanced harness features */ - -/* Get current state */ -state : ..listen; - -/* Create a branching scenario */ -branch_scenario : when state is - { action: "create_branch", name: branchName, fromVersion: version } then { - action: "branch_created", - branch_name: branchName, - base_version: version, - timestamp: Date.now(), - status: "ready" - } - { action: "merge_branch", source: sourceBranch, target: targetBranch } then { - action: "branch_merged", - source_branch: sourceBranch, - target_branch: targetBranch, - timestamp: Date.now(), - status: "merged" - } - { action: "compare_versions", from: fromVersion, to: toVersion } then { - action: "version_compared", - from_version: fromVersion, - to_version: toVersion, - timestamp: Date.now(), - status: "compared" - } - _ then { - action: "unknown", - timestamp: Date.now(), - status: "unknown" - }; - -/* Log the branching operation */ -..emit { - action: "console_log", - message: "Branching operation: " + branch_scenario.action + " - " + branch_scenario.status -}; - -/* Save branch state */ -..emit { - action: "save_file", - filename: "branch_" + branch_scenario.action + "_" + Date.now() + ".json", - data: branch_scenario -}; - -/* Return branch scenario */ -branch_scenario` - }, - 'error-recovery-demo': { - title: 'Error Recovery and Resilience', - description: 'Demonstrates error recovery, retry mechanisms, and resilience', - code: `/* Error recovery and resilience demonstration */ -/* Shows how the harness handles errors gracefully */ - -/* Get current state */ -state : ..listen; - -/* Simulate different error scenarios */ -error_scenario : when state is - { action: "simulate_timeout" } then { - action: "timeout_simulation", - retry_count: 0, - max_retries: 3, - status: "retrying" - } - { action: "simulate_network_error" } then { - action: "network_error_simulation", - retry_count: 0, - max_retries: 5, - backoff_delay: 2000, - status: "retrying" - } - { action: "simulate_script_error" } then { - action: "script_error_simulation", - recovery_action: "rollback", - rollback_version: state.version - 1, - status: "recovering" - } - { action: "test_resilience", data: testData } then { - action: "resilience_test", - test_data: testData, - attempts: 0, - max_attempts: 3, - status: "testing" - } - _ then { - action: "no_error", - status: "normal", - timestamp: Date.now() - }; - -/* Log the error recovery operation */ -..emit { - action: "console_log", - message: "Error recovery: " + error_scenario.action + " - " + error_scenario.status -}; - -/* Save error recovery state */ -..emit { - action: "save_file", - filename: "error_recovery_" + error_scenario.action + ".json", - data: error_scenario -}; - -/* Return error scenario */ -error_scenario` - }, - 'state-diffing-demo': { - title: 'State Diffing and Analysis', - description: 'Demonstrates state comparison, diffing, and analysis', - code: `/* State diffing and analysis demonstration */ -/* Shows how to compare and analyze state changes */ - -/* Get current state */ -state : ..listen; - -/* Analyze state changes */ -state_analysis : when state is - { action: "analyze_changes", fromVersion: fromVersion, toVersion: toVersion } then { - action: "state_analysis", - from_version: fromVersion, - to_version: toVersion, - analysis_type: "diff", - timestamp: Date.now() - } - { action: "track_properties", properties: propList } then { - action: "property_tracking", - tracked_properties: propList, - change_count: 0, - timestamp: Date.now() - } - { action: "detect_drift", baseline: baselineState } then { - action: "drift_detection", - baseline_state: baselineState, - current_state: state, - drift_detected: false, - timestamp: Date.now() - } - { action: "optimize_state", optimization: optType } then { - action: "state_optimization", - optimization_type: optType, - original_size: 0, - optimized_size: 0, - timestamp: Date.now() - } - _ then { - action: "state_snapshot", - snapshot_data: state, - timestamp: Date.now() - }; - -/* Log the state analysis */ -..emit { - action: "console_log", - message: "State analysis: " + state_analysis.action + " completed" -}; - -/* Save analysis results */ -..emit { - action: "save_file", - filename: "state_analysis_" + state_analysis.action + ".json", - data: state_analysis -}; - -/* Return analysis results */ -state_analysis` - } - }; - } - - /** - * Initialize the REPL and harness integration - * - * This method sets up the complete REPL environment, including: - * - Display welcome message and feature overview - * - Load command history from file - * - Set up readline interface for user interaction - * - Initialize the harness (deferred until first script execution) - * - * ## Integration Pattern - * - * This method demonstrates the initialization sequence for a harness-integrated application: - * - * ```javascript - * // 1. Display application information - * console.log('Welcome to Baba Yaga REPL'); - * - * // 2. Load persistent state (history, configuration) - * await this.loadHistory(); - * - * // 3. Set up user interface - * this.setupReadline(); - * - * // 4. Harness initialization is deferred until first use - * // This improves startup performance and allows for lazy loading - * ``` - * - * ## Key Design Decisions - * - * ### Lazy Harness Initialization - * The harness is not initialized here but deferred until the first script execution. - * This improves startup performance and allows the REPL to start even if there are - * issues with the harness setup. - * - * ### History Management - * Command history is loaded from a persistent file, demonstrating how to maintain - * user state across sessions. - * - * ### User Interface Setup - * The readline interface is configured with custom prompts and event handlers, - * showing how to create an interactive command-line interface. - * - * ## Usage in Integration - * - * When integrating the harness into your own application, follow this pattern: - * - * ```javascript - * class MyApp { - * async init() { - * // 1. Set up your application UI/interface - * this.setupInterface(); - * - * // 2. Load any persistent state - * await this.loadState(); - * - * // 3. Set up harness (or defer until needed) - * this.harness = new FunctionalHarness(scriptPath, config); - * - * // 4. Start your application - * this.start(); - * } - * } - * ``` - */ - async init() { - console.log('\x1b[36m╔══════════════════════════════════════════════════════════════╗\x1b[0m'); - console.log('\x1b[36m║ Baba Yaga ║\x1b[0m'); - console.log('\x1b[36m║ REPL ║\x1b[0m'); - console.log('\x1b[36m╚══════════════════════════════════════════════════════════════╝\x1b[0m'); - console.log(''); - console.log('\x1b[33m🎯 Features:\x1b[0m'); - console.log(' • Multi-line input (end with semicolon)'); - console.log(' • Always shows execution results'); - console.log(' • Function calls: result : func args;'); - console.log(' • Branching history, and versioning with rollbacks'); - console.log(''); - console.log('\x1b[33mQuick Commands:\x1b[0m'); - console.log(' :help - Show full help'); - console.log(' :examples - List examples'); - console.log(' :run - Run a script from a file (supports any path)'); - console.log(' :branch - Create branches'); - console.log(' :menu - Interactive history'); - console.log(' :state - Show current state'); - console.log(' :quit - Exit REPL'); - console.log(' :exit - Exit REPL'); - console.log(' :bye - Exit REPL'); - - console.log(''); - - await this.loadHistory(); - this.setupReadline(); - } - - /** - * Set up readline interface - */ - setupReadline() { - this.rl = createInterface({ - input: process.stdin, - output: process.stdout, - prompt: this.getPrompt(), - historySize: 1000 - }); - - this.rl.on('line', (input) => this.handleInput(input)); - this.rl.on('close', () => this.cleanup()); - - this.rl.prompt(); - } - - /** - * Get current prompt - */ - getPrompt() { - if (this.isMultiLine) { - return '\x1b[32m... \x1b[0m'; - } - return `\x1b[32m[${this.currentVersion}] .. \x1b[0m`; - } - - /** - * Handle user input - */ - async handleInput(input) { - const trimmed = input.trim(); - - // Handle empty input - if (!trimmed) { - if (this.isMultiLine) { - // Continue multi-line input - this.rl.prompt(); - return; - } - this.rl.prompt(); - return; - } - - // Handle REPL commands - if (trimmed.startsWith(':')) { - await this.processCommand(trimmed); - this.rl.prompt(); - return; - } - - // Handle multi-line input (continue if no semicolon) - if (!trimmed.endsWith(';')) { - this.isMultiLine = true; - this.multiLineBuffer += (this.multiLineBuffer ? '\n' : '') + trimmed; - this.rl.setPrompt(this.getPrompt()); - this.rl.prompt(); - return; - } - - // Handle single line or end of multi-line (has semicolon) - if (this.isMultiLine) { - this.multiLineBuffer += '\n' + trimmed; - await this.executeMultiLine(); - } else { - await this.executeScript(trimmed); - } - - this.rl.prompt(); - } - - /** - * Execute multi-line script - */ - async executeMultiLine() { - const script = this.multiLineBuffer; - this.multiLineBuffer = ''; - this.isMultiLine = false; - this.rl.setPrompt(this.getPrompt()); - - // Auto-format the script for better readability - const formattedScript = this.autoFormatScript(script); - await this.executeScript(formattedScript); - } - - /** - * Auto-format multi-line script for better readability - */ - autoFormatScript(script) { - // Remove trailing semicolon from the last line - const lines = script.split('\n'); - if (lines[lines.length - 1].trim().endsWith(';')) { - lines[lines.length - 1] = lines[lines.length - 1].trim().slice(0, -1); - } - - // Join lines and clean up - return lines.join('\n').trim(); - } - - /** - * Execute Baba Yaga script using the functional harness - * - * This method demonstrates harness integration by: - * - Initializing the harness on first use (lazy initialization) - * - Executing scripts with the current state - * - Processing side effects through adapters - * - Managing state versioning - * - Handling errors gracefully - * - * ## Core Integration Pattern - * - * This method implements the harness integration flow: - * - * ```javascript - * // 1. Lazy harness initialization - * if (!this.harness) { - * this.harness = new FunctionalHarness(script, config); - * await this.harness.initialize(); - * } - * - * // 2. Execute script with current state - * const result = await this.harness.update(this.currentState); - * // result = { model: newState, commands: [...], version: 1 } - * - * // 3. Update application state - * this.currentState = result.model; - * this.currentVersion = result.version; - * - * // 4. Process side effects through adapters - * for (const command of result.commands) { - * await this.processAdapterCommand(command); - * } - * - * // 5. Display results to user - * this.displayResult(result); - * ``` - * - * ## Key Design Principles - * - * ### 1. Script Execution - * Scripts are executed in a controlled environment managed by the harness. - * Side effects are extracted as commands and processed separately. - * - * ### 2. State Management - * State is managed with automatic versioning. Each script execution - * creates a new version, enabling history tracking and rollback capabilities. - * - * ### 3. Side Effect Isolation - * Side effects (I/O, network, etc.) are isolated from script execution - * through the command/adapter pattern. - * - * ### 4. Error Handling - * Errors are caught and displayed gracefully, with the harness maintaining - * its state even when scripts fail. - * - * ## Integration Example - * - * When integrating the harness into your own application: - * - * ```javascript - * class MyApp { - * async executeUserScript(script) { - * try { - * // 1. Initialize harness if needed - * if (!this.harness) { - * this.harness = new FunctionalHarness(script, config); - * await this.harness.initialize(); - * } - * - * // 2. Execute script with current state - * const result = await this.harness.update(this.currentState); - * - * // 3. Update application state - * this.currentState = result.model; - * this.currentVersion = result.version; - * - * // 4. Process side effects - * for (const command of result.commands) { - * await this.processCommand(command); - * } - * - * // 5. Handle results - * this.handleResult(result); - * - * } catch (error) { - * this.handleError(error); - * } - * } - * } - * ``` - * - * ## State Flow - * - * ``` - * User Input → executeScript() → harness.update(currentState) - * ↓ - * { model, commands, version } - * ↓ - * Update currentState & version - * ↓ - * Process commands through adapters - * ↓ - * Display results to user - * ``` - * - * @param {string} script - The Baba Yaga script to execute - * @returns {Promise<void>} - Resolves when script execution is complete - */ - async executeScript(script) { - try { - // Add to history - this.addToHistory(script); - - // Create or update harness - if (!this.harness) { - this.harness = new FunctionalHarness(script, { - logStateChanges: false, - logCommands: false, - debug: false - }); - // Initialize the harness - await this.harness.initialize(); - } else { - // Update script content for this execution - this.harness.scriptContent = script; - } - - // Process state through harness (get commands without processing them) - const result = await this.harness.update(this.currentState); - - // Update current state and version - this.currentState = result.model; - this.currentVersion = result.version; - - // Update the prompt to reflect the new version - this.rl.setPrompt(this.getPrompt()); - - // Process commands through adapters (silently) - if (result.commands && result.commands.length > 0) { - for (const command of result.commands) { - await this.processAdapterCommand(command); - } - } - - // Always display the result clearly - this.displayResult(result); - - } catch (error) { - this.displayError(error); - } - } - - /** - * Process commands through the adapter registry - * - * This method demonstrates the adapter pattern by routing commands - * from script execution to side-effect handlers (adapters). - * - * ## Adapter Pattern Implementation - * - * The adapter pattern allows the harness to focus on script execution while - * enabling side effects through structured command processing: - * - * ```javascript - * // Script generates command - * ..emit { action: 'save_file', filename: 'data.json', data: { x: 1 } }; - * - * // Harness extracts command - * const result = await harness.update(currentState); - * // result.commands = [{ type: 'emit', value: { action: 'save_file', ... } }] - * - * // REPL routes to appropriate adapter - * await this.processAdapterCommand(result.commands[0]); - * // Routes to file adapter's process() method - * ``` - * - * ## Command Routing Strategy - * - * This implementation uses a "broadcast" strategy where each command is - * sent to all adapters. Each adapter decides whether to handle the command - * based on its content: - * - * ```javascript - * // Each adapter checks if it should handle the command - * if (command.type === 'emit' && command.value.action === 'save_file') { - * // File adapter handles this command - * await fs.writeFile(command.value.filename, JSON.stringify(command.value.data)); - * } - * - * if (command.type === 'emit' && command.value.action === 'http_request') { - * // Network adapter handles this command - * const response = await fetch(command.value.url, options); - * } - * ``` - * - * ## Command Structure - * - * Commands are structured objects with: - * - `type`: Usually 'emit' for side effects - * - `value`: Action-specific data (e.g., { action: 'http_request', url: '...' }) - * - * ## Error Handling - * - * Each adapter processes commands independently. If one adapter fails, - * others continue processing. This provides error isolation. - * - * ## Integration Example - * - * When implementing adapters in your own application: - * - * ```javascript - * class MyApp { - * constructor() { - * this.adapters = { - * database: { - * name: 'Database Adapter', - * process: async (command) => { - * if (command.type === 'emit' && command.value.action === 'save_record') { - * await this.db.save(command.value.table, command.value.data); - * } - * } - * }, - * email: { - * name: 'Email Adapter', - * process: async (command) => { - * if (command.type === 'emit' && command.value.action === 'send_email') { - * await this.emailService.send(command.value.to, command.value.subject); - * } - * } - * } - * }; - * } - * - * async processCommand(command) { - * for (const [name, adapter] of Object.entries(this.adapters)) { - * try { - * await adapter.process(command); - * } catch (error) { - * console.error(`[${adapter.name}] Error:`, error.message); - * } - * } - * } - * } - * ``` - * - * ## Alternative Routing Strategies - * - * Instead of broadcasting to all adapters, you could implement: - * - * ### 1. Action-Based Routing - * ```javascript - * const action = command.value?.action; - * const adapter = this.adapters[action]; - * if (adapter) { - * await adapter.process(command); - * } - * ``` - * - * ### 2. Type-Based Routing - * ```javascript - * const type = command.type; - * const adapter = this.adapters[type]; - * if (adapter) { - * await adapter.process(command); - * } - * ``` - * - * ### 3. Priority-Based Routing - * ```javascript - * const adapters = Object.values(this.adapters).sort((a, b) => b.priority - a.priority); - * for (const adapter of adapters) { - * if (await adapter.canHandle(command)) { - * await adapter.process(command); - * break; // Stop after first handler - * } - * } - * ``` - * - * @param {Object} command - The command object from harness execution - * @param {string} command.type - Command type (usually 'emit') - * @param {Object} command.value - Action-specific data - * @returns {Promise<void>} - Resolves when all adapters have processed the command - */ - async processAdapterCommand(command) { - // Process through all adapters silently - for (const [name, adapter] of Object.entries(this.adapters)) { - try { - await adapter.process(command); - } catch (error) { - console.log(`\x1b[31m[${adapter.name}] Error: ${error.message}\x1b[0m`); - } - } - } - - /** - * Display execution result - */ - displayResult(result) { - // Find the last result from the script execution - const lastResult = this.findLastResult(result.model); - - if (lastResult !== undefined) { - console.log(`\x1b[32m→\x1b[0m ${this.formatValue(lastResult)}`); - } else { - console.log(`\x1b[90m→\x1b[0m (no result)`); - } - } - - /** - * Find the last result from the model (usually the last defined variable) - */ - findLastResult(model) { - if (!model || typeof model !== 'object') return undefined; - - const keys = Object.keys(model); - if (keys.length === 0) return undefined; - - // Look for common result variable names - const resultKeys = ['result', 'output', 'value', 'data']; - for (const key of resultKeys) { - if (model[key] !== undefined) { - return model[key]; - } - } - - // Return the last defined variable - return model[keys[keys.length - 1]]; - } - - /** - * Display error - */ - displayError(error) { - console.log('\x1b[31m✗ Error:\x1b[0m', error.message); - - if (error.message.includes('Unexpected token')) { - console.log('\x1b[33m💡 Tip:\x1b[0m Check your syntax. Use :help for examples.'); - } else if (error.message.includes('not defined')) { - console.log('\x1b[33m💡 Tip:\x1b[0m Use :examples to see available patterns.'); - } - } - - /** - * Format value for display - */ - formatValue(value, depth = 0) { - if (depth > 2) return '...'; - - if (value === null) return '\x1b[90mnull\x1b[0m'; - if (value === undefined) return '\x1b[90mundefined\x1b[0m'; - - const type = typeof value; - - switch (type) { - case 'string': - return `\x1b[32m"${value}"\x1b[0m`; - case 'number': - return `\x1b[33m${value}\x1b[0m`; - case 'boolean': - return `\x1b[35m${value}\x1b[0m`; - case 'function': - return `\x1b[36m[Function]\x1b[0m`; - case 'object': - if (Array.isArray(value)) { - return `\x1b[34m[${value.map(v => this.formatValue(v, depth + 1)).join(', ')}]\x1b[0m`; - } - const entries = Object.entries(value).slice(0, 5).map(([k, v]) => - `${k}: ${this.formatValue(v, depth + 1)}` - ); - const suffix = Object.keys(value).length > 5 ? '...' : ''; - return `\x1b[34m{${entries.join(', ')}${suffix}}\x1b[0m`; - default: - return String(value); - } - } - - /** - * Process REPL commands - */ - async processCommand(command) { - const args = command.trim().split(/\s+/); - const cmd = args[0].toLowerCase(); - - switch (cmd) { - case ':help': - this.showHelp(); - break; - case ':examples': - this.showExamples(); - break; - case ':state': - this.showState(); - break; - case ':history': - this.showHistory(); - break; - case ':adapters': - this.showAdapters(); - break; - case ':clear': - this.clearState(); - break; - case ':save': - await this.saveState(); - break; - case ':load': - await this.loadState(); - break; - case ':menu': - await this.showInteractiveMenu(); - break; - case ':branch': - if (args.length >= 3) { - await this.createBranch(parseInt(args[1]), args[2]); - } else { - console.log('\x1b[31mUsage: :branch <version> <name>\x1b[0m'); - } - break; - case ':diff': - if (args.length >= 2) { - const fromVersion = parseInt(args[1]); - const toVersion = args.length >= 3 ? parseInt(args[2]) : this.currentVersion; - this.showStateDiff(fromVersion, toVersion); - } else { - console.log('\x1b[31mUsage: :diff <fromVersion> [toVersion]\x1b[0m'); - } - break; - case ':replay': - if (args.length >= 2) { - const fromVersion = parseInt(args[1]); - const newState = args.length >= 3 ? JSON.parse(args[2]) : {}; - await this.replayFromVersion(fromVersion, newState); - } else { - console.log('\x1b[31mUsage: :replay <fromVersion> [newState]\x1b[0m'); - } - break; - case ':recover': - if (args.length >= 2) { - const errorType = args[1]; - await this.simulateErrorRecovery(errorType); - } else { - console.log('\x1b[31mUsage: :recover <errorType>\x1b[0m'); - console.log('\x1b[33mError types: timeout, network, script, filesystem\x1b[0m'); - } - break; - case ':quit': - case ':exit': - case ':bye': - await this.cleanup(); - process.exit(0); - break; - default: - if (cmd === ':run' && args.length >= 2) { - const filename = args[1]; - await this.runScriptFile(filename); - } else if (cmd === ':example' && args.length >= 2) { - const exampleName = args[1]; - await this.loadExample(exampleName); - } else { - console.log(`\x1b[31mUnknown command: ${cmd}\x1b[0m`); - console.log('\x1b[33mType :help for available commands\x1b[0m'); - } - } - } - - /** - * Show help information - */ - showHelp() { - console.log('\x1b[36m╔══════════════════════════════════════════════════════════════╗\x1b[0m'); - console.log('\x1b[36m║ Baba Yaga ║\x1b[0m'); - console.log('\x1b[36m║ REPL ║\x1b[0m'); - console.log('\x1b[36m╚══════════════════════════════════════════════════════════════╝\x1b[0m'); - console.log(''); - console.log('\x1b[33m🎯 Features:\x1b[0m'); - console.log(' • Multi-line input (end with semicolon)'); - console.log(' • Always shows execution results'); - console.log(' • Function calls: result : func args;'); - console.log(' • Branching history, and versioning with rollbacks'); - console.log(''); - console.log('\x1b[32mQuick Commands:\x1b[0m'); - console.log(' :help - Show full help'); - console.log(' :examples - List examples'); - console.log(' :run - Run a script from a file (supports any path)'); - console.log(' :branch - Create branches'); - console.log(' :menu - Interactive history'); - console.log(' :state - Show current state'); - console.log(' :quit - Exit REPL'); - console.log(' :exit - Exit REPL'); - console.log(' :bye - Exit REPL'); - console.log(''); - console.log('\x1b[34mLanguage Examples:\x1b[0m'); - console.log(' result : add 5 3; // Basic arithmetic'); - console.log(' result : multiply 4 7; // Multiplication'); - console.log(' result : subtract 10 3; // Subtraction'); - console.log(' result : divide 15 3; // Division'); - console.log(' result : modulo 17 5; // Modulo'); - console.log(' result : negate 5; // Unary minus'); - console.log(' result : subtract 5 -3; // Binary minus with unary'); - console.log(''); - console.log(' result : equals 5 5; // Comparison'); - console.log(' result : greater 10 5; // Greater than'); - console.log(' result : less 3 7; // Less than'); - console.log(' result : greaterEqual 5 5; // Greater or equal'); - console.log(' result : lessEqual 3 7; // Less or equal'); - console.log(' result : notEqual 5 3; // Not equal'); - console.log(''); - console.log(' result : and true false; // Logical AND'); - console.log(' result : or true false; // Logical OR'); - console.log(' result : not true; // Logical NOT'); - console.log(''); - console.log(' result : print "Hello"; // Output'); - console.log(' result : input; // Input'); - console.log(''); - console.log(' result : when 5 is 5 then "yes" else "no"; // Conditional'); - console.log(' result : when x is 10 then "ten" else "other"; // Pattern matching'); - console.log(''); - console.log(' result : {1, 2, 3}; // Table literal'); - console.log(' result : t.get {1, 2, 3} 1; // Table access'); - console.log(' result : t.set {1, 2, 3} 1 10; // Table update'); - console.log(' result : t.length {1, 2, 3}; // Table length'); - console.log(''); - console.log(' result : compose add1 multiply2; // Function composition'); - console.log(' result : pipe 5 add1 multiply2; // Pipeline'); - console.log(' result : each add1 {1, 2, 3}; // Map over table'); - console.log(' result : filter greater5 {1, 6, 3, 8}; // Filter table'); - console.log(' result : reduce add 0 {1, 2, 3}; // Reduce table'); - console.log(''); - console.log('\x1b[35m💡 Tips:\x1b[0m'); - console.log(' • Use semicolon (;) to end multi-line expressions'); - console.log(' • Negative numbers work without parentheses: -5'); - console.log(' • Use spaces around binary operators: 5 - 3'); - console.log(' • Tables are the primary data structure'); - console.log(' • All operations are function calls'); - console.log(' • Use :menu for interactive history navigation'); - console.log(''); - console.log('\x1b[36m📁 :run Command Examples:\x1b[0m'); - console.log(' :run tests/09_tables.txt // Relative to project'); - console.log(' :run ./my_script.txt // Relative to current dir'); - console.log(' :run ~/Documents/scripts/test.txt // Relative to home'); - console.log(' :run /absolute/path/to/script.txt // Absolute path'); - console.log(' :run ../other-project/script.txt // Parent directory'); - console.log(''); - console.log('\x1b[36m🌐 HTTP Adapter Examples:\x1b[0m'); - console.log(' ..emit { action: "http_request", method: "GET", url: "..." }'); - console.log(' ..emit { action: "http_request", method: "POST", url: "...", body: {...} }'); - console.log(' ..emit { action: "http_request", method: "PUT", url: "...", headers: {...} }'); - console.log(' :example http-get // Simple GET request'); - console.log(' :example http-post // POST with JSON body'); - console.log(' :example http-weather // Weather API integration'); - console.log(''); - console.log('\x1b[36m📁 File Adapter Examples:\x1b[0m'); - console.log(' ..emit { action: "read_file", filename: "..." }'); - console.log(' ..emit { action: "save_file", filename: "...", data: {...} }'); - console.log(' :example file-operations // File read/write operations'); - console.log(' :example state-driven-adapters // Conditional adapter usage'); - console.log(' :example harness-features // Versioning and state management'); - console.log(' :example branching-demo // Branching and state diffing'); - console.log(' :example error-recovery-demo // Error recovery and resilience'); - console.log(' :example state-diffing-demo // State diffing and analysis'); - console.log(''); - console.log('\x1b[36m🔄 Advanced Harness Commands:\x1b[0m'); - console.log(' :branch <version> <name> - Create branch from version'); - console.log(' :diff <from> [to] - Show state diff between versions'); - console.log(' :replay <version> [state] - Replay from version with new state'); - console.log(' :recover <type> - Simulate error recovery'); - console.log(''); - console.log('\x1b[36m📁 File Adapter Examples:\x1b[0m'); - console.log(' ..emit { action: "read_file", filename: "..." }'); - console.log(' ..emit { action: "save_file", filename: "...", data: {...} }'); - console.log(' :example file-operations // File read/write operations'); - console.log(' :example state-driven-adapters // Conditional adapter usage'); - console.log(' :example harness-features // Versioning and state management'); - console.log(' :example branching-demo // Branching and state diffing'); - console.log(' :example error-recovery-demo // Error recovery and resilience'); - console.log(' :example state-diffing-demo // State diffing and analysis'); - console.log(''); - console.log('\x1b[36m🧪 Advanced Features Examples:\x1b[0m'); - console.log(' :example branching-demo // Branching and version control'); - console.log(' :example error-recovery-demo // Error recovery patterns'); - console.log(' :example state-diffing-demo // State analysis and diffing'); - console.log(''); - } - - /** - * Show available examples - */ - showExamples() { - console.log('\x1b[33mAvailable Examples:\x1b[0m'); - Object.entries(this.examples).forEach(([name, example]) => { - console.log(` ${name.padEnd(15)} - ${example.title}`); - console.log(` ${' '.repeat(17)} ${example.description}`); - }); - console.log(''); - console.log('Use :example <name> to load an example'); - } - - /** - * Load an example - */ - async loadExample(name) { - const example = this.examples[name]; - if (!example) { - console.log(`\x1b[31mExample '${name}' not found\x1b[0m`); - this.showExamples(); - return; - } - - console.log(`\x1b[33mLoading example: ${example.title}\x1b[0m`); - console.log(`\x1b[90m${example.description}\x1b[0m`); - console.log('\x1b[90m' + example.code + '\x1b[0m'); - console.log(''); - - // Execute the example - await this.executeScript(example.code); - } - - /** - * Show current state - */ - showState() { - if (Object.keys(this.currentState).length === 0) { - console.log('\x1b[90mNo state defined\x1b[0m'); - return; - } - - console.log('\x1b[33mCurrent State (Version ' + this.currentVersion + '):\x1b[0m'); - console.log(this.formatValue(this.currentState)); - } - - /** - * Show version history - */ - showHistory() { - if (!this.harness || !this.harness.stateHistory) { - console.log('\x1b[90mNo history available - no scripts executed yet\x1b[0m'); - return; - } - - try { - const history = this.harness.getVersionHistory(); - if (!history || history.length === 0) { - console.log('\x1b[90mNo version history available\x1b[0m'); - return; - } - - console.log('\x1b[33mVersion History:\x1b[0m'); - history.slice(-10).forEach((entry, i) => { - console.log(` ${entry.version}: ${new Date(entry.timestamp).toLocaleTimeString()}`); - }); - } catch (error) { - console.log(`\x1b[31mError loading history: ${error.message}\x1b[0m`); - } - } - - /** - * Rollback to version - */ - async rollbackToVersion(version) { - if (!this.harness || !this.harness.stateHistory) { - throw new Error('No harness or history available'); - } - - try { - await this.harness.rollbackToVersion(version); - this.currentState = this.harness.getCurrentState(); - this.currentVersion = version; - - // Update the prompt to reflect the new version - this.rl.setPrompt(this.getPrompt()); - - console.log(`\x1b[32mRolled back to version ${version}\x1b[0m`); - this.showState(); - } catch (error) { - throw new Error(`Rollback failed: ${error.message}`); - } - } - - /** - * Show available adapters - */ - showAdapters() { - console.log('\x1b[33mAvailable Adapters:\x1b[0m'); - Object.entries(this.adapters).forEach(([name, adapter]) => { - console.log(` ${name.padEnd(10)} - ${adapter.name}`); - console.log(` ${' '.repeat(12)} ${adapter.description}`); - }); - } - - /** - * Clear current state - */ - clearState() { - this.currentState = {}; - this.currentVersion = 0; - this.harness = null; - console.log('\x1b[33mState cleared\x1b[0m'); - } - - /** - * Save state to file - */ - async saveState(filename = 'harness_state.json') { - try { - const stateData = { - state: this.currentState, - version: this.currentVersion, - timestamp: Date.now() - }; - await fs.writeFile(filename, JSON.stringify(stateData, null, 2)); - console.log(`\x1b[32mState saved to ${filename}\x1b[0m`); - } catch (error) { - console.log(`\x1b[31mFailed to save state: ${error.message}\x1b[0m`); - } - } - - /** - * Load state from file - */ - async loadState(filename = 'harness_state.json') { - try { - const content = await fs.readFile(filename, 'utf8'); - const stateData = JSON.parse(content); - this.currentState = stateData.state; - this.currentVersion = stateData.version; - console.log(`\x1b[32mState loaded from ${filename}\x1b[0m`); - this.showState(); - } catch (error) { - console.log(`\x1b[31mFailed to load state: ${error.message}\x1b[0m`); - } - } - - /** - * Run script from file - */ - async runScriptFile(filename) { - try { - // Import path module for robust path handling - const path = await import('path'); - const { fileURLToPath } = await import('url'); - - let resolvedPath; - - // Check if the path is absolute (starts with / on Unix or C:\ on Windows) - if (path.isAbsolute(filename)) { - // Use absolute path as-is - resolvedPath = filename; - } else { - // For relative paths, try multiple resolution strategies - const __filename = fileURLToPath(import.meta.url); - const replDir = path.dirname(__filename); - const projectRoot = path.dirname(replDir); // Go up one level from repl/ to project root - - // Strategy 1: Try relative to project root (current behavior) - const projectPath = path.resolve(projectRoot, filename); - - // Strategy 2: Try relative to current working directory - const cwdPath = path.resolve(process.cwd(), filename); - - // Strategy 3: Try relative to user's home directory - const homePath = path.resolve(process.env.HOME || process.env.USERPROFILE || '', filename); - - // Check which path exists - const fs = await import('fs'); - if (fs.existsSync(projectPath)) { - resolvedPath = projectPath; - } else if (fs.existsSync(cwdPath)) { - resolvedPath = cwdPath; - } else if (fs.existsSync(homePath)) { - resolvedPath = homePath; - } else { - // If none exist, use project root as fallback (will show clear error) - resolvedPath = projectPath; - } - } - - console.log(`\x1b[33mRunning script from ${resolvedPath}:\x1b[0m`); - - // Create a script that uses the file adapter to read the file - const fileAdapterScript = `/* File adapter demonstration */ -/* This script uses the file adapter to read and execute the target file */ - -/* Emit command to read the file using file adapter */ -..emit { - action: "read_file", - filename: "${resolvedPath.replace(/\\/g, '\\\\')}" -}; - -/* Return info about the operation */ -{ - operation: "read_file", - filename: "${resolvedPath.replace(/\\/g, '\\\\')}", - note: "File content will be available through file adapter" -}`; - - // Execute the file adapter script - await this.executeScript(fileAdapterScript); - - // Also read and display the file content directly for immediate feedback - const content = await fs.readFile(resolvedPath, 'utf8'); - console.log('\x1b[90m' + content + '\x1b[0m'); - console.log(''); - - // Execute the actual file content - await this.executeScript(content); - - } catch (error) { - console.log(`\x1b[31mFailed to run script: ${error.message}\x1b[0m`); - console.log(`\x1b[33m💡 Path resolution strategies:\x1b[0m`); - console.log(` • Absolute paths: /path/to/script.txt`); - console.log(` • Relative to project: tests/script.txt`); - console.log(` • Relative to current directory: ./script.txt`); - console.log(` • Relative to home: ~/scripts/script.txt`); - console.log(` • Full paths: /Users/username/Documents/script.txt`); - } - } - - /** - * Show state diff between versions - */ - showStateDiff(fromVersion, toVersion) { - if (!this.harness) { - console.log('\x1b[31mNo harness available. Execute a script first.\x1b[0m'); - return; - } - - const diff = this.harness.getStateDiff(fromVersion, toVersion); - - if (!diff) { - console.log(`\x1b[31mCould not generate diff between versions ${fromVersion} and ${toVersion}\x1b[0m`); - return; - } - - console.log(`\x1b[36m📊 State Diff: v${fromVersion} → v${toVersion}\x1b[0m`); - console.log(''); - - if (Object.keys(diff.added).length > 0) { - console.log('\x1b[32m➕ Added Properties:\x1b[0m'); - for (const [key, value] of Object.entries(diff.added)) { - console.log(` ${key}: ${JSON.stringify(value)}`); - } - console.log(''); - } - - if (Object.keys(diff.removed).length > 0) { - console.log('\x1b[31m➖ Removed Properties:\x1b[0m'); - for (const [key, value] of Object.entries(diff.removed)) { - console.log(` ${key}: ${JSON.stringify(value)}`); - } - console.log(''); - } - - if (Object.keys(diff.changed).length > 0) { - console.log('\x1b[33m🔄 Changed Properties:\x1b[0m'); - for (const [key, change] of Object.entries(diff.changed)) { - console.log(` ${key}:`); - console.log(` From: ${JSON.stringify(change.from)}`); - console.log(` To: ${JSON.stringify(change.to)}`); - } - console.log(''); - } - - if (Object.keys(diff.added).length === 0 && - Object.keys(diff.removed).length === 0 && - Object.keys(diff.changed).length === 0) { - console.log('\x1b[90mNo changes detected between versions\x1b[0m'); - } - } - - /** - * Replay from a specific version with new state - */ - async replayFromVersion(fromVersion, newState) { - if (!this.harness) { - console.log('\x1b[31mNo harness available. Execute a script first.\x1b[0m'); - return; - } - - console.log(`\x1b[36m🔄 Replaying from version ${fromVersion} with new state...\x1b[0m`); - - try { - const result = await this.harness.replayFromVersion(fromVersion, newState); - console.log(`\x1b[32m✅ Replay completed successfully\x1b[0m`); - console.log(`\x1b[36m📊 Result: ${JSON.stringify(result, null, 2)}\x1b[0m`); - } catch (error) { - console.log(`\x1b[31m❌ Replay failed: ${error.message}\x1b[0m`); - } - } - - /** - * Simulate error recovery scenarios - */ - async simulateErrorRecovery(errorType) { - if (!this.harness) { - console.log('\x1b[31mNo harness available. Execute a script first.\x1b[0m'); - return; - } - - console.log(`\x1b[36m🧪 Simulating ${errorType} error recovery...\x1b[0m`); - - // Create a mock error based on the type - let mockError; - switch (errorType) { - case 'timeout': - mockError = new Error('Script execution timeout'); - break; - case 'network': - mockError = new Error('Network error: ECONNREFUSED'); - break; - case 'script': - mockError = new Error('Unexpected token in parsePrimary: INVALID'); - break; - case 'filesystem': - mockError = new Error('File system error: ENOENT'); - break; - default: - mockError = new Error(`Unknown error type: ${errorType}`); - } - - try { - const recoveryResult = await this.harness.recoverFromError(mockError, { - lastState: this.currentState - }); - - console.log(`\x1b[32m✅ Error recovery completed\x1b[0m`); - console.log(`\x1b[36m📊 Recovery result: ${JSON.stringify(recoveryResult, null, 2)}\x1b[0m`); - } catch (error) { - console.log(`\x1b[31m❌ Error recovery failed: ${error.message}\x1b[0m`); - } - } - - /** - * Enhanced branch creation with better feedback - */ - async createBranch(fromVersion, branchName) { - if (!this.harness) { - console.log('\x1b[31mNo harness available. Execute a script first.\x1b[0m'); - return; - } - - console.log(`\x1b[36m🌿 Creating branch '${branchName}' from version ${fromVersion}...\x1b[0m`); - - try { - const branchHarness = await this.harness.createBranch(fromVersion, branchName); - const branchInfo = branchHarness.getBranchInfo(); - - console.log(`\x1b[32m✅ Branch '${branchName}' created successfully\x1b[0m`); - console.log(`\x1b[36m📊 Branch info: ${JSON.stringify(branchInfo, null, 2)}\x1b[0m`); - - // Store the branch harness for potential use - this.branches = this.branches || {}; - this.branches[branchName] = branchHarness; - - } catch (error) { - console.log(`\x1b[31m❌ Branch creation failed: ${error.message}\x1b[0m`); - } - } - - /** - * Show interactive menu for navigating history and branches - */ - async showInteractiveMenu() { - const readline = await import('readline'); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - const question = (prompt) => new Promise((resolve) => { - rl.question(prompt, (answer) => { - resolve(answer); - }); - }); - - // Handle Ctrl+C gracefully - const originalSigint = process.listeners('SIGINT').length > 0 ? - process.listeners('SIGINT')[0] : null; - - const handleSigint = () => { - console.log('\n\x1b[33mReturning to REPL...\x1b[0m'); - rl.close(); - process.exit(0); - }; - - process.on('SIGINT', handleSigint); - - try { - while (true) { - console.clear(); - console.log('\x1b[36m╔══════════════════════════════════════════════════════════════╗\x1b[0m'); - console.log('\x1b[36m║ Interactive Menu ║\x1b[0m'); - console.log('\x1b[36m╚══════════════════════════════════════════════════════════════╝\x1b[0m'); - - // Show current state - console.log(`\x1b[33m📍 Current Version: ${this.currentVersion}\x1b[0m`); - console.log(`\x1b[33m📊 State Keys: ${Object.keys(this.currentState || {}).length}\x1b[0m`); - console.log(''); - - // Show history - handle null harness gracefully - console.log('\x1b[32m📜 Version History:\x1b[0m'); - if (!this.harness || !this.harness.stateHistory) { - console.log(' \x1b[90mNo history available - no scripts executed yet\x1b[0m'); - } else { - try { - const history = this.harness.stateHistory.getAllVersions(); - if (history && history.length > 0) { - history.forEach((entry, index) => { - const isCurrent = entry.version === this.currentVersion; - const marker = isCurrent ? '\x1b[33m▶\x1b[0m' : ' '; - const time = new Date(entry.timestamp).toLocaleTimeString(); - console.log(` ${marker} ${entry.version}: ${time}`); - }); - } else { - console.log(' \x1b[90mNo version history available\x1b[0m'); - } - } catch (error) { - console.log(` \x1b[31mError loading history: ${error.message}\x1b[0m`); - } - } - console.log(''); - - // Show branches (if any) - handle null harness gracefully - if (this.harness) { - try { - // Check if getBranches method exists - if (typeof this.harness.getBranches === 'function') { - const branches = this.harness.getBranches(); - if (branches && branches.length > 0) { - console.log('\x1b[35m🌿 Branches:\x1b[0m'); - branches.forEach(branch => { - console.log(` 🌿 ${branch.name} (from v${branch.fromVersion})`); - }); - console.log(''); - } - } else { - // Branches feature not implemented yet - console.log('\x1b[90m🌿 Branches: Feature not implemented yet\x1b[0m'); - console.log(''); - } - } catch (error) { - console.log(`\x1b[31mError loading branches: ${error.message}\x1b[0m`); - } - } - - // Menu options - disable options that require harness - const hasHarness = this.harness && this.harness.stateHistory; - console.log('\x1b[34m🎯 Options:\x1b[0m'); - console.log(` 1. View version details${!hasHarness ? ' (disabled)' : ''}`); - console.log(` 2. Rollback to version${!hasHarness ? ' (disabled)' : ''}`); - console.log(` 3. Create branch${!hasHarness ? ' (disabled)' : ''}`); - console.log(` 4. Compare versions${!hasHarness ? ' (disabled)' : ''}`); - console.log(' 5. Show current state'); - console.log(' 6. Return to REPL'); - console.log(' 0. Cancel / Exit menu'); - console.log(''); - console.log('\x1b[90m💡 Tip: Press Ctrl+C to exit at any time\x1b[0m'); - console.log(''); - - if (!hasHarness) { - console.log('\x1b[33m💡 Tip: Execute a script first to enable history features\x1b[0m'); - console.log(''); - } - - const choice = await question('\x1b[33mEnter choice (0-6): \x1b[0m'); - - switch (choice.trim()) { - case '0': - console.log('\x1b[33mReturning to REPL...\x1b[0m'); - rl.close(); - return; - case '1': - if (hasHarness) { - await this.menuViewVersionDetails(question); - } else { - console.log('\x1b[31mNo history available. Execute a script first.\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - break; - case '2': - if (hasHarness) { - await this.menuRollbackToVersion(question); - } else { - console.log('\x1b[31mNo history available. Execute a script first.\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - break; - case '3': - if (hasHarness) { - await this.menuCreateBranch(question); - } else { - console.log('\x1b[31mNo history available. Execute a script first.\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - break; - case '4': - if (hasHarness) { - await this.menuCompareVersions(question); - } else { - console.log('\x1b[31mNo history available. Execute a script first.\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - break; - case '5': - await this.menuShowCurrentState(question); - break; - case '6': - console.log('\x1b[33mReturning to REPL...\x1b[0m'); - rl.close(); - return; - default: - console.log('\x1b[31mInvalid choice. Press Enter to continue...\x1b[0m'); - await question(''); - } - } - } catch (error) { - console.log(`\x1b[31mMenu error: ${error.message}\x1b[0m`); - console.log('\x1b[33mPress Enter to return to REPL...\x1b[0m'); - await question(''); - } finally { - // Restore original SIGINT handler - process.removeListener('SIGINT', handleSigint); - if (originalSigint) { - process.on('SIGINT', originalSigint); - } - rl.close(); - } - } - - /** - * Menu option: View version details - */ - async menuViewVersionDetails(question) { - if (!this.harness || !this.harness.stateHistory) { - console.log('\x1b[31mNo history available\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - const version = await question('\x1b[33mEnter version number: \x1b[0m'); - const versionNum = parseInt(version.trim()); - - if (isNaN(versionNum)) { - console.log('\x1b[31mInvalid version number\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - try { - const state = this.harness.stateHistory.getVersion(versionNum); - if (!state) { - console.log('\x1b[31mVersion not found\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - console.log(`\x1b[32m📋 Version ${versionNum} Details:\x1b[0m`); - const versionData = this.harness.stateHistory.versions.get(versionNum); - if (versionData) { - console.log(`Time: ${new Date(versionData.timestamp).toLocaleString()}`); - console.log(`State Keys: ${Object.keys(state).length}`); - console.log('\x1b[33mState Contents:\x1b[0m'); - console.log(this.formatValue(state)); - } else { - console.log('Time: Unknown'); - console.log(`State Keys: ${Object.keys(state).length}`); - console.log('\x1b[33mState Contents:\x1b[0m'); - console.log(this.formatValue(state)); - } - } catch (error) { - console.log(`\x1b[31mError loading version details: ${error.message}\x1b[0m`); - } - - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - - /** - * Menu option: Rollback to version - */ - async menuRollbackToVersion(question) { - if (!this.harness || !this.harness.stateHistory) { - console.log('\x1b[31mNo history available\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - const version = await question('\x1b[33mEnter version to rollback to: \x1b[0m'); - const versionNum = parseInt(version.trim()); - - if (isNaN(versionNum)) { - console.log('\x1b[31mInvalid version number\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - const confirm = await question('\x1b[31m⚠️ This will reset current state. Continue? (y/N): \x1b[0m'); - if (confirm.toLowerCase() !== 'y') { - console.log('\x1b[33mRollback cancelled\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - try { - await this.rollbackToVersion(versionNum); - console.log(`\x1b[32m✅ Rolled back to version ${versionNum}\x1b[0m`); - } catch (error) { - console.log(`\x1b[31mRollback failed: ${error.message}\x1b[0m`); - } - - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - - /** - * Menu option: Create branch - */ - async menuCreateBranch(question) { - if (!this.harness || !this.harness.stateHistory) { - console.log('\x1b[31mNo history available\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - const fromVersion = await question('\x1b[33mEnter source version: \x1b[0m'); - const branchName = await question('\x1b[33mEnter branch name: \x1b[0m'); - - const versionNum = parseInt(fromVersion.trim()); - if (isNaN(versionNum)) { - console.log('\x1b[31mInvalid version number\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - if (!branchName.trim()) { - console.log('\x1b[31mBranch name required\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - try { - await this.createBranch(versionNum, branchName.trim()); - } catch (error) { - console.log(`\x1b[31mBranch creation failed: ${error.message}\x1b[0m`); - } - - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - - /** - * Menu option: Compare versions - */ - async menuCompareVersions(question) { - if (!this.harness || !this.harness.stateHistory) { - console.log('\x1b[31mNo history available\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - const version1 = await question('\x1b[33mEnter first version: \x1b[0m'); - const version2 = await question('\x1b[33mEnter second version: \x1b[0m'); - - const v1 = parseInt(version1.trim()); - const v2 = parseInt(version2.trim()); - - if (isNaN(v1) || isNaN(v2)) { - console.log('\x1b[31mInvalid version number\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - try { - const state1 = this.harness.stateHistory.getVersion(v1); - const state2 = this.harness.stateHistory.getVersion(v2); - - if (!state1 || !state2) { - console.log('\x1b[31mOne or both versions not found\x1b[0m'); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - return; - } - - console.log(`\x1b[32m📊 Comparing Version ${v1} vs ${v2}:\x1b[0m`); - - const keys1 = Object.keys(state1); - const keys2 = Object.keys(state2); - - console.log(`\x1b[33mKeys in v${v1}: ${keys1.length}\x1b[0m`); - console.log(`\x1b[33mKeys in v${v2}: ${keys2.length}\x1b[0m`); - - const onlyInV1 = keys1.filter(k => !keys2.includes(k)); - const onlyInV2 = keys2.filter(k => !keys1.includes(k)); - const common = keys1.filter(k => keys2.includes(k)); - - if (onlyInV1.length > 0) { - console.log(`\x1b[31mOnly in v${v1}: ${onlyInV1.join(', ')}\x1b[0m`); - } - if (onlyInV2.length > 0) { - console.log(`\x1b[32mOnly in v${v2}: ${onlyInV2.join(', ')}\x1b[0m`); - } - if (common.length > 0) { - console.log(`\x1b[33mCommon keys: ${common.join(', ')}\x1b[0m`); - } - } catch (error) { - console.log(`\x1b[31mError comparing versions: ${error.message}\x1b[0m`); - } - - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - - /** - * Menu option: Show current state - */ - async menuShowCurrentState(question) { - console.log('\x1b[32m📋 Current State:\x1b[0m'); - this.showState(); - await question('\x1b[33mPress Enter to continue...\x1b[0m'); - } - - /** - * Add command to history - */ - addToHistory(command) { - this.history.push(command); - if (this.history.length > 100) { - this.history.shift(); - } - } - - /** - * Load history from file - */ - async loadHistory() { - try { - const content = await fs.readFile(this.historyFile, 'utf8'); - this.history = content.split('\n').filter(line => line.trim()); - } catch (error) { - this.history = []; - } - } - - /** - * Save history to file - */ - async saveHistory() { - try { - await fs.writeFile(this.historyFile, this.history.join('\n')); - } catch (error) { - // Ignore history save errors - } - } - - /** - * Cleanup on exit - */ - async cleanup() { - await this.saveHistory(); - console.log('\n\x1b[33mGoodbye! 👋\x1b[0m'); - } -} - -// Main execution -async function main() { - const repl = new REPL(); - await repl.init(); -} - -// Handle process termination -process.on('SIGINT', () => { - console.log('\n'); - process.exit(0); -}); - -process.on('SIGTERM', () => { - process.exit(0); -}); - -// Start the REPL -if (import.meta.url === `file://${process.argv[1]}`) { - main().catch(console.error); -} - -export { REPL }; </code></pre> - </article> - </section> - - - - -</div> - -<nav> - <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="REPL.html">REPL</a></li></ul> -</nav> - -<br class="clear"> - -<footer> - Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Jul 29 2025 14:41:59 GMT-0400 (Eastern Daylight Time) -</footer> - -<script> prettyPrint(); </script> -<script src="scripts/linenumber.js"> </script> -</body> -</html> |