diff --git a/implement/pine/Elm/ElmPackageSource.cs b/implement/pine/Elm/ElmPackageSource.cs
new file mode 100644
index 00000000..c4c683b4
--- /dev/null
+++ b/implement/pine/Elm/ElmPackageSource.cs
@@ -0,0 +1,173 @@
+using Pine.Core;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Pine.Elm;
+
+///
+/// Provides functionality to load Elm packages by either retrieving a locally cached ZIP file
+/// or downloading it from GitHub. The loaded package contents are returned as an in-memory
+/// dictionary mapping path segments to file contents. If no valid local cache is found,
+/// the package is fetched from GitHub and saved to the first cache directory for future use.
+///
+/// Usage:
+///
+/// var packageFiles = await ElmPackageSource.LoadElmPackageAsync("agu-z/elm-zip", "3.0.1");
+/// // Access file contents by path segments from the returned dictionary
+///
+///
+public class ElmPackageSource
+{
+ ///
+ /// Default local cache directories (example: ~/.cache/elm-package).
+ /// You can add more directories if you want a search path.
+ ///
+ public static IReadOnlyList LocalCacheDirectoriesDefault =>
+ [
+ Path.Combine(Filesystem.CacheDirectory, "elm-package"),
+ ];
+
+ ///
+ /// Public entry point that uses the default cache directories.
+ ///
+ public static async Task, ReadOnlyMemory>> LoadElmPackageAsync(
+ string packageName,
+ string versionId)
+ {
+ return await LoadElmPackageAsync(packageName, versionId, LocalCacheDirectoriesDefault);
+ }
+
+ ///
+ /// Tries to load from any of the given local cache directories; if not found, downloads from GitHub and saves to the first cache dir.
+ ///
+ public static async Task, ReadOnlyMemory>> LoadElmPackageAsync(
+ string packageName,
+ string versionId,
+ IReadOnlyList localCacheDirectories)
+ {
+ // 1) Try to load from each cache directory, in order:
+ foreach (var cacheDirectory in localCacheDirectories)
+ {
+ // Construct the path for the cached file, for example "agu-z-elm-zip@3.0.1.zip".
+ var localZipPath = GetLocalZipPath(cacheDirectory, packageName, versionId);
+
+ if (File.Exists(localZipPath))
+ {
+ try
+ {
+ // Attempt to load and parse the ZIP from disk.
+ var data = await File.ReadAllBytesAsync(localZipPath);
+
+ var fromCache = LoadElmPackageFromZipBytes(data);
+
+ return fromCache;
+ }
+ catch
+ {
+ // If anything failed (e.g. file is corrupt), ignore and keep going.
+ }
+ }
+ }
+
+ // 2) No valid cache found — download from GitHub:
+ var zipData = await DownloadPackageZipAsync(packageName, versionId);
+
+ var fromGitHub = LoadElmPackageFromZipBytes(zipData);
+
+ // 3) Write the ZIP to the *first* cache directory, if present (and possible).
+ if (localCacheDirectories.Count > 0)
+ {
+ var firstCache = localCacheDirectories[0];
+
+ try
+ {
+ Directory.CreateDirectory(firstCache); // Ensure the directory exists.
+
+ var localZipPath = GetLocalZipPath(firstCache, packageName, versionId);
+
+ await File.WriteAllBytesAsync(localZipPath, zipData);
+ }
+ catch
+ {
+ // Caching is non-critical. Swallow any exception here (e.g. no write permission).
+ }
+ }
+
+ return fromGitHub;
+ }
+
+ ///
+ /// Given the raw bytes of a ZIP file, extract all files and return them as a dictionary
+ /// from path segments to the file contents.
+ ///
+ private static IReadOnlyDictionary, ReadOnlyMemory> LoadElmPackageFromZipBytes(
+ byte[] zipBytes)
+ {
+ var result = new Dictionary, ReadOnlyMemory>(
+ EnumerableExtension.EqualityComparer>());
+
+ using var memoryStream = new MemoryStream(zipBytes);
+
+ using var archive = new System.IO.Compression.ZipArchive(memoryStream, ZipArchiveMode.Read);
+
+ foreach (var entry in archive.Entries)
+ {
+ // If it's a directory entry, skip
+ if (string.IsNullOrEmpty(entry.Name))
+ continue;
+
+ using var entryStream = entry.Open();
+ using var entryMemoryStream = new MemoryStream();
+ entryStream.CopyTo(entryMemoryStream);
+
+ var fileContents = entryMemoryStream.ToArray();
+
+ // Split the full name into segments
+ var pathSegments = entry.FullName.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+
+ result[pathSegments] = fileContents;
+ }
+
+ return result;
+ }
+
+
+ ///
+ /// Downloads the ZIP from GitHub as a byte array.
+ ///
+ private static async Task DownloadPackageZipAsync(string packageName, string versionId)
+ {
+ // Example: "https://github.com/agu-z/elm-zip/archive/refs/tags/3.0.1.zip"
+
+ var downloadUrl = "https://github.com/" + packageName.Trim('/') + "/archive/refs/tags/" + versionId + ".zip";
+
+ using var httpClient = new HttpClient();
+
+ using var zipStream = await httpClient.GetStreamAsync(downloadUrl);
+
+ // Copy to memory
+ using var memoryStream = new MemoryStream();
+
+ await zipStream.CopyToAsync(memoryStream);
+
+ return memoryStream.ToArray();
+ }
+
+ ///
+ /// Constructs a local ZIP file path: e.g. "agu-z-elm-zip@3.0.1.zip" in the given cache directory.
+ /// Replaces slashes so the filename is filesystem-safe.
+ ///
+ private static string GetLocalZipPath(string cacheDirectory, string packageName, string versionId)
+ {
+ var safePkgName = packageName.Replace('/', '-');
+
+ // Example: "agu-z-elm-zip@3.0.1.zip"
+ var fileName = $"{safePkgName}@{versionId}.zip";
+
+ return Path.Combine(cacheDirectory, fileName);
+ }
+}
diff --git a/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm b/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm
index 42e0144d..e0b31ef4 100644
--- a/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm
+++ b/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm
@@ -703,53 +703,18 @@ loweredForBlobEntryPoint :
-> AppFiles
-> Result (List (LocatedInSourceFiles CompilationError)) ( AppFiles, ElmMakeEntryPointStruct )
loweredForBlobEntryPoint { compilationRootFilePath, compilationRootModule } sourceFiles =
- ([ compilationRootModule.fileText
- , String.trim """
-blob_main_as_base64 : String
-blob_main_as_base64 =
- blobMain
- |> Base64.fromBytes
- |> Maybe.withDefault "Failed to encode as Base64"
-
-
-{-| Support function-level dead code elimination () Elm code needed to inform the Elm compiler about our entry points.
--}
-main : Program Int {} String
-main =
- Platform.worker
- { init = always ( {}, Cmd.none )
- , update = always (always ( blob_main_as_base64 |> always {}, Cmd.none ))
- , subscriptions = always Sub.none
- }
-"""
- ]
- |> String.join "\n\n"
- |> addImportsInElmModuleText [ ( [ "Base64" ], Nothing ) ]
- |> Result.mapError
- (\err ->
- [ LocatedInSourceFiles
- { filePath = compilationRootFilePath
- , locationInModuleText = Elm.Syntax.Range.emptyRange
- }
- (OtherCompilationError ("Failed to add import: " ++ err))
- ]
- )
- )
- |> Result.map
- (\rootModuleText ->
- ( sourceFiles
- |> updateFileContentAtPath (always (fileContentFromString rootModuleText)) compilationRootFilePath
- , { elmMakeJavaScriptFunctionName =
- ((compilationRootModule.parsedSyntax.moduleDefinition
- |> Elm.Syntax.Node.value
- |> Elm.Syntax.Module.moduleName
- )
- ++ [ "blob_main_as_base64" ]
- )
- |> String.join "."
- }
+ Ok
+ ( sourceFiles
+ , { elmMakeJavaScriptFunctionName =
+ ((compilationRootModule.parsedSyntax.moduleDefinition
+ |> Elm.Syntax.Node.value
+ |> Elm.Syntax.Module.moduleName
+ )
+ ++ [ "blobMain" ]
)
- )
+ |> String.join "."
+ }
+ )
sourceFileFunctionNameStart : String
diff --git a/implement/pine/Elm019/ElmPackage.cs b/implement/pine/Elm019/ElmPackage.cs
new file mode 100644
index 00000000..a8d05e0b
--- /dev/null
+++ b/implement/pine/Elm019/ElmPackage.cs
@@ -0,0 +1,110 @@
+using Pine.Core;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+
+namespace Pine.Elm019;
+
+public class ElmPackage
+{
+ record ParsedModule(
+ IReadOnlyList ModuleName,
+ IImmutableSet> ImportedModulesNames);
+
+ public static IReadOnlyDictionary, (ReadOnlyMemory fileContent, string moduleText, IReadOnlyList moduleName)>
+ ExposedModules(
+ IReadOnlyDictionary, ReadOnlyMemory> packageSourceFiles)
+ {
+ Dictionary, (ReadOnlyMemory fileContent, string moduleText, IReadOnlyList moduleName)> exposedModules =
+ new(EnumerableExtension.EqualityComparer>());
+
+ var elmJsonFile =
+ /*
+ * If package files come from GitHub zip archive, then elm.json file might not be at the root but in
+ * a directory named after the repository, e.g. "elm-flate-2.0.5".
+ * */
+ packageSourceFiles
+ .OrderBy(kv => kv.Key.Count)
+ .FirstOrDefault(kv => kv.Key.Last() is "elm.json");
+
+ if (elmJsonFile.Key is null)
+ {
+ return exposedModules;
+ }
+
+ var elmJson = JsonSerializer.Deserialize(elmJsonFile.Value.Span);
+
+ Dictionary, (IReadOnlyList filePath, ReadOnlyMemory fileContent, string moduleText, ParsedModule parsedModule)> parsedModulesByName =
+ new(EnumerableExtension.EqualityComparer>());
+
+ foreach (var (filePath, fileContent) in packageSourceFiles)
+ {
+ try
+ {
+ var moduleText = Encoding.UTF8.GetString(fileContent.Span);
+
+ if (ElmTime.ElmSyntax.ElmModule.ParseModuleName(moduleText).IsOkOrNull() is not { } moduleName)
+ {
+ continue;
+ }
+
+ parsedModulesByName[moduleName] =
+ (filePath,
+ fileContent,
+ moduleText,
+ new ParsedModule(
+ ModuleName: moduleName,
+ ImportedModulesNames: [.. ElmTime.ElmSyntax.ElmModule.ParseModuleImportedModulesNames(moduleText)]));
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ IReadOnlySet> ListImportsOfModuleTransitive(IReadOnlyList moduleName)
+ {
+ var queue =
+ new Queue>([moduleName]);
+
+ var set =
+ new HashSet>(EnumerableExtension.EqualityComparer>());
+
+ while (queue.TryDequeue(out var currentModuleName))
+ {
+ if (set.Add(currentModuleName) &&
+ parsedModulesByName.TryGetValue(currentModuleName, out var currentModule))
+ {
+ foreach (var importedModuleName in currentModule.parsedModule.ImportedModulesNames)
+ {
+ queue.Enqueue(importedModuleName);
+ }
+ }
+ }
+
+ return set;
+ }
+
+ foreach (var exposedModuleNameFlat in elmJson.ExposedModules)
+ {
+ var exposedModuleName = exposedModuleNameFlat.Split('.').ToImmutableList();
+
+ var exposedNameIncludingDependencies =
+ ListImportsOfModuleTransitive(exposedModuleName)
+ .Prepend(exposedModuleName);
+
+ foreach (var moduleName in exposedNameIncludingDependencies)
+ {
+ if (parsedModulesByName.TryGetValue(moduleName, out var module))
+ {
+ exposedModules[module.filePath] =
+ (module.fileContent, module.moduleText, module.parsedModule.ModuleName);
+ }
+ }
+ }
+
+ return exposedModules;
+ }
+}
diff --git a/implement/pine/ElmSyntax/ElmModule.cs b/implement/pine/ElmSyntax/ElmModule.cs
index f9c864da..b87c4a75 100644
--- a/implement/pine/ElmSyntax/ElmModule.cs
+++ b/implement/pine/ElmSyntax/ElmModule.cs
@@ -146,6 +146,21 @@ public class DelegateComparer(Func func) : IComparer
public int Compare(T? x, T? y) => func(x, y);
}
+ public static Result> ParseModuleName(ReadOnlyMemory moduleContent)
+ {
+ try
+ {
+ var moduleText =
+ System.Text.Encoding.UTF8.GetString(moduleContent.Span);
+
+ return ParseModuleName(moduleText);
+ }
+ catch (Exception exception)
+ {
+ return "Failed decoding text: " + exception.Message;
+ }
+ }
+
public static Result> ParseModuleName(string moduleText)
{
{
diff --git a/implement/pine/Program.cs b/implement/pine/Program.cs
index 3fd43d3e..80545b98 100644
--- a/implement/pine/Program.cs
+++ b/implement/pine/Program.cs
@@ -9,6 +9,7 @@
using Pine.Elm;
using Pine.Elm.Platform;
using Pine.Elm019;
+using Pine.ElmInteractive;
using Pine.PineVM;
using System;
using System.Collections.Generic;
@@ -2278,91 +2279,268 @@ private record LoadForMakeResult(
{
workingDirectoryRelative ??= [];
- var pathToFileWithElmEntryPointFromWorkingDir =
- pathToFileWithElmEntryPoint.Skip(workingDirectoryRelative.Count).ToImmutableList();
+ IReadOnlyList pathToFileWithElmEntryPointFromWorkingDir =
+ [.. pathToFileWithElmEntryPoint.Skip(workingDirectoryRelative.Count)];
- return
+ var loweringResult =
ElmAppCompilation.AsCompletelyLoweredElmApp(
- sourceFiles: sourceFiles.ToImmutableDictionary(),
+ sourceFiles: sourceFiles.ToImmutableDictionary(),
+ workingDirectoryRelative: workingDirectoryRelative,
+ interfaceConfig: new ElmAppInterfaceConfig(compilationRootFilePath: pathToFileWithElmEntryPoint));
+
+ var entryPointSourceFile =
+ sourceFiles[pathToFileWithElmEntryPoint];
+
+ var entryPointSourceFileText =
+ Encoding.UTF8.GetString(entryPointSourceFile.Span);
+
+ var entryPointModuleNameResult =
+ ElmSyntax.ElmModule.ParseModuleName(entryPointSourceFileText);
+
+ if (entryPointModuleNameResult.IsErrOrNull() is { } entryPointModuleNameErr)
+ {
+ return
+ "Failed to parse module name from entry point file: " + entryPointModuleNameErr;
+ }
+
+ if (entryPointModuleNameResult.IsOkOrNull() is not { } entryPointModuleNameOk)
+ {
+ throw new Exception(
+ "Unexpected entry point module name result type: " + entryPointModuleNameResult);
+ }
+
+ if (loweringResult.IsErrOrNull() is { } loweringErr)
+ {
+ return
+ "Failed lowering Elm code with " + loweringErr.Count + " error(s):\n" +
+ ElmAppCompilation.CompileCompilationErrorsDisplayText(loweringErr);
+ }
+
+ if (loweringResult.IsOkOrNull() is not { } loweringOk)
+ {
+ throw new Exception("Unexpected lowering result type: " + loweringResult);
+ }
+
+ var sourceFilesAfterLowering = loweringOk.result.compiledFiles;
+
+ Result continueWithClassicEntryPoint()
+ {
+ return
+ Elm019Binaries.ElmMake(
+ sourceFilesAfterLowering,
workingDirectoryRelative: workingDirectoryRelative,
- interfaceConfig: new ElmAppInterfaceConfig(compilationRootFilePath: pathToFileWithElmEntryPoint))
- .MapError(err =>
- "Failed lowering Elm code with " + err.Count + " error(s):\n" +
- ElmAppCompilation.CompileCompilationErrorsDisplayText(err))
- .AndThen(loweringOk =>
- {
- var sourceFilesAfterLowering = loweringOk.result.compiledFiles;
+ pathToFileWithElmEntryPoint: pathToFileWithElmEntryPointFromWorkingDir,
+ outputFileName: outputFileName.Replace('\\', '/').Split('/').Last(),
+ elmMakeCommandAppendix: elmMakeCommandAppendix);
+ }
- Result continueWithClassicEntryPoint()
+ Result continueWithBlobEntryPoint(
+ CompilerSerialInterface.ElmMakeEntryPointStruct entryPointStruct)
+ {
+ /*
+ * TODO: select elm.json for the given entry point
+ * */
+
+ var elmJsonFiles =
+ sourceFilesAfterLowering
+ .Where(entry => entry.Key.Last() is "elm.json")
+ .ToImmutableDictionary();
+
+ var elmJsonAggregateDependencies =
+ elmJsonFiles
+ .SelectMany(elmJsonFile =>
+ {
+ try
{
+ var elmJsonParsed =
+ System.Text.Json.JsonSerializer.Deserialize(elmJsonFile.Value.Span);
+
return
- Elm019Binaries.ElmMake(
- sourceFilesAfterLowering,
- workingDirectoryRelative: workingDirectoryRelative,
- pathToFileWithElmEntryPoint: pathToFileWithElmEntryPointFromWorkingDir,
- outputFileName: outputFileName.Replace('\\', '/').Split('/').Last(),
- elmMakeCommandAppendix: elmMakeCommandAppendix);
+ new[]
+ {
+ elmJsonParsed?.Dependencies.Direct,
+ elmJsonParsed?.Dependencies.Indirect,
+ elmJsonParsed?.Dependencies.Flat
+ }
+ .WhereNotNull();
}
+ catch (Exception e)
+ {
+ Console.WriteLine("Failed to parse elm.json file: " + e);
- Result continueWithBlobEntryPoint(
- CompilerSerialInterface.ElmMakeEntryPointStruct entryPointStruct)
+ return [];
+ }
+ })
+ .SelectMany(dependency => dependency)
+ .ToImmutableDictionary(
+ keySelector: dependency => dependency.Key,
+ elementSelector:
+ dependency =>
{
+ var packageFiles =
+ ElmPackageSource.LoadElmPackageAsync(dependency.Key, dependency.Value).Result;
+
+ return packageFiles;
+ });
+
+ var sourceElmModulesNames =
+ sourceFilesAfterLowering
+ .Where(entry => entry.Key.Last().EndsWith(".elm", StringComparison.OrdinalIgnoreCase))
+ .Select(entry => ElmSyntax.ElmModule.ParseModuleName(entry.Value).WithDefault(null))
+ .WhereNotNull()
+ .ToImmutableHashSet(EnumerableExtension.EqualityComparer>());
+
+ var packagesModulesNames =
+ new HashSet>(EnumerableExtension.EqualityComparer>());
+
+ var sourceFilesWithMergedPackages =
+ elmJsonAggregateDependencies
+ .Aggregate(
+ seed: sourceFilesAfterLowering,
+ func: (aggregate, package) =>
+ {
+ if (package.Key is "elm/core" ||
+ package.Key is "elm/json" ||
+ package.Key is "elm/bytes" ||
+ package.Key is "elm/parser")
+ {
+ return aggregate;
+ }
+
+ var packageExposedModuleFiles =
+ ElmPackage.ExposedModules(package.Value);
+
return
- Elm019Binaries.ElmMakeToJavascript(
- sourceFilesAfterLowering,
- workingDirectoryRelative: workingDirectoryRelative,
- pathToFileWithElmEntryPoint: pathToFileWithElmEntryPointFromWorkingDir,
- elmMakeCommandAppendix: elmMakeCommandAppendix)
- .AndThen(makeJavascriptOk =>
+ packageExposedModuleFiles
+ .Aggregate(
+ seed: aggregate,
+ func: (innerAggregate, packageElmModuleFile) =>
+ {
+ var relativePath = packageElmModuleFile.Key;
+
+ var moduleName = packageElmModuleFile.Value.moduleName;
+
+ if (sourceElmModulesNames.Contains(moduleName))
{
- var javascriptFromElmMake =
- Encoding.UTF8.GetString(makeJavascriptOk.producedFile.Span);
+ Console.WriteLine(
+ "Skipping Elm module file " +
+ string.Join("/", relativePath) +
+ " from package " + package.Key + " because it is already present in the source files.");
- var javascriptMinusCrashes =
- ProcessFromElm019Code.JavascriptMinusCrashes(javascriptFromElmMake);
+ return innerAggregate;
+ }
- var functionNameInElm = entryPointStruct.elmMakeJavaScriptFunctionName;
+ if (packagesModulesNames.Contains(moduleName))
+ {
+ Console.WriteLine(
+ "Skipping Elm module file " +
+ string.Join("/", relativePath) +
+ " from package " + package.Key + " because it is already present in the packages.");
- var listFunctionToPublish =
- new[]
- {
- (functionNameInElm: functionNameInElm,
- publicName: "blob_main_as_base64",
- arity: 0),
- };
+ return innerAggregate;
+ }
- var finalJs =
- ProcessFromElm019Code.PublishFunctionsFromJavascriptFromElmMake(
- javascriptMinusCrashes,
- listFunctionToPublish);
+ packagesModulesNames.Add(moduleName);
- using var javascriptEngine = IJavaScriptEngine.BuildJavaScriptEngine();
+ return
+ innerAggregate.SetItem(
+ ["dependencies", .. package.Key.Split('/'), .. packageElmModuleFile.Key],
+ packageElmModuleFile.Value.fileContent);
+ });
+ });
- javascriptEngine.Evaluate(finalJs);
+ var elmCompilerFromBundle =
+ BundledElmEnvironments.BundledElmCompilerCompiledEnvValue()
+ ??
+ throw new Exception("Failed to load Elm compiler from bundle.");
- var blobBase64 = (string)javascriptEngine.Evaluate("blob_main_as_base64");
+ var elmCompiler =
+ ElmCompiler.ElmCompilerFromEnvValue(elmCompilerFromBundle)
+ .Extract(err => throw new Exception(err));
- return
- Result.ok(
- new Elm019Binaries.ElmMakeOk(
- producedFile: Convert.FromBase64String(blobBase64)));
- });
- }
+ var pineVMCache = new PineVMCache();
- return
- loweringOk.result.rootModuleEntryPointKind
- .MapError(err => "Failed to get entry point main declaration: " + err)
- .AndThen(rootModuleEntryPointKind =>
- rootModuleEntryPointKind switch
- {
- CompilerSerialInterface.ElmMakeEntryPointKind.ClassicMakeEntryPoint =>
- continueWithClassicEntryPoint(),
- CompilerSerialInterface.ElmMakeEntryPointKind.BlobMakeEntryPoint blob =>
- continueWithBlobEntryPoint(blob.EntryPointStruct),
+ var pineVM =
+ new PineVM(evalCache: pineVMCache.EvalCache);
- _ => throw new NotImplementedException()
- });
- });
+ var parseCache = new PineVMParseCache();
+
+ var elmCompilerCache = new ElmCompilerCache();
+
+ var compileResult =
+ InteractiveSessionPine.CompileInteractiveEnvironment(
+ appCodeTree:
+ PineValueComposition.SortedTreeFromSetOfBlobsWithStringPath(sourceFilesWithMergedPackages),
+ overrideSkipLowering: true,
+ elmCompiler: elmCompiler);
+
+ if (compileResult.IsErrOrNull() is { } compileErr)
+ {
+ return
+ "Failed to compile Elm interactive env: " + compileErr;
+ }
+
+ if (compileResult.IsOkOrNull() is not { } compileOk)
+ {
+ throw new Exception("Unexpected compile result type: " + compileResult);
+ }
+
+ var parseFromEnvResult =
+ ElmInteractiveEnvironment.ParseFunctionFromElmModule(
+ interactiveEnvironment: compileOk,
+ moduleName: string.Join(".", entryPointModuleNameOk.ToArray()),
+ "blobMain",
+ parseCache);
+
+ {
+ if (parseFromEnvResult.IsErrOrNull() is { } parseErr)
+ {
+ return "Failed to parse Elm module: " + parseErr;
+ }
+ }
+
+ if (parseFromEnvResult.IsOkOrNullable() is not { } parseFromEnvOk)
+ {
+ throw new Exception("Unexpected parse result type: " + parseFromEnvResult);
+ }
+
+ var parseDeclResult = ElmValueEncoding.PineValueAsElmValue(parseFromEnvOk.declValue, null, null);
+
+ if (parseDeclResult.IsErrOrNull() is { } parseDeclErr)
+ {
+ return "Failed to parse Elm value: " + parseDeclErr;
+ }
+
+ if (parseDeclResult.IsOkOrNull() is not { } parseDeclOk)
+ {
+ throw new Exception("Unexpected parse result type: " + parseDeclResult);
+ }
+
+ if (parseDeclOk is not ElmValue.ElmBytes bytesValue)
+ {
+ return "Expected Elm bytes value, but got: " + parseDeclOk;
+ }
+
+ return
+ new Elm019Binaries.ElmMakeOk(producedFile: bytesValue.Value);
+ }
+
+ return
+ loweringOk.result.rootModuleEntryPointKind
+ .MapError(err => "Failed to get entry point main declaration: " + err)
+ .AndThen(rootModuleEntryPointKind =>
+ rootModuleEntryPointKind switch
+ {
+ CompilerSerialInterface.ElmMakeEntryPointKind.ClassicMakeEntryPoint =>
+ continueWithClassicEntryPoint(),
+
+ CompilerSerialInterface.ElmMakeEntryPointKind.BlobMakeEntryPoint blob =>
+ continueWithBlobEntryPoint(blob.EntryPointStruct),
+
+ _ =>
+ throw new NotImplementedException(
+ "Unexpected root module entry point kind: " + rootModuleEntryPointKind),
+ });
}
public static IEnumerable DescribeCompositionForHumans(
diff --git a/implement/test-elm-time/ProgramCommandMakeTests.cs b/implement/test-elm-time/ProgramCommandMakeTests.cs
index 1b6ffb97..aa7380c5 100644
--- a/implement/test-elm-time/ProgramCommandMakeTests.cs
+++ b/implement/test-elm-time/ProgramCommandMakeTests.cs
@@ -31,24 +31,11 @@ public void TestCommandMake_with_blobMain()
"dependencies": {
"direct": {
"TSFoster/elm-bytes-extra": "1.3.0",
- "agu-z/elm-zip": "3.0.1",
- "danfishgold/base64-bytes": "1.1.0",
- "elm/browser": "1.0.2",
"elm/bytes": "1.0.8",
"elm/core": "1.0.5",
- "elm/html": "1.0.0",
- "elm/json": "1.1.3",
- "elm/time": "1.0.0",
- "elm/url": "1.0.0",
- "mdgriffith/elm-ui": "1.1.8"
+ "elm/json": "1.1.3"
},
"indirect": {
- "elm/parser": "1.1.0",
- "elm/virtual-dom": "1.0.3",
- "elm-community/list-extra": "8.7.0",
- "folkertdev/elm-flate": "2.0.5",
- "justinmimbs/date": "4.0.1",
- "justinmimbs/time-extra": "1.1.1"
}
},
"test-dependencies": {
@@ -71,17 +58,20 @@ import Bytes.Extra
blobMain : Bytes.Bytes
blobMain =
- Bytes.Extra.fromByteValues [ 0, 1, 3, 4 ]
+ Bytes.Extra.fromByteValues [ 0, 1, 3, 4, 71 ]
""",
},
};
- var outputBlob = GetOutputFileContentForCommandMake(
- projectFiles: projectFiles.Select(file => ((IReadOnlyList)file.path, (ReadOnlyMemory)Encoding.UTF8.GetBytes(file.content))).ToImmutableList(),
- entryPointFilePath: ["src", "Build.elm"]);
+ var outputBlob =
+ GetOutputFileContentForCommandMake(
+ projectFiles:
+ [.. projectFiles
+ .Select(file => ((IReadOnlyList)file.path, (ReadOnlyMemory)Encoding.UTF8.GetBytes(file.content)))],
+ entryPointFilePath: ["src", "Build.elm"]);
- CollectionAssert.AreEqual(new byte[] { 0, 1, 3, 4 }, outputBlob.Span.ToArray());
+ CollectionAssert.AreEqual(new byte[] { 0, 1, 3, 4, 71 }, outputBlob.Span.ToArray());
}
private static ReadOnlyMemory GetOutputFileContentForCommandMake(
@@ -98,12 +88,13 @@ private static ReadOnlyMemory GetOutputFileContentForCommandMake(
IImmutableDictionary, ReadOnlyMemory> projectFiles,
IReadOnlyList entryPointFilePath)
{
- var makeResult = Program.Make(
- sourceFiles: projectFiles,
- workingDirectoryRelative: null,
- pathToFileWithElmEntryPoint: entryPointFilePath,
- outputFileName: "should-not-matter",
- elmMakeCommandAppendix: null);
+ var makeResult =
+ Program.Make(
+ sourceFiles: projectFiles,
+ workingDirectoryRelative: null,
+ pathToFileWithElmEntryPoint: entryPointFilePath,
+ outputFileName: "should-not-matter",
+ elmMakeCommandAppendix: null);
return makeResult.Extract(fromErr: err => throw new Exception("Failed make command:\n" + err)).producedFile;
}