Skip to content

feat(invariants): parse Forge invariants into structured expressions #10

Description

@dev-queiroz

Summary

Enhance the Forge language parser to extract and structure invariant expressions currently parsed as strings. This foundation enables invariant-based code generation in v0.2.

Motivation

Invariants are a critical semantic feature of Forge contracts but are currently captured only as string expressions. To enable:

  • Runtime validation in Zod schemas
  • Database constraints in SQL DDL
  • Swagger documentation with constraints

...we need a structured representation of invariant expressions that captures operator, left/right operands, and type information.

Scope

In Scope

  • Parse invariant expressions into AST: invariant left operator right
  • Support 6 comparison operators: ==, !=, <, >, <=, >=
  • Validate invariant syntax and report meaningful errors
  • Type-check operands (e.g., left side must be field name)
  • Extend semantic model with structured InvariantExpression type
  • Preserve location information (file, line, column) for error reporting

Out of Scope

  • Complex expressions (AND, OR, NOT)
  • Arithmetic operators
  • Function calls in invariants
  • Invariant evaluation/enforcement (Phase 1.1.2)

Technical Design

Current State

interface InvariantModel {
  expression: string;  // "valor > 0" stored as string
}

Proposed State

interface InvariantExpression {
  left: string;           // Field name
  operator: ComparisonOp; // Type: ==|!=|<|>|<=|>=
  right: InvariantValue;  // Number | String | Field reference
  location: SourceLocation;
}

interface InvariantModel {
  expression: string;     // Original text
  parsed?: InvariantExpression;  // NEW: Structured form
}

Parser Changes

  1. Extend Langium grammar (forge.langium):

    Invariant: 'invariant' left=ID operator=ComparisonOp right=InvariantValue
    ComparisonOp: '==' | '!=' | '<' | '>' | '<=' | '>='
    InvariantValue: NUMBER | STRING | ID
    
  2. New validation in validateInvariant():

    • Left operand must match existing field name
    • Right operand type must be compatible with left operand type
    • Report precise errors with file:line:column

Error Codes

  • FORGE_SEMANTIC_005: Invalid invariant syntax
  • FORGE_SEMANTIC_006: Type mismatch in invariant operands
  • FORGE_SEMANTIC_007: Unsupported operator in invariant

Acceptance Criteria

  • Invariant expression successfully parses with structured type
  • All 6 operators parse correctly
  • Error: invalid operator → meaningful diagnostic
  • Error: invalid left operand → field name suggestion
  • Error: missing right operand → syntax error
  • Semantic model preserves both original expression and parsed form
  • Source location available for error reporting
  • Backward compatible: existing contracts still parse

Testing Strategy

Unit Tests

// tests/invariants-parsing.test.mjs

test('parse comparison operators', () => {
  const cases = [
    'invariant valor > 0',
    'invariant idade >= 18', 
    'invariant status != ""',
  ];
  // Verify each parses into correct operator
})

test('detect invalid operator', () => {
  const invalid = 'invariant valor ~= 0';
  // Verify error: unknown operator
})

test('detect missing left operand', () => {
  const invalid = 'invariant > 0';
  // Verify syntax error
})

test('detect invalid right operand type', () => {
  const invalid = 'invariant valor > invalidField';
  // Verify: reference must exist or be literal
})

Integration Tests

  • Compile contract with invariants → no errors
  • Use contract in generator → invariant accessible
  • Multiple invariants per field → all parsed

Files to Create/Modify

  • packages/language/src/forge.langium (MODIFY - 2 lines)
  • packages/language/src/index.ts (MODIFY - 50 lines)
  • packages/language/src/invariants/parser.ts (NEW - 80 lines)
  • packages/language/src/invariants/types.ts (NEW - 30 lines)
  • tests/invariants-parsing.test.mjs (NEW - 150 lines)

Non-Goals

  • Multi-field complex boolean expressions
  • Arithmetic or string operations in invariants
  • Evaluation or enforcement of invariants
  • GraphQL/SQL invariant generation (v0.2 later features)

Future Extensions

  • Conditional invariants: invariant if status == 'ACTIVE' then valor > 0
  • Complex expressions: invariant valor > 0 AND valor < 999999
  • Cross-field validation: invariant dateFim > dataInicio
  • Custom invariant functions
  • Pre/post condition assertions

PR Template Reminder

When implementing, create a PR with:

  • Title: feat(invariants): parse expressions into structured AST
  • Branch: feature/invariant-parser
  • Tests: All passing, new coverage included
  • Docs: README update with invariant examples

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions