Skip to content

Conversation

@jessegavin
Copy link
Contributor

Note

This PR was mostly vibe-coded with Claude Code. It is offered as food for thought.


Add Variable Support to Mirrow DSL

This PR implements a compile-time variable system for the Mirrow DSL, enabling developers to define reusable values at the document level and reference them throughout their SVG definitions. Variables are resolved during compilation, producing clean SVG output with no runtime overhead.

Adding support for variables opens the door for compilation of Mirrow documents as components (with props) for framework libraries.

Example Usage

vars {
  width: 150
  height: 150
  stroke: #333
  hoverStroke: #0070f3
}

svg {
  size: ($width, $height)

  rect {
    at: (20, 20)
    stroke: $stroke
    id: "box"
  }

  @hover {
    #box { stroke: $hoverStroke; }
  }
}

Implementation Strategy

The implementation follows a three-stage compilation pipeline with compile-time variable resolution:

1. Lexer Stage - Token Recognition

  • Added TokenType.DOLLAR to recognize the $ prefix
  • Implemented readVariableReference() to tokenize variable references (e.g., $width)
  • Variable references are validated at lexing time (must start with letter/underscore, followed by alphanumeric/hyphen/underscore)

2. Parser Stage - AST Construction & Validation

  • Introduced new AST node types:
    • VarsBlock - Container for variable declarations at document root
    • VariableDeclaration - Individual variable definition (name + value)
    • VariableReference - Reference to a declared variable
  • Built a symbol table (Map<string, LiteralValue>) during parsing to track declared variables
  • Implemented validation rules:
    • Variables must be declared before use
    • No duplicate variable declarations allowed
    • Variable values must be literal types (numbers, strings, identifiers, tuples)
  • Extended parseAttributeValue() and parseTupleItem() to handle $variable syntax

3. Compiler Stage - Variable Resolution

  • Created VariableContext (Map of variable names to resolved values)
  • Implemented resolveValue() function that:
    • Recursively resolves VariableReference nodes to concrete values
    • Handles variables within tuples
    • Supports variable usage in CSS states (@hover) and JS events (@click)
  • Resolution occurs before SVG attribute generation, ensuring clean output

Design Decisions

Compile-time Resolution: Variables are resolved during AST → SVG compilation, not at runtime. This keeps the output SVG simple, eliminates runtime overhead, and aligns with Mirrow's design-first philosophy.

Global Scope: All variables in a vars block are globally scoped within the document. This covers the common use case (document-level constants) and can be extended to nested scopes in the future.

Supported Types: Variables can store any literal value type (numbers, strings, identifiers, tuples). Variable-to-variable references are not supported to prevent circular dependencies.

Notable Changes

Core Compiler Changes

packages/core/src/compiler/lexer.ts (+51 lines)

  • Added TokenType.DOLLAR enum value
  • Implemented readVariableReference() method with validation
  • Error handling for invalid variable names (e.g., $, $123)

packages/core/src/compiler/ast.ts (+22 lines)

  • Added VariableReference, VariableDeclaration, and VarsBlock interfaces
  • Extended LiteralValue union type to include VariableReference
  • Added optional varsBlock field to RootNode

packages/core/src/compiler/parser.ts (+179 lines)

  • Implemented parseVarsBlock() to handle vars { ... } syntax
  • Created parseVariableDeclarations() to build symbol table
  • Added parseVariableReference() to create VariableReference AST nodes
  • Extended attribute validators to accept VariableReference nodes
  • Symbol table passed through nested parsers to maintain scope awareness

packages/core/src/compiler/compiler.ts (+107 lines)

  • Implemented buildVariableContext() to extract variables from VarsBlock
  • Created resolveValue() for recursive variable resolution
  • Added resolveVariablesInText() for variable substitution in CSS/JS blocks
  • Updated createAttributeValue() to resolve variables before processing
  • Integrated VariableContext into CompileContext

Testing

packages/core/test/parser.test.js (+94 lines)

Added comprehensive test coverage for:

  • Error cases: Undeclared variables, duplicate declarations, invalid values
  • Basic usage: Numeric, string, and identifier variables
  • Complex usage: Variables in tuples, multiple variables, CSS states
  • Edge cases: Empty vars blocks, variable names with hyphens/underscores

All tests pass with clear error messages and proper AST validation.

Error Handling

The implementation provides clear, actionable error messages:

  • Undeclared variable: Variable '$width' is not defined (with line/column)
  • Duplicate declaration: Variable 'width' is already declared (with line/column)
  • Invalid variable name: Invalid variable reference: variable name must start with a letter or underscore
  • Invalid variable value: Variables can only contain literal values (strings, numbers, identifiers, or tuples)

Future Enhancements

Eventually framework adapters could be built which will expose vars as component props for (React, Vue, Svelte, etc)

Testing Instructions

# Build the project
bun run build:core
bun run build:cli

# Run all tests
bun test

# Test variable functionality
cd packages/cli
echo 'vars { w: 150 } svg { size: ($w, $w) }' | bun run dist/index.js

Expected output:

<svg width="150" height="150">
</svg>

@osa-mannella
Copy link
Member

osa-mannella commented Oct 16, 2025

@jessegavin Thank you so much for the PR, I'm reviewing it now; this is mostly a QOL PR most akin to allowing CSS variables for code-base cleanliness as far as I can tell. Is this assumption correct?

@osa-mannella
Copy link
Member

Thanks so much for the contribution! I finished reviewing the PR and everything looks great, the code structure, tests, etc. I'll merge and deploy this soon.

@osa-mannella osa-mannella added the enhancement New feature or request label Oct 16, 2025
@osa-mannella osa-mannella merged commit 2ce292c into MirrowApp:main Oct 16, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants