From 29d4a780341b7d859be60fbfdbf9d8c2360202bc Mon Sep 17 00:00:00 2001 From: nub31 Date: Sun, 25 May 2025 02:13:26 +0200 Subject: [PATCH] ... --- example/program.nub | 4 +- .../Nub.Lang/Frontend/DiagnosticsResult.cs | 21 ++ .../Nub.Lang/Frontend/Parsing/Parser.cs | 6 +- .../Nub.Lang/Frontend/Typing/TypeChecker.cs | 220 ++++++++++++------ src/compiler/Nub.Lang/Program.cs | 54 +++-- 5 files changed, 202 insertions(+), 103 deletions(-) create mode 100644 src/compiler/Nub.Lang/Frontend/DiagnosticsResult.cs diff --git a/example/program.nub b/example/program.nub index 1a98306..4d0de37 100644 --- a/example/program.nub +++ b/example/program.nub @@ -4,7 +4,7 @@ import c // Test2 // Test3 // Test4 -global func main(args: f []string) { +global func main(args: []string) { i = 0 printf("%d\n", args.count) while i < args.count { @@ -12,4 +12,6 @@ global func main(args: f []string) { printf("%s\n", args[i]) i = i + 1 } + + i: string = "test" } diff --git a/src/compiler/Nub.Lang/Frontend/DiagnosticsResult.cs b/src/compiler/Nub.Lang/Frontend/DiagnosticsResult.cs new file mode 100644 index 0000000..d657d93 --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/DiagnosticsResult.cs @@ -0,0 +1,21 @@ +using Nub.Lang.Frontend.Diagnostics; + +namespace Nub.Lang.Frontend; + +public class DiagnosticsResult(List diagnostics) +{ + public bool HasErrors => diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error); + + public void PrintAllDiagnostics() + { + foreach (var diagnostic in diagnostics) + { + Console.WriteLine(diagnostic.Format()); + } + } +} + +public class DiagnosticsResult(List diagnostics, TResult value) : DiagnosticsResult(diagnostics) +{ + public TResult Value { get; } = value; +} \ No newline at end of file diff --git a/src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs b/src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs index 3481f86..fbd7f64 100644 --- a/src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs +++ b/src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs @@ -10,9 +10,7 @@ public class Parser private int _index; private List _diagnostics = []; - public IReadOnlyList Diagnostics => _diagnostics; - - public ModuleNode ParseModule(List tokens, string rootFilePath) + public DiagnosticsResult ParseModule(List tokens, string rootFilePath) { _index = 0; _tokens = tokens; @@ -42,7 +40,7 @@ public class Parser } } - return new ModuleNode(GetTokensForNode(0), rootFilePath, imports, definitions); + return new DiagnosticsResult(_diagnostics, new ModuleNode(GetTokensForNode(0), rootFilePath, imports, definitions)); } private DefinitionNode ParseDefinition() diff --git a/src/compiler/Nub.Lang/Frontend/Typing/TypeChecker.cs b/src/compiler/Nub.Lang/Frontend/Typing/TypeChecker.cs index 372b248..f2cc226 100644 --- a/src/compiler/Nub.Lang/Frontend/Typing/TypeChecker.cs +++ b/src/compiler/Nub.Lang/Frontend/Typing/TypeChecker.cs @@ -1,18 +1,13 @@ using Nub.Lang.Frontend.Parsing; +using Nub.Lang.Frontend.Diagnostics; namespace Nub.Lang.Frontend.Typing; -public class TypeCheckingException : Exception -{ - public TypeCheckingException(string message) : base(message) - { - } -} - public class TypeChecker { private readonly Dictionary _variables = new(); private readonly List _definitions; + private readonly List _diagnostics = []; private NubType? _currentFunctionReturnType; private bool _hasReturnStatement; @@ -21,8 +16,10 @@ public class TypeChecker _definitions = definitions; } - public void TypeCheck() + public DiagnosticsResult TypeCheck() { + _diagnostics.Clear(); + foreach (var structDef in _definitions.OfType()) { TypeCheckStructDef(structDef); @@ -32,6 +29,8 @@ public class TypeChecker { TypeCheckFuncDef(funcDef); } + + return new DiagnosticsResult(_diagnostics.ToList()); } private void TypeCheckStructDef(StructDefinitionNode structDef) @@ -41,14 +40,16 @@ public class TypeChecker { if (fields.ContainsKey(field.Name)) { - throw new TypeCheckingException($"Duplicate field '{field.Name}' in struct '{structDef.Name}'"); + ReportError($"Duplicate field '{field.Name}' in struct '{structDef.Name}'", structDef); + continue; } if (field.Value.HasValue) { - if (!TypeCheckExpression(field.Value.Value).Equals(field.Type)) + var fieldType = TypeCheckExpression(field.Value.Value); + if (fieldType != null && !fieldType.Equals(field.Type)) { - throw new TypeCheckingException("Default field initializer does not match the defined type"); + ReportError("Default field initializer does not match the defined type", field.Value.Value); } } @@ -71,7 +72,7 @@ public class TypeChecker if (_currentFunctionReturnType != null && !_hasReturnStatement) { - throw new TypeCheckingException($"Function '{funcDef.Name}' must return a value of type '{_currentFunctionReturnType}'"); + ReportError($"Function '{funcDef.Name}' must return a value of type '{_currentFunctionReturnType}'", funcDef); } } @@ -91,7 +92,7 @@ public class TypeChecker TypeCheckVariableAssignment(varAssign); break; case FuncCallStatementNode funcCall: - TypeCheckFuncCall(funcCall.FuncCall); + TypeCheckFuncCall(funcCall.FuncCall, funcCall); break; case IfNode ifNode: TypeCheckIf(ifNode); @@ -106,42 +107,67 @@ public class TypeChecker case ContinueNode: break; default: - throw new TypeCheckingException($"Unsupported statement type: {statement.GetType().Name}"); + ReportError($"Unsupported statement type: {statement.GetType().Name}", statement); + break; } } private void TypeCheckVariableAssignment(VariableAssignmentNode varAssign) { var valueType = TypeCheckExpression(varAssign.Value); + if (valueType == null) return; - if (varAssign.ExplicitType.HasValue) + if (_variables.TryGetValue(varAssign.Name, out var existingVariable)) { - var explicitType = varAssign.ExplicitType.Value; - if (!AreTypesCompatible(valueType, explicitType)) + if (varAssign.ExplicitType.HasValue) { - throw new TypeCheckingException($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{explicitType}'"); + if (!AreTypesCompatible(existingVariable, varAssign.ExplicitType.Value)) + { + ReportError($"Explicit type '{varAssign.ExplicitType.Value}' on variable '{varAssign.Name}' is not compatible with declared type '{existingVariable}'", varAssign); + return; + } + } + + if (!AreTypesCompatible(valueType, existingVariable)) + { + ReportError($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{existingVariable}'", varAssign); } - - _variables[varAssign.Name] = explicitType; } else { - _variables[varAssign.Name] = valueType; + if (varAssign.ExplicitType.HasValue) + { + var explicitType = varAssign.ExplicitType.Value; + if (!AreTypesCompatible(valueType, explicitType)) + { + ReportError($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{explicitType}'", varAssign); + return; + } + + _variables[varAssign.Name] = explicitType; + } + else + { + _variables[varAssign.Name] = valueType; + } } } - private NubType TypeCheckDereference(DereferenceNode dereference) + private NubType? TypeCheckDereference(DereferenceNode dereference) { - TypeCheckExpression(dereference.Expression); - if (dereference.Expression.Type is not NubPointerType nubPointerType) + var exprType = TypeCheckExpression(dereference.Expression); + if (exprType == null) return null; + + if (exprType is not NubPointerType nubPointerType) { - throw new TypeCheckingException($"Cannot dereference a non-pointer type {dereference.Expression.Type}"); + ReportError($"Cannot dereference a non-pointer type {exprType}", dereference); + return null; } return nubPointerType.BaseType; } - private NubType TypeCheckFuncCall(FuncCall funcCall) + private NubType? TypeCheckFuncCall(FuncCall funcCall, Node node) { var localFuncDef = _definitions.OfType().FirstOrDefault(f => f.Name == funcCall.Name); var externFuncDef = _definitions.OfType().FirstOrDefault(f => f.Name == funcCall.Name); @@ -158,20 +184,22 @@ public class TypeChecker parameters = externFuncDef.Parameters; returnType = externFuncDef.ReturnType; } - else { - throw new TypeCheckingException($"Function '{funcCall.Name}' is not defined"); + ReportError($"Function '{funcCall.Name}' is not defined", node); + return null; } if (parameters.Take(parameters.Count - 1).Any(x => x.Variadic)) { - throw new TypeCheckingException($"Function '{funcCall.Name}' has multiple variadic parameters"); + ReportError($"Function '{funcCall.Name}' has multiple variadic parameters", node); + return null; } for (var i = 0; i < funcCall.Parameters.Count; i++) { var argType = TypeCheckExpression(funcCall.Parameters[i]); + if (argType == null) continue; NubType paramType; if (i < parameters.Count) @@ -180,16 +208,17 @@ public class TypeChecker } else if (parameters.LastOrDefault()?.Variadic ?? false) { - return parameters[^1].Type; + paramType = parameters[^1].Type; } else { - throw new TypeCheckingException($"Function '{funcCall.Name}' does not take {funcCall.Parameters.Count} parameters"); + ReportError($"Function '{funcCall.Name}' does not take {funcCall.Parameters.Count} parameters", node); + continue; } if (!AreTypesCompatible(argType, paramType)) { - throw new TypeCheckingException($"Parameter {i} of function '{funcCall.Name}' expects type '{paramType}', but got '{argType}'"); + ReportError($"Parameter {i + 1} of function '{funcCall.Name}' expects type '{paramType}', but got '{argType}'", funcCall.Parameters[i]); } } @@ -199,9 +228,9 @@ public class TypeChecker private void TypeCheckIf(IfNode ifNode) { var conditionType = TypeCheckExpression(ifNode.Condition); - if (!conditionType.Equals(NubPrimitiveType.Bool)) + if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool)) { - throw new TypeCheckingException($"If condition must be a boolean expression, got '{conditionType}'"); + ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition); } TypeCheckBlock(ifNode.Body); @@ -216,9 +245,9 @@ public class TypeChecker private void TypeCheckWhile(WhileNode whileNode) { var conditionType = TypeCheckExpression(whileNode.Condition); - if (!conditionType.Equals(NubPrimitiveType.Bool)) + if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool)) { - throw new TypeCheckingException($"While condition must be a boolean expression, got '{conditionType}'"); + ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition); } TypeCheckBlock(whileNode.Body); @@ -231,24 +260,26 @@ public class TypeChecker if (returnNode.Value.HasValue) { var returnType = TypeCheckExpression(returnNode.Value.Value); + if (returnType == null) return; if (_currentFunctionReturnType == null) { - throw new TypeCheckingException("Cannot return a value from a function with no return type"); + ReportError("Cannot return a value from a function with no return type", returnNode.Value.Value); + return; } if (!AreTypesCompatible(returnType, _currentFunctionReturnType)) { - throw new TypeCheckingException($"Return value of type '{returnType}' is not compatible with function return type '{_currentFunctionReturnType}'"); + ReportError($"Return value of type '{returnType}' is not compatible with function return type '{_currentFunctionReturnType}'", returnNode.Value.Value); } } else if (_currentFunctionReturnType != null) { - throw new TypeCheckingException($"Function must return a value of type '{_currentFunctionReturnType}'"); + ReportError($"Function must return a value of type '{_currentFunctionReturnType}'", returnNode); } } - private NubType TypeCheckExpression(ExpressionNode expression) + private NubType? TypeCheckExpression(ExpressionNode expression) { var resultType = expression switch { @@ -259,63 +290,83 @@ public class TypeChecker BinaryExpressionNode binaryExpr => TypeCheckBinaryExpression(binaryExpr), CastNode cast => TypeCheckCast(cast), DereferenceNode dereference => TypeCheckDereference(dereference), - FuncCallExpressionNode funcCallExpr => TypeCheckFuncCall(funcCallExpr.FuncCall), + FuncCallExpressionNode funcCallExpr => TypeCheckFuncCall(funcCallExpr.FuncCall, funcCallExpr), StructInitializerNode structInit => TypeCheckStructInitializer(structInit), UnaryExpressionNode unaryExpression => TypeCheckUnaryExpression(unaryExpression), MemberAccessNode memberAccess => TypeCheckMemberAccess(memberAccess), - _ => throw new TypeCheckingException($"Unsupported expression type: {expression.GetType().Name}") + _ => ReportUnsupportedExpression(expression) }; - expression.Type = resultType; + if (resultType != null) + { + expression.Type = resultType; + } + return resultType; } - private NubType TypeCheckArrayIndex(ArrayIndexNode arrayIndex) + private NubType? ReportUnsupportedExpression(ExpressionNode expression) + { + ReportError($"Unsupported expression type: {expression.GetType().Name}", expression); + return null; + } + + private NubType? TypeCheckArrayIndex(ArrayIndexNode arrayIndex) { var expressionType = TypeCheckExpression(arrayIndex.Expression); + if (expressionType == null) return null; + if (expressionType is not NubArrayType arrayType) { - throw new TypeCheckingException($"Annot access index of non-array type {expressionType}"); + ReportError($"Cannot access index of non-array type {expressionType}", arrayIndex.Expression); + return null; } var indexType = TypeCheckExpression(arrayIndex.Index); - if (!IsInteger(indexType)) + if (indexType != null && !IsInteger(indexType)) { - throw new TypeCheckingException("Array index type must be an integer"); + ReportError("Array index type must be an integer", arrayIndex.Index); } return arrayType.BaseType; } - private NubType TypeCheckIdentifier(IdentifierNode identifier) + private NubType? TypeCheckIdentifier(IdentifierNode identifier) { if (!_variables.TryGetValue(identifier.Identifier, out var varType)) { - throw new TypeCheckingException($"Variable '{identifier.Identifier}' is not defined"); + ReportError($"Variable '{identifier.Identifier}' is not defined", identifier); + return null; } return varType; } - private NubType TypeCheckAddressOf(AddressOfNode addressOf) + private NubType? TypeCheckAddressOf(AddressOfNode addressOf) { - TypeCheckExpression(addressOf.Expression); + var exprType = TypeCheckExpression(addressOf.Expression); + if (exprType == null) return null; + if (addressOf.Expression is not (IdentifierNode or MemberAccessNode)) { - throw new TypeCheckingException($"Cannot take the address of {addressOf.Expression.Type}"); + ReportError($"Cannot take the address of {exprType}", addressOf.Expression); + return null; } - return new NubPointerType(addressOf.Expression.Type); + return new NubPointerType(exprType); } - private NubType TypeCheckBinaryExpression(BinaryExpressionNode binaryExpr) + private NubType? TypeCheckBinaryExpression(BinaryExpressionNode binaryExpr) { var leftType = TypeCheckExpression(binaryExpr.Left); var rightType = TypeCheckExpression(binaryExpr.Right); + if (leftType == null || rightType == null) return null; + if (!leftType.Equals(rightType)) { - throw new TypeCheckingException($"Left '{leftType}' and right '{rightType}' side of the binary expression is not equal"); + ReportError($"Left '{leftType}' and right '{rightType}' side of the binary expression must be the same type", binaryExpr); + return null; } switch (binaryExpr.Operator) @@ -329,7 +380,8 @@ public class TypeChecker case BinaryExpressionOperator.LessThanOrEqual: if (!IsNumeric(leftType)) { - throw new TypeCheckingException($"Comparison operators require numeric operands, got '{leftType}' and '{rightType}'"); + ReportError($"Comparison operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr); + return null; } return NubPrimitiveType.Bool; @@ -339,36 +391,40 @@ public class TypeChecker case BinaryExpressionOperator.Divide: if (!IsNumeric(leftType)) { - throw new TypeCheckingException($"Arithmetic operators require numeric operands, got '{leftType}' and '{rightType}'"); + ReportError($"Arithmetic operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr); + return null; } return leftType; default: - throw new TypeCheckingException($"Unsupported binary operator: {binaryExpr.Operator}"); + ReportError($"Unsupported binary operator: {binaryExpr.Operator}", binaryExpr); + return null; } } - private NubType TypeCheckCast(CastNode cast) + private NubType? TypeCheckCast(CastNode cast) { TypeCheckExpression(cast.Expression); // TODO: Check if castable return cast.TargetType; } - private NubType TypeCheckStructInitializer(StructInitializerNode structInit) + private NubType? TypeCheckStructInitializer(StructInitializerNode structInit) { var initialized = new HashSet(); var structType = structInit.StructType; if (structType is not NubStructType customType) { - throw new TypeCheckingException($"Type '{structType}' is not a struct type"); + ReportError($"Type '{structType}' is not a struct type", structInit); + return null; } var definition = _definitions.OfType().FirstOrDefault(s => s.Name == structInit.StructType.Name); if (definition == null) { - throw new TypeCheckingException($"Struct type '{customType.Name}' is not defined"); + ReportError($"Struct type '{customType.Name}' is not defined", structInit); + return null; } foreach (var initializer in structInit.Initializers) @@ -376,13 +432,14 @@ public class TypeChecker var definitionField = definition.Fields.FirstOrDefault(f => f.Name == initializer.Key); if (definitionField == null) { - throw new TypeCheckingException($"Field '{initializer.Key}' does not exist in struct '{customType.Name}'"); + ReportError($"Field '{initializer.Key}' does not exist in struct '{customType.Name}'", initializer.Value); + continue; } var initializerType = TypeCheckExpression(initializer.Value); - if (!AreTypesCompatible(initializerType, definitionField.Type)) + if (initializerType != null && !AreTypesCompatible(initializerType, definitionField.Type)) { - throw new TypeCheckingException($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'"); + ReportError($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'", initializer.Value); } initialized.Add(initializer.Key); @@ -397,16 +454,17 @@ public class TypeChecker { if (!initialized.Contains(field.Name)) { - throw new TypeCheckingException($"Struct field '{field.Name}' is not initialized on type '{customType.Name}'"); + ReportError($"Struct field '{field.Name}' is not initialized on type '{customType.Name}'", structInit); } } return structType; } - private NubType TypeCheckUnaryExpression(UnaryExpressionNode unaryExpression) + private NubType? TypeCheckUnaryExpression(UnaryExpressionNode unaryExpression) { var operandType = TypeCheckExpression(unaryExpression.Operand); + if (operandType == null) return null; switch (unaryExpression.Operator) { @@ -422,27 +480,32 @@ public class TypeChecker return operandType; } - throw new TypeCheckingException($"Cannot negate non-numeric type {operandType}"); + ReportError($"Cannot negate non-numeric type {operandType}", unaryExpression.Operand); + return null; } case UnaryExpressionOperator.Invert: { if (!operandType.Equals(NubPrimitiveType.Bool)) { - throw new TypeCheckingException($"Cannot invert non-boolean type {operandType}"); + ReportError($"Cannot invert non-boolean type {operandType}", unaryExpression.Operand); + return null; } return operandType; } default: { - throw new ArgumentOutOfRangeException(); + ReportError($"Unsupported unary operator: {unaryExpression.Operator}", unaryExpression); + return null; } } } - private NubType TypeCheckMemberAccess(MemberAccessNode memberAccess) + private NubType? TypeCheckMemberAccess(MemberAccessNode memberAccess) { var expressionType = TypeCheckExpression(memberAccess.Expression); + if (expressionType == null) return null; + switch (expressionType) { case NubArrayType: @@ -459,20 +522,29 @@ public class TypeChecker var definition = _definitions.OfType().FirstOrDefault(s => s.Name == structType.Name); if (definition == null) { - throw new TypeCheckingException($"Struct type '{structType.Name}' is not defined"); + ReportError($"Struct type '{structType.Name}' is not defined", memberAccess); + return null; } var field = definition.Fields.FirstOrDefault(f => f.Name == memberAccess.Member); if (field == null) { - throw new TypeCheckingException($"Field '{memberAccess.Member}' does not exist in struct '{structType.Name}'"); + ReportError($"Field '{memberAccess.Member}' does not exist in struct '{structType.Name}'", memberAccess); + return null; } return field.Type; } } - throw new TypeCheckingException($"Cannot access member '{memberAccess.Member}' on type '{expressionType}'"); + ReportError($"Cannot access member '{memberAccess.Member}' on type '{expressionType}'", memberAccess); + return null; + } + + private void ReportError(string message, Node node) + { + var diagnostic = Diagnostic.Error(message).At(node).Build(); + _diagnostics.Add(diagnostic); } private static bool AreTypesCompatible(NubType sourceType, NubType targetType) diff --git a/src/compiler/Nub.Lang/Program.cs b/src/compiler/Nub.Lang/Program.cs index 5537887..8ce43cc 100644 --- a/src/compiler/Nub.Lang/Program.cs +++ b/src/compiler/Nub.Lang/Program.cs @@ -10,7 +10,7 @@ internal static class Program { private static readonly Lexer Lexer = new(); private static readonly Parser Parser = new(); - + public static int Main(string[] args) { if (args.Length != 2) @@ -19,16 +19,16 @@ internal static class Program Console.WriteLine("Example: nub src out.asm"); return 1; } - + var input = Path.GetFullPath(args[0]); var output = Path.GetFullPath(args[1]); - + if (!Directory.Exists(input)) { Console.WriteLine($"Error: Input directory '{input}' does not exist."); return 1; } - + var outputDir = Path.GetDirectoryName(output); if (outputDir == null || !Directory.Exists(outputDir)) { @@ -42,24 +42,25 @@ internal static class Program return 1; } - List diagnostics = []; - - var modules = RunFrontend(input, diagnostics); - - foreach (var diagnostic in diagnostics) + List modules; + try { - Console.WriteLine(diagnostic.Format()); + modules = RunFrontend(input); } - - if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)) + catch (Exception) { return 1; } - + var definitions = modules.SelectMany(f => f.Definitions).ToList(); var typeChecker = new TypeChecker(definitions); - typeChecker.TypeCheck(); + var typeCheckResult = typeChecker.TypeCheck(); + typeCheckResult.PrintAllDiagnostics(); + if (typeCheckResult.HasErrors) + { + return 1; + } var generator = new Generator(definitions); var result = generator.Generate(); @@ -68,14 +69,14 @@ internal static class Program return 0; } - private static List RunFrontend(string rootFilePath, List diagnostics) + private static List RunFrontend(string rootFilePath) { List modules = []; - RunFrontend(rootFilePath, modules, diagnostics); + RunFrontend(rootFilePath, modules); return modules; } - private static void RunFrontend(string rootFilePath, List modules, List diagnostics) + private static void RunFrontend(string rootFilePath, List modules) { var filePaths = Directory.EnumerateFiles(rootFilePath, "*.nub", SearchOption.TopDirectoryOnly); @@ -86,16 +87,21 @@ internal static class Program tokens.AddRange(Lexer.Lex(src, new SourceFile(filePath, src))); } - var module = Parser.ParseModule(tokens, rootFilePath); - diagnostics.AddRange(Parser.Diagnostics); - modules.Add(module); - - foreach (var import in module.Imports) + var parseResult = Parser.ParseModule(tokens, rootFilePath); + parseResult.PrintAllDiagnostics(); + if (parseResult.HasErrors) { - var importPath = Path.GetFullPath(import, module.Path); + throw new Exception(); + } + + modules.Add(parseResult.Value); + + foreach (var import in parseResult.Value.Imports) + { + var importPath = Path.GetFullPath(import, parseResult.Value.Path); if (modules.All(m => m.Path != importPath)) { - RunFrontend(importPath, modules, diagnostics); + RunFrontend(importPath, modules); } } }