Skip to content

Parser Refactoring: Separating Grammar from AST Building

Overview

This refactoring introduces the Builder pattern to separate parsing concerns from AST construction in the Onion compiler. This separation provides several benefits:

  1. Testability: AST construction can be tested independently of parsing
  2. Flexibility: Different AST builders can be used for different purposes
  3. Maintainability: Grammar changes don't require AST construction changes and vice versa
  4. Extensibility: New behaviors can be added without modifying the parser

Architecture

Before Refactoring

The original parser (JJOnionParser.jj) directly constructs AST nodes within the grammar rules:

AST.ClassDeclaration class_decl(int mset) : {
  // ... variable declarations ...
}{
  t1="class" t2=<ID> /* ... parsing ... */ {
    return new AST.ClassDeclaration(  // Direct AST construction
      p(t1), mset, t2.image, ty1, toList(ty2s), sec3, toList(sec2s)
    );
  }
}

After Refactoring

The refactored parser uses an ASTBuilder interface:

AST.ClassDeclaration class_decl(int mset) : {
  // ... variable declarations ...
}{
  t1="class" t2=<ID> /* ... parsing ... */ {
    return builder.createClassDeclaration(  // Delegated to builder
      p(t1), mset, t2.image, ty1, toList(ty2s), sec3, toList(sec2s)
    );
  }
}

Components

1. ASTBuilder Trait (ASTBuilder.scala)

Defines the interface for AST construction:

trait ASTBuilder {
  def createCompilationUnit(...): AST.CompilationUnit
  def createClassDeclaration(...): AST.ClassDeclaration
  def createMethodDeclaration(...): AST.MethodDeclaration
  // ... other AST node creation methods
}

2. DefaultASTBuilder (ASTBuilder.scala)

Provides the default implementation that simply constructs AST nodes:

class DefaultASTBuilder extends ASTBuilder {
  def createClassDeclaration(...) = {
    AST.ClassDeclaration(location, modifiers, name, ...)
  }
}

3. ASTBuilderAdapter (ASTBuilderAdapter.java)

Java adapter for seamless integration with JavaCC:

public class ASTBuilderAdapter {
  private final ASTBuilder builder;

  // Handles Java-Scala interop complexities
  public AST.ClassDeclaration createClassDeclaration(...) {
    return builder.createClassDeclaration(...);
  }
}

4. JJOnionParserRefactored (JJOnionParserRefactored.jj)

Modified JavaCC grammar that uses the builder pattern instead of direct AST construction.

Use Cases

1. Custom Analysis

class AnalyzingASTBuilder extends DefaultASTBuilder {
  var methodCount = 0

  override def createMethodDeclaration(...) = {
    methodCount += 1
    super.createMethodDeclaration(...)
  }
}

2. Validation

class ValidatingASTBuilder extends DefaultASTBuilder {
  override def createMethodDeclaration(...) = {
    if (args.length > 10) {
      throw new IllegalArgumentException("Too many parameters")
    }
    super.createMethodDeclaration(...)
  }
}

3. Transformation

class TransformingASTBuilder extends DefaultASTBuilder {
  override def createMethodDeclaration(...) = {
    val modifiedBody = addLogging(body)
    super.createMethodDeclaration(..., modifiedBody)
  }
}

4. Debugging

class LoggingASTBuilder extends DefaultASTBuilder {
  override def createClassDeclaration(...) = {
    println(s"Creating class: $name at $location")
    super.createClassDeclaration(...)
  }
}

Migration Strategy

  1. Phase 1: Create builder infrastructure (completed)
  2. ASTBuilder trait
  3. DefaultASTBuilder implementation
  4. ASTBuilderAdapter for Java interop

  5. Phase 2: Refactor parser gradually

  6. Start with simple constructs (literals, identifiers)
  7. Move to complex constructs (classes, methods)
  8. Maintain backward compatibility

  9. Phase 3: Update existing code

  10. Modify Parsing.scala to use new parser
  11. Update tests to use refactored components

  12. Phase 4: Leverage new capabilities

  13. Add validation builders
  14. Implement transformation builders
  15. Create specialized builders for different compilation modes

Benefits Realized

  1. Separation of Concerns: Grammar rules focus on syntax; builders focus on semantics
  2. Testability: AST construction logic can be unit tested without parsing
  3. Extensibility: New compilation features can be added via custom builders
  4. Maintainability: Changes to AST structure don't require grammar modifications
  5. Debugging: Logging/tracing can be added without touching the parser

Future Enhancements

  1. Builder Composition: Chain multiple builders for complex transformations
  2. Context-Aware Building: Builders that maintain compilation context
  3. Error Recovery: Builders that can construct partial ASTs for better error messages
  4. Optimization: Builders that perform early optimizations during parsing