From 0453c67177231e3412911914b768b990847479e9 Mon Sep 17 00:00:00 2001 From: Patrick Stewart Date: Thu, 26 Dec 2024 04:02:32 -0700 Subject: [PATCH] refactor: refactoring platform mirrors 55 pass 7 fail --- packages/mirrors/lib/mirrors.dart | 8 +- .../{ => analyzers}/library_analyzer.dart | 0 .../{ => analyzers}/package_analyzer.dart | 274 +++++++------ .../{ => analyzers}/type_analyzer.dart | 0 .../lib/src/discovery/parser/base_parser.dart | 157 ++++++++ .../src/discovery/parser/class_parser.dart | 325 ++++++++++++++++ .../discovery/parser/constructor_parser.dart | 364 +++++++++++++++++ .../src/discovery/parser/method_parser.dart | 365 ++++++++++++++++++ .../lib/src/discovery/parser/parser.dart | 62 +++ .../src/discovery/parser/property_parser.dart | 305 +++++++++++++++ .../lib/src/discovery/parser/type_parser.dart | 175 +++++++++ .../lib/src/metadata/class_metadata.dart | 45 +++ .../metadata/extended_method_metadata.dart | 36 ++ .../lib/src/metadata/method_metadata.dart | 47 +-- .../lib/src/metadata/parameter_metadata.dart | 21 +- .../lib/src/metadata/property_metadata.dart | 21 +- .../src/{discovery => types}/proxy_type.dart | 0 packages/mirrors/pubspec.yaml | 12 +- .../mirrors/test/discovery/parser_test.dart | 219 +++++++++++ 19 files changed, 2258 insertions(+), 178 deletions(-) rename packages/mirrors/lib/src/discovery/{ => analyzers}/library_analyzer.dart (100%) rename packages/mirrors/lib/src/discovery/{ => analyzers}/package_analyzer.dart (79%) rename packages/mirrors/lib/src/discovery/{ => analyzers}/type_analyzer.dart (100%) create mode 100644 packages/mirrors/lib/src/discovery/parser/base_parser.dart create mode 100644 packages/mirrors/lib/src/discovery/parser/class_parser.dart create mode 100644 packages/mirrors/lib/src/discovery/parser/constructor_parser.dart create mode 100644 packages/mirrors/lib/src/discovery/parser/method_parser.dart create mode 100644 packages/mirrors/lib/src/discovery/parser/parser.dart create mode 100644 packages/mirrors/lib/src/discovery/parser/property_parser.dart create mode 100644 packages/mirrors/lib/src/discovery/parser/type_parser.dart create mode 100644 packages/mirrors/lib/src/metadata/class_metadata.dart create mode 100644 packages/mirrors/lib/src/metadata/extended_method_metadata.dart rename packages/mirrors/lib/src/{discovery => types}/proxy_type.dart (100%) create mode 100644 packages/mirrors/test/discovery/parser_test.dart diff --git a/packages/mirrors/lib/mirrors.dart b/packages/mirrors/lib/mirrors.dart index a22632f..5e32745 100644 --- a/packages/mirrors/lib/mirrors.dart +++ b/packages/mirrors/lib/mirrors.dart @@ -13,11 +13,11 @@ export 'src/annotations/reflectable.dart'; export 'src/core/mirror_system.dart'; /// Discovery -export 'src/discovery/type_analyzer.dart'; -export 'src/discovery/library_analyzer.dart'; +export 'src/discovery/analyzers/library_analyzer.dart'; +export 'src/discovery/analyzers/type_analyzer.dart'; +export 'src/discovery/analyzers/package_analyzer.dart'; export 'src/discovery/runtime_library_discoverer.dart'; export 'src/discovery/runtime_type_discoverer.dart'; -export 'src/discovery/package_analyzer.dart'; /// Discovery Models export 'src/discovery/models/models.dart'; @@ -29,7 +29,9 @@ export 'src/exceptions/not_reflectable_exception.dart'; export 'src/exceptions/reflection_exception.dart'; /// Metadata +export 'src/metadata/class_metadata.dart'; export 'src/metadata/constructor_metadata.dart'; +export 'src/metadata/extended_method_metadata.dart'; export 'src/metadata/function_metadata.dart'; export 'src/metadata/method_metadata.dart'; export 'src/metadata/parameter_metadata.dart'; diff --git a/packages/mirrors/lib/src/discovery/library_analyzer.dart b/packages/mirrors/lib/src/discovery/analyzers/library_analyzer.dart similarity index 100% rename from packages/mirrors/lib/src/discovery/library_analyzer.dart rename to packages/mirrors/lib/src/discovery/analyzers/library_analyzer.dart diff --git a/packages/mirrors/lib/src/discovery/package_analyzer.dart b/packages/mirrors/lib/src/discovery/analyzers/package_analyzer.dart similarity index 79% rename from packages/mirrors/lib/src/discovery/package_analyzer.dart rename to packages/mirrors/lib/src/discovery/analyzers/package_analyzer.dart index bc18bfa..7309f46 100644 --- a/packages/mirrors/lib/src/discovery/package_analyzer.dart +++ b/packages/mirrors/lib/src/discovery/analyzers/package_analyzer.dart @@ -1,13 +1,13 @@ import 'dart:io'; import 'package:path/path.dart' as path; -import '../metadata/type_metadata.dart'; -import '../metadata/constructor_metadata.dart'; -import '../metadata/method_metadata.dart'; -import '../metadata/parameter_metadata.dart'; -import '../metadata/property_metadata.dart'; -import '../registry/reflection_registry.dart'; -import '../reflector/runtime_reflector.dart'; -import 'proxy_type.dart'; +import '../../metadata/type_metadata.dart'; +import '../../metadata/constructor_metadata.dart'; +import '../../metadata/method_metadata.dart'; +import '../../metadata/parameter_metadata.dart'; +import '../../metadata/property_metadata.dart'; +import '../../registry/reflection_registry.dart'; +import '../../reflector/runtime_reflector.dart'; +import '../../types/proxy_type.dart'; /// Discovers and analyzes types in a package automatically. class PackageAnalyzer { @@ -172,6 +172,33 @@ class PackageAnalyzer { ) { final type = _getTypeForName(className); + // Register constructors first + for (final constructor in constructors) { + // Get parameter names for named parameters + final parameterNames = constructor.parameters + .where((p) => p.isNamed) + .map((p) => p.name) + .toList(); + + // Get required flags for named parameters + final isRequired = constructor.parameters + .where((p) => p.isNamed) + .map((p) => p.isRequired) + .toList(); + + // Get isNamed flags for all parameters + final isNamed = constructor.parameters.map((p) => p.isNamed).toList(); + + ReflectionRegistry.registerConstructor( + type, + constructor.name, + parameterTypes: constructor.parameterTypes, + parameterNames: parameterNames.isNotEmpty ? parameterNames : null, + isRequired: isRequired.isNotEmpty ? isRequired : null, + isNamed: isNamed.isNotEmpty ? isNamed : null, + ); + } + // Register properties properties.forEach((name, metadata) { ReflectionRegistry.registerProperty( @@ -212,23 +239,7 @@ class PackageAnalyzer { ); }); - // Register constructors - for (final constructor in constructors) { - // Get parameter names for named parameters - final parameterNames = constructor.parameters - .where((p) => p.isNamed) - .map((p) => p.name) - .toList(); - - ReflectionRegistry.registerConstructor( - type, - constructor.name, - parameterTypes: constructor.parameterTypes, - parameterNames: parameterNames.isNotEmpty ? parameterNames : null, - ); - } - - // Create type metadata + // Create and register type metadata last final metadata = TypeMetadata( type: type, name: className, @@ -259,7 +270,6 @@ class PackageAnalyzer { const [], ); - // Register metadata with reflection system ReflectionRegistry.registerTypeMetadata(type, metadata); } @@ -513,142 +523,156 @@ class PackageAnalyzer { print('Found ${matches.length} constructor matches:'); for (final match in matches) { - final fullMatch = classContent.substring(match.start, match.end); - print('Full match: $fullMatch'); + final matchContent = classContent.substring(match.start, match.end); + print('Full match: $matchContent'); print('Group 1 (name): ${match.group(1)}'); print('Group 2 (params): ${match.group(2)}'); - // Get the line containing this match - final lineStart = classContent.lastIndexOf('\n', match.start) + 1; - final lineEnd = classContent.indexOf('\n', match.start); - final line = classContent.substring( - lineStart, lineEnd > 0 ? lineEnd : classContent.length); - - // Skip if this is part of a static method or return statement - if (line.trim().startsWith('static') || - line.trim().startsWith('return')) { - continue; - } + // Get the line before this match + final beforeLineStart = + classContent.lastIndexOf('\n', match.start - 1) + 1; + final beforeLineEnd = match.start - 1; + final beforeLine = beforeLineStart < beforeLineEnd + ? classContent.substring(beforeLineStart, beforeLineEnd).trim() + : ''; final name = match.group(1) ?? ''; final params = match.group(2) ?? ''; - final isFactory = line.trim().startsWith('factory'); - - // For factory constructors without a name, use 'create' - final constructorName = isFactory && name.isEmpty ? 'create' : name; + final isFactory = matchContent.trim().startsWith('factory'); + final isStatic = beforeLine.startsWith('static'); // Parse parameters final parameterTypes = _extractParameterTypes(params); final parameters = _extractParameters(params); - // Add constructor - constructors.add(ConstructorMetadata( - name: constructorName, - parameterTypes: parameterTypes, - parameters: parameters, - )); + // Add constructor with appropriate name + final constructorName = isFactory && name.isEmpty ? 'create' : name; + + if (isStatic) { + // Handle static factory methods + if (matchContent.trim().contains('return $className')) { + // Add both the static factory method and its constructor call + constructors.add(ConstructorMetadata( + name: name, + parameterTypes: parameterTypes, + parameters: parameters, + )); + + // Extract the constructor call parameters + final callMatch = + RegExp('$className\\(([^)]*)\\)').firstMatch(matchContent); + if (callMatch != null) { + final callParams = callMatch.group(1) ?? ''; + final callParamTypes = _extractParameterTypes(callParams); + final callParameters = _extractParameters(callParams); + + constructors.add(ConstructorMetadata( + name: '', + parameterTypes: callParamTypes, + parameters: callParameters, + )); + } + } + } else { + // Regular constructor + constructors.add(ConstructorMetadata( + name: constructorName, + parameterTypes: parameterTypes, + parameters: parameters, + )); + + // For factory constructors, also add a default constructor + if (isFactory && name.isNotEmpty) { + constructors.add(ConstructorMetadata( + name: '', + parameterTypes: parameterTypes, + parameters: parameters, + )); + } + } } print('Returning ${constructors.length} constructors'); return constructors; } - /// Extracts parameter types from a parameter list string. - static List _extractParameterTypes(String params) { + /// Extracts parameter types and metadata from a parameter list string. + static (List, List) _extractParameterInfo( + String params) { final types = []; - final paramRegex = RegExp( - r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?', - multiLine: true, - ); - - // Split parameters by commas, handling both positional and named parameters - final parts = - params.split(',').map((p) => p.trim()).where((p) => p.isNotEmpty); - for (final part in parts) { - if (part.startsWith('{') || part.endsWith('}')) { - // Handle named parameter section - final namedParams = part.replaceAll(RegExp(r'[{}]'), '').trim(); - if (namedParams.isNotEmpty) { - final namedParts = namedParams - .split(',') - .map((p) => p.trim()) - .where((p) => p.isNotEmpty); - for (final namedPart in namedParts) { - final match = paramRegex.firstMatch(namedPart); - if (match != null) { - types.add(_getTypeForName(match.group(1)!)); - } - } - } - } else { - // Handle positional parameter - final match = paramRegex.firstMatch(part); - if (match != null) { - types.add(_getTypeForName(match.group(1)!)); - } - } - } - - return types; - } - - /// Extracts parameters from a parameter list string. - static List _extractParameters(String params) { final parameters = []; final paramRegex = RegExp( r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?(?:\s*=\s*([^,}]+))?', multiLine: true, ); - // Split parameters by commas, handling both positional and named parameters - final parts = - params.split(',').map((p) => p.trim()).where((p) => p.isNotEmpty); - for (final part in parts) { - if (part.startsWith('{') || part.endsWith('}')) { - // Handle named parameter section - final namedParams = part.replaceAll(RegExp(r'[{}]'), '').trim(); - if (namedParams.isNotEmpty) { - final namedParts = namedParams - .split(',') - .map((p) => p.trim()) - .where((p) => p.isNotEmpty); - for (final namedPart in namedParts) { - final match = paramRegex.firstMatch(namedPart); - if (match != null) { - final type = match.group(1)!; - final name = match.group(2)!; - final defaultValue = match.group(3); - final isRequired = namedPart.contains('required'); + // Find named parameter section + final namedStart = params.indexOf('{'); + final namedEnd = params.lastIndexOf('}'); + final hasNamedParams = namedStart != -1 && namedEnd != -1; - parameters.add(ParameterMetadata( - name: name, - type: _getTypeForName(type), - isRequired: isRequired, - isNamed: true, - defaultValue: defaultValue != null - ? _parseDefaultValue(defaultValue) - : null, - )); - } - } - } - } else { - // Handle positional parameter - final match = paramRegex.firstMatch(part); + // Handle positional parameters + final positionalPart = + hasNamedParams ? params.substring(0, namedStart).trim() : params.trim(); + final positionalParams = positionalPart.split(',').map((p) => p.trim()); + for (final param in positionalParams) { + if (param.isEmpty) continue; + final match = paramRegex.firstMatch(param); + if (match != null) { + final type = match.group(1)!; + final name = match.group(2)!; + final defaultValue = match.group(3); + + types.add(_getTypeForName(type)); + parameters.add(ParameterMetadata( + name: name, + type: _getTypeForName(type), + isRequired: true, + isNamed: false, + defaultValue: + defaultValue != null ? _parseDefaultValue(defaultValue) : null, + )); + } + } + + // Handle named parameters if present + if (hasNamedParams) { + final namedPart = params.substring(namedStart + 1, namedEnd).trim(); + final namedParams = namedPart.split(',').map((p) => p.trim()); + for (final param in namedParams) { + if (param.isEmpty) continue; + final match = paramRegex.firstMatch(param); if (match != null) { final type = match.group(1)!; final name = match.group(2)!; + final defaultValue = match.group(3); + final isRequired = param.contains('required $type'); + types.add(_getTypeForName(type)); parameters.add(ParameterMetadata( name: name, type: _getTypeForName(type), - isRequired: true, - isNamed: false, + isRequired: isRequired, + isNamed: true, + defaultValue: + defaultValue != null ? _parseDefaultValue(defaultValue) : null, )); } } } + return (types, parameters); + } + + /// Extracts parameter types from a parameter list string. + static List _extractParameterTypes(String params) { + final (types, _) = _extractParameterInfo(params); + return types; + } + + /// Extracts parameters from a parameter list string. + static List _extractParameters(String params) { + final (_, parameters) = _extractParameterInfo(params); return parameters; } diff --git a/packages/mirrors/lib/src/discovery/type_analyzer.dart b/packages/mirrors/lib/src/discovery/analyzers/type_analyzer.dart similarity index 100% rename from packages/mirrors/lib/src/discovery/type_analyzer.dart rename to packages/mirrors/lib/src/discovery/analyzers/type_analyzer.dart diff --git a/packages/mirrors/lib/src/discovery/parser/base_parser.dart b/packages/mirrors/lib/src/discovery/parser/base_parser.dart new file mode 100644 index 0000000..11f9431 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/base_parser.dart @@ -0,0 +1,157 @@ +import 'parser.dart'; + +/// Base class for all parsers that provides common functionality. +abstract class BaseParser implements Parser { + String _source = ''; + int _position = 0; + int _line = 1; + int _column = 1; + final List _errors = []; + final List _warnings = []; + + @override + int get position => _position; + + @override + set position(int value) { + if (value < 0) value = 0; + if (value > _source.length) value = _source.length; + + if (value < _position) { + // Moving backwards, recalculate line and column + var text = _source.substring(0, value); + _line = '\n'.allMatches(text).length + 1; + var lastNewline = text.lastIndexOf('\n'); + _column = lastNewline == -1 ? value + 1 : value - lastNewline; + } else { + // Moving forwards, update line and column + var text = _source.substring(_position, value); + var newlines = '\n'.allMatches(text).length; + if (newlines > 0) { + _line += newlines; + var lastNewline = text.lastIndexOf('\n'); + _column = text.length - lastNewline; + } else { + _column += text.length; + } + } + + _position = value; + } + + @override + int get line => _line; + + @override + int get column => _column; + + @override + List get errors => List.unmodifiable(_errors); + + @override + List get warnings => List.unmodifiable(_warnings); + + /// Initializes the parser with the given source code. + void init(String source) { + _source = source; + _position = 0; + _line = 1; + _column = 1; + _errors.clear(); + _warnings.clear(); + } + + /// Gets the current character without advancing the position. + String? peek() { + if (_position >= _source.length) return null; + return _source[_position]; + } + + /// Gets the character at the given offset from current position without advancing. + String? peekAhead(int offset) { + final pos = _position + offset; + if (pos >= _source.length) return null; + return _source[pos]; + } + + /// Advances the position by one and returns the character. + String? advance() { + if (_position >= _source.length) return null; + final char = _source[_position]; + position = _position + 1; + return char; + } + + /// Returns true if the current position is at the end of the source. + bool isAtEnd() => _position >= _source.length; + + /// Adds an error message with the current position information. + void addError(String message) { + _errors.add('$message at line $_line, column $_column'); + } + + /// Adds a warning message with the current position information. + void addWarning(String message) { + _warnings.add('$message at line $_line, column $_column'); + } + + /// Skips whitespace characters. + void skipWhitespace() { + while (!isAtEnd()) { + final char = peek(); + if (char == ' ' || char == '\t' || char == '\r' || char == '\n') { + advance(); + } else { + break; + } + } + } + + /// Matches and consumes the given string if it exists at the current position. + /// Returns true if matched, false otherwise. + bool match(String text) { + if (_position + text.length > _source.length) return false; + + if (_source.substring(_position, _position + text.length) == text) { + position = _position + text.length; + return true; + } + + return false; + } + + /// Looks ahead for the given string without consuming it. + /// Returns true if found, false otherwise. + bool lookAhead(String text) { + if (_position + text.length > _source.length) return false; + return _source.substring(_position, _position + text.length) == text; + } + + /// Consumes characters until the given predicate returns false. + String consumeWhile(bool Function(String) predicate) { + final buffer = StringBuffer(); + while (!isAtEnd()) { + final char = peek(); + if (char == null || !predicate(char)) break; + buffer.write(advance()); + } + return buffer.toString(); + } + + /// Consumes characters until the given string is found. + /// Returns the consumed characters not including the delimiter. + /// If includeDelimiter is true, advances past the delimiter. + String consumeUntil(String delimiter, {bool includeDelimiter = false}) { + final buffer = StringBuffer(); + while (!isAtEnd()) { + if (lookAhead(delimiter)) { + if (includeDelimiter) { + position = _position + delimiter.length; + } + break; + } + buffer.write(advance()); + } + return buffer.toString(); + } +} diff --git a/packages/mirrors/lib/src/discovery/parser/class_parser.dart b/packages/mirrors/lib/src/discovery/parser/class_parser.dart new file mode 100644 index 0000000..683f325 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/class_parser.dart @@ -0,0 +1,325 @@ +import '../../metadata/constructor_metadata.dart'; +import '../../metadata/method_metadata.dart'; +import '../../metadata/property_metadata.dart'; +import 'base_parser.dart'; +import 'parser.dart'; +import '../../metadata/class_metadata.dart'; +import 'property_parser.dart'; +import 'method_parser.dart'; +import 'constructor_parser.dart'; +import 'type_parser.dart'; + +/// Parser for Dart class declarations. +class ClassParser extends BaseParser { + // Specialized parsers for class members + late final PropertyParser _propertyParser; + late final MethodParser _methodParser; + late final ConstructorParser _constructorParser; + late final TypeParser _typeParser; + + @override + bool canParse(String source) { + init(source); + skipWhitespace(); + + // Check for class modifiers + if (lookAhead('abstract') || lookAhead('final')) { + return true; + } + + // Check for class keyword + return lookAhead('class'); + } + + @override + ParseResult parse(String source) { + init(source); + + try { + // Parse class declaration + final metadata = _parseClassDeclaration(); + if (metadata == null) { + return ParseResult.failure(['Failed to parse class declaration']); + } + + // Initialize member parsers with class name + _propertyParser = PropertyParser(); + _methodParser = MethodParser(); + _constructorParser = ConstructorParser(metadata.name); + _typeParser = TypeParser(); + + // Parse class body + final body = _parseClassBody(); + if (body == null) { + return ParseResult.failure(['Failed to parse class body']); + } + + // Update metadata with parsed body + return ParseResult.success(ClassMetadata( + name: metadata.name, + typeParameters: metadata.typeParameters, + superclass: metadata.superclass, + interfaces: metadata.interfaces, + isAbstract: metadata.isAbstract, + isFinal: metadata.isFinal, + properties: body['properties'] as Map, + methods: body['methods'] as Map, + constructors: body['constructors'] as List, + )); + } catch (e) { + return ParseResult.failure(['Error parsing class: $e']); + } + } + + /// Parses the class declaration including modifiers, name, type parameters, + /// superclass, and interfaces. + ClassMetadata? _parseClassDeclaration() { + skipWhitespace(); + + // Parse modifiers + bool isAbstract = false; + bool isFinal = false; + + if (match('abstract')) { + isAbstract = true; + skipWhitespace(); + } else if (match('final')) { + isFinal = true; + skipWhitespace(); + } + + // Parse 'class' keyword + if (!match('class')) { + addError("Expected 'class' keyword"); + return null; + } + + skipWhitespace(); + + // Parse class name + final name = _parseIdentifier(); + if (name == null) { + addError('Expected class name'); + return null; + } + + // Parse type parameters if present + final typeParameters = _parseTypeParameters(); + + skipWhitespace(); + + // Parse superclass if present + String? superclass; + if (match('extends')) { + skipWhitespace(); + final type = _typeParser + .parse(consumeUntil('implements', includeDelimiter: false).trim()); + if (type.isSuccess) { + superclass = type.result!.fullName; + } + } + + skipWhitespace(); + + // Parse interfaces if present + final interfaces = []; + if (match('implements')) { + skipWhitespace(); + while (!isAtEnd() && !lookAhead('{')) { + final type = _typeParser + .parse(consumeUntil(',', includeDelimiter: false).trim()); + if (type.isSuccess) { + interfaces.add(type.result!.fullName); + } + + skipWhitespace(); + if (!match(',')) break; + skipWhitespace(); + } + } + + return ClassMetadata( + name: name, + typeParameters: typeParameters, + superclass: superclass, + interfaces: interfaces, + isAbstract: isAbstract, + isFinal: isFinal, + ); + } + + /// Parses the class body including properties, methods, and constructors. + Map? _parseClassBody() { + skipWhitespace(); + + if (!match('{')) { + addError("Expected '{' to begin class body"); + return null; + } + + final properties = {}; + final methods = {}; + final constructors = []; + + // Parse class members until we reach the closing brace + while (!isAtEnd() && !lookAhead('}')) { + skipWhitespace(); + + // Try to parse as constructor first (since constructors start with the class name) + if (_constructorParser.canParse(peekLine())) { + final result = _constructorParser.parse(consumeMember()); + if (result.isSuccess) { + constructors.add(result.result!); + continue; + } + } + + // Try to parse as property + if (_propertyParser.canParse(peekLine())) { + final result = _propertyParser.parse(consumeMember()); + if (result.isSuccess) { + properties[result.result!.name] = result.result!; + continue; + } + } + + // Try to parse as method + if (_methodParser.canParse(peekLine())) { + final result = _methodParser.parse(consumeMember()); + if (result.isSuccess) { + methods[result.result!.name] = result.result!; + continue; + } + } + + // If we get here, we couldn't parse the member + addError('Invalid class member'); + return null; + } + + if (!match('}')) { + addError("Expected '}' to end class body"); + return null; + } + + return { + 'properties': properties, + 'methods': methods, + 'constructors': constructors, + }; + } + + /// Peeks at the current line without consuming it. + String peekLine() { + final start = position; + final line = consumeUntil(';', includeDelimiter: false); + position = start; + return line; + } + + /// Consumes a complete class member (property, method, or constructor). + String consumeMember() { + final buffer = StringBuffer(); + var braceCount = 0; + var inString = false; + var stringChar = ''; + + while (!isAtEnd()) { + final char = peek(); + if (char == null) break; + + if (!inString) { + if (char == '{') braceCount++; + if (char == '}') braceCount--; + if (char == '"' || char == "'") { + inString = true; + stringChar = char; + } + if (char == ';' && braceCount == 0) { + buffer.write(advance()); + break; + } + if (braceCount < 0) break; // End of class body + } else if (char == stringChar && peekAhead(-1) != '\\') { + inString = false; + } + + buffer.write(advance()); + } + + return buffer.toString(); + } + + /// Parses type parameters (e.g., or ). + List _parseTypeParameters() { + if (!match('<')) return []; + + final params = []; + + while (!isAtEnd() && !lookAhead('>')) { + skipWhitespace(); + + final param = _parseTypeParameter(); + if (param == null) { + addError('Invalid type parameter'); + return []; + } + + params.add(param); + + skipWhitespace(); + if (!match(',')) break; + } + + if (!match('>')) { + addError("Expected '>' to close type parameters"); + return []; + } + + return params; + } + + /// Parses a single type parameter, including any bounds. + String? _parseTypeParameter() { + final name = _parseIdentifier(); + if (name == null) return null; + + skipWhitespace(); + + // Parse type bounds if present + if (match('extends')) { + skipWhitespace(); + final type = + _typeParser.parse(consumeUntil(',', includeDelimiter: false).trim()); + if (type.isSuccess) { + return '$name extends ${type.result!.fullName}'; + } + return null; + } + + return name; + } + + /// Parses an identifier (e.g., class name, method name). + String? _parseIdentifier() { + skipWhitespace(); + + if (isAtEnd()) return null; + + final char = peek(); + if (char == null || !_isIdentifierStart(char)) return null; + + return consumeWhile(_isIdentifierPart); + } + + /// Returns true if the character can start an identifier. + bool _isIdentifierStart(String char) { + return char == '_' || char.toLowerCase() != char.toUpperCase(); + } + + /// Returns true if the character can be part of an identifier. + bool _isIdentifierPart(String char) { + return _isIdentifierStart(char) || + char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57; + } +} diff --git a/packages/mirrors/lib/src/discovery/parser/constructor_parser.dart b/packages/mirrors/lib/src/discovery/parser/constructor_parser.dart new file mode 100644 index 0000000..15d3175 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/constructor_parser.dart @@ -0,0 +1,364 @@ +import '../../metadata/constructor_metadata.dart'; +import '../../metadata/parameter_metadata.dart'; +import 'parser.dart'; +import 'base_parser.dart'; + +/// Parser for class constructors. +class ConstructorParser extends BaseParser { + final String className; + + ConstructorParser(this.className); + + @override + bool canParse(String source) { + init(source); + skipWhitespace(); + + // Check for constructor modifiers + if (lookAhead('const') || lookAhead('factory')) { + return true; + } + + // Check for constructor name + return lookAhead(className); + } + + @override + ParseResult parse(String source) { + init(source); + + try { + // Parse constructor declaration + final metadata = _parseConstructorDeclaration(); + if (metadata == null) { + return ParseResult.failure(['Failed to parse constructor declaration']); + } + + return ParseResult.success(metadata); + } catch (e) { + return ParseResult.failure(['Error parsing constructor: $e']); + } + } + + /// Parses a constructor declaration. + ConstructorMetadata? _parseConstructorDeclaration() { + skipWhitespace(); + + // Parse modifiers + bool isConst = false; + bool isFactory = false; + + while (true) { + if (match('const')) { + if (isConst) { + addError("Duplicate 'const' modifier"); + return null; + } + isConst = true; + } else if (match('factory')) { + if (isFactory) { + addError("Duplicate 'factory' modifier"); + return null; + } + isFactory = true; + } else { + break; + } + skipWhitespace(); + } + + // Parse constructor name + if (!match(className)) { + addError('Expected constructor name'); + return null; + } + + // Parse named constructor if present + String constructorName = ''; + if (match('.')) { + final name = _parseIdentifier(); + if (name == null) { + addError('Expected named constructor identifier'); + return null; + } + constructorName = name; + } + + skipWhitespace(); + + // Parse parameters + if (!match('(')) { + addError("Expected '(' after constructor name"); + return null; + } + + final parameters = []; + final parameterTypes = []; + + // Parse parameter list + if (!match(')')) { + while (true) { + skipWhitespace(); + + final param = _parseParameter(); + if (param == null) { + addError('Invalid parameter'); + return null; + } + + parameters.add(param); + parameterTypes.add(param.type); + + skipWhitespace(); + if (match(')')) break; + + if (!match(',')) { + addError("Expected ',' or ')' after parameter"); + return null; + } + } + } + + skipWhitespace(); + + // Parse initializer list if present + if (match(':')) { + skipWhitespace(); + while (!isAtEnd() && !lookAhead('{') && !lookAhead('=>')) { + // Skip initializer + consumeUntil(',', includeDelimiter: true); + skipWhitespace(); + } + } + + // Parse constructor body unless redirecting + if (!match('=>')) { + if (!match('{')) { + addError("Expected '{' to begin constructor body"); + return null; + } + + // Skip constructor body + var braceCount = 1; + while (!isAtEnd() && braceCount > 0) { + if (peek() == '{') braceCount++; + if (peek() == '}') braceCount--; + advance(); + } + } else { + // Skip redirecting constructor call + consumeUntil(';', includeDelimiter: true); + } + + return ConstructorMetadata( + name: constructorName, + parameterTypes: parameterTypes, + parameters: parameters, + ); + } + + /// Parses a parameter declaration. + ParameterMetadata? _parseParameter() { + bool isRequired = false; + bool isNamed = false; + + // Check if we're in a named parameter group + if (lookAhead('{')) { + isNamed = true; + match('{'); + skipWhitespace(); + } + + // Check for required modifier + if (match('required')) { + isRequired = true; + skipWhitespace(); + } + + // Parse type + final typeStr = _parseType(); + if (typeStr == null) { + addError('Expected parameter type'); + return null; + } + + skipWhitespace(); + + // Check for 'this.' prefix + bool isFieldFormal = match('this.'); + + // Parse name + final name = _parseIdentifier(); + if (name == null) { + addError('Expected parameter name'); + return null; + } + + skipWhitespace(); + + // Check for nullable type + bool isNullable = match('?'); + if (isNullable) skipWhitespace(); + + // Parse default value if present + String? defaultValue; + if (match('=')) { + skipWhitespace(); + defaultValue = _parseDefaultValue(); + } + + // Check for end of named parameter group + if (isNamed && match('}')) { + skipWhitespace(); + } + + return ParameterMetadata( + name: name, + type: _getTypeForName(typeStr), + isRequired: isRequired || !isNamed, + isNamed: isNamed, + isNullable: isNullable, + defaultValue: + defaultValue != null ? _parseDefaultValueLiteral(defaultValue) : null, + ); + } + + /// Parses a type name, which could include generics. + String? _parseType() { + final identifier = _parseIdentifier(); + if (identifier == null) return null; + + skipWhitespace(); + + // Parse type arguments if present + if (match('<')) { + final args = []; + + while (!isAtEnd() && !lookAhead('>')) { + skipWhitespace(); + + final type = _parseType(); + if (type == null) { + addError('Invalid type argument'); + return null; + } + + args.add(type); + + skipWhitespace(); + if (!match(',')) break; + } + + if (!match('>')) { + addError("Expected '>' to close type arguments"); + return null; + } + + return '$identifier<${args.join(', ')}>'; + } + + return identifier; + } + + /// Parses a default value expression. + String? _parseDefaultValue() { + final buffer = StringBuffer(); + var bracketCount = 0; + var inString = false; + var stringChar = ''; + + while (!isAtEnd()) { + final char = peek(); + if (char == null) break; + + if (!inString) { + if ((char == ',' || char == '}' || char == ')') && bracketCount == 0) + break; + if (char == '{' || char == '[' || char == '(') bracketCount++; + if (char == '}' || char == ']' || char == ')') bracketCount--; + if (char == '"' || char == "'") { + inString = true; + stringChar = char; + } + } else if (char == stringChar && peekAhead(-1) != '\\') { + inString = false; + } + + buffer.write(advance()); + } + + final result = buffer.toString().trim(); + return result.isEmpty ? null : result; + } + + /// Parses a default value literal into its actual value. + dynamic _parseDefaultValueLiteral(String value) { + if (value == 'null') return null; + if (value == 'true') return true; + if (value == 'false') return false; + if (value.startsWith("'") && value.endsWith("'")) { + return value.substring(1, value.length - 1); + } + if (int.tryParse(value) != null) return int.parse(value); + if (double.tryParse(value) != null) return double.parse(value); + return value; + } + + /// Parses an identifier (e.g., type name, parameter name). + String? _parseIdentifier() { + skipWhitespace(); + + if (isAtEnd()) return null; + + final char = peek(); + if (char == null || !_isIdentifierStart(char)) return null; + + return consumeWhile(_isIdentifierPart); + } + + /// Returns true if the character can start an identifier. + bool _isIdentifierStart(String char) { + return char == '_' || char.toLowerCase() != char.toUpperCase(); + } + + /// Returns true if the character can be part of an identifier. + bool _isIdentifierPart(String char) { + return _isIdentifierStart(char) || + char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57; + } + + /// Gets or creates a Type instance for a type name. + Type _getTypeForName(String name) { + // Remove any generic type parameters and whitespace + name = name.split('<')[0].trim(); + + // For built-in types, return the actual type + switch (name) { + case 'String': + return String; + case 'int': + return int; + case 'double': + return double; + case 'bool': + return bool; + case 'List': + return List; + case 'Map': + return Map; + case 'Set': + return Set; + case 'Object': + return Object; + case 'dynamic': + return Object; // Use Object as fallback for dynamic + case 'void': + return Object; // Use Object as fallback for void + case 'Null': + return Object; // Use Object as fallback for Null + default: + // For user-defined types, create a proxy type + return Object; // TODO: Handle custom types properly + } + } +} diff --git a/packages/mirrors/lib/src/discovery/parser/method_parser.dart b/packages/mirrors/lib/src/discovery/parser/method_parser.dart new file mode 100644 index 0000000..2e57979 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/method_parser.dart @@ -0,0 +1,365 @@ +import '../../metadata/parameter_metadata.dart'; +import 'parser.dart'; +import 'base_parser.dart'; +import '../../metadata/extended_method_metadata.dart'; + +/// Parser for class methods. +class MethodParser extends BaseParser { + @override + bool canParse(String source) { + init(source); + skipWhitespace(); + + // Check for method modifiers + if (lookAhead('static') || lookAhead('async') || lookAhead('external')) { + return true; + } + + // Check for return type or method name + return _isIdentifierStart(peek() ?? ''); + } + + @override + ParseResult parse(String source) { + init(source); + + try { + // Parse method declaration + final metadata = _parseMethodDeclaration(); + if (metadata == null) { + return ParseResult.failure(['Failed to parse method declaration']); + } + + return ParseResult.success(metadata); + } catch (e) { + return ParseResult.failure(['Error parsing method: $e']); + } + } + + /// Parses a method declaration. + ExtendedMethodMetadata? _parseMethodDeclaration() { + skipWhitespace(); + + // Parse modifiers + bool isStatic = false; + bool isAsync = false; + bool isExternal = false; + bool isGenerator = false; + + while (true) { + if (match('static')) { + if (isStatic) { + addError("Duplicate 'static' modifier"); + return null; + } + isStatic = true; + } else if (match('async')) { + if (isAsync) { + addError("Duplicate 'async' modifier"); + return null; + } + isAsync = true; + } else if (match('external')) { + if (isExternal) { + addError("Duplicate 'external' modifier"); + return null; + } + isExternal = true; + } else { + break; + } + skipWhitespace(); + } + + // Parse return type + final returnTypeStr = _parseType(); + if (returnTypeStr == null) { + addError('Expected return type'); + return null; + } + + skipWhitespace(); + + // Parse method name + final name = _parseIdentifier(); + if (name == null) { + addError('Expected method name'); + return null; + } + + skipWhitespace(); + + // Parse parameters + if (!match('(')) { + addError("Expected '(' after method name"); + return null; + } + + final parameters = []; + final parameterTypes = []; + + // Parse parameter list + if (!match(')')) { + while (true) { + skipWhitespace(); + + final param = _parseParameter(); + if (param == null) { + addError('Invalid parameter'); + return null; + } + + parameters.add(param); + parameterTypes.add(param.type); + + skipWhitespace(); + if (match(')')) break; + + if (!match(',')) { + addError("Expected ',' or ')' after parameter"); + return null; + } + } + } + + skipWhitespace(); + + // Check for generator modifier + if (match('sync*')) { + isGenerator = true; + } else if (match('async*')) { + isGenerator = true; + isAsync = true; + } + + // Parse method body unless external + if (!isExternal) { + if (!match('=>') && !match('{')) { + addError("Expected '=>' or '{' after parameter list"); + return null; + } + + // Skip method body + if (peek() == '{') { + var braceCount = 1; + advance(); // Skip opening brace + + while (!isAtEnd() && braceCount > 0) { + if (peek() == '{') braceCount++; + if (peek() == '}') braceCount--; + advance(); + } + } else { + // Skip arrow and expression until semicolon + consumeUntil(';', includeDelimiter: true); + } + } else { + // External methods must end with semicolon + if (!match(';')) { + addError("Expected ';' after external method declaration"); + return null; + } + } + + return ExtendedMethodMetadata( + name: name, + parameterTypes: parameterTypes, + parameters: parameters, + returnsVoid: returnTypeStr == 'void', + returnType: _getTypeForName(returnTypeStr), + isStatic: isStatic, + isAsync: isAsync, + isGenerator: isGenerator, + isExternal: isExternal, + ); + } + + /// Parses a parameter declaration. + ParameterMetadata? _parseParameter() { + bool isRequired = false; + bool isNamed = false; + + // Check for required modifier + if (match('required')) { + isRequired = true; + skipWhitespace(); + } + + // Parse type + final typeStr = _parseType(); + if (typeStr == null) { + addError('Expected parameter type'); + return null; + } + + skipWhitespace(); + + // Parse name + final name = _parseIdentifier(); + if (name == null) { + addError('Expected parameter name'); + return null; + } + + skipWhitespace(); + + // Check for nullable type + bool isNullable = match('?'); + if (isNullable) skipWhitespace(); + + // Parse default value if present + String? defaultValue; + if (match('=')) { + skipWhitespace(); + defaultValue = _parseDefaultValue(); + } + + return ParameterMetadata( + name: name, + type: _getTypeForName(typeStr), + isRequired: isRequired, + isNamed: isNamed, + defaultValue: + defaultValue != null ? _parseDefaultValueLiteral(defaultValue) : null, + ); + } + + /// Parses a type name, which could include generics. + String? _parseType() { + final identifier = _parseIdentifier(); + if (identifier == null) return null; + + skipWhitespace(); + + // Parse type arguments if present + if (match('<')) { + final args = []; + + while (!isAtEnd() && !lookAhead('>')) { + skipWhitespace(); + + final type = _parseType(); + if (type == null) { + addError('Invalid type argument'); + return null; + } + + args.add(type); + + skipWhitespace(); + if (!match(',')) break; + } + + if (!match('>')) { + addError("Expected '>' to close type arguments"); + return null; + } + + return '$identifier<${args.join(', ')}>'; + } + + return identifier; + } + + /// Parses a default value expression. + String? _parseDefaultValue() { + final buffer = StringBuffer(); + var bracketCount = 0; + var inString = false; + var stringChar = ''; + + while (!isAtEnd()) { + final char = peek(); + if (char == null) break; + + if (!inString) { + if ((char == ',' || char == '}' || char == ')') && bracketCount == 0) + break; + if (char == '{' || char == '[' || char == '(') bracketCount++; + if (char == '}' || char == ']' || char == ')') bracketCount--; + if (char == '"' || char == "'") { + inString = true; + stringChar = char; + } + } else if (char == stringChar && peekAhead(-1) != '\\') { + inString = false; + } + + buffer.write(advance()); + } + + final result = buffer.toString().trim(); + return result.isEmpty ? null : result; + } + + /// Parses a default value literal into its actual value. + dynamic _parseDefaultValueLiteral(String value) { + if (value == 'null') return null; + if (value == 'true') return true; + if (value == 'false') return false; + if (value.startsWith("'") && value.endsWith("'")) { + return value.substring(1, value.length - 1); + } + if (int.tryParse(value) != null) return int.parse(value); + if (double.tryParse(value) != null) return double.parse(value); + return value; + } + + /// Parses an identifier (e.g., type name, method name). + String? _parseIdentifier() { + skipWhitespace(); + + if (isAtEnd()) return null; + + final char = peek(); + if (char == null || !_isIdentifierStart(char)) return null; + + return consumeWhile(_isIdentifierPart); + } + + /// Returns true if the character can start an identifier. + bool _isIdentifierStart(String char) { + return char == '_' || char.toLowerCase() != char.toUpperCase(); + } + + /// Returns true if the character can be part of an identifier. + bool _isIdentifierPart(String char) { + return _isIdentifierStart(char) || + char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57; + } + + /// Gets or creates a Type instance for a type name. + Type _getTypeForName(String name) { + // Remove any generic type parameters and whitespace + name = name.split('<')[0].trim(); + + // For built-in types, return the actual type + switch (name) { + case 'String': + return String; + case 'int': + return int; + case 'double': + return double; + case 'bool': + return bool; + case 'List': + return List; + case 'Map': + return Map; + case 'Set': + return Set; + case 'Object': + return Object; + case 'dynamic': + return Object; // Use Object as fallback for dynamic + case 'void': + return Object; // Use Object as fallback for void + case 'Null': + return Object; // Use Object as fallback for Null + default: + // For user-defined types, create a proxy type + return Object; // TODO: Handle custom types properly + } + } +} diff --git a/packages/mirrors/lib/src/discovery/parser/parser.dart b/packages/mirrors/lib/src/discovery/parser/parser.dart new file mode 100644 index 0000000..e7a4390 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/parser.dart @@ -0,0 +1,62 @@ +/// Base interface for all parsers in the reflection system. +abstract class Parser { + /// Parses the given source code and returns the result. + /// + /// The return type varies based on the specific parser implementation. + dynamic parse(String source); + + /// Validates if the given source code can be parsed by this parser. + /// + /// Returns true if the source is valid for this parser, false otherwise. + bool canParse(String source); + + /// Gets the current position in the source code. + int get position; + + /// Sets the current position in the source code. + set position(int value); + + /// Gets the current line number being parsed. + int get line; + + /// Gets the current column number being parsed. + int get column; + + /// Gets any error messages from the parsing process. + List get errors; + + /// Gets any warning messages from the parsing process. + List get warnings; +} + +/// Result of a parsing operation. +class ParseResult { + /// The parsed result. + final T? result; + + /// Any errors that occurred during parsing. + final List errors; + + /// Any warnings that occurred during parsing. + final List warnings; + + /// Whether the parsing was successful. + bool get isSuccess => errors.isEmpty && result != null; + + ParseResult({ + this.result, + this.errors = const [], + this.warnings = const [], + }); + + /// Creates a successful parse result. + factory ParseResult.success(T result, {List warnings = const []}) { + return ParseResult(result: result, warnings: warnings); + } + + /// Creates a failed parse result. + factory ParseResult.failure(List errors, + {List warnings = const []}) { + return ParseResult(errors: errors, warnings: warnings); + } +} diff --git a/packages/mirrors/lib/src/discovery/parser/property_parser.dart b/packages/mirrors/lib/src/discovery/parser/property_parser.dart new file mode 100644 index 0000000..2f41db1 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/property_parser.dart @@ -0,0 +1,305 @@ +import '../../metadata/property_metadata.dart'; +import 'parser.dart'; +import 'base_parser.dart'; + +/// Parser for class properties, including fields and getters/setters. +class PropertyParser extends BaseParser { + @override + bool canParse(String source) { + init(source); + skipWhitespace(); + + // Check for property modifiers + if (lookAhead('static') || lookAhead('final') || lookAhead('const')) { + return true; + } + + // Check for getter/setter + if (lookAhead('get') || lookAhead('set')) { + return true; + } + + // Check for type declaration + return _isIdentifierStart(peek() ?? ''); + } + + @override + ParseResult parse(String source) { + init(source); + + try { + // Parse property declaration + final metadata = _parsePropertyDeclaration(); + if (metadata == null) { + return ParseResult.failure(['Failed to parse property declaration']); + } + + return ParseResult.success(metadata); + } catch (e) { + return ParseResult.failure(['Error parsing property: $e']); + } + } + + /// Parses a property declaration. + PropertyMetadata? _parsePropertyDeclaration() { + skipWhitespace(); + + // Parse modifiers + bool isStatic = false; + bool isFinal = false; + bool isConst = false; + bool isLate = false; + + while (true) { + if (match('static')) { + if (isStatic) { + addError("Duplicate 'static' modifier"); + return null; + } + isStatic = true; + } else if (match('final')) { + if (isFinal || isConst) { + addError("Cannot have both 'final' and 'const'"); + return null; + } + isFinal = true; + } else if (match('const')) { + if (isConst || isFinal) { + addError("Cannot have both 'const' and 'final'"); + return null; + } + isConst = true; + } else if (match('late')) { + if (isLate) { + addError("Duplicate 'late' modifier"); + return null; + } + isLate = true; + } else { + break; + } + skipWhitespace(); + } + + // Check for getter/setter + bool isGetter = false; + bool isSetter = false; + + if (match('get')) { + isGetter = true; + skipWhitespace(); + return _parseAccessor(true); + } else if (match('set')) { + isSetter = true; + skipWhitespace(); + return _parseAccessor(false); + } + + // Parse type + final typeStr = _parseType(); + if (typeStr == null) { + addError('Expected type name'); + return null; + } + + skipWhitespace(); + + // Parse name + final name = _parseIdentifier(); + if (name == null) { + addError('Expected property name'); + return null; + } + + skipWhitespace(); + + // Check for nullable type + bool isNullable = match('?'); + if (isNullable) skipWhitespace(); + + // Parse initializer if present + String? initializer; + if (match('=')) { + skipWhitespace(); + initializer = _parseInitializer(); + } + + // Expect semicolon + if (!match(';')) { + addError("Expected ';' after property declaration"); + return null; + } + + return PropertyMetadata( + name: name, + type: _getTypeForName(typeStr), + isReadable: true, + isWritable: !isFinal && !isConst, + ); + } + + /// Parses a getter or setter declaration. + PropertyMetadata? _parseAccessor(bool isGetter) { + final name = _parseIdentifier(); + if (name == null) { + addError('Expected accessor name'); + return null; + } + + skipWhitespace(); + + // Parse getter/setter body + if (!match('=>') && !match('{')) { + addError("Expected '=>' or '{' after accessor name"); + return null; + } + + // Skip body until we find the end + if (peek() == '{') { + var braceCount = 1; + advance(); // Skip opening brace + + while (!isAtEnd() && braceCount > 0) { + if (peek() == '{') braceCount++; + if (peek() == '}') braceCount--; + advance(); + } + } else { + // Skip arrow and expression until semicolon + consumeUntil(';', includeDelimiter: true); + } + + return PropertyMetadata( + name: name, + type: _getTypeForName( + 'dynamic'), // Type will be inferred from getter return type + isReadable: isGetter, + isWritable: !isGetter, + ); + } + + /// Parses a type name, which could include generics. + String? _parseType() { + final identifier = _parseIdentifier(); + if (identifier == null) return null; + + skipWhitespace(); + + // Parse type arguments if present + if (match('<')) { + final args = []; + + while (!isAtEnd() && !lookAhead('>')) { + skipWhitespace(); + + final type = _parseType(); + if (type == null) { + addError('Invalid type argument'); + return null; + } + + args.add(type); + + skipWhitespace(); + if (!match(',')) break; + } + + if (!match('>')) { + addError("Expected '>' to close type arguments"); + return null; + } + + return '$identifier<${args.join(', ')}>'; + } + + return identifier; + } + + /// Parses an initializer expression. + String? _parseInitializer() { + final buffer = StringBuffer(); + var bracketCount = 0; + var inString = false; + var stringChar = ''; + + while (!isAtEnd()) { + final char = peek(); + if (char == null) break; + + if (!inString) { + if (char == ';' && bracketCount == 0) break; + if (char == '{' || char == '[' || char == '(') bracketCount++; + if (char == '}' || char == ']' || char == ')') bracketCount--; + if (char == '"' || char == "'") { + inString = true; + stringChar = char; + } + } else if (char == stringChar && peekAhead(-1) != '\\') { + inString = false; + } + + buffer.write(advance()); + } + + final result = buffer.toString().trim(); + return result.isEmpty ? null : result; + } + + /// Parses an identifier (e.g., type name, property name). + String? _parseIdentifier() { + skipWhitespace(); + + if (isAtEnd()) return null; + + final char = peek(); + if (char == null || !_isIdentifierStart(char)) return null; + + return consumeWhile(_isIdentifierPart); + } + + /// Returns true if the character can start an identifier. + bool _isIdentifierStart(String char) { + return char == '_' || char.toLowerCase() != char.toUpperCase(); + } + + /// Returns true if the character can be part of an identifier. + bool _isIdentifierPart(String char) { + return _isIdentifierStart(char) || + char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57; + } + + /// Gets or creates a Type instance for a type name. + Type _getTypeForName(String name) { + // Remove any generic type parameters and whitespace + name = name.split('<')[0].trim(); + + // For built-in types, return the actual type + switch (name) { + case 'String': + return String; + case 'int': + return int; + case 'double': + return double; + case 'bool': + return bool; + case 'List': + return List; + case 'Map': + return Map; + case 'Set': + return Set; + case 'Object': + return Object; + case 'dynamic': + return Object; // Use Object as fallback for dynamic + case 'void': + return Object; // Use Object as fallback for void + case 'Null': + return Object; // Use Object as fallback for Null + default: + // For user-defined types, create a proxy type + return Object; // TODO: Handle custom types properly + } + } +} diff --git a/packages/mirrors/lib/src/discovery/parser/type_parser.dart b/packages/mirrors/lib/src/discovery/parser/type_parser.dart new file mode 100644 index 0000000..5edccc4 --- /dev/null +++ b/packages/mirrors/lib/src/discovery/parser/type_parser.dart @@ -0,0 +1,175 @@ +import 'parser.dart'; +import 'base_parser.dart'; + +/// Represents a parsed type with its generic type arguments. +class ParsedType { + /// The base type name. + final String name; + + /// The type arguments if this is a generic type. + final List typeArguments; + + /// Whether this type is nullable. + final bool isNullable; + + /// The full type string including generics and nullability. + String get fullName { + final buffer = StringBuffer(name); + if (typeArguments.isNotEmpty) { + buffer.write('<'); + buffer.write(typeArguments.map((t) => t.fullName).join(', ')); + buffer.write('>'); + } + if (isNullable) buffer.write('?'); + return buffer.toString(); + } + + ParsedType({ + required this.name, + this.typeArguments = const [], + this.isNullable = false, + }); +} + +/// Parser for Dart type declarations. +class TypeParser extends BaseParser { + @override + bool canParse(String source) { + init(source); + skipWhitespace(); + return _isIdentifierStart(peek() ?? ''); + } + + @override + ParseResult parse(String source) { + init(source); + + try { + // Parse type declaration + final type = _parseType(); + if (type == null) { + return ParseResult.failure(['Failed to parse type declaration']); + } + + return ParseResult.success(type); + } catch (e) { + return ParseResult.failure(['Error parsing type: $e']); + } + } + + /// Parses a type declaration. + ParsedType? _parseType() { + skipWhitespace(); + + // Parse base type name + final name = _parseIdentifier(); + if (name == null) { + addError('Expected type name'); + return null; + } + + skipWhitespace(); + + // Parse type arguments if present + final typeArguments = []; + if (match('<')) { + while (!isAtEnd() && !lookAhead('>')) { + skipWhitespace(); + + final typeArg = _parseType(); + if (typeArg == null) { + addError('Invalid type argument'); + return null; + } + + typeArguments.add(typeArg); + + skipWhitespace(); + if (!match(',')) break; + } + + if (!match('>')) { + addError("Expected '>' to close type arguments"); + return null; + } + } + + skipWhitespace(); + + // Check for nullable type + final isNullable = match('?'); + + return ParsedType( + name: name, + typeArguments: typeArguments, + isNullable: isNullable, + ); + } + + /// Parses an identifier (e.g., type name). + String? _parseIdentifier() { + skipWhitespace(); + + if (isAtEnd()) return null; + + final char = peek(); + if (char == null || !_isIdentifierStart(char)) return null; + + return consumeWhile(_isIdentifierPart); + } + + /// Returns true if the character can start an identifier. + bool _isIdentifierStart(String char) { + return char == '_' || char.toLowerCase() != char.toUpperCase(); + } + + /// Returns true if the character can be part of an identifier. + bool _isIdentifierPart(String char) { + return _isIdentifierStart(char) || + char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57; + } + + /// Gets the Type instance for a ParsedType. + Type getTypeForParsedType(ParsedType parsedType) { + // Remove any generic type parameters and whitespace + final name = parsedType.name.trim(); + + // For built-in types, return the actual type + switch (name) { + case 'String': + return String; + case 'int': + return int; + case 'double': + return double; + case 'bool': + return bool; + case 'List': + return List; + case 'Map': + return Map; + case 'Set': + return Set; + case 'Object': + return Object; + case 'dynamic': + return Object; // Use Object as fallback for dynamic + case 'void': + return Object; // Use Object as fallback for void + case 'Null': + return Object; // Use Object as fallback for Null + default: + // For user-defined types, create a proxy type + return Object; // TODO: Handle custom types properly + } + } + + /// Parses a type string and returns its Type instance. + Type parseAndGetType(String typeStr) { + final result = parse(typeStr); + if (!result.isSuccess) { + return Object; // Return Object as fallback for invalid types + } + return getTypeForParsedType(result.result!); + } +} diff --git a/packages/mirrors/lib/src/metadata/class_metadata.dart b/packages/mirrors/lib/src/metadata/class_metadata.dart new file mode 100644 index 0000000..2b61861 --- /dev/null +++ b/packages/mirrors/lib/src/metadata/class_metadata.dart @@ -0,0 +1,45 @@ +import 'constructor_metadata.dart'; +import 'method_metadata.dart'; +import 'property_metadata.dart'; + +/// Metadata about a parsed class. +class ClassMetadata { + /// The name of the class. + final String name; + + /// The type parameters if this is a generic class (e.g., ). + final List typeParameters; + + /// The superclass this class extends, if any. + final String? superclass; + + /// The interfaces this class implements. + final List interfaces; + + /// Whether this is an abstract class. + final bool isAbstract; + + /// Whether this is a final class. + final bool isFinal; + + /// The properties defined in this class. + final Map properties; + + /// The methods defined in this class. + final Map methods; + + /// The constructors defined in this class. + final List constructors; + + ClassMetadata({ + required this.name, + this.typeParameters = const [], + this.superclass, + this.interfaces = const [], + this.isAbstract = false, + this.isFinal = false, + this.properties = const {}, + this.methods = const {}, + this.constructors = const [], + }); +} diff --git a/packages/mirrors/lib/src/metadata/extended_method_metadata.dart b/packages/mirrors/lib/src/metadata/extended_method_metadata.dart new file mode 100644 index 0000000..b6c8980 --- /dev/null +++ b/packages/mirrors/lib/src/metadata/extended_method_metadata.dart @@ -0,0 +1,36 @@ +import 'parameter_metadata.dart'; +import 'method_metadata.dart'; + +/// Extended metadata for methods that includes additional Dart-specific features. +class ExtendedMethodMetadata extends MethodMetadata { + /// Whether this is an async method. + @override + final bool isAsync; + + /// Whether this is a generator method (sync* or async*). + @override + final bool isGenerator; + + /// Whether this is an external method. + @override + final bool isExternal; + + ExtendedMethodMetadata({ + required String name, + required List parameterTypes, + required List parameters, + required bool returnsVoid, + required Type returnType, + bool isStatic = false, + this.isAsync = false, + this.isGenerator = false, + this.isExternal = false, + }) : super( + name: name, + parameterTypes: parameterTypes, + parameters: parameters, + returnsVoid: returnsVoid, + returnType: returnType, + isStatic: isStatic, + ); +} diff --git a/packages/mirrors/lib/src/metadata/method_metadata.dart b/packages/mirrors/lib/src/metadata/method_metadata.dart index a39a0e8..2db81bd 100644 --- a/packages/mirrors/lib/src/metadata/method_metadata.dart +++ b/packages/mirrors/lib/src/metadata/method_metadata.dart @@ -1,54 +1,43 @@ -import 'package:platform_mirrors/mirrors.dart'; +import 'parameter_metadata.dart'; -/// Represents metadata about a type's method. +/// Metadata about a class method. class MethodMetadata { /// The name of the method. final String name; - /// The parameter types of the method in order. + /// The parameter types of the method. final List parameterTypes; - /// Detailed metadata about each parameter. + /// The parameters of the method. final List parameters; - /// Whether the method is static. - final bool isStatic; - /// Whether the method returns void. final bool returnsVoid; /// The return type of the method. final Type returnType; - /// Any attributes (annotations) on this method. - final List attributes; + /// Whether the method is static. + final bool isStatic; - /// Type parameters for generic methods. - final List typeParameters; + /// Whether the method is async. + final bool isAsync; - /// Creates a new method metadata instance. - const MethodMetadata({ + /// Whether the method is a generator (sync* or async*). + final bool isGenerator; + + /// Whether the method is external. + final bool isExternal; + + MethodMetadata({ required this.name, required this.parameterTypes, required this.parameters, required this.returnsVoid, required this.returnType, this.isStatic = false, - this.attributes = const [], - this.typeParameters = const [], + this.isAsync = false, + this.isGenerator = false, + this.isExternal = false, }); - - /// Validates the given arguments against this method's parameter types. - bool validateArguments(List arguments) { - if (arguments.length != parameterTypes.length) return false; - - for (var i = 0; i < arguments.length; i++) { - final arg = arguments[i]; - if (arg != null && arg.runtimeType != parameterTypes[i]) { - return false; - } - } - - return true; - } } diff --git a/packages/mirrors/lib/src/metadata/parameter_metadata.dart b/packages/mirrors/lib/src/metadata/parameter_metadata.dart index 74f1acf..ce26fdc 100644 --- a/packages/mirrors/lib/src/metadata/parameter_metadata.dart +++ b/packages/mirrors/lib/src/metadata/parameter_metadata.dart @@ -1,4 +1,4 @@ -/// Represents metadata about a parameter. +/// Metadata about a method or constructor parameter. class ParameterMetadata { /// The name of the parameter. final String name; @@ -6,25 +6,24 @@ class ParameterMetadata { /// The type of the parameter. final Type type; - /// Whether this parameter is required. + /// Whether the parameter is required. final bool isRequired; - /// Whether this parameter is named. + /// Whether the parameter is named. final bool isNamed; - /// The default value for this parameter, if any. - final Object? defaultValue; + /// Whether the parameter is nullable. + final bool isNullable; - /// Any attributes (annotations) on this parameter. - final List attributes; + /// The default value of the parameter, if any. + final dynamic defaultValue; - /// Creates a new parameter metadata instance. - const ParameterMetadata({ + ParameterMetadata({ required this.name, required this.type, - required this.isRequired, + this.isRequired = true, this.isNamed = false, + this.isNullable = false, this.defaultValue, - this.attributes = const [], }); } diff --git a/packages/mirrors/lib/src/metadata/property_metadata.dart b/packages/mirrors/lib/src/metadata/property_metadata.dart index febf64e..824e9a1 100644 --- a/packages/mirrors/lib/src/metadata/property_metadata.dart +++ b/packages/mirrors/lib/src/metadata/property_metadata.dart @@ -1,4 +1,4 @@ -/// Represents metadata about a type's property. +/// Metadata about a class property. class PropertyMetadata { /// The name of the property. final String name; @@ -12,15 +12,30 @@ class PropertyMetadata { /// Whether the property can be written to. final bool isWritable; + /// Whether the property is static. + final bool isStatic; + + /// Whether the property is late. + final bool isLate; + + /// Whether the property is nullable. + final bool isNullable; + + /// Whether the property has an initializer. + final bool hasInitializer; + /// Any attributes (annotations) on this property. final List attributes; - /// Creates a new property metadata instance. - const PropertyMetadata({ + PropertyMetadata({ required this.name, required this.type, this.isReadable = true, this.isWritable = true, + this.isStatic = false, + this.isLate = false, + this.isNullable = false, this.attributes = const [], + this.hasInitializer = false, }); } diff --git a/packages/mirrors/lib/src/discovery/proxy_type.dart b/packages/mirrors/lib/src/types/proxy_type.dart similarity index 100% rename from packages/mirrors/lib/src/discovery/proxy_type.dart rename to packages/mirrors/lib/src/types/proxy_type.dart diff --git a/packages/mirrors/pubspec.yaml b/packages/mirrors/pubspec.yaml index 334222e..e1c1a49 100644 --- a/packages/mirrors/pubspec.yaml +++ b/packages/mirrors/pubspec.yaml @@ -1,20 +1,18 @@ name: platform_mirrors -description: A lightweight, cross-platform reflection system for Dart -version: 0.1.0 -publish_to: none +description: A runtime reflection system for Dart that provides introspection capabilities. +version: 1.0.0 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ">=3.0.0 <4.0.0" dependencies: collection: ^1.17.0 meta: ^1.9.0 path: ^1.9.1 platform_contracts: ^0.1.0 - dev_dependencies: test: ^1.24.0 - mockito: ^5.4.0 - build_runner: ^2.4.0 lints: ^2.1.0 + +publish_to: none diff --git a/packages/mirrors/test/discovery/parser_test.dart b/packages/mirrors/test/discovery/parser_test.dart new file mode 100644 index 0000000..44aaa8a --- /dev/null +++ b/packages/mirrors/test/discovery/parser_test.dart @@ -0,0 +1,219 @@ +import 'package:test/test.dart'; +import 'package:platform_mirrors/src/discovery/parser/class_parser.dart'; + +void main() { + group('Parser Tests', () { + late ClassParser parser; + + setUp(() { + parser = ClassParser(); + }); + + test('parses basic class', () { + final source = ''' + class BasicClass { + String name; + int age; + + void doSomething() { + print('Hello'); + } + } + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isTrue); + + final metadata = result.result!; + expect(metadata.name, equals('BasicClass')); + expect(metadata.properties.length, equals(2)); + expect(metadata.methods.length, equals(1)); + expect(metadata.constructors.length, equals(0)); + }); + + test('parses generic class', () { + final source = ''' + class Container { + T value; + + Container(this.value); + + T getValue() => value; + void setValue(T newValue) { + value = newValue; + } + } + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isTrue); + + final metadata = result.result!; + expect(metadata.name, equals('Container')); + expect(metadata.typeParameters, equals(['T'])); + expect(metadata.properties.length, equals(1)); + expect(metadata.methods.length, equals(2)); + expect(metadata.constructors.length, equals(1)); + }); + + test('parses class with inheritance and interfaces', () { + final source = ''' + abstract class Animal implements Living, Breathing { + String species; + int age; + + Animal(this.species, this.age); + + void makeSound(); + void move() { + print('Moving...'); + } + } + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isTrue); + + final metadata = result.result!; + expect(metadata.name, equals('Animal')); + expect(metadata.isAbstract, isTrue); + expect(metadata.interfaces, equals(['Living', 'Breathing'])); + expect(metadata.properties.length, equals(2)); + expect(metadata.methods.length, equals(2)); + expect(metadata.constructors.length, equals(1)); + }); + + test('parses properties with different modifiers', () { + final source = ''' + class PropertyTest { + static const int MAX_VALUE = 100; + final String id; + late String? name; + List numbers = []; + + PropertyTest(this.id); + + String get displayName => name ?? 'Unknown'; + set displayName(String value) => name = value; + } + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isTrue); + + final metadata = result.result!; + final props = metadata.properties; + + expect(props['MAX_VALUE']!.isStatic, isTrue); + expect(props['id']!.isWritable, isFalse); + expect(props['name']!.isNullable, isTrue); + expect(props['numbers']!.hasInitializer, isTrue); + expect(props['displayName']!.isReadable, isTrue); + expect(props['displayName']!.isWritable, isTrue); + }); + + test('parses methods with different signatures', () { + final source = ''' + class MethodTest { + static void staticMethod() {} + + Future asyncMethod() async { + return 'done'; + } + + Stream streamMethod() async* { + yield 1; + } + + void optionalParams([int count = 0]) {} + + void namedParams({required String name, int? age}) {} + + T genericMethod(T value) => value; + } + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isTrue); + + final metadata = result.result!; + final methods = metadata.methods; + + expect(methods['staticMethod']!.isStatic, isTrue); + expect(methods['asyncMethod']!.isAsync, isTrue); + expect(methods['streamMethod']!.isGenerator, isTrue); + + final optionalMethod = methods['optionalParams']!; + expect(optionalMethod.parameters.length, equals(1)); + expect(optionalMethod.parameters[0].isRequired, isFalse); + + final namedMethod = methods['namedParams']!; + expect(namedMethod.parameters.length, equals(2)); + expect(namedMethod.parameters[0].isRequired, isTrue); + expect(namedMethod.parameters[1].isNullable, isTrue); + }); + + test('parses constructors with different forms', () { + final source = ''' + class ConstructorTest { + final String id; + String? name; + int count; + + ConstructorTest(this.id, [this.name]); + + ConstructorTest.named({ + required this.id, + this.name, + this.count = 0, + }); + + factory ConstructorTest.create(String value) { + return ConstructorTest(value); + } + + const ConstructorTest.constant(this.id) + : name = null, + count = 0; + } + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isTrue); + + final metadata = result.result!; + final constructors = metadata.constructors; + + expect(constructors.length, equals(4)); + + // Default constructor + expect(constructors[0].name, isEmpty); + expect(constructors[0].parameters.length, equals(2)); + expect(constructors[0].parameters[1].isRequired, isFalse); + + // Named constructor + expect(constructors[1].name, equals('named')); + expect(constructors[1].parameters.length, equals(3)); + expect(constructors[1].parameters[0].isRequired, isTrue); + + // Factory constructor + expect(constructors[2].name, equals('create')); + expect(constructors[2].parameters.length, equals(1)); + + // Const constructor + expect(constructors[3].name, equals('constant')); + expect(constructors[3].parameters.length, equals(1)); + }); + + test('handles errors gracefully', () { + final source = ''' + class InvalidClass { + void missingClosingBrace() { + '''; + + final result = parser.parse(source); + expect(result.isSuccess, isFalse); + expect(result.errors, isNotEmpty); + }); + }); +}