diff --git a/Source/AtlusScriptLibrary/FlowScriptLanguage/Compiler/FlowScriptCompiler.cs b/Source/AtlusScriptLibrary/FlowScriptLanguage/Compiler/FlowScriptCompiler.cs index 0d4a3497..bbee33b6 100644 --- a/Source/AtlusScriptLibrary/FlowScriptLanguage/Compiler/FlowScriptCompiler.cs +++ b/Source/AtlusScriptLibrary/FlowScriptLanguage/Compiler/FlowScriptCompiler.cs @@ -125,14 +125,59 @@ public void AddListener(LogListener listener) listener.Subscribe(mLogger); } + /// + /// Tries to get a list of all files that would be imported (directly or transitively) when compiling a flowscript file. + /// + /// A List of paths to .bf, .flow, and .msg files to be used as a base when checking for imports. + /// A list of all imports found. This includes the passed in . + /// True if imports could be resolved, false otherwise + public bool TryGetImports(List files, out string[] resolvedImports) + { + var imports = files.Select(import => new Import(import)).ToList(); + mCurrentBaseDirectory = ""; + InitializeCompilationState(); + + // Resolve imports + if (imports.Count > 0) + { + do + { + if (!TryResolveImportsSimple(imports)) + { + Error("Failed to resolve imports"); + resolvedImports = Array.Empty(); + return false; + } + } while (mReresolveImports); + } + + resolvedImports = imports.Select(import => import.CompilationUnitFileName).ToArray(); + return true; + } + /// /// Tries to compile the provided FlowScript source with given imports. Returns a boolean indicating if the operation succeeded. /// /// A FileStream of the base bf file /// A List of paths to .bf, .flow, and .msg files that will be forcibly imported + /// A full path to the base .flow file to use for compilation /// The compiled FlowScript /// True if the file successfully compiled, false otherwise public bool TryCompileWithImports(FileStream baseBfStream, List imports, string baseFlow, out FlowScript flowScript) + { + return TryCompileWithImports(baseBfStream, imports, baseFlow, out flowScript, out _); + } + + /// + /// Tries to compile the provided FlowScript source with given imports. Returns a boolean indicating if the operation succeeded. + /// + /// A FileStream of the base bf file + /// A List of paths to .bf, .flow, and .msg files that will be forcibly imported + /// A full path to the base .flow file to use for compilation + /// The compiled FlowScript or null if compilation failed + /// A list of full paths to all source files used to compile this bf or null if compilation failed + /// True if the file successfully compiled, false otherwise + public bool TryCompileWithImports(FileStream baseBfStream, List imports, string baseFlow, out FlowScript flowScript, out List sources) { // Parse base flow file CompilationUnit compilationUnit; @@ -160,6 +205,7 @@ public bool TryCompileWithImports(FileStream baseBfStream, List imports, { Error("Failed to parse compilation unit"); flowScript = null; + sources = null; return false; } } @@ -178,7 +224,17 @@ public bool TryCompileWithImports(FileStream baseBfStream, List imports, compilationUnit.Imports.AddRange(imports.Select(import => new Import(import))); mCurrentBaseDirectory = ""; - return TryCompile(compilationUnit, out flowScript); + if (TryCompile(compilationUnit, out flowScript)) + { + sources = compilationUnit.Imports.Select(import => import.CompilationUnitFileName).ToList(); + sources.Add(baseFlow); + return true; + } + else + { + sources = null; + return false; + } } /// @@ -442,7 +498,101 @@ private void ExpandImportStatementsPaths(CompilationUnit compilationUnit, string import.CompilationUnitFileName = Path.Combine(baseDirectory, import.CompilationUnitFileName); } } + + private void ExpandImportStatementsPaths(List imports, string baseDirectory) + { + foreach (var import in imports) + { + import.CompilationUnitFileName = Path.Combine(baseDirectory, import.CompilationUnitFileName); + } + } + + /// + /// Tries to resolve a list of imports whilst only parsing flowscript files (since they can contain additional imports). + /// Compiled flowscript and message files are not parsed, they are just added to the list of imports. + /// + /// This can be used to determine a list of all imports starting from some initial ones. + /// It is not sufficient to actually compile the flowscript. + /// + /// is set to true this should be run again to determine additional imports from + /// flowscript files. + /// + /// The imports to resolve. Newly found imports are added to this. + /// True if imports could be resolved, false otherwise + private bool TryResolveImportsSimple(List imports) + { + Info("Resolving imports"); + ExpandImportStatementsPaths(imports, mCurrentBaseDirectory); + + var importedFlowScripts = new List(); + var importedMsgAndBfs = new List(); + + foreach (var import in imports) + { + var ext = Path.GetExtension(import.CompilationUnitFileName).ToLowerInvariant(); + + switch (ext) + { + case ".msg" or ".bf": + { + if (!TryGetFullImportPath(import, out var compilationUnitFilePath)) + { + Error($"Failed to resolve import: {import.CompilationUnitFileName}"); + return false; + } + importedMsgAndBfs.Add(new Import(compilationUnitFilePath)); + } + break; + + case ".flow": + { + // FlowScript + if (!TryResolveFlowScriptImport(import, out var importedCompilationUnit)) + { + Error(import, $"Failed to resolve FlowScript import: {import.CompilationUnitFileName}"); + return false; + } + + // Will be null if it was already imported before + if (importedCompilationUnit != null) + importedFlowScripts.Add(importedCompilationUnit); + } + break; + + default: + // Unknown + Error(import, $"Unknown import file type: {import.CompilationUnitFileName}"); + return false; + } + } + + // Resolve FlowScript imports + bool shouldReresolveImports = false; + if (importedFlowScripts.Count > 0) + { + // Merge compilation units + foreach (var importedFlowScript in importedFlowScripts) + { + if (importedFlowScript.Imports.Count > 0) + { + // If any of the imported FlowScripts have import, we have to re-resolve the imports again + shouldReresolveImports = true; + imports.AddRange(importedFlowScript.Imports); + } + } + } + + mReresolveImports = shouldReresolveImports; + + if (!mReresolveImports) + Info("Done resolving imports"); + + imports.AddRange(importedMsgAndBfs); + + return true; + } + // // Resolving imports //