Skip to content

samuelkchris/dart_macros

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ dart_macros pub package License: MIT

A powerful Dart package that brings C-style macro preprocessing capabilities to Dart, enabling compile-time code generation and manipulation.

πŸ“¦ Installation

Add this to your package's pubspec.yaml file:

dependencies:
  dart_macros: ^1.0.2

Install it:

dart pub get

🌟 Overview

dart_macros provides a familiar C-like macro system for Dart developers, offering features such as:

  • βœ… Object-like macros for constant definitions
  • βœ… Function-like macros for code generation
  • βœ… Token concatenation operations
  • βœ… Conditional compilation directives
  • βœ… Macro expansion and evaluation
  • βœ… Built-in predefined macros
  • βœ… Cross-platform support (Flutter/iOS/Android)

πŸ“‹ Use Cases

  • Code generation without external build tools
  • Platform-specific code branching
  • Debug and release mode configurations
  • Compile-time constants and computations
  • Code reusability through macro templates
  • Meta-programming capabilities
  • White-label applications with client-specific configurations

✨ Features

  • πŸ” Clean, lightweight syntax that feels natural in Dart
  • πŸ”’ Type-safe macro expansions
  • πŸ”₯ Detailed error reporting and debugging support
  • πŸ”„ Integration with existing Dart tooling
  • ⚑ Performance optimization through compile-time evaluation
  • 🧩 Support for nested macro definitions
  • πŸ“± Full support for Flutter on all platforms

πŸš€ Usage

Object-like Macros

Simple macros that define constants or expressions:

import 'package:dart_macros/dart_macros.dart';

// Definition
@MacroFile()
@Define('MAX_SIZE', 100)
@Define('PI', 3.14159)
@Define('DEBUG', true)
void main() async {
  await initializeDartMacros(); // This is optional but won't hurt

  // Usage
  var array = List<int>.filled(Macros.get<int>('MAX_SIZE'), 0);
  var circleArea = Macros.get<double>('PI') * radius * radius;
  
  if (Macros.get<bool>('DEBUG')) {
    print('Debug mode enabled');
  }
}

Function-like Macros

Macros that take parameters and expand to code:

import 'package:dart_macros/dart_macros.dart';

@MacroFile()
@DefineMacro(
  'SQUARE',
  'x * x',
  parameters: ['x'],
)
@DefineMacro(
  'MIN',
  'a < b ? a : b',
  parameters: ['a', 'b'],
)
@DefineMacro(
  'VALIDATE',
  'x >= 0 && x <= max',
  parameters: ['x', 'max'],
)
void main() async {
  await initializeDartMacros();

  // Usage
  var squared = MacroFunctions.SQUARE(5);  // Evaluates to 25
  var minimum = MacroFunctions.MIN(x, y);  // Returns the smaller of x and y
  var isValid = MacroFunctions.VALIDATE(value, 100);  // Checks if value is in range
}

Flutter Support

Use dart_macros in Flutter applications on all platforms:

import 'package:flutter/material.dart';
import 'package:dart_macros/dart_macros.dart';

void main() {
  // Initialize Flutter macros
  FlutterMacros.initialize();
  
  // Register macros for Flutter apps
  FlutterMacros.registerFromAnnotations([
    Define('APP_NAME', 'My Flutter App'),
    Define('API_ENDPOINT', 'https://api.example.com'),
    Define('DEBUG', true),
    DefineMacro('FORMAT_CURRENCY', '"\$" + amount.toStringAsFixed(2)', parameters: ['amount']),
  ]);
  
  // Configure platform-specific settings
  FlutterMacros.configurePlatform(
    platform: 'android',
    debug: true,
    additionalValues: {
      'MIN_SDK_VERSION': 21,
      'TARGET_SDK_VERSION': 33,
    },
  );
  
  runApp(MyApp());
}

// Use macros just like on other platforms
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Macros.get<String>('APP_NAME'),
      theme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Macros.get<bool>('DEBUG') ? Brightness.light : Brightness.dark,
      ),
      home: HomeScreen(),
    );
  }
}

Stringizing

Convert macro arguments to string literals:

@MacroFile()
@DefineMacro(
  'STRINGIFY',
  '"x"',
  parameters: ['x'],
)
@DefineMacro(
  'REPORT_VAR',
  '"Variable " + "var" + " = " + var.toString()',
  parameters: ['var'],
)
void main() async {
  await initializeDartMacros();

  // Usage
  var name = MacroFunctions.STRINGIFY(user);  // Evaluates to "user"
  MacroFunctions.REPORT_VAR(count);  // Prints: Variable count = 5
}

Concatenation

Join tokens together:

@MacroFile()
@DefineMacro(
  'CONCAT',
  'a + b',
  parameters: ['a', 'b'],
)
void main() async {
  await initializeDartMacros();

  // Usage
  var fullName = MacroFunctions.CONCAT("John", "Doe");  // Evaluates to "JohnDoe"
}

Debug Operations

Special macros for debugging:

@MacroFile()
@Define('__DEBUG__', true)
@DefineMacro(
  'DEBUG_PRINT',
  '"Debug [" + __FILE__ + ":" + __LINE__ + "]: " + text',
  parameters: ['text'],
)
void main() async {
  await initializeDartMacros();

  // Usage
  MacroFunctions.DEBUG_PRINT("Starting initialization");
  // Prints: Debug [example.dart:15]: Starting initialization
}

Predefined Macros

Built-in system macros:

void main() async {
  await initializeDartMacros();

  print(Macros.file);      // Current source file name
  print(Macros.line);      // Current line number
  print(Macros.date);      // Compilation date
  print(Macros.time);      // Compilation time
}

Conditional Compilation

Control compilation based on conditions:

@MacroFile()
@Define('DEBUG', true)
@Define('PLATFORM', 'android')
@Define('API_VERSION', 2)
class App {
  void initialize() {
    if (MacroFunctions.IFDEF('DEBUG')) {
      print('Debug mode initialization');
    }

    if (MacroFunctions.IF_PLATFORM('android')) {
      print('Initializing Android platform');
    }

    if (MacroFunctions.IF('DEBUG && API_VERSION >= 2')) {
      print('Advanced debug features available');
    }
  }
}

πŸ“± Mobile-Specific Features

Platform Detection

dart_macros automatically selects the appropriate implementation based on platform capabilities:

// The same API works across all platforms
// On Flutter mobile, a non-reflection based implementation is used
// On Dart VM and web, a reflection-based implementation is used
final appName = Macros.get<String>('APP_NAME');

Flutter Configuration

For Flutter applications, use the FlutterMacros class to set up platform-specific configurations:

// Initialize with base settings
FlutterMacros.initialize();

// Configure for a specific platform
FlutterMacros.configurePlatform(
  platform: defaultTargetPlatform.name.toLowerCase(),
  debug: kDebugMode,
  additionalValues: {
    'DEVICE_TYPE': MediaQuery.of(context).size.width > 600 ? 'tablet' : 'phone',
    'API_BASE_URL': _getApiUrl(),
    'TIMEOUT_MS': 5000,
  },
);

Registration Helpers

Register macros from annotations for mobile platforms:

// Register multiple macros at once
FlutterMacros.registerFromAnnotations([
  Define('VERSION', '1.0.0'),
  Define('MAX_ITEMS', 100),
  Define('FEATURE_NEW_UI', false),
  DefineMacro('SQUARE', 'x * x', parameters: ['x']),
]);

πŸ“ Best Practices

  1. πŸ“‹ Document macro behavior and expansion
  2. πŸ”€ Use meaningful and clear macro names
  3. 🚧 Avoid side effects in macro arguments
  4. πŸ§ͺ Test macro expansion in different contexts
  5. πŸ” Consider using const or static final instead of simple object-like macros
  6. ⚠️ Be careful with token concatenation and stringizing operators
  7. πŸ“± For mobile apps, centralize macro registration in a configuration class

❌ Common Pitfalls

Side Effects in Arguments

// Bad
@DefineMacro(
  'SQUARE',
  'x * x',
  parameters: ['x'],
)
var result = MacroFunctions.SQUARE(i++);  // i gets incremented twice

// Good
@DefineMacro(
  'SQUARE',
  '(x) * (x)',
  parameters: ['x'],
)

Missing Parentheses

// Bad
@DefineMacro(
  'DOUBLE',
  'x + x',
  parameters: ['x'],
)
var result = 10 * MacroFunctions.DOUBLE(5);  // Evaluates to 10 * 5 + 5 = 55

// Good
@DefineMacro(
  'DOUBLE',
  '(x) + (x)',
  parameters: ['x'],
)
// Evaluates to 10 * (5 + 5) = 100

πŸ“š API Reference

Core Classes

Macros

The main class for accessing macro values:

// Get a macro value
var debug = Macros.get<bool>('DEBUG');

// Access predefined macros
var currentFile = Macros.file;
var currentLine = Macros.line;

MacroFunctions

For invoking function-like macros:

// Using a function-like macro
var squared = MacroFunctions.SQUARE(5);

// Using predefined function-like macros
MacroFunctions.DEBUG_PRINT("Error occurred");

FlutterMacros

For Flutter-specific macro operations:

// Initialize for Flutter
FlutterMacros.initialize();

// Register macros for Flutter
FlutterMacros.registerFromAnnotations([...]);

// Configure platform-specific settings
FlutterMacros.configurePlatform(...);

Annotations

// Mark a file for macro processing
@MacroFile()

// Define a simple constant macro
@Define('VERSION', '1.0.0')

// Define a function-like macro
@DefineMacro(
  'MAX',
  'a > b ? a : b',
  parameters: ['a', 'b'],
)

// Platform-specific code
@Platform('android')

// Debug-only code
@Debug()

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published