meta_expression

A meta-expression is one of a kind of (code generator-based) macros.

Version: 0.2.1 (experimental)

What is meta-expression?

A meta-expression is one of a kind of macros. The macro is represented as a function that returns the source code of the expression. The macro is executed at build time, the result of the return of the macro (source code) is substituted (expanded) in the place where it is used.

To enable the macro to be executed at build time, the macro consists of two declarations. The first declaration defines the type of the source code expression and used as a macro. The second declaration is an implementation of the macro, that is, it returns the source code. The macro implementation is executed at build time.

How to define meta-expression?

To define a macro, you need to declare two functions.

import 'package:meta_expression_annotation/meta_expression_annotation.dart';

@MetaExpression(addImpl)
external int Function() add(int x, int y);

String addImpl(MetaContext context) => '''
() => x + y
''';

The add function is a stub (macro) function. It is intended to be used to invoke this function in program code.

Example:

@pragma('meta_expression:build')
library template;

@pragma('meta_expression:import')
import 'macros.dart';

void main() {
  final x = add(add(1, 2)() * 3, 4)();
  print(x);
}

As a result of code generation, this code will be transformed into the following code.

library template;

void main() {
  final x = (() => ((() => 1 + 2)() * 3) + 4)();
  print(x);
}

How to build library that uses meta expressions?

Until augmentation appears in Dart, only code generation in a separate file is possible.
To build a separate library, you need to add special annotations for the builder.

@pragma('meta_expression:build')
library my_library;

@pragma('meta_expression:import')
import 'my/cool/macros.dart';

You also need to add the following packages as dev_dependencies to pubspec.yaml:

  • build_runner
  • meta_expression

To start the build process, you need to run the command:

dart run build_runner build

That's all.

Is the transformed code hygienic?

Yes. The generated source code is hygienic.
According to the following terminology the code can be considered as guaranteed not to cause the accidental capture of identifiers.

https://en.wikipedia.org/wiki/Hygienic_macro

Name conflicts are avoided by analyzing where identifiers are declared and used.
Everything is transformed, including type arguments.

Example:

void _testTypeArgumentExpansion() {
  test('Test type argument expansion', () {
    {
      const source = '''
() {
  var l1 = <O>[];
  var l2 = <T>[];
  foo<X>() {
    var l3 = <X>[];
    var l4 = <O>[];
    var l5 = <T>[];
  }
}''';
      const source2 = r'''
() {
  var l1 = <X>[];
  var l2 = <Foo<baz>>[];
  foo<X$>() {
    var l3 = <X$>[];
    var l4 = <X>[];
    var l5 = <Foo<baz>>[];
  }
}''';
      final arguments = {
        'O': 'X',
        'T': 'Foo<baz>',
      };
      final transformer = MetaExpressionTransformer(
        arguments: arguments,
        source: source,
      );
      final result = transformer.transform();
      expect(result.compact, source2.compact);
    }
  });
}

About possible errors in work

If you have any problems using this software, please post issues on GitHub.

https://github.com/mezoni/meta_expression