mateusfccp / pinto

The pint° programming language
https://mateusfccp.me/pinto
MIT License
5 stars 1 forks source link

Migrate our tree generation tooling to use Dart macros #14

Open mateusfccp opened 1 day ago

mateusfccp commented 1 day ago

Currently, we generate code for the AST tree and the element tree by using the TreeGenerator class.

It helps us to not have to change the visitors and other methods manually for every change we do on these trees.

However, this script is complex and has its limitations.

With the upcoming macros feature in Dart, it would be better to migrate this system to generate the code this way.

The results will be better development ergonomics (as we can avoid much of the declarative API we currently have, as specifying which node implements which) and speed (as we don't have to run the script every time we make a change).

The proposed API would be something like this:

@Tree
sealed class Element {
  const Element();

  Element? get enclosingElement;
}

abstract interface class TypedElement extends Element {
  Type? get type;
}

abstract interface class TypeDefiningElement extends Element {
  Type get definedType;
}

final class TypeParameterElement extends Element implements TypedElement, TypeDefiningElement {
  TypeParameterElement({required this.name});

  final String name;

  @override
  late TypeDefinitionElement enclosingElement;

  @override
  Type? type = TypeType();

  @override
  late Type definedType;
}

final class StructMemberElement extends Element {
  StructMemberElement();

  late final String name;

  late final ExpressionElement value;

  @override
  late LiteralElement enclosingElement;
}

final class ParameterElement extends Element implements TypedElement {
  ParameterElement({
    required this.name,
    this.type,
  });

  final String name;

  @override
  Type? type;

  @override
  late Element enclosingElement;
}

sealed class ExpressionElement extends Element implements TypedElement {
  @override
  late Element enclosingElement;

  bool get constant;
  Object? get constantValue;
}

final class InvocationElement extends ExpressionElement {
  InvocationElement({
    this.type,
    required this.identifier,
    required this.argument,
    required this.constant,
    required this.constantValue,
  });

  @override
  Type? type;

  final IdentifierElement identifier;

  final ExpressionElement argument;

  @override
  final bool constant;

  @override
  final Object? constantValue;

  String get name => identifier.name;
}

final class IdentifierElement extends ExpressionElement {
  IdentifierElement({
    required this.name,
    this.type,
    required this.constant,
    required this.constantValue,
    required this.definition,
  });

  final String name;

  @override
  Type? type;

  @override
  final bool constant;

  @override
  final Object? constantValue;

  final TypedElement definition;
}

sealed class LiteralElement extends ExpressionElement {}

final class SingletonLiteralElement extends LiteralElement {
  SingletonLiteralElement({this.type});

  @override
  Type? type;

  @override
  late final bool constant;

  @override
  late final Object? constantValue;
}

final class StructLiteralElement extends LiteralElement {
  @override
  late final StructType type;

  final List<StructMemberElement> members = [];

  @override
  late final bool constant;

  @override
  late final Object? constantValue;
}

final class TypeVariantElement extends Element {
  TypeVariantElement({required this.name});

  final String name;

  final List<ParameterElement> parameters = [];

  @override
  late TypeDefinitionElement enclosingElement;
}

sealed class DeclarationElement extends Element {
  DeclarationElement();

  @override
  late ProgramElement enclosingElement;
}

final class ImportElement extends DeclarationElement {
  ImportElement({required this.package});

  final Package package;
}

sealed class LetDeclarationElement extends DeclarationElement implements TypedElement {
  LetDeclarationElement({required this.name});

  final String name;

  late final ExpressionElement body;
}

final class LetFunctionDeclaration extends LetDeclarationElement {
  LetFunctionDeclaration({
    required super.name,
    required this.parameter,
  });

  final StructLiteralElement parameter;

  @override
  late FunctionType type;
}

final class LetVariableDeclaration extends LetDeclarationElement {
  LetVariableDeclaration({
    required super.name,
    this.type,
  });

  @override
  Type? type;
}

final class ImportedSymbolSyntheticElement extends DeclarationElement implements TypedElement {
  ImportedSymbolSyntheticElement({
    required this.name,
    required this.syntheticElement,
  });

  final String name;

  final TypedElement syntheticElement;

  @override
  Type get type => syntheticElement.type!;
}

final class TypeDefinitionElement extends DeclarationElement implements TypedElement, TypeDefiningElement {
  TypeDefinitionElement({required this.name});

  final String name;

  final List<TypeParameterElement> parameters = [];

  final List<TypeVariantElement> variants = [];

  @override
  Type? type = TypeType();

  @override
  late Type definedType;
}

final class ProgramElement extends Element {
  final List<ImportElement> imports = [];

  final List<DeclarationElement> declarations = [];

  @override
  late final Null enclosingElement = null;
}

By providing a @Tree annotation, all visitChildren and accept methods would be augmented in the children classes, and the correspondent visitors generated in the augmented library.

mateusfccp commented 1 day ago

Currently, the experimental macro system does not allow us to do it. I tried for some hours with no success, so I am tagging this as blocked.