diff options
Diffstat (limited to 'js/baba-yaga/web')
-rw-r--r-- | js/baba-yaga/web/editor/index.html | 12 | ||||
-rw-r--r-- | js/baba-yaga/web/editor/js/baba-yaga-runner.js | 102 | ||||
-rw-r--r-- | js/baba-yaga/web/editor/js/formatter.js | 621 | ||||
-rw-r--r-- | js/baba-yaga/web/editor/test-formatter.html | 155 |
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> |