0 purchases
visitor
visitor #
A library that generates code matching the visitor pattern
based on annotations made on classes and typedefs.
For example, the following definition in a func.dart file
import 'package:visitor/visitor.dart';
part 'func.g.dart';
@Visitor(name: "Func")
class _Func<R> {}
@VisitorBranch(of: _Func)
typedef _Func$Supplier<T, R> = T Function();
@VisitorBranch(of: _Func)
typedef _Func$UnaryOperator<T, R> = T Function(R x);
@VisitorBranch(of: _Func)
typedef _Func$BinaryOperator<T, R> = T Function(R x, R y);
copied to clipboard
generates the following code in a func.g.dart file:
abstract class Func<R> {
const factory Func.supplier() = _OnSupplierFunc;
const factory Func.unaryOperator(R x) = _OnUnaryOperatorFunc;
const factory Func.binaryOperator(R x, R y) = _OnBinaryOperatorFunc;
const Func._();
T choose<T>({
required _Func$Supplier<T, R> onSupplier,
required _Func$UnaryOperator<T, R> onUnaryOperator,
required _Func$BinaryOperator<T, R> onBinaryOperator,
});
}
class _OnSupplierFunc<R> extends Func<R> {
const _OnSupplierFunc() : super._();
@override
T choose<T>({
required _Func$Supplier<T, R> onSupplier,
required _Func$UnaryOperator<T, R> onUnaryOperator,
required _Func$BinaryOperator<T, R> onBinaryOperator,
}) {
return onSupplier();
}
}
class _OnUnaryOperatorFunc<R> extends Func<R> {
final R x;
const _OnUnaryOperatorFunc(this.x) : super._();
@override
T choose<T>({
required _Func$Supplier<T, R> onSupplier,
required _Func$UnaryOperator<T, R> onUnaryOperator,
required _Func$BinaryOperator<T, R> onBinaryOperator,
}) {
return onUnaryOperator(x);
}
}
class _OnBinaryOperatorFunc<R> extends Func<R> {
final R x;
final R y;
const _OnBinaryOperatorFunc(this.x, this.y) : super._();
@override
T choose<T>({
required _Func$Supplier<T, R> onSupplier,
required _Func$UnaryOperator<T, R> onUnaryOperator,
required _Func$BinaryOperator<T, R> onBinaryOperator,
}) {
return onBinaryOperator(x, y);
}
}
copied to clipboard
You can now use the generated code:
int apply(Func<int> func) {
final int result = func.choose<int>(
onSupplier: () => 1,
onUnaryOperator: (x) => 2 * x,
onBinaryOperator: (x, y) => x + y,
);
return result;
}
void main() {
Func<int> f;
f = Func.supplier();
print(apply(f));
// reaches `onSupplier: () => 1`, outputs 1
f = Func.unaryOperator(2);
print(apply(f));
// reaches `onUnaryOperator: (x) => 2 * x`, outputs 4
f = Func.binaryOperator(3, 5);
print(apply(f));
// reaches `onBinaryOperator: (x, y) => x + y`, outputs 8
}
copied to clipboard
Alternatively, you can think of it as a similar concept of Kotlin's sealed classes.
Features #
Handles type parameters (as seen in the example above).
Handles recursive parameter types.
Handles named and optional parameters.
Getting started #
Add this dependency to your pubspec.yaml as a dev dependency.
Usage #
This library exports two annotations: Visitor and VisitorBranch.
The Visitor annotation is meant to be used in classes. The class name must
be prefixed with one or more underscores (e.g. _Example).
@Visitor()
class _Example {}
copied to clipboard
The VisitorBranch annotation is meant to be used in type definitions (typedefs).
It receives one required named parameter of, which should be its Visitor type.
@VisitorBranch(of: _Example)
copied to clipboard
Each typedef annotated with VisitorBranch will become a branch of its respective visitor, and
must alias a function with zero or more parameters;
must have a parameter type T, which the aliased function must return;
can have any name, but as a convention, it should start with _$ to avoid external referencing.
If more than one visitor is being declared in a single file, to avoid name clashing, its name
should be _ + the camel-cased name of its visitor + $ + the camel-cased name of the branch.
@VisitorBranch(of: _Example)
typedef _$FirstBranch<T> = T Function(int);
copied to clipboard
Below the imports of the current file, add a part statement. E.g. if the name of the file is
file.dart:
part 'file.g.dart';
copied to clipboard
After adding the Visitor, its VisitorBranches and the part statement, run the following command
in the root directory of your project:
dart run build_runner build
# Alternatively, if you're using Flutter
flutter pub run build_runner build
copied to clipboard
The generated code will be added in the file described in the part statement. The following changes
will be applied:
the name of the generated visitor will be the class name without the first underscore (e.g. Example).
the names of the generated factory methods in the visitor will be each branch name as camel-case
(e.g. firstBranch).
the names of the generated subclasses of the visitor will be each branch name as Pascal-case,
prefixed with _On and suffixed with the Pascal-cased visitor name (e.g. _OnFirstBranchExample).
the names of the callbacks of the visitor will be each branch name as Pascal-case, prefixed with
on (e.g. onFirstBranch).
abstract class Example implements _Example {
const factory Example.firstBranch(int p0) = _OnFirstBranchExample;
const Example._();
T choose<T>({
required T Function(int) onFirstBranch,
});
}
class _OnFirstBranchExample extends Example {
final int p0;
const _OnFirstBranchExample(int p0) : super._();
T choose<T>({
required T Function(int) onFirstBranch,
}) {
return onFirstBranch(p0);
}
}
copied to clipboard
On typedefs annotated with VisitorBranch, the linter may produce an unused element
warning. To avoid that, you can add a
// ignore_for_file: unused_element
copied to clipboard
to the top of the file.
Examples #
You can see the usage in the example/lib/visitor_example.dart file.
In the example/lib/examples,
the expression.dart shows usage on recursive parameter types;
the diff_change.dart shows usage on named parameters;
the loading_state.dart shows usage on type parameters.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.