generic_reader 0.5.0 copy "generic_reader: ^0.5.0" to clipboard
generic_reader: ^0.5.0 copied to clipboard

Methods for the systematic reading of compile-time constant expressions.

Generic Reader #

Dart

Introduction #

The premise of source code generation is that we can specify (hopefully few) details and create libraries, classes, variables, and methods during the build process.

Source code generation relies heavily on constants known at compile time, represented by a DartObject. For built-in types, DartObject has methods that allow reading the underlying constant object to obtain a runtime object.

The package generic_reader includes an extention on DartObject that simplifies reading constants of type bool, double,int,num,String,Symbol,Type , List, Set, Map,Iterabel, Enum and provides a systematic way of reading arbitrary constants of known data-type.

Usage #

To use the package generic_reader the following steps are required:

  1. Include generic_reader as a dependencies in your pubspec.yaml file.

  2. Register a Decoder object for each user defined data-type T that is going to be read. Note: The built-in types bool, double, int, num,String, Type, Symbol do not require a decoder.

  3. Use Dart's static analyzer to read a library, get the relevant VariableElement, and calculate the constant expression represented by a DartObject using the method computeConstantValue().

  4. Read the compile-time constant values using the extension methods read<T>, readList<T>, readIterator<T> readSet<T>, readMap<T>.

  5. Use the constant values to generate the source-code and complete the building process.

Decoder #

The extension Reader provides a systematic method of retrieving constants of arbitrary data-types by allowing users to register Decoder objects.

Decoder<T> is an abstract parameterized class with a method T read<T>(DartObject obj) that attempt to read a variable of type T from obj and return the result. The example below demonstrates how to create a custom decode for the sample class Annotation and register an instance of the decoder with the extension Reader.

import 'package:generic_reader/generic_reader.dart';

class Annotation {
  const A({required this.id, required this.names,);
  final int id;
  final Set<String> names;

  @override
  String toString() => 'A(id: $id, names: $names)';
}

class AnnotationDecoder extends Decoder<Annotation> {
  const AnnotationDecoder();

  @override
  Annotation read(DartObject obj) {
    final id = obj.read<int>(fieldName: 'id');
    final names = obj.readSet<String>(fieldName: 'names');
    return A(id: id, names: names);
  }
}

Read.addDecoder(const AnnotationDecoder());

The example below show how to register a decoder for a Dart Enum and read an instance of the enumeration. In this case, instead of creating a custom decoder class we just register an instance of the already defined generic class EnumDecoder:

Click to show source-code.
import 'package:ansi_modifier/ansi_modifier.dart';
import 'package:build_test/build_test.dart' show resolveSource;
import 'package:generic_reader/generic_reader.dart';

/// Demonstrates how to use [Reader] to read an enum.
enum Order { asc, desc }

Future<void> main() async {
  print('\nReading library: example\n');

  // Read the dart library
  final lib = await resolveSource(
    r'''
    library example;

    enum Order { asc, desc }

    class A {
      const A();
      final Order order = Order.asc;
    }
    ''',
    (resolver) => resolver.findLibraryByName('example'),
    readAllSourcesFromFilesystem: false,
  );

  if (lib == null) return;

  /// Add a decoder for the enum:
  Reader.addDecoder(const EnumDecoder<Order>(Order.values));

  /// Compute the compile-time constant value
  final enumObj = lib.classes[0].fields[0].computeConstantValue();

  /// Read the compile-time constant value to obtain a runtime instance of the
  /// enumeration.
  final enum0 = enumObj?.read<Order>();

  print(
    '\nReading an enum of type ${'Order'.style(Ansi.green)}: '
    '$enum0\n',
  );
}

The program listed below show how to read a constant of type List<List<String>>:

Click to show source-code.
import 'package:ansi_modifier/ansi_modifier.dart';
import 'package:build_test/build_test.dart' show resolveSource;
import 'package:generic_reader/generic_reader.dart';

/// Demonstrates how to use [Reader] to read a nested list.
Future<void> main() async {
  print('\nReading library: example\n');

  final lib = await resolveSource(
    r'''
    library example;

    class A {
      const A();
      final nestedList = List<List<String>> [['a'], ['b']];
    }
    ''',
    (resolver) => resolver.findLibraryByName('example'),
    readAllSourcesFromFilesystem: false,
  );

  if (lib == null) return;

  final listOfString = 'List<String>'.style(Ansi.green);
  final listOfListOfString = 'List<List<String>>'.style(Ansi.green);

  print('\nAdding decoder for $listOfString and $listOfListOfString\n');
  Reader.addDecoder(const ListDecoder<String>());
  Reader.addDecoder(const ListDecoder<List<String>>());

  print(Reader.info);

  final listObj = lib.classes[0].fields[0].computeConstantValue();
  final list1 = listObj?.read<List<List<String>>>();
  final list2 = listObj?.read();
  final list3 = listObj?.readList<List<String>>();

  print('\nlistObj.read<$listOfListOfString>: $list1');

  print('\nlistObj.read(): $list2');

  print('\nlistObj.readList<$listOfString>(): $list3\n');
}

The program above produces the following terminal output:

Click to show terminal output.
$ dart example/bin/list_example.dart

Reading library: example

  0s _ResolveSourceBuilder<LibraryElement?> on 5 inputs; $package$
  1s _ResolveSourceBuilder<LibraryElement?> on 5 inputs: 5 no-op
  Built with build_runner in 1s; wrote 0 outputs.

Adding decoder for List<String> and List<List<String>>

Reader:
  Registered types: (bool, double, int, num,
    String, Symbol, Type, List<String>, List<List<String>>)
  Mapped types    : {}

listObj.read<List<List<String>>>: [[a], [b]]

listObj.read(): [[a], [b]]

listObj.readList<List<String>>(): [[a], [b]]

Limitations #

  1. Constants retrievable with Reader must have a built-in Dart type, a type made available by depending on a package, or a type defined in the file being read.

  2. Defining decoder functions for each data-type has its obvious limitiations when it comes to generic types. In practice, however, generic classes are often designed in such a manner that only few type parameters are valid or likely to be useful. Constants that need to be retrieved during the source-generation process are most likely annotations and simple data-types that convey information to source code generators.

Examples #

For further information on how to use Reader to retrieve constants of arbitrary type see example.

Features and bugs #

Please file feature requests and bugs at the issue tracker.

6
likes
160
points
207
downloads

Publisher

verified publishersimphotonics.com

Weekly Downloads

Methods for the systematic reading of compile-time constant expressions.

Repository (GitHub)
View/report issues

Topics

#analyzer #build #constant-expression #source-generation

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

analyzer, exception_templates

More

Packages that depend on generic_reader