NeonLang (.neon) & Compiler (neonc)

NeonLang (.neon) is a first-class declarative UI programming language designed specifically for the Neon Framework, blending the ergonomic elegance of SwiftUI and Kotlin Jetpack Compose while remaining 100% coexistent and two-way interoperable with Dart (.dart).

The NeonLang Compiler (neonc) parses .neon source files into an Abstract Syntax Tree (AST), validates state bindings and modifier types, and compiles them into optimized Neon Framework Dart classes (NeonStatefulWidget / NeonStatelessWidget) with 1:1 Source Maps.


1. Compiler Pipeline Architecture

The NeonLang toolchain (lib/src/language/) consists of five core stages:

  1. Formal Grammar Specification (grammar.ebnf): Defines the lexical and syntactic rules for module, import, component, route, view, control flow (ForEach, If / Else), and modifier chains.
  2. Lexer & Tokenizer (lexer.dart): Tokenizes .neon source files with line/column source span tracking, supporting reactive annotations (@state, @computed, @persist), hex color literals (#4F46E5), dimensional/timing unit literals (16.dp, 200.ms, 14.sp), and enum shorthands (.bold, .spaceBetween, .filled).
  3. AST & Recursive Descent Parser (ast.dart & parser.dart): Constructs strongly-typed AST nodes for module headers, state declarations, view hierarchies, lambda bindings, and chained modifiers (.padding(), .font(), .color()).
  4. Semantic Analyzer (analyzer.dart): Validates widget identifiers, detects reactive state mutations to automatically decide between NeonStatefulWidget and NeonStatelessWidget, and checks modifier signatures.
  5. Code Generator (codegen.dart & neon_compiler.dart): Emits clean, formatted Dart code alongside 1:1 line-mapping source maps.

2. Syntax Showcase (task_studio.neon)

Below is the reference showcase comparing declarative .neon code with the equivalent Dart code generated by neonc:

Declarative .neon Source (task_studio.neon)

module app.screens

import "package:neon_framework/neon.dart"

@state count: Int = 0
@state tasks: List<String> = ["Design NeonLang", "Build Neon Studio", "Ship Cross-OS IPA"]
@computed totalTasks: Int = tasks.length
@persist themeMode: String = "dark"

component TaskStudioScreen {
    route: "/tasks"

    view {
        Scaffold(title: "Neon Task Studio") {
            Column(padding: 16.dp, gap: 12.dp) {
                Text("Active Tasks: ${totalTasks}")
                    .font(size: 20.sp, weight: .bold)
                    .color(#4F46E5)
                    .padding(8.dp)

                If (count > 0) {
                    Badge("Completed Actions: ${count}", color: .emerald)
                } Else {
                    Text("Tap below to start")
                        .color(#94A3B8)
                }

                ForEach(tasks) -> item {
                    Card(elevation: 2) {
                        Row(align: .spaceBetween) {
                            Text(item)
                                .font(size: 14.sp)
                            Badge("High", color: .emerald)
                        }
                    }
                }

                Button("+ Increment (${count})") {
                    count += 1
                }
                .style(.filled)
            }
        }
    }
}

Compiled .dart Output (Generated by neonc)

// GENERATED CODE - COMPILED BY NEONC FROM task_studio.neon
// Source Map: 1:1 line mapping enabled
import 'package:neon_framework/neon.dart';

class TaskStudioScreen extends NeonStatefulWidget {
  const TaskStudioScreen({super.key});

  static const String routeName = '/tasks';

  @override
  NeonState<TaskStudioScreen> createState() => _TaskStudioScreenState();
}

class _TaskStudioScreenState extends NeonState<TaskStudioScreen> {
  int count = 0;
  List<String> tasks = ['Design NeonLang', 'Build Neon Studio', 'Ship Cross-OS IPA'];
  int get totalTasks => tasks.length;
  String themeMode = 'dark'; // @persist bound to NeonKeyValueStore

  @override
  NeonWidget build(NeonBuildContext context) {
    return NeonScaffold(
      appBar: NeonAppBar(title: 'Neon Task Studio'),
      body: NeonPadding(
        padding: const NeonEdgeInsets.all(16),
        child: NeonColumn(
          spacing: 12,
          children: [
            NeonPadding(
              padding: const NeonEdgeInsets.all(8),
              child: NeonText(
                'Active Tasks: $totalTasks',
                style: const NeonTextStyle(
                  fontSize: 20,
                  fontWeight: NeonFontWeight.bold,
                  color: NeonColor(0xFF4F46E5),
                ),
              ),
            ),
            if (count > 0)
              NeonBadge(label: 'Completed Actions: $count')
            else
              NeonText(
                'Tap below to start',
                style: const NeonTextStyle(color: NeonColor(0xFF94A3B8)),
              ),
            ...tasks.map((item) => NeonCard(
              elevation: 2,
              child: NeonRow(
                mainAxisAlignment: NeonMainAxisAlignment.spaceBetween,
                children: [
                  NeonText(item, style: const NeonTextStyle(fontSize: 14)),
                  NeonBadge(label: 'High'),
                ],
              ),
            )),
            FilledButton(
              key: 'btn_increment_count',
              onPressed: () => setState(() => count += 1),
              child: Text('+ Increment ($count)'),
            ),
          ],
        ),
      ),
    );
  }
}

3. Core Language Features

| Feature | Syntax Example | Description | | :--- | :--- | :--- | | Reactive State | @state count: Int = 0 | Declares mutable state that automatically wraps mutations in setState() and triggers incremental RFC-6902 tree patches. | | Computed State | @computed doubled: Int = count * 2 | Generates reactive Dart getters derived from @state fields. | | Persistent State | @persist theme: String = "dark" | Automatically syncs state values with NeonKeyValueStore across app restarts. | | Hex Color Literals | #4F46E5, #00E5FF | Compiles directly to const NeonColor(0xFF4F46E5). | | Unit Literals | 16.dp, 14.sp, 200.ms | First-class dimensional, font-scale, and Duration(milliseconds: 200) literals. | | Enum Shorthands | .bold, .spaceBetween, .filled | Context-aware resolution to NeonFontWeight.bold, NeonMainAxisAlignment.spaceBetween, etc. | | Modifier Chains | .padding(16.dp).font(...).color(...) | Eliminates deeply nested wrapper widgets by composing modifiers cleanly. | | Declarative Control Flow | ForEach(items) -> item { ... }, If (...) { ... } Else { ... } | Compiles to idiomatic Dart collection spreads (...items.map(...)) and inline conditional elements. |


4. 100% Coexistence & Two-Way Dart Interoperability

Zero Breaking Changes: .neon is an additive superpower layer on top of the Dart runtime. All existing .dart widgets, services, state stores, and tests continue to work without modification.

  • Import .dart inside .neon: Import any Dart file or package (import "package:my_app/services/api.dart") and invoke Dart models or repositories directly from .neon components.
  • Use .neon inside .dart: Every .neon component compiles to a standard Dart NeonWidget class that can be instantiated in main.dart or registered in NeonRouter.
  • Hybrid Projects: Combine .neon for rapid declarative UI screens and .dart for business logic, repositories, and low-level plugins.

5. CLI Usage (neon compile)

Use the neon compile command (command_compile.dart) to compile .neon files or watch a directory for live compilation:

# Compile a single .neon file to .dart with 1:1 Source Maps
neon compile lib/screens/task_studio.neon

# Compile all .neon files in a project directory
neon compile lib/ --out lib/generated/ --source-maps

# Watch mode for instant sub-50ms recompilation
neon compile lib/ --watch

6. Editor Support: neon-lsp & TextMate Grammar

Neon ships with a full Language Server Protocol implementation (neon_lsp_server.dart) and a TextMate Grammar (neon.tmLanguage.json):

  • initialize: Negotiates workspace capabilities for .neon files.
  • Real-Time Diagnostics (textDocument/didOpen, textDocument/didChange): Runs the lexer, parser, and semantic analyzer in memory to report syntax errors and unknown modifiers with exact line/column spans.
  • Autocomplete (textDocument/completion): Provides context-aware completions for @state, @computed, @persist, all Neon widgets (Scaffold, Column, Row, Card, Button, Text, Badge, ForEach), modifiers (.padding(), .font(), .color(), .style()), and enum shorthands.
  • Markdown Hover Docs (textDocument/hover): Displays rich documentation and compiled Dart signatures when hovering over any .neon keyword, widget, or modifier.