Skip to content

Commit

Permalink
Begin migrate the web service app interface to JS-free infra
Browse files Browse the repository at this point in the history
  • Loading branch information
Viir committed Jan 23, 2025
1 parent 0a40a47 commit b01aea9
Show file tree
Hide file tree
Showing 7 changed files with 1,547 additions and 126 deletions.
3 changes: 1 addition & 2 deletions implement/example-apps/minimal-backend-hello-world/elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
"danfishgold/base64-bytes": "1.1.0",
"elm/bytes": "1.0.8",
"elm/core": "1.0.5",
"elm/json": "1.1.3",
"elm/url": "1.0.0"
"elm/json": "1.1.3"
},
"indirect": {}
},
Expand Down
129 changes: 129 additions & 0 deletions implement/pine/Elm/ElmAppDependencyResolution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Pine.Core;
using Pine.Elm019;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Pine.Elm;

public class ElmAppDependencyResolution
{
public static IReadOnlyDictionary<IReadOnlyList<string>, ReadOnlyMemory<byte>> MergePackagesElmModules(
IReadOnlyDictionary<IReadOnlyList<string>, ReadOnlyMemory<byte>> appSourceFiles)
{
/*
* TODO: select elm.json for the given entry point
* */

var elmJsonFiles =
appSourceFiles
.Where(entry => entry.Key.Last() is "elm.json")
.ToImmutableDictionary();

var elmJsonAggregateDependencies =
elmJsonFiles
.SelectMany(elmJsonFile =>
{
try
{
var elmJsonParsed =
System.Text.Json.JsonSerializer.Deserialize<ElmJsonStructure>(elmJsonFile.Value.Span);

return
new[]
{
elmJsonParsed?.Dependencies.Direct,
elmJsonParsed?.Dependencies.Indirect,
elmJsonParsed?.Dependencies.Flat
}
.WhereNotNull();
}
catch (Exception e)
{
Console.WriteLine("Failed to parse elm.json file: " + e);

return [];
}
})
.SelectMany(dependency => dependency)
.ToImmutableDictionary(
keySelector: dependency => dependency.Key,
elementSelector:
dependency =>
{
var packageFiles =
ElmPackageSource.LoadElmPackageAsync(dependency.Key, dependency.Value).Result;

return packageFiles;
});

var sourceElmModulesNames =
appSourceFiles
.Where(entry => entry.Key.Last().EndsWith(".elm", StringComparison.OrdinalIgnoreCase))
.Select(entry => ElmTime.ElmSyntax.ElmModule.ParseModuleName(entry.Value).WithDefault(null))
.WhereNotNull()
.ToImmutableHashSet(EnumerableExtension.EqualityComparer<IReadOnlyList<string>>());

var packagesModulesNames =
new HashSet<IReadOnlyList<string>>(EnumerableExtension.EqualityComparer<IReadOnlyList<string>>());

var sourceFilesWithMergedPackages =
elmJsonAggregateDependencies
.Aggregate(
seed: appSourceFiles,
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
packageExposedModuleFiles
.Aggregate(
seed: aggregate.ToImmutableDictionary(),
func: (innerAggregate, packageElmModuleFile) =>
{
var relativePath = packageElmModuleFile.Key;

var moduleName = packageElmModuleFile.Value.moduleName;

if (sourceElmModulesNames.Contains(moduleName))
{
Console.WriteLine(
"Skipping Elm module file " +
string.Join("/", relativePath) +
" from package " + package.Key + " because it is already present in the source files.");

return innerAggregate;
}

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.");

return innerAggregate;
}

packagesModulesNames.Add(moduleName);

return
innerAggregate.SetItem(
["dependencies", .. package.Key.Split('/'), .. packageElmModuleFile.Key],
packageElmModuleFile.Value.fileContent);
});
});

return sourceFilesWithMergedPackages;
}
}
42 changes: 40 additions & 2 deletions implement/pine/Elm/Platform/CommandLineApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Pine.Elm.Platform;

/// <summary>
/// A mutating container for an Elm command-line interface app.
/// Mutating container for an Elm command-line interface app.
/// </summary>
public class MutatingCommandLineApp
{
Expand Down Expand Up @@ -97,6 +96,45 @@ private void MutateConsolidatingAppResponse(CommandLineAppConfig.CommandLineAppE
}
}

/*
{-| Use the type `CommandLineAppConfig` on a declaration named `runRoot` to declare a command-line program in an Elm module.
A command-line program can subscribe to incoming bytes from standard input and can write to standard output and standard error.
-}
type alias CommandLineAppConfig state =
{ init : InitEnvironment -> ( state, EventResponse state )
, subscriptions : state -> Subscriptions state
}
type alias InitEnvironment =
{ commandLine : String
, environmentVariables : List ( String, String )
}
type alias Subscriptions state =
{ stdIn : Maybe (Bytes.Bytes -> state -> ( state, EventResponse state ))
, posixTimeIsPast :
Maybe
{ minimumPosixTimeMilli : Int
, update : { currentPosixTimeMilli : Int } -> state -> ( state, EventResponse state )
}
}
type alias EventResponse state =
{ commands : List (Command state)
, exit : Maybe Int
}
type Command state
= SendToStdOutCmd Bytes.Bytes
| SendToStdErrCmd Bytes.Bytes
* */

/// <summary>
/// Configuration of an Elm command-line interface (CLI) app.
/// </summary>
Expand Down
67 changes: 67 additions & 0 deletions implement/pine/Elm/Platform/WebServiceApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Pine.Core;
using Pine.PineVM;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Pine.Elm.Platform;

/// <summary>
/// Mutating container for a web service app.
/// </summary>
public class MutatingWebServiceApp
{
private readonly PineVMCache pineVMCache = new();

private readonly PineVM.PineVM pineVM;

private readonly WebServiceInterface.WebServiceConfig appConfig;

private PineValue appState;

private readonly ConcurrentQueue<WebServiceInterface.Command> commands = new();

public IReadOnlyList<WebServiceInterface.Command> DequeueCommands() =>
[.. commands.DequeueAllEnumerable()];

public MutatingWebServiceApp(
WebServiceInterface.WebServiceConfig appConfig)
{
this.appConfig = appConfig;

pineVM = new PineVM.PineVM(pineVMCache.EvalCache);

appState = appConfig.Init.State;

foreach (var command in appConfig.Init.Commands)
{
commands.Enqueue(command);
}
}

public WebServiceInterface.WebServiceEventResponse EventHttpRequest(
WebServiceInterface.HttpRequestEventStruct httpRequest)
{
var eventResponse =
WebServiceInterface.WebServiceConfig.EventHttpRequest(
appConfig,
httpRequest,
appState,
pineVM);

appState = eventResponse.State;

MutateConsolidatingAppResponse(eventResponse);

return eventResponse;
}


private void MutateConsolidatingAppResponse(WebServiceInterface.WebServiceEventResponse eventResponse)
{
foreach (var cmd in eventResponse.Commands)
{
commands.Enqueue(cmd);
}
}
}
Loading

0 comments on commit b01aea9

Please sign in to comment.