about summary refs log tree commit diff stats
path: root/js/baba-yaga/web/editor
diff options
context:
space:
mode:
Diffstat (limited to 'js/baba-yaga/web/editor')
-rw-r--r--js/baba-yaga/web/editor/index.html12
-rw-r--r--js/baba-yaga/web/editor/js/baba-yaga-runner.js102
-rw-r--r--js/baba-yaga/web/editor/js/formatter.js621
-rw-r--r--js/baba-yaga/web/editor/test-formatter.html155
4 files changed, 877 insertions, 13 deletions
diff --git a/js/baba-yaga/web/editor/index.html b/js/baba-yaga/web/editor/index.html
index 572d9f6..6344cee 100644
--- a/js/baba-yaga/web/editor/index.html
+++ b/js/baba-yaga/web/editor/index.html
@@ -33,6 +33,9 @@
     <!-- Baba Yaga Language Mode -->
     <script src="js/baba-yaga-mode.js"></script>
     
+    <!-- Baba Yaga Formatter -->
+    <script src="js/formatter.js"></script>
+    
     <style>
         * {
             margin: 0;
@@ -237,6 +240,14 @@
             background-color: #5a8a45;
         }
 
+        .format-btn {
+            background-color: #ff9500;
+        }
+
+        .format-btn:hover {
+            background-color: #e6850e;
+        }
+
         .structural-editor-btn {
             background-color: #8b5cf6;
         }
@@ -518,6 +529,7 @@
             <h1>Baba Yaga Code Runner</h1>
             <div class="header-controls">
                 <button id="run-btn" class="btn">▶ Run Code</button>
+                <button id="format-btn" class="btn format-btn">📝 Format</button>
                 <button id="sample-btn" class="btn sample-code-btn">Load Sample</button>
             </div>
         </header>
diff --git a/js/baba-yaga/web/editor/js/baba-yaga-runner.js b/js/baba-yaga/web/editor/js/baba-yaga-runner.js
index 1ca5607..6dd0312 100644
--- a/js/baba-yaga/web/editor/js/baba-yaga-runner.js
+++ b/js/baba-yaga/web/editor/js/baba-yaga-runner.js
@@ -116,6 +116,17 @@ class BabaYagaRunner {
             });
         }
 
+        // Format button
+        const formatBtn = document.getElementById('format-btn');
+        if (formatBtn) {
+            formatBtn.addEventListener('click', () => this.formatCode());
+            // Add touch event for mobile
+            formatBtn.addEventListener('touchend', (e) => {
+                e.preventDefault();
+                this.formatCode();
+            });
+        }
+
         // Sample code button
         const sampleBtn = document.getElementById('sample-btn');
         if (sampleBtn) {
@@ -429,6 +440,69 @@ class BabaYagaRunner {
         if (astText) astText.textContent = 'AST will appear here after parsing.';
     }
 
+    async formatCode() {
+        const formatBtn = document.getElementById('format-btn');
+        const originalText = formatBtn.textContent;
+        
+        try {
+            // Update button state
+            formatBtn.textContent = 'Formatting...';
+            formatBtn.className = 'btn format-btn';
+            formatBtn.disabled = true;
+            
+            // Get code from editor
+            const code = this.getCode();
+            if (!code.trim()) {
+                this.showError('No code to format. Please enter some Baba Yaga code.');
+                return;
+            }
+
+            // Check if formatter is available
+            if (typeof BabaYagaFormatter === 'undefined') {
+                this.showError('Formatter not available. Please refresh the page.');
+                return;
+            }
+
+            // Format the code
+            const formatter = new BabaYagaFormatter({
+                indentSize: 2,
+                maxLineLength: 100
+            });
+            
+            const formattedCode = formatter.format(code);
+            
+            // Update the editor with formatted code
+            if (this.editor) {
+                this.editor.setValue(formattedCode);
+            } else {
+                document.getElementById('code-editor').value = formattedCode;
+            }
+            
+            // Show success message
+            this.showInfo('Code formatted successfully!');
+            this.switchTab('output');
+            
+            // Update button state
+            formatBtn.textContent = 'Formatted!';
+            formatBtn.className = 'btn format-btn success';
+            
+        } catch (error) {
+            console.error('Code formatting error:', error);
+            this.showError('Formatting failed: ' + error.message);
+            
+            // Update button state
+            formatBtn.textContent = 'Error';
+            formatBtn.className = 'btn format-btn error';
+        } finally {
+            // Reset button after delay
+            setTimeout(() => {
+                formatBtn.textContent = originalText;
+                formatBtn.className = 'btn format-btn';
+                formatBtn.disabled = false;
+            }, 2000);
+        }
+    }
+
     getCode() {
         if (this.editor) {
             return this.editor.getValue();
@@ -438,27 +512,28 @@ class BabaYagaRunner {
 
     loadSampleCode() {
         const sampleCode = `// Sample Baba Yaga code - try running this!
-add : x y -> x + y;
+// Notice the inconsistent formatting - use the Format button to clean it up!
+add:x y->x+y;
 
-multiply : x y -> x * y;
+multiply : x y->x*y;
 
-// Calculate factorial
-factorial : n -> 
+// Calculate factorial with inconsistent spacing
+factorial:n-> 
     when n is
         0 then 1
-        1 then 1
-        _ then n * factorial (n - 1);
+        1    then 1
+        _  then n*factorial(n-1);
 
 // Test the functions
-result1 : add 5 3;
-result2 : multiply 4 6;
-result3 : factorial 5;
+result1:add 5 3;
+result2:multiply 4 6;
+result3:factorial 5;
 
 // Use io.out to display results
-io.out "Results:";
-io.out "add 5 3 = " result1;
+io.out"Results:";
+io.out"add 5 3 = "result1;
 io.out "multiply 4 6 = " result2;
-io.out "factorial 5 = " result3;
+io.out"factorial 5 = "result3;
 
 // Return the factorial result
 result3`;
@@ -476,12 +551,13 @@ result3`;
 
 // Initialize the runner when the page loads
 document.addEventListener('DOMContentLoaded', () => {
-    new BabaYagaRunner();
+    window.babaYagaRunner = new BabaYagaRunner();
 });
 
 // Add some helpful console commands
 window.babaYagaRunnerCommands = {
     run: () => window.babaYagaRunner?.runCode(),
+    format: () => window.babaYagaRunner?.formatCode(),
     getCode: () => window.babaYagaRunner?.getCode(),
     loadSample: () => window.babaYagaRunner?.loadSampleCode(),
     clearOutput: () => window.babaYagaRunner?.clearOutput()
diff --git a/js/baba-yaga/web/editor/js/formatter.js b/js/baba-yaga/web/editor/js/formatter.js
new file mode 100644
index 0000000..b0485d6
--- /dev/null
+++ b/js/baba-yaga/web/editor/js/formatter.js
@@ -0,0 +1,621 @@
+/**
+ * Browser-compatible Baba Yaga code formatter
+ * Adapted from fmt.js for use in the web editor
+ */
+
+class BabaYagaFormatter {
+  constructor(options = {}) {
+    this.indentSize = options.indentSize || 2;
+    this.maxLineLength = options.maxLineLength || 100;
+    this.preserveComments = options.preserveComments !== false;
+  }
+
+  /**
+   * Format source code string
+   */
+  format(source) {
+    try {
+      if (typeof createLexer === 'undefined' || typeof createParser === 'undefined') {
+        throw new Error('Baba Yaga language components not loaded');
+      }
+
+      const lexer = createLexer(source);
+      const tokens = lexer.allTokens();
+      
+      // Extract comments before parsing
+      const comments = this.extractComments(source);
+      
+      const parser = createParser(tokens);
+      const ast = parser.parse();
+      
+      return this.formatAST(ast, comments, source);
+    } catch (error) {
+      throw new Error(`Formatting failed: ${error.message}`);
+    }
+  }
+
+  /**
+   * Extract comments from source with their positions
+   */
+  extractComments(source) {
+    const comments = [];
+    const lines = source.split('\n');
+    
+    lines.forEach((line, lineIndex) => {
+      const commentMatch = line.match(/\/\/(.*)$/);
+      if (commentMatch) {
+        const column = line.indexOf('//');
+        comments.push({
+          line: lineIndex + 1,
+          column: column,
+          text: commentMatch[0],
+          content: commentMatch[1].trim()
+        });
+      }
+    });
+    
+    return comments;
+  }
+
+  /**
+   * Format AST node
+   */
+  formatAST(ast, comments = [], originalSource = '') {
+    return this.visitNode(ast, 0, comments);
+  }
+
+  /**
+   * Visit and format a node
+   */
+  visitNode(node, depth = 0, comments = []) {
+    if (!node) return '';
+
+    switch (node.type) {
+      case 'Program':
+        return this.formatProgram(node, depth, comments);
+      case 'TypeDeclaration':
+        return this.formatTypeDeclaration(node, depth);
+      case 'VariableDeclaration':
+        return this.formatVariableDeclaration(node, depth, comments);
+      case 'FunctionDeclaration':
+        return this.formatFunctionDeclaration(node, depth, comments);
+      case 'CurriedFunctionDeclaration':
+        return this.formatCurriedFunctionDeclaration(node, depth, comments);
+      case 'WithHeader':
+        return this.formatWithHeader(node, depth, comments);
+      case 'WhenExpression':
+        return this.formatWhenExpression(node, depth, comments);
+      case 'BinaryExpression':
+        return this.formatBinaryExpression(node, depth, comments);
+      case 'UnaryExpression':
+        return this.formatUnaryExpression(node, depth, comments);
+      case 'FunctionCall':
+        return this.formatFunctionCall(node, depth, comments);
+      case 'AnonymousFunction':
+        return this.formatAnonymousFunction(node, depth, comments);
+      case 'ListLiteral':
+        return this.formatListLiteral(node, depth, comments);
+      case 'TableLiteral':
+        return this.formatTableLiteral(node, depth, comments);
+      case 'MemberExpression':
+        return this.formatMemberExpression(node, depth, comments);
+      case 'ResultExpression':
+        return this.formatResultExpression(node, depth, comments);
+      case 'NumberLiteral':
+        return this.formatNumberLiteral(node);
+      case 'StringLiteral':
+        return this.formatStringLiteral(node);
+      case 'BooleanLiteral':
+        return this.formatBooleanLiteral(node);
+      case 'Identifier':
+        return this.formatIdentifier(node);
+      default:
+        // Fallback for unknown node types - avoid infinite recursion
+        if (typeof node === 'string') {
+          return node;
+        }
+        if (typeof node === 'number') {
+          return node.toString();
+        }
+        if (typeof node === 'boolean') {
+          return node.toString();
+        }
+        if (node && typeof node === 'object') {
+          // Try to handle as a literal value
+          if (node.value !== undefined) {
+            return node.value.toString();
+          }
+          if (node.name !== undefined) {
+            return node.name;
+          }
+        }
+        return JSON.stringify(node);
+    }
+  }
+
+  /**
+   * Format program (top level)
+   */
+  formatProgram(node, depth, comments) {
+    const statements = [];
+    let lastWasFunction = false;
+
+    node.body.forEach((stmt, index) => {
+      const formatted = this.visitNode(stmt, depth, comments);
+      const isFunction = stmt.type === 'FunctionDeclaration' || 
+                        stmt.type === 'CurriedFunctionDeclaration';
+      
+      // Add extra spacing between functions and other statements
+      if (index > 0 && (isFunction || lastWasFunction)) {
+        statements.push('');
+      }
+      
+      statements.push(formatted);
+      lastWasFunction = isFunction;
+    });
+
+    return statements.join('\n') + (statements.length > 0 ? '\n' : '');
+  }
+
+  /**
+   * Format type declaration
+   */
+  formatTypeDeclaration(node, depth) {
+    const indent = this.getIndent(depth);
+    return `${indent}${node.name} ${node.typeAnnotation};`;
+  }
+
+  /**
+   * Format variable declaration
+   */
+  formatVariableDeclaration(node, depth, comments) {
+    const indent = this.getIndent(depth);
+    
+    // Check if the value is a complex expression that should be on its own line
+    if (node.value.type === 'WhenExpression' || node.value.type === 'WithHeader') {
+      const value = this.visitNode(node.value, depth + 1, comments);
+      return `${indent}${node.name} :\n${value};`;
+    } else {
+      const value = this.visitNode(node.value, depth, comments);
+      return `${indent}${node.name} : ${value};`;
+    }
+  }
+
+  /**
+   * Format function declaration
+   */
+  formatFunctionDeclaration(node, depth, comments) {
+    const indent = this.getIndent(depth);
+    let result = `${indent}${node.name} : `;
+    
+    // Format parameters
+    if (node.params && node.params.length > 0) {
+      if (this.hasTypedParams(node.params)) {
+        result += this.formatTypedParameters(node.params);
+      } else {
+        result += node.params.map(p => typeof p === 'string' ? p : p.name).join(' ');
+      }
+    }
+    
+    // Add return type if present
+    if (node.returnType) {
+      result += ` -> ${this.formatType(node.returnType)}`;
+    }
+    
+    result += ' ->\n';
+    
+    // Format body with proper indentation
+    const body = this.visitNode(node.body, depth + 1, comments);
+    // If the body doesn't start with indentation, add it
+    if (body && !body.startsWith(this.getIndent(depth + 1))) {
+      result += this.getIndent(depth + 1) + body;
+    } else {
+      result += body;
+    }
+    
+    result += ';';
+    return result;
+  }
+
+  /**
+   * Format curried function declaration
+   */
+  formatCurriedFunctionDeclaration(node, depth, comments) {
+    const indent = this.getIndent(depth);
+    let result = `${indent}${node.name} : `;
+    
+    // Format first typed parameter
+    result += `(${node.param.name}: ${this.formatType(node.param.type)})`;
+    
+    // Format return type
+    if (node.returnType) {
+      result += ` -> ${this.formatType(node.returnType)}`;
+    }
+    
+    result += ' ->\n';
+    
+    // Format body with proper indentation
+    const body = this.visitNode(node.body, depth + 1, comments);
+    result += body + ';';
+    
+    return result;
+  }
+
+  /**
+   * Format with header
+   */
+  formatWithHeader(node, depth, comments) {
+    const indent = this.getIndent(depth);
+    let result = `${indent}with`;
+    
+    if (node.recursive) {
+      result += ' rec';
+    }
+    
+    result += ' (\n';
+    
+    // Format entries
+    node.entries.forEach((entry, index) => {
+      const entryIndent = this.getIndent(depth + 1);
+      if (entry.type === 'WithTypeDecl') {
+        result += `${entryIndent}${entry.name} ${this.formatType(entry.typeAnnotation)};`;
+      } else if (entry.type === 'WithAssign') {
+        const value = this.visitNode(entry.value, depth + 1, comments);
+        result += `${entryIndent}${entry.name} : ${value};`;
+      }
+      
+      if (index < node.entries.length - 1) {
+        result += '\n';
+      }
+    });
+    
+    result += `\n${indent}) ->\n`;
+    const body = this.visitNode(node.body, depth + 1, comments);
+    // Ensure the body is properly indented
+    if (body && !body.startsWith(this.getIndent(depth + 1))) {
+      result += this.getIndent(depth + 1) + body;
+    } else {
+      result += body;
+    }
+    
+    return result;
+  }
+
+  /**
+   * Format when expression
+   */
+  formatWhenExpression(node, depth, comments) {
+    const indent = this.getIndent(depth);
+    let result = `${indent}when `;
+    
+    // Format discriminants
+    const discriminants = node.discriminants.map(d => 
+      this.visitNode(d, 0, comments)
+    ).join(' ');
+    result += `${discriminants} is\n`;
+    
+    // Calculate the maximum pattern width to align 'then' keywords
+    const caseIndent = this.getIndent(depth + 1);
+    const formattedCases = node.cases.map(caseNode => {
+      const patterns = caseNode.patterns.map(p => 
+        this.formatPattern(p, depth + 1, comments)
+      ).join(' ');
+      return {
+        patterns,
+        consequent: caseNode.consequent,
+        originalCase: caseNode
+      };
+    });
+    
+    // Find the maximum pattern length for alignment
+    const maxPatternLength = Math.max(
+      ...formattedCases.map(c => c.patterns.length)
+    );
+    
+    // Format cases with aligned 'then' keywords
+    formattedCases.forEach((formattedCase, index) => {
+      const { patterns, consequent } = formattedCase;
+      
+      // Pad patterns to align 'then' keywords
+      const paddedPatterns = patterns.padEnd(maxPatternLength);
+      result += `${caseIndent}${paddedPatterns} then `;
+      
+      // Format consequent - handle nested when expressions specially
+      if (consequent.type === 'WhenExpression') {
+        // For nested when expressions, add newline and proper indentation
+        result += '\n' + this.visitNode(consequent, depth + 2, comments);
+      } else {
+        // For simple consequents, add inline
+        const consequentFormatted = this.visitNode(consequent, 0, comments);
+        result += consequentFormatted;
+      }
+      
+      // Add newline between cases (but not after the last one)
+      if (index < formattedCases.length - 1) {
+        result += '\n';
+      }
+    });
+    
+    return result;
+  }
+
+  /**
+   * Format pattern
+   */
+  formatPattern(pattern, depth, comments) {
+    if (!pattern) return '';
+    
+    switch (pattern.type) {
+      case 'WildcardPattern':
+        return '_';
+      case 'TypePattern':
+        return pattern.name;
+      case 'ResultPattern':
+        return `${pattern.variant} ${pattern.identifier.name}`;
+      case 'ListPattern':
+        const elements = pattern.elements.map(e => 
+          this.formatPattern(e, depth, comments)
+        ).join(', ');
+        return `[${elements}]`;
+      case 'TablePattern':
+        const properties = pattern.properties.map(prop => 
+          `${prop.key}: ${this.formatPattern(prop.value, depth, comments)}`
+        ).join(', ');
+        return `{${properties}}`;
+      case 'NumberLiteral':
+        return pattern.value.toString();
+      case 'StringLiteral':
+        return `"${pattern.value}"`;
+      case 'BooleanLiteral':
+        return pattern.value.toString();
+      case 'Identifier':
+        return pattern.name;
+      default:
+        // For literal patterns, try to format them directly
+        if (typeof pattern === 'string') {
+          return pattern;
+        }
+        if (typeof pattern === 'number') {
+          return pattern.toString();
+        }
+        return this.visitNode(pattern, depth, comments);
+    }
+  }
+
+  /**
+   * Format binary expression
+   */
+  formatBinaryExpression(node, depth, comments) {
+    const left = this.visitNode(node.left, depth, comments);
+    const right = this.visitNode(node.right, depth, comments);
+    
+    // Add spaces around operators
+    const needsSpaces = !['.', '..'].includes(node.operator);
+    if (needsSpaces) {
+      return `${left} ${node.operator} ${right}`;
+    } else {
+      return `${left}${node.operator}${right}`;
+    }
+  }
+
+  /**
+   * Format unary expression
+   */
+  formatUnaryExpression(node, depth, comments) {
+    const operand = this.visitNode(node.operand, depth, comments);
+    return `${node.operator}${operand}`;
+  }
+
+  /**
+   * Format function call
+   */
+  formatFunctionCall(node, depth, comments) {
+    const callee = this.visitNode(node.callee, depth, comments);
+    const args = node.arguments.map(arg => 
+      this.visitNode(arg, depth, comments)
+    );
+    
+    if (args.length === 0) {
+      return callee;
+    }
+    
+    // Handle parentheses for complex expressions
+    const formattedArgs = args.map(arg => {
+      // If argument contains operators or is complex, wrap in parentheses
+      if (arg.includes(' -> ') || (arg.includes(' ') && !arg.startsWith('"') && !arg.startsWith('['))) {
+        return `(${arg})`;
+      }
+      return arg;
+    });
+    
+    return `${callee} ${formattedArgs.join(' ')}`;
+  }
+
+  /**
+   * Format anonymous function
+   */
+  formatAnonymousFunction(node, depth, comments) {
+    // Handle both string parameters and object parameters
+    const params = node.params.map(param => {
+      if (typeof param === 'string') {
+        return param;
+      } else if (param && typeof param === 'object' && param.name) {
+        return param.name;
+      } else if (param && typeof param === 'object' && param.type === 'Identifier') {
+        return param.name;
+      } else {
+        return String(param);
+      }
+    }).join(' ');
+    const body = this.visitNode(node.body, depth, comments);
+    return `${params} -> ${body}`;
+  }
+
+  /**
+   * Format list literal
+   */
+  formatListLiteral(node, depth, comments) {
+    if (node.elements.length === 0) {
+      return '[]';
+    }
+    
+    const elements = node.elements.map(el => 
+      this.visitNode(el, depth, comments)
+    );
+    
+    // Single line if short, multi-line if long
+    const singleLine = `[${elements.join(', ')}]`;
+    if (singleLine.length <= 50) {
+      return singleLine;
+    }
+    
+    const indent = this.getIndent(depth);
+    const elementIndent = this.getIndent(depth + 1);
+    let result = '[\n';
+    elements.forEach((el, index) => {
+      result += `${elementIndent}${el}`;
+      if (index < elements.length - 1) {
+        result += ',';
+      }
+      result += '\n';
+    });
+    result += `${indent}]`;
+    return result;
+  }
+
+  /**
+   * Format table literal
+   */
+  formatTableLiteral(node, depth, comments) {
+    if (node.properties.length === 0) {
+      return '{}';
+    }
+    
+    const properties = node.properties.map(prop => {
+      const value = this.visitNode(prop.value, depth + 1, comments);
+      return `${prop.key}: ${value}`;
+    });
+    
+    // Single line if short, multi-line if long
+    const singleLine = `{${properties.join(', ')}}`;
+    if (singleLine.length <= 50 && !properties.some(p => p.includes('\n'))) {
+      return singleLine;
+    }
+    
+    const indent = this.getIndent(depth);
+    const propIndent = this.getIndent(depth + 1);
+    let result = '{\n';
+    properties.forEach((prop, index) => {
+      result += `${propIndent}${prop}`;
+      if (index < properties.length - 1) {
+        result += ',';
+      }
+      result += '\n';
+    });
+    result += `${indent}}`;
+    return result;
+  }
+
+  /**
+   * Format member expression
+   */
+  formatMemberExpression(node, depth, comments) {
+    const object = this.visitNode(node.object, depth, comments);
+    const property = this.visitNode(node.property, depth, comments);
+    return `${object}.${property}`;
+  }
+
+  /**
+   * Format result expression
+   */
+  formatResultExpression(node, depth, comments) {
+    const value = this.visitNode(node.value, depth, comments);
+    return `${node.variant} ${value}`;
+  }
+
+  /**
+   * Format number literal
+   */
+  formatNumberLiteral(node) {
+    return node.value.toString();
+  }
+
+  /**
+   * Format string literal
+   */
+  formatStringLiteral(node) {
+    return `"${node.value}"`;
+  }
+
+  /**
+   * Format boolean literal
+   */
+  formatBooleanLiteral(node) {
+    return node.value.toString();
+  }
+
+  /**
+   * Format identifier
+   */
+  formatIdentifier(node) {
+    return node.name;
+  }
+
+  // Helper methods
+
+  /**
+   * Get indentation string
+   */
+  getIndent(depth) {
+    return ' '.repeat(depth * this.indentSize);
+  }
+
+  /**
+   * Check if parameters have type annotations
+   */
+  hasTypedParams(params) {
+    return params.some(p => 
+      typeof p === 'object' && p.type && p.type !== 'Identifier'
+    );
+  }
+
+  /**
+   * Format typed parameters
+   */
+  formatTypedParameters(params) {
+    const formatted = params.map(p => {
+      if (typeof p === 'string') {
+        return p;
+      } else if (p.type && p.type !== 'Identifier') {
+        return `${p.name}: ${this.formatType(p.type)}`;
+      } else {
+        return p.name;
+      }
+    });
+    return `(${formatted.join(', ')})`;
+  }
+
+  /**
+   * Format type annotation
+   */
+  formatType(type) {
+    if (typeof type === 'string') {
+      return type;
+    }
+    
+    if (type.type === 'PrimitiveType') {
+      return type.name;
+    }
+    
+    if (type.type === 'FunctionType') {
+      const paramTypes = type.paramTypes.map(t => this.formatType(t)).join(', ');
+      const returnType = this.formatType(type.returnType);
+      return `(${paramTypes}) -> ${returnType}`;
+    }
+    
+    return 'Unknown';
+  }
+}
+
+// Make formatter available globally
+window.BabaYagaFormatter = BabaYagaFormatter;
diff --git a/js/baba-yaga/web/editor/test-formatter.html b/js/baba-yaga/web/editor/test-formatter.html
new file mode 100644
index 0000000..616afe2
--- /dev/null
+++ b/js/baba-yaga/web/editor/test-formatter.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Test Baba Yaga Formatter</title>
+    <style>
+        body {
+            font-family: monospace;
+            padding: 20px;
+            background: #1e1e1e;
+            color: #d4d4d4;
+        }
+        .test-section {
+            margin: 20px 0;
+            padding: 20px;
+            border: 1px solid #3e3e42;
+            border-radius: 8px;
+        }
+        .code-block {
+            background: #2d2d30;
+            padding: 10px;
+            border-radius: 4px;
+            white-space: pre-wrap;
+            margin: 10px 0;
+        }
+        .success { color: #4ec9b0; }
+        .error { color: #f14c4c; }
+        button {
+            background: #007acc;
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 4px;
+            cursor: pointer;
+            margin: 10px 0;
+        }
+        button:hover {
+            background: #005a9e;
+        }
+    </style>
+</head>
+<body>
+    <h1>Baba Yaga Formatter Test</h1>
+    
+    <div class="test-section">
+        <h2>Test 1: Basic Function Formatting</h2>
+        <div class="code-block" id="input1">add:x y->x+y;</div>
+        <button onclick="testFormat1()">Format Test 1</button>
+        <div class="code-block" id="output1"></div>
+        <div id="result1"></div>
+    </div>
+
+    <div class="test-section">
+        <h2>Test 2: Complex Code Formatting</h2>
+        <div class="code-block" id="input2">factorial:n->when n is 0 then 1 1 then 1 _ then n*factorial(n-1);</div>
+        <button onclick="testFormat2()">Format Test 2</button>
+        <div class="code-block" id="output2"></div>
+        <div id="result2"></div>
+    </div>
+
+    <div class="test-section">
+        <h2>Test 3: Multiple Functions</h2>
+        <div class="code-block" id="input3">add:x y->x+y;
+multiply:x y->x*y;
+result:add 5 3;</div>
+        <button onclick="testFormat3()">Format Test 3</button>
+        <div class="code-block" id="output3"></div>
+        <div id="result3"></div>
+    </div>
+
+    <!-- Load Baba Yaga components -->
+    <script type="module">
+        import { createLexer, tokenTypes } from '../../lexer.js';
+        import { createParser } from '../../parser.js';
+        
+        // Make them globally available
+        window.createLexer = createLexer;
+        window.createParser = createParser;
+        window.tokenTypes = tokenTypes;
+        
+        console.log('Baba Yaga modules loaded');
+    </script>
+
+    <!-- Load formatter -->
+    <script src="js/formatter.js"></script>
+
+    <script>
+        function testFormat1() {
+            const input = document.getElementById('input1').textContent;
+            const output = document.getElementById('output1');
+            const result = document.getElementById('result1');
+            
+            try {
+                const formatter = new BabaYagaFormatter();
+                const formatted = formatter.format(input);
+                output.textContent = formatted;
+                result.innerHTML = '<span class="success">✓ Formatting successful!</span>';
+            } catch (error) {
+                output.textContent = 'Error: ' + error.message;
+                result.innerHTML = '<span class="error">✗ Formatting failed: ' + error.message + '</span>';
+            }
+        }
+
+        function testFormat2() {
+            const input = document.getElementById('input2').textContent;
+            const output = document.getElementById('output2');
+            const result = document.getElementById('result2');
+            
+            try {
+                const formatter = new BabaYagaFormatter();
+                const formatted = formatter.format(input);
+                output.textContent = formatted;
+                result.innerHTML = '<span class="success">✓ Formatting successful!</span>';
+            } catch (error) {
+                output.textContent = 'Error: ' + error.message;
+                result.innerHTML = '<span class="error">✗ Formatting failed: ' + error.message + '</span>';
+            }
+        }
+
+        function testFormat3() {
+            const input = document.getElementById('input3').textContent;
+            const output = document.getElementById('output3');
+            const result = document.getElementById('result3');
+            
+            try {
+                const formatter = new BabaYagaFormatter();
+                const formatted = formatter.format(input);
+                output.textContent = formatted;
+                result.innerHTML = '<span class="success">✓ Formatting successful!</span>';
+            } catch (error) {
+                output.textContent = 'Error: ' + error.message;
+                result.innerHTML = '<span class="error">✗ Formatting failed: ' + error.message + '</span>';
+            }
+        }
+
+        // Test formatter availability on load
+        window.addEventListener('load', () => {
+            setTimeout(() => {
+                if (typeof BabaYagaFormatter !== 'undefined') {
+                    console.log('✓ BabaYagaFormatter is available');
+                } else {
+                    console.error('✗ BabaYagaFormatter is not available');
+                }
+                
+                if (typeof createLexer !== 'undefined' && typeof createParser !== 'undefined') {
+                    console.log('✓ Baba Yaga language components are available');
+                } else {
+                    console.error('✗ Baba Yaga language components are not available');
+                }
+            }, 1000);
+        });
+    </script>
+</body>
+</html>