Monorepo for Aesthetic.Computer aesthetic.computer
core plans kidlisp-malformed-code-protection.md
7.1 kB

KidLisp Malformed Code Protection Plan #

Problem Statement #

When KidLisp code has syntax errors (especially missing closing parentheses), the system crashes with errors like:

❌ Error updating nested embedded layer: TypeError: api.page is not a function

This happens repeatedly and floods the console, making the system unusable.

Root Causes #

  1. Missing Pre-Validation: Code is parsed and executed without checking for syntax validity first
  2. Auto-Closing Limitations: While tokenize() attempts to auto-close parentheses, malformed code can still slip through
  3. No Error Recovery in Embedded Layers: When an embedded layer fails, it continues to retry every frame
  4. Missing API Safety Checks: The code assumes api.page exists without verifying

Solution Architecture #

Phase 1: Pre-Execution Validation (Linting Layer) #

Add a validation step before code execution that checks for:

  • Balanced parentheses
  • Valid syntax structure
  • Safe AST structure
  • Recognized function names (optional warning)
class KidLispValidator {
  validate(input) {
    const errors = [];
    const warnings = [];
    
    // 1. Check parenthesis balance
    const parenBalance = this.checkParenthesisBalance(input);
    if (parenBalance !== 0) {
      errors.push({
        type: 'UNBALANCED_PARENS',
        message: `Missing ${Math.abs(parenBalance)} closing ${parenBalance > 0 ? ')' : '('}`,
        severity: 'error'
      });
    }
    
    // 2. Check for common syntax errors
    this.checkSyntaxErrors(input, errors);
    
    // 3. Try to parse and catch tokenization errors
    try {
      const tokens = tokenize(input);
      const ast = readFromTokens([...tokens]);
      
      // 4. Validate AST structure
      this.validateAST(ast, errors, warnings);
    } catch (e) {
      errors.push({
        type: 'PARSE_ERROR',
        message: e.message,
        severity: 'error'
      });
    }
    
    return {
      valid: errors.length === 0,
      errors,
      warnings
    };
  }
  
  checkParenthesisBalance(input) {
    let balance = 0;
    for (const char of input) {
      if (char === '(') balance++;
      if (char === ')') balance--;
    }
    return balance;
  }
  
  checkSyntaxErrors(input, errors) {
    // Check for mismatched quotes
    const doubleQuotes = (input.match(/"/g) || []).length;
    const singleQuotes = (input.match(/'/g) || []).length;
    
    if (doubleQuotes % 2 !== 0) {
      errors.push({
        type: 'UNMATCHED_QUOTE',
        message: 'Unmatched double quote',
        severity: 'error'
      });
    }
    
    if (singleQuotes % 2 !== 0) {
      errors.push({
        type: 'UNMATCHED_QUOTE',
        message: 'Unmatched single quote',
        severity: 'error'
      });
    }
  }
  
  validateAST(ast, errors, warnings) {
    // Walk the AST and check for structural issues
    // This can be expanded based on common error patterns
  }
}

Phase 2: Safe Error Recovery #

Modify the parse() method to catch and handle validation errors:

parse(input) {
  const parseStart = this.startTiming('parse');
  
  // Pre-validation step
  if (!this.validator) {
    this.validator = new KidLispValidator();
  }
  
  const validation = this.validator.validate(input);
  
  if (!validation.valid) {
    // Store validation errors for debugging
    this.lastValidationErrors = validation.errors;
    
    // Log errors to console
    console.error('❌ KidLisp Validation Failed:', validation.errors);
    
    // Return a safe empty AST instead of crashing
    this.endTiming('parse', parseStart);
    return [];
  }
  
  // Continue with existing parsing logic...
  try {
    // ... existing code ...
  } catch (error) {
    console.error('❌ KidLisp Parse Error:', error);
    this.lastParseError = error;
    this.endTiming('parse', parseStart);
    return [];
  }
}

Phase 3: Embedded Layer Safety #

Add safety checks in updateEmbeddedLayer():

async updateEmbeddedLayer(embeddedLayer, api, env) {
  let didRender = false;

  try {
    // Validate that api has required methods
    if (!api || typeof api.page !== 'function') {
      console.error(`❌ Invalid API object for embedded layer ${embeddedLayer.cacheId}`);
      return didRender;
    }
    
    // Check if layer has valid parsed code
    if (!embeddedLayer.parsedCode || embeddedLayer.parsedCode.length === 0) {
      console.warn(`⚠️ Empty parsed code for embedded layer ${embeddedLayer.cacheId}`);
      return didRender;
    }
    
    // Continue with existing logic...
    const originalPage = api.screen;
    api.page(embeddedLayer.buffer);
    
    // ... rest of existing code ...
    
  } catch (error) {
    console.error(`❌ Error in embedded layer ${embeddedLayer.cacheId}:`, error);
    
    // Mark layer as failed to prevent infinite retry loops
    embeddedLayer.failed = true;
    embeddedLayer.failureCount = (embeddedLayer.failureCount || 0) + 1;
    
    // Stop retrying after 3 failures
    if (embeddedLayer.failureCount >= 3) {
      console.warn(`⚠️ Disabling failed embedded layer ${embeddedLayer.cacheId}`);
      embeddedLayer.disabled = true;
    }
    
    // Always try to restore original page
    try {
      if (api && typeof api.page === 'function' && api.screen) {
        api.page(api.screen);
      }
    } catch (restoreError) {
      console.error("❌ Failed to restore page:", restoreError);
    }
  }
  
  return didRender;
}

Phase 4: User Feedback #

Add visual feedback for validation errors:

// In the appropriate rendering location
renderValidationErrors(api) {
  if (this.lastValidationErrors && this.lastValidationErrors.length > 0) {
    const errors = this.lastValidationErrors;
    let y = 10;
    
    api.ink('red');
    api.write(`Syntax Errors (${errors.length}):`, 10, y);
    y += 15;
    
    errors.forEach((error, i) => {
      api.ink('yellow');
      api.write(`${i + 1}. ${error.message}`, 10, y);
      y += 12;
    });
  }
}

Implementation Steps #

  1. Create KidLispValidator class (new file: kidlisp-validator.mjs)
  2. Integrate validation into parse() method
  3. Add safety checks to updateEmbeddedLayer()
  4. Add failure tracking to prevent infinite retry
  5. Add visual error feedback
  6. Test with known malformed code

Testing Strategy #

Test cases to verify:

  1. Missing closing parenthesis: (blue (ink rainbow
  2. Missing opening parenthesis: blue) ink rainbow)
  3. Unmatched quotes: (write "hello)
  4. Empty parentheses: ()()()
  5. Deeply nested malformed: (repeat 10 (repeat 10 (line
  6. Comma-separated with error: blue, ink rainbow, (line

Expected Outcomes #

  • ✅ No more system crashes from malformed code
  • ✅ Clear error messages for users
  • ✅ Graceful degradation (show previous valid frame)
  • ✅ Performance maintained (validation is fast)
  • ✅ Better developer experience

Future Enhancements #

  • Auto-fix common errors
  • Suggest corrections
  • Real-time validation in editor
  • Syntax highlighting based on validation