update: updating files with detailed comments

This commit is contained in:
Patrick Stewart 2024-09-08 23:09:22 -07:00
parent 5fd57e1ebd
commit ea26e024ce
12 changed files with 448 additions and 40 deletions

View file

@ -7,10 +7,15 @@
* file that was distributed with this source code.
*/
/// The main library for the Protevus runtime package.
///
/// This library provides core functionality for the Protevus Platform,
/// including compilation, analysis, and runtime management.
library runtime;
import 'dart:io';
// Export statements for various components of the runtime package
export 'package:protevus_runtime/src/analyzer.dart';
export 'package:protevus_runtime/src/build.dart';
export 'package:protevus_runtime/src/build_context.dart';
@ -21,19 +26,36 @@ export 'package:protevus_runtime/src/exceptions.dart';
export 'package:protevus_runtime/src/generator.dart';
export 'package:protevus_runtime/src/mirror_coerce.dart';
export 'package:protevus_runtime/src/mirror_context.dart';
import 'package:protevus_runtime/runtime.dart';
/// Compiler for the runtime package itself.
/// A specialized compiler for the runtime package itself.
///
/// Removes dart:mirror from a replica of this package, and adds
/// a generated runtime to the replica's pubspec.
/// This compiler is responsible for creating a mirror-free version of the
/// runtime package. It removes dart:mirror dependencies and adds a generated
/// runtime to the package's pubspec.
class RuntimePackageCompiler extends Compiler {
/// Compiles the runtime package.
///
/// This method is currently a no-op, returning an empty map. It may be
/// extended in the future to perform actual compilation tasks.
///
/// [context] - The mirror context for compilation (unused in this implementation).
///
/// Returns an empty map, as no compilation is currently performed.
@override
Map<String, Object> compile(MirrorContext context) => {};
/// Modifies the package structure to remove mirror dependencies.
///
/// This method performs the following tasks:
/// 1. Rewrites the main library file to remove mirror-related exports.
/// 2. Updates the context file to use the generated runtime instead of mirror context.
/// 3. Modifies the pubspec.yaml to include the generated runtime as a dependency.
///
/// [destinationDirectory] - The directory where the modified package will be created.
@override
void deflectPackage(Directory destinationDirectory) {
// Rewrite the main library file
final libraryFile = File.fromUri(
destinationDirectory.uri.resolve("lib/").resolve("runtime.dart"),
);
@ -41,6 +63,7 @@ class RuntimePackageCompiler extends Compiler {
"library runtime;\nexport 'src/context.dart';\nexport 'src/exceptions.dart';",
);
// Update the context file
final contextFile = File.fromUri(
destinationDirectory.uri
.resolve("lib/")
@ -53,6 +76,7 @@ class RuntimePackageCompiler extends Compiler {
);
contextFile.writeAsStringSync(contextFileContents);
// Modify the pubspec.yaml
final pubspecFile =
File.fromUri(destinationDirectory.uri.resolve("pubspec.yaml"));
final pubspecContents = pubspecFile.readAsStringSync().replaceFirst(

View file

@ -9,12 +9,30 @@
import 'package:protevus_runtime/runtime.dart';
/// Prefix string for List types in type checking.
const String _listPrefix = "List<";
/// Prefix string for Map types in type checking.
const String _mapPrefix = "Map<String,";
/// Casts a dynamic input to a specified type T.
///
/// This function attempts to cast the input to the specified type T.
/// It handles nullable types, Lists, and Maps with various element types.
///
/// Parameters:
/// - input: The dynamic value to be cast.
///
/// Returns:
/// The input cast to type T.
///
/// Throws:
/// - [TypeCoercionException] if the casting fails.
T cast<T>(dynamic input) {
try {
var typeString = T.toString();
// Handle nullable types
if (typeString.endsWith('?')) {
if (input == null) {
return null as T;
@ -22,11 +40,14 @@ T cast<T>(dynamic input) {
typeString = typeString.substring(0, typeString.length - 1);
}
}
// Handle List types
if (typeString.startsWith(_listPrefix)) {
if (input is! List) {
throw TypeError();
}
// Cast List to various element types
if (typeString.startsWith("List<int>")) {
return List<int>.from(input) as T;
} else if (typeString.startsWith("List<num>")) {
@ -50,12 +71,16 @@ T cast<T>(dynamic input) {
} else if (typeString.startsWith("List<Map<String, dynamic>>")) {
return List<Map<String, dynamic>>.from(input) as T;
}
} else if (typeString.startsWith(_mapPrefix)) {
}
// Handle Map types
else if (typeString.startsWith(_mapPrefix)) {
if (input is! Map) {
throw TypeError();
}
final inputMap = input as Map<String, dynamic>;
// Cast Map to various value types
if (typeString.startsWith("Map<String, int>")) {
return Map<String, int>.from(inputMap) as T;
} else if (typeString.startsWith("Map<String, num>")) {
@ -79,8 +104,10 @@ T cast<T>(dynamic input) {
}
}
// If no specific casting is needed, return the input as T
return input as T;
} on TypeError {
// If a TypeError occurs during casting, throw a TypeCoercionException
throw TypeCoercionException(T, input.runtimeType);
}
}

View file

@ -14,7 +14,11 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:path/path.dart';
/// A class for analyzing Dart code.
class CodeAnalyzer {
/// Constructs a CodeAnalyzer with the given URI.
///
/// Throws an [ArgumentError] if the URI is not absolute or if no analysis context is found.
CodeAnalyzer(this.uri) {
if (!uri.isAbsolute) {
throw ArgumentError("'uri' must be absolute for CodeAnalyzer");
@ -27,16 +31,23 @@ class CodeAnalyzer {
}
}
/// Gets the path from the URI.
String get path {
return getPath(uri);
}
/// The URI of the code to analyze.
late final Uri uri;
/// The collection of analysis contexts.
late AnalysisContextCollection contexts;
/// A cache of resolved ASTs.
final _resolvedAsts = <String, AnalysisResult>{};
/// Resolves the unit or library at the given URI.
///
/// Returns a [Future] that completes with an [AnalysisResult].
Future<AnalysisResult> resolveUnitOrLibraryAt(Uri uri) async {
if (FileSystemEntity.isFileSync(
uri.toFilePath(windows: Platform.isWindows),
@ -47,6 +58,10 @@ class CodeAnalyzer {
}
}
/// Resolves the library at the given URI.
///
/// Returns a [Future] that completes with a [ResolvedLibraryResult].
/// Throws an [ArgumentError] if the URI could not be resolved.
Future<ResolvedLibraryResult> resolveLibraryAt(Uri uri) async {
assert(
FileSystemEntity.isDirectorySync(
@ -68,6 +83,10 @@ class CodeAnalyzer {
"${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})");
}
/// Resolves the unit at the given URI.
///
/// Returns a [Future] that completes with a [ResolvedUnitResult].
/// Throws an [ArgumentError] if the URI could not be resolved.
Future<ResolvedUnitResult> resolveUnitAt(Uri uri) async {
assert(
FileSystemEntity.isFileSync(
@ -89,6 +108,9 @@ class CodeAnalyzer {
"${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})");
}
/// Gets the class declaration from the file at the given URI.
///
/// Returns null if the class is not found or if there's an error.
ClassDeclaration? getClassFromFile(String className, Uri fileUri) {
try {
return _getFileAstRoot(fileUri)
@ -103,6 +125,9 @@ class CodeAnalyzer {
}
}
/// Gets all subclasses of the given superclass from the file at the given URI.
///
/// Returns a list of [ClassDeclaration]s.
List<ClassDeclaration> getSubclassesFromFile(
String superclassName,
Uri fileUri,
@ -115,6 +140,9 @@ class CodeAnalyzer {
.toList();
}
/// Gets the AST root of the file at the given URI.
///
/// Returns a [CompilationUnit].
CompilationUnit _getFileAstRoot(Uri fileUri) {
assert(
FileSystemEntity.isFileSync(
@ -135,6 +163,9 @@ class CodeAnalyzer {
return unit.unit;
}
/// Converts the input URI to a normalized path string.
///
/// This is a static utility method.
static String getPath(dynamic inputUri) {
return PhysicalResourceProvider.INSTANCE.pathContext.normalize(
PhysicalResourceProvider.INSTANCE.pathContext.fromUri(inputUri),

View file

@ -11,16 +11,34 @@
import 'dart:convert';
import 'dart:io';
import 'dart:mirrors';
import 'package:protevus_runtime/runtime.dart';
import 'package:io/io.dart';
import 'package:package_config/package_config.dart';
/// A class responsible for building and compiling the application.
///
/// This class handles the entire build process, including resolving ASTs,
/// generating runtime, compiling packages, and creating the final executable.
class Build {
/// Creates a new [Build] instance with the given [BuildContext].
///
/// [context] is the build context containing necessary information for the build process.
Build(this.context);
/// The build context for this build operation.
final BuildContext context;
/// Executes the build process.
///
/// This method performs the following steps:
/// 1. Resolves ASTs
/// 2. Generates runtime
/// 3. Compiles packages
/// 4. Prepares the build directory
/// 5. Fetches dependencies
/// 6. Compiles the final executable (if not for tests)
///
/// Throws a [StateError] if any step fails.
Future execute() async {
final compilers = context.context.compilers;
@ -129,6 +147,10 @@ class Build {
}
}
/// Fetches dependencies for the project.
///
/// This method runs 'dart pub get' with the '--offline' and '--no-precompile' flags.
/// It throws a [StateError] if the command fails.
Future getDependencies() async {
const String cmd = "dart";
@ -147,6 +169,13 @@ class Build {
}
}
/// Compiles the source file to an executable.
///
/// [srcUri] is the URI of the source file to compile.
/// [dstUri] is the URI where the compiled executable will be saved.
///
/// This method runs 'dart compile exe' with verbose output.
/// It throws a [StateError] if the compilation fails.
Future compile(Uri srcUri, Uri dstUri) async {
final res = await Process.run(
"dart",
@ -173,6 +202,13 @@ class Build {
print("${res.stdout}");
}
/// Copies a package from source to destination.
///
/// [srcUri] is the URI of the source package.
/// [dstUri] is the URI where the package will be copied.
///
/// This method creates the destination directory if it doesn't exist,
/// copies the package contents, and handles Windows-specific file system issues.
Future copyPackage(Uri srcUri, Uri dstUri) async {
final dstDir = Directory.fromUri(dstUri);
if (!dstDir.existsSync()) {
@ -206,6 +242,12 @@ class Build {
);
}
/// Retrieves package information for a given compiler.
///
/// [compiler] is the compiler instance to get package information for.
///
/// This method uses reflection to find the source URI of the compiler
/// and then retrieves the corresponding package information.
Future<Package> _getPackageInfoForCompiler(Compiler compiler) async {
final compilerUri = reflect(compiler).type.location!.sourceUri;

View file

@ -9,7 +9,6 @@
import 'dart:io';
import 'dart:mirrors';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:protevus_runtime/runtime.dart';
import 'package:package_config/package_config.dart';
@ -17,8 +16,18 @@ import 'package:path/path.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:yaml/yaml.dart';
/// Configuration and context values used during [Build.execute].
/// BuildContext provides configuration and context values used during the build process.
/// It encapsulates information about the application being compiled, build artifacts,
/// and provides utility methods for file and package management.
class BuildContext {
/// Constructs a new BuildContext instance.
///
/// [rootLibraryFileUri]: The URI of the root library file of the application being compiled.
/// [buildDirectoryUri]: The URI of the directory where build artifacts will be stored.
/// [executableUri]: The URI of the executable build product file.
/// [source]: The source script for the executable.
/// [environment]: Optional map of environment variables.
/// [forTests]: Boolean flag indicating whether this context is for tests (includes dev dependencies).
BuildContext(
this.rootLibraryFileUri,
this.buildDirectoryUri,
@ -30,6 +39,12 @@ class BuildContext {
analyzer = CodeAnalyzer(sourceApplicationDirectory.uri);
}
/// Creates a BuildContext instance from a map of values.
///
/// This factory constructor allows for easy serialization and deserialization of BuildContext objects.
///
/// [map]: A map containing the necessary values to construct a BuildContext.
/// Returns: A new BuildContext instance.
factory BuildContext.fromMap(Map<String, dynamic> map) {
return BuildContext(
Uri.parse(map['rootLibraryFileUri']),
@ -41,6 +56,10 @@ class BuildContext {
);
}
/// Returns a map representation of the BuildContext with safe-to-serialize values.
///
/// This getter is useful for serialization purposes, ensuring that all values are
/// in a format that can be easily serialized (e.g., converting URIs to strings).
Map<String, dynamic> get safeMap => {
'rootLibraryFileUri': sourceLibraryFile.uri.toString(),
'buildDirectoryUri': buildDirectoryUri.toString(),
@ -50,41 +69,56 @@ class BuildContext {
'forTests': forTests
};
/// The CodeAnalyzer instance used for analyzing Dart code.
late final CodeAnalyzer analyzer;
/// A [Uri] to the library file of the application to be compiled.
/// The URI of the root library file of the application being compiled.
final Uri rootLibraryFileUri;
/// A [Uri] to the executable build product file.
/// The URI of the executable build product file.
final Uri executableUri;
/// A [Uri] to directory where build artifacts are stored during the build process.
/// The URI of the directory where build artifacts are stored during the build process.
final Uri buildDirectoryUri;
/// The source script for the executable.
final String source;
/// Whether dev dependencies of the application package are included in the dependencies of the compiled executable.
/// Indicates whether dev dependencies of the application package should be included
/// in the dependencies of the compiled executable.
final bool forTests;
/// Cached PackageConfig instance.
PackageConfig? _packageConfig;
/// Optional map of environment variables.
final Map<String, String>? environment;
/// The [RuntimeContext] available during the build process.
/// The current RuntimeContext, cast as a MirrorContext.
///
/// This getter provides access to the runtime context available during the build process.
MirrorContext get context => RuntimeContext.current as MirrorContext;
/// The URI of the target script file.
///
/// If [forTests] is true, this will be a test file, otherwise it will be the main application file.
Uri get targetScriptFileUri => forTests
? getDirectory(buildDirectoryUri.resolve("test/"))
.uri
.resolve("main_test.dart")
: buildDirectoryUri.resolve("main.dart");
/// The Pubspec of the source application.
///
/// This getter parses and returns the pubspec.yaml file of the application being compiled.
Pubspec get sourceApplicationPubspec => Pubspec.parse(
File.fromUri(sourceApplicationDirectory.uri.resolve("pubspec.yaml"))
.readAsStringSync(),
);
/// The pubspec of the source application as a map.
///
/// This getter loads and returns the pubspec.yaml file as a YAML map.
Map<dynamic, dynamic> get sourceApplicationPubspecMap => loadYaml(
File.fromUri(
sourceApplicationDirectory.uri.resolve("pubspec.yaml"),
@ -101,26 +135,32 @@ class BuildContext {
/// The directory where build artifacts are stored.
Directory get buildDirectory => getDirectory(buildDirectoryUri);
/// The generated runtime directory
/// The generated runtime directory.
Directory get buildRuntimeDirectory =>
getDirectory(buildDirectoryUri.resolve("generated_runtime/"));
/// Directory for compiled packages
/// Directory for compiled packages.
Directory get buildPackagesDirectory =>
getDirectory(buildDirectoryUri.resolve("packages/"));
/// Directory for compiled application
/// Directory for the compiled application.
Directory get buildApplicationDirectory => getDirectory(
buildPackagesDirectory.uri.resolve("${sourceApplicationPubspec.name}/"),
);
/// Gets dependency package location relative to [sourceApplicationDirectory].
/// Gets the dependency package configuration relative to [sourceApplicationDirectory].
///
/// This getter lazily loads and caches the package configuration.
/// Returns: A Future that resolves to a PackageConfig instance.
Future<PackageConfig> get packageConfig async {
return _packageConfig ??=
(await findPackageConfig(sourceApplicationDirectory))!;
}
/// Returns a [Directory] at [uri], creates it recursively if it doesn't exist.
/// Returns a [Directory] at the specified [uri], creating it recursively if it doesn't exist.
///
/// [uri]: The URI of the directory.
/// Returns: A Directory instance.
Directory getDirectory(Uri uri) {
final dir = Directory.fromUri(uri);
if (!dir.existsSync()) {
@ -129,7 +169,10 @@ class BuildContext {
return dir;
}
/// Returns a [File] at [uri], creates all parent directories recursively if necessary.
/// Returns a [File] at the specified [uri], creating all parent directories recursively if necessary.
///
/// [uri]: The URI of the file.
/// Returns: A File instance.
File getFile(Uri uri) {
final file = File.fromUri(uri);
if (!file.parent.existsSync()) {
@ -139,6 +182,11 @@ class BuildContext {
return file;
}
/// Retrieves a Package instance from a given URI.
///
/// [uri]: The URI to resolve to a package.
/// Returns: A Future that resolves to a Package instance, or null if not found.
/// Throws: ArgumentError if the URI is not absolute or a package URI.
Future<Package?> getPackageFromUri(Uri? uri) async {
if (uri == null) {
return null;
@ -152,6 +200,13 @@ class BuildContext {
return null;
}
/// Retrieves import directives from a URI or source code.
///
/// [uri]: The URI of the file to analyze.
/// [source]: The source code to analyze.
/// [alsoImportOriginalFile]: Whether to include an import for the original file.
/// Returns: A Future that resolves to a list of import directive strings.
/// Throws: ArgumentError for invalid combinations of parameters.
Future<List<String>> getImportDirectives({
Uri? uri,
String? source,
@ -201,6 +256,10 @@ class BuildContext {
return imports;
}
/// Retrieves a ClassDeclaration from a given Type.
///
/// [type]: The Type to analyze.
/// Returns: A Future that resolves to a ClassDeclaration, or null if not found.
Future<ClassDeclaration?> getClassDeclarationFromType(Type type) async {
final classMirror = reflectType(type);
Uri uri = classMirror.location!.sourceUri;
@ -214,6 +273,11 @@ class BuildContext {
);
}
/// Retrieves a FieldDeclaration from a ClassMirror and property name.
///
/// [type]: The ClassMirror to analyze.
/// [propertyName]: The name of the property to find.
/// Returns: A Future that resolves to a FieldDeclaration, or null if not found.
Future<FieldDeclaration?> _getField(ClassMirror type, String propertyName) {
return getClassDeclarationFromType(type.reflectedType).then((cd) {
try {
@ -229,6 +293,13 @@ class BuildContext {
});
}
/// Retrieves annotations from a field of a given Type.
///
/// This method searches for the field in the given type and its superclasses.
///
/// [type1]: The Type to analyze.
/// [propertyName]: The name of the property to find.
/// Returns: A Future that resolves to a list of Annotations.
Future<List<Annotation>> getAnnotationsFromField(
Type type1,
String propertyName,

View file

@ -8,20 +8,31 @@
*/
import 'dart:io';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:protevus_isolate/isolate.dart';
import 'package:protevus_runtime/runtime.dart';
import 'package:io/io.dart';
/// A class that represents an executable build process.
///
/// This class extends the [Executable] class and is responsible for
/// executing a build process based on the provided build context.
class BuildExecutable extends Executable {
/// Constructs a new [BuildExecutable] instance.
///
/// [message] is a map containing the build context information.
BuildExecutable(Map<String, dynamic> message) : super(message) {
context = BuildContext.fromMap(message);
}
/// The build context for this executable.
late final BuildContext context;
/// Executes the build process.
///
/// This method creates a new [Build] instance with the current context
/// and calls its execute method.
@override
Future execute() async {
final build = Build(context);
@ -29,21 +40,33 @@ class BuildExecutable extends Executable {
}
}
/// A class that manages the build process for non-mirrored builds.
class BuildManager {
/// Creates a new build manager to compile a non-mirrored build.
/// Creates a new [BuildManager] instance.
///
/// [context] is the [BuildContext] for this build manager.
BuildManager(this.context);
/// The build context for this manager.
final BuildContext context;
/// Gets the URI of the source directory.
Uri get sourceDirectoryUri => context.sourceApplicationDirectory.uri;
/// Performs the build process.
///
/// This method handles the following steps:
/// 1. Creates the build directory if it doesn't exist.
/// 2. Creates a temporary copy of the script file with the main function stripped.
/// 3. Analyzes the script file and removes all main functions.
/// 4. Copies the 'not_tests' directory if it exists.
/// 5. Runs the build executable in an isolate.
Future build() async {
if (!context.buildDirectory.existsSync()) {
context.buildDirectory.createSync();
}
// Here is where we need to provide a temporary copy of the script file with the main function stripped;
// this is because when the RuntimeGenerator loads, it needs Mirror access to any declarations in this file
// Create a temporary copy of the script file with the main function stripped
var scriptSource = context.source;
final strippedScriptFile = File.fromUri(context.targetScriptFileUri)
..writeAsStringSync(scriptSource);
@ -52,6 +75,7 @@ class BuildManager {
final parsedUnit = analyzerContext.currentSession
.getParsedUnit(analyzer.path) as ParsedUnitResult;
// Find and remove all main functions
final mainFunctions = parsedUnit.unit.declarations
.whereType<FunctionDeclaration>()
.where((f) => f.name.value() == "main")
@ -63,12 +87,14 @@ class BuildManager {
strippedScriptFile.writeAsStringSync(scriptSource);
// Copy the 'not_tests' directory if it exists
try {
await copyPath(
context.sourceApplicationDirectory.uri.resolve('test/not_tests').path,
context.buildDirectoryUri.resolve('not_tests').path);
} catch (_) {}
// Run the build executable in an isolate
await IsolateExecutor.run(
BuildExecutable(context.safeMap),
packageConfigURI:
@ -81,6 +107,9 @@ class BuildManager {
);
}
/// Cleans up the build directory.
///
/// This method deletes the build directory and its contents if it exists.
Future clean() async {
if (context.buildDirectory.existsSync()) {
context.buildDirectory.deleteSync(recursive: true);

View file

@ -8,32 +8,70 @@
*/
import 'dart:io';
import 'package:protevus_runtime/runtime.dart';
/// An abstract class that defines the interface for compilers in the Protevus Platform.
///
/// This class provides methods for modifying packages, compiling runtime objects,
/// and handling post-generation tasks. Implementations of this class are used to
/// remove dart:mirrors usage from packages and prepare them for runtime execution.
abstract class Compiler {
/// Modifies a package on the filesystem in order to remove dart:mirrors from the package.
/// Modifies a package on the filesystem to remove dart:mirrors usage.
///
/// A copy of this compiler's package will be written to [destinationDirectory].
/// This method is overridden to modify the contents of that directory
/// to remove all uses of dart:mirrors.
/// This method creates a copy of the compiler's package in the [destinationDirectory]
/// and modifies its contents to remove all uses of dart:mirrors. This is crucial for
/// preparing packages for environments where reflection is not available or desired.
///
/// Packages should export their [Compiler] in their main library file and only
/// import mirrors in files directly or transitively imported by the Compiler file.
/// This method should remove that export statement and therefore remove all transitive mirror imports.
///
/// [destinationDirectory] The directory where the modified package will be written.
void deflectPackage(Directory destinationDirectory);
/// Returns a map of runtime objects that can be used at runtime while running in mirrored mode.
/// Compiles and returns a map of runtime objects for use in mirrored mode.
///
/// This method is responsible for creating runtime representations of objects
/// that can be used when the application is running in mirrored mode.
///
/// [context] The [MirrorContext] providing reflection capabilities for compilation.
///
/// Returns a [Map] where keys are String identifiers and values are compiled Objects.
Map<String, Object> compile(MirrorContext context);
/// A hook method called after package generation is complete.
///
/// This method can be overridden to perform any necessary cleanup or additional
/// tasks after the package has been generated.
///
/// [context] The [BuildContext] containing information about the build process.
void didFinishPackageGeneration(BuildContext context) {}
/// Returns a list of URIs that need to be resolved during the build process.
///
/// This method can be overridden to specify additional URIs that should be
/// resolved as part of the compilation process.
///
/// [context] The [BuildContext] containing information about the build process.
///
/// Returns a [List] of [Uri] objects to be resolved.
List<Uri> getUrisToResolve(BuildContext context) => [];
}
/// Runtimes that generate source code implement this method.
/// An abstract class for compilers that generate source code.
///
/// This class extends the functionality of [Compiler] to include
/// the ability to generate source code that represents the runtime behavior.
abstract class SourceCompiler {
/// The source code, including directives, that declare a class that is equivalent in behavior to this runtime.
/// Generates source code that declares a class equivalent in behavior to this runtime.
///
/// This method should be implemented to produce a string of Dart source code
/// that includes all necessary directives and class declarations to replicate
/// the behavior of the runtime in a static context.
///
/// [ctx] The [BuildContext] containing information about the build process.
///
/// Returns a [Future] that resolves to a [String] containing the generated source code.
Future<String> compile(BuildContext ctx) async {
throw UnimplementedError();
}

View file

@ -10,20 +10,26 @@
import 'package:protevus_runtime/runtime.dart';
/// Contextual values used during runtime.
///
/// This abstract class defines the structure for runtime contexts in the Protevus Platform.
/// It provides access to runtime objects and coercion functionality.
abstract class RuntimeContext {
/// The current [RuntimeContext] available to the executing application.
///
/// Is either a `MirrorContext` or a `GeneratedContext`,
/// This static property holds either a `MirrorContext` or a `GeneratedContext`,
/// depending on the execution type.
static final RuntimeContext current = instance;
/// The runtimes available to the executing application.
///
/// This property stores a collection of runtime objects that can be accessed
/// during the application's execution.
late RuntimeCollection runtimes;
/// Gets a runtime object for [type].
/// Gets a runtime object for the specified [type].
///
/// Callers typically invoke this method, passing their [runtimeType]
/// in order to retrieve their runtime object.
/// Callers typically invoke this operator, passing their [runtimeType]
/// to retrieve their runtime object.
///
/// It is important to note that a runtime object must exist for every
/// class that extends a class that has a runtime. Use `MirrorContext.getSubclassesOf` when compiling.
@ -31,18 +37,44 @@ abstract class RuntimeContext {
/// In other words, if the type `Base` has a runtime and the type `Subclass` extends `Base`,
/// `Subclass` must also have a runtime. The runtime objects for both `Subclass` and `Base`
/// must be the same type.
///
/// @param type The Type to retrieve the runtime object for.
/// @return The runtime object associated with the specified type.
dynamic operator [](Type type) => runtimes[type];
/// Coerces the given [input] to the specified type [T].
///
/// This method is used to convert or cast the input to the desired type.
///
/// @param input The input value to be coerced.
/// @return The coerced value of type [T].
T coerce<T>(dynamic input);
}
/// A collection of runtime objects indexed by type names.
///
/// This class provides a way to store and retrieve runtime objects
/// associated with specific types.
class RuntimeCollection {
/// Creates a new [RuntimeCollection] with the given [map].
///
/// @param map A map where keys are type names and values are runtime objects.
RuntimeCollection(this.map);
/// The underlying map storing runtime objects.
final Map<String, Object> map;
/// Returns an iterable of all runtime objects in the collection.
Iterable<Object> get iterable => map.values;
/// Retrieves the runtime object for the specified type [t].
///
/// This operator first attempts to find an exact match for the type name.
/// If not found, it tries to match a generic type by removing type parameters.
///
/// @param t The Type to retrieve the runtime object for.
/// @return The runtime object associated with the specified type.
/// @throws ArgumentError if no runtime object is found for the given type.
Object operator [](Type t) {
//todo: optimize by keeping a cache where keys are of type [Type] to avoid the
// expensive indexOf and substring calls in this method
@ -67,10 +99,10 @@ class RuntimeCollection {
}
}
/// Prevents a type from being compiled when it otherwise would be.
/// An annotation to prevent a type from being compiled when it otherwise would be.
///
/// Annotate a type with the const instance of this type to prevent its
/// compilation.
/// Use this annotation on a type to exclude it from compilation.
class PreventCompilation {
/// Creates a constant instance of [PreventCompilation].
const PreventCompilation();
}

View file

@ -7,13 +7,38 @@
* file that was distributed with this source code.
*/
/// Exception thrown when type coercion fails.
///
/// This exception is used to indicate that an attempt to convert a value
/// from one type to another has failed. It provides information about
/// the expected type and the actual type of the value.
class TypeCoercionException implements Exception {
/// Creates a new [TypeCoercionException].
///
/// [expectedType] is the type that was expected for the conversion.
/// [actualType] is the actual type of the value that couldn't be converted.
///
/// Example:
/// ```dart
/// throw TypeCoercionException(String, int);
/// ```
TypeCoercionException(this.expectedType, this.actualType);
/// The type that was expected for the conversion.
final Type expectedType;
/// The actual type of the value that couldn't be converted.
final Type actualType;
@override
/// Returns a string representation of the exception.
///
/// The returned string includes both the expected type and the actual type
/// to provide clear information about the nature of the coercion failure.
///
/// Returns:
/// A string describing the type coercion exception.
String toString() {
return "input is not expected type '$expectedType' (input is '$actualType')";
}

View file

@ -10,16 +10,32 @@
import 'dart:async';
import 'dart:io';
/// Token used to replace directives in the loader shell.
const String _directiveToken = "___DIRECTIVES___";
/// Token used to replace assignments in the loader shell.
const String _assignmentToken = "___ASSIGNMENTS___";
/// A class responsible for generating runtime code.
///
/// This class allows adding runtime elements and writing them to a specified directory.
class RuntimeGenerator {
/// List of runtime elements to be generated.
final _elements = <_RuntimeElement>[];
/// Adds a new runtime element to the generator.
///
/// [name] is the name of the runtime element.
/// [source] is the source code of the runtime element.
void addRuntime({required String name, required String source}) {
_elements.add(_RuntimeElement(name, source));
}
/// Writes the generated runtime code to the specified directory.
///
/// [directoryUri] is the URI of the directory where the code will be written.
/// This method creates necessary directories, writes the library file,
/// pubspec file, and individual runtime element files.
Future<void> writeTo(Uri directoryUri) async {
final dir = Directory.fromUri(directoryUri);
final libDir = Directory.fromUri(dir.uri.resolve("lib/"));
@ -48,6 +64,7 @@ class RuntimeGenerator {
});
}
/// Returns the content of the pubspec.yaml file as a string.
String get pubspecSource => """
name: generated_runtime
description: A runtime generated by package:conduit_runtime
@ -57,6 +74,9 @@ environment:
sdk: '>=3.4.0 <4.0.0'
""";
/// Returns the shell of the loader file as a string.
///
/// This shell contains placeholders for directives and assignments.
String get _loaderShell => """
import 'package:conduit_runtime/runtime.dart';
import 'package:conduit_runtime/slow_coerce.dart' as runtime_cast;
@ -80,12 +100,17 @@ class GeneratedContext extends RuntimeContext {
}
""";
/// Returns the complete source of the loader file.
///
/// This method replaces the directive and assignment tokens in the loader shell
/// with the actual directives and assignments.
String get loaderSource {
return _loaderShell
.replaceFirst(_directiveToken, _directives)
.replaceFirst(_assignmentToken, _assignments);
}
/// Generates the import directives for all runtime elements.
String get _directives {
final buf = StringBuffer();
@ -98,6 +123,7 @@ class GeneratedContext extends RuntimeContext {
return buf.toString();
}
/// Generates the assignments for all runtime elements.
String get _assignments {
final buf = StringBuffer();
@ -109,14 +135,24 @@ class GeneratedContext extends RuntimeContext {
}
}
/// A class representing a single runtime element.
class _RuntimeElement {
/// Creates a new runtime element.
///
/// [typeName] is the name of the type for this runtime element.
/// [source] is the source code of this runtime element.
_RuntimeElement(this.typeName, this.source);
/// The name of the type for this runtime element.
final String typeName;
/// The source code of this runtime element.
final String source;
/// Returns the relative URI for this runtime element's file.
Uri get relativeUri => Uri.file("${typeName.toLowerCase()}.dart");
/// Returns the import alias for this runtime element.
String get importAlias {
return "g_${typeName.toLowerCase()}";
}

View file

@ -8,9 +8,23 @@
*/
import 'dart:mirrors';
import 'package:protevus_runtime/runtime.dart';
/// Attempts to cast an object to a specified type at runtime.
///
/// This function uses Dart's mirror system to perform runtime type checking
/// and casting. It handles casting to List and Map types, including their
/// generic type arguments.
///
/// Parameters:
/// - [object]: The object to be cast.
/// - [intoType]: A [TypeMirror] representing the type to cast into.
///
/// Returns:
/// The object cast to the specified type.
///
/// Throws:
/// - [TypeCoercionException] if the casting fails.
Object runtimeCast(Object object, TypeMirror intoType) {
final exceptionToThrow =
TypeCoercionException(intoType.reflectedType, object.runtimeType);
@ -51,6 +65,16 @@ Object runtimeCast(Object object, TypeMirror intoType) {
throw exceptionToThrow;
}
/// Determines if a given type is fully primitive.
///
/// A type is considered fully primitive if it's a basic type (num, String, bool),
/// dynamic, or a collection (List, Map) where all nested types are also primitive.
///
/// Parameters:
/// - [type]: A [TypeMirror] representing the type to check.
///
/// Returns:
/// true if the type is fully primitive, false otherwise.
bool isTypeFullyPrimitive(TypeMirror type) {
if (type == reflectType(dynamic)) {
return true;

View file

@ -8,12 +8,19 @@
*/
import 'dart:mirrors';
import 'package:protevus_runtime/runtime.dart';
/// Global instance of the MirrorContext.
RuntimeContext instance = MirrorContext._();
/// A runtime context implementation using Dart's mirror system.
///
/// This class provides runtime type information and compilation capabilities
/// using reflection.
class MirrorContext extends RuntimeContext {
/// Private constructor to ensure singleton instance.
///
/// Initializes the context by compiling all available runtimes.
MirrorContext._() {
final m = <String, Object>{};
@ -31,6 +38,9 @@ class MirrorContext extends RuntimeContext {
runtimes = RuntimeCollection(m);
}
/// List of all class mirrors in the current mirror system.
///
/// Excludes classes marked with @PreventCompilation.
final List<ClassMirror> types = currentMirrorSystem()
.libraries
.values
@ -40,6 +50,9 @@ class MirrorContext extends RuntimeContext {
.where((cm) => firstMetadataOfType<PreventCompilation>(cm) == null)
.toList();
/// List of all available compilers.
///
/// Returns instances of non-abstract classes that are subclasses of Compiler.
List<Compiler> get compilers {
return types
.where((b) => b.isSubclassOf(reflectClass(Compiler)) && !b.isAbstract)
@ -47,6 +60,10 @@ class MirrorContext extends RuntimeContext {
.toList();
}
/// Retrieves all non-abstract subclasses of a given type.
///
/// [type] The base type to find subclasses of.
/// Returns a list of ClassMirror objects representing the subclasses.
List<ClassMirror> getSubclassesOf(Type type) {
final mirror = reflectClass(type);
return types.where((decl) {
@ -68,6 +85,13 @@ class MirrorContext extends RuntimeContext {
}).toList();
}
/// Coerces an input to a specified type T.
///
/// Attempts to cast the input directly, and if that fails,
/// uses runtime casting.
///
/// [input] The object to be coerced.
/// Returns the coerced object of type T.
@override
T coerce<T>(dynamic input) {
try {
@ -78,6 +102,11 @@ class MirrorContext extends RuntimeContext {
}
}
/// Retrieves the first metadata annotation of a specific type from a declaration.
///
/// [dm] The DeclarationMirror to search for metadata.
/// [dynamicType] Optional TypeMirror to use instead of T.
/// Returns the first metadata of type T, or null if not found.
T? firstMetadataOfType<T>(DeclarationMirror dm, {TypeMirror? dynamicType}) {
final tMirror = dynamicType ?? reflectType(T);
try {