Skip to content

Commit

Permalink
Migrate compilation for migrations to new compilation pipeline
Browse files Browse the repository at this point in the history
Simplify the implementation of migrations by reusing the same compilation pipeline as the compilation interface for app code.

To implement the emitting for migrations, reuse earlier implemented `mapAppFilesAndModuleTextToSupportJsonCoding`. With this new application/integration, I noticed we can simplify the implementation in and around this function, leading to refactoring to `mapAppFilesToSupportJsonCoding`: Instead of returning a function to update the module text, we now what is necessary to build (or update) that module: The names of the modules we depend on for types and the name of the generated module.

Besides what is necessary for migrations, also simplify the compilation in some places.
  • Loading branch information
Viir committed May 18, 2021
1 parent e4f2919 commit da02778
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 1,874 deletions.
1,206 changes: 0 additions & 1,206 deletions implement/elm-fullstack/ElmFullstack/CompileElmValueSerializer.cs

This file was deleted.

306 changes: 1 addition & 305 deletions implement/elm-fullstack/ElmFullstack/ElmApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Pine;

namespace ElmFullstack
Expand Down Expand Up @@ -48,194 +47,12 @@ static public IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>>

static public IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> AsCompletelyLoweredElmApp(
IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> sourceFiles,
ElmAppInterfaceConfig interfaceConfig,
Action<string> logWriteLine) =>
ElmAppInterfaceConfig interfaceConfig) =>
AsCompletelyLoweredElmApp(
sourceFiles,
rootModuleName: interfaceConfig.RootModuleName.Split('.').ToImmutableList(),
interfaceToHostRootModuleName: InterfaceToHostRootModuleName.Split('.').ToImmutableList());

static public IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> WithSupportForCodingElmType(
IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> originalAppFiles,
string elmTypeName,
string elmModuleToAddFunctionsIn,
Action<string> logWriteLine,
out (string encodeFunctionName, string decodeFunctionName) functionNames)
{
var interfaceModuleFilePath = FilePathFromModuleName(elmModuleToAddFunctionsIn);

if (!originalAppFiles.ContainsKey(interfaceModuleFilePath))
{
throw new ArgumentException("Did not find the module '" + elmModuleToAddFunctionsIn + "'.");
}

var interfaceModuleOriginalFile = originalAppFiles[interfaceModuleFilePath];

var interfaceModuleOriginalFileText =
Encoding.UTF8.GetString(interfaceModuleOriginalFile.ToArray());

string getOriginalModuleText(string moduleName)
{
var filePath = FilePathFromModuleName(moduleName);

originalAppFiles.TryGetValue(filePath, out var moduleFile);

if (moduleFile == null)
throw new Exception("Did not find the module named '" + moduleFile + "'");

return Encoding.UTF8.GetString(moduleFile.ToArray());
}

var allOriginalElmModulesNames =
originalAppFiles
.Select(originalAppFilePathAndContent =>
{
var fileName = originalAppFilePathAndContent.Key.Last();

if (originalAppFilePathAndContent.Key.First() != "src" || !fileName.EndsWith(".elm"))
return null;

return
(IEnumerable<string>)
originalAppFilePathAndContent.Key.Skip(1).Reverse().Skip(1).Reverse()
.Concat(new[] { fileName.Substring(0, fileName.Length - 4) })
.ToImmutableList();
})
.Where(module => module != null)
.OrderBy(module => string.Join(".", module))
.ToImmutableHashSet();

var sourceModules =
allOriginalElmModulesNames
.Select(moduleName => string.Join(".", moduleName))
.ToImmutableDictionary(
moduleName => moduleName,
moduleName =>
{
var originalModuleText = getOriginalModuleText(moduleName);

if (moduleName == elmModuleToAddFunctionsIn)
{
return
CompileElm.WithImportsAdded(originalModuleText, allOriginalElmModulesNames);
}

return originalModuleText;
});

var getExpressionsAndDependenciesForType = new Func<string, CompileElmValueSerializer.ResolveTypeResult>(canonicalTypeName =>
{
return
CompileElmValueSerializer.ResolveType(
canonicalTypeName,
elmModuleToAddFunctionsIn,
sourceModules,
logWriteLine);
});

var functionCodingExpressions =
CompileElmValueSerializer.EnumerateExpressionsResolvingAllDependencies(
getExpressionsAndDependenciesForType,
ImmutableHashSet.Create(elmTypeName))
.ToImmutableList();

var functionCodingExpressionsDict =
functionCodingExpressions
.ToImmutableDictionary(entry => entry.elmType, entry => entry.result);

var appFilesAfterExposingCustomTypesInModules =
functionCodingExpressionsDict
.Select(exprResult => exprResult.Key)
.Aggregate(
originalAppFiles,
(partiallyUpdatedAppFiles, elmType) =>
{
{
var enclosingParenthesesMatch = Regex.Match(elmType.Trim(), @"^\(([^,^\)]+)\)$");

if (enclosingParenthesesMatch.Success)
elmType = enclosingParenthesesMatch.Groups[1].Value;
}

var qualifiedMatch = Regex.Match(elmType.Trim(), @"^(.+)\.([^\s^\.]+)(\s+[a-z][^\s^\.]*)*$");

if (!qualifiedMatch.Success)
return partiallyUpdatedAppFiles;

var moduleName = qualifiedMatch.Groups[1].Value;
var localTypeName = qualifiedMatch.Groups[2].Value;

var expectedFilePath = FilePathFromModuleName(moduleName);

var moduleBefore =
partiallyUpdatedAppFiles
.FirstOrDefault(candidate => candidate.Key.SequenceEqual(expectedFilePath));

if (moduleBefore.Value == null)
return partiallyUpdatedAppFiles;

var moduleTextBefore = Encoding.UTF8.GetString(moduleBefore.Value.ToArray());

var isCustomTypeMatch = Regex.Match(
moduleTextBefore,
@"^type\s+" + localTypeName + @"(\s+[a-z][^\s]*){0,}\s*=", RegexOptions.Multiline);

if (!isCustomTypeMatch.Success)
return partiallyUpdatedAppFiles;

var moduleText = CompileElm.ExposeCustomTypeAllTagsInElmModule(moduleTextBefore, localTypeName);

return partiallyUpdatedAppFiles.SetItem(moduleBefore.Key, Encoding.UTF8.GetBytes(moduleText).ToImmutableList());
});

var supportingCodingFunctions =
functionCodingExpressionsDict
.Select(typeResult => CompileElmValueSerializer.BuildJsonCodingFunctionTexts(
typeResult.Key,
typeResult.Value.encodeExpression,
typeResult.Value.decodeExpression))
.SelectMany(encodeAndDecodeFunctions => new[] { encodeAndDecodeFunctions.encodeFunction, encodeAndDecodeFunctions.decodeFunction })
.ToImmutableHashSet()
.Union(CompileElmValueSerializer.generalSupportingFunctionsTexts);

var modulesToImport =
functionCodingExpressions
.SelectMany(functionReplacement =>
functionReplacement.result.referencedModules.Select(moduleName => moduleName.Split(".")))
.ToImmutableHashSet(EnumerableExtension.EqualityComparer<string>())
.Remove(elmModuleToAddFunctionsIn.Split("."))
.Add(new[] { "Set" })
.Add(new[] { "Dict" })
.Add(new[] { "Json.Decode" })
.Add(new[] { "Json.Encode" })
.Add(new[] { "Base64" })
.Add(new[] { "Bytes" })
.Add(new[] { "Bytes.Encode" })
.Add(new[] { "Bytes.Decode" });

var interfaceModuleWithImports =
CompileElm.WithImportsAdded(interfaceModuleOriginalFileText, modulesToImport);

var interfaceModuleWithSupportingFunctions =
supportingCodingFunctions
.Aggregate(
interfaceModuleWithImports,
(intermediateModuleText, supportingFunction) => CompileElm.WithFunctionAdded(intermediateModuleText, supportingFunction));

var elmTypeCodingFunctionNames =
CompileElmValueSerializer.GetFunctionNamesAndTypeParametersFromTypeText(
getExpressionsAndDependenciesForType(elmTypeName).canonicalTypeText);

functionNames =
(encodeFunctionName: elmTypeCodingFunctionNames.functionNames.encodeFunctionName,
decodeFunctionName: elmTypeCodingFunctionNames.functionNames.decodeFunctionName);

return
appFilesAfterExposingCustomTypesInModules.SetItem(
interfaceModuleFilePath,
Encoding.UTF8.GetBytes(interfaceModuleWithSupportingFunctions).ToImmutableList());
}

static IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> AsCompletelyLoweredElmApp(
IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> sourceFiles,
IImmutableList<string> rootModuleName,
Expand Down Expand Up @@ -407,129 +224,8 @@ static public IImmutableList<string> FilePathFromModuleName(IReadOnlyList<string
static public IImmutableList<string> FilePathFromModuleName(string moduleName) =>
FilePathFromModuleName(moduleName.Split(new[] { '.' }).ToImmutableList());

static public string StateTypeNameFromRootElmModule(string elmModuleText)
{
var match = Regex.Match(
elmModuleText,
"^" + ElmAppInterfaceConvention.ProcessSerializedEventFunctionName +
@"\s*:\s*String\s*->\s*([\w\d_]+)\s*->\s*\(\s*",
RegexOptions.Multiline);

if (!match.Success)
throw new System.Exception("Did not find the expected type anotation for function " + ElmAppInterfaceConvention.ProcessSerializedEventFunctionName);

return match.Groups[1].Value;
}

static public string InterfaceToHostRootModuleName => "Backend.InterfaceToHost_Root";

static public IImmutableList<string> InterfaceToHostRootModuleFilePathFromSourceFiles(
IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> sourceFiles) =>
FilePathFromModuleName(InterfaceToHostRootModuleName);

static public string InitialRootElmModuleText(
string rootModuleNameBeforeLowering,
string stateTypeNameInRootModuleBeforeLowering) =>
$@"
module " + InterfaceToHostRootModuleName + $@" exposing
( State
, interfaceToHost_deserializeState
, interfaceToHost_initState
, interfaceToHost_processEvent
, interfaceToHost_serializeState
, main
)
import " + rootModuleNameBeforeLowering + $@"
import Platform
type alias DeserializedState = " + rootModuleNameBeforeLowering + "." + stateTypeNameInRootModuleBeforeLowering + $@"
type State
= DeserializeFailed String
| DeserializeSuccessful DeserializedState
interfaceToHost_initState = " + rootModuleNameBeforeLowering + $@".interfaceToHost_initState |> DeserializeSuccessful
interfaceToHost_processEvent hostEvent stateBefore =
case stateBefore of
DeserializeFailed _ ->
( stateBefore, ""[]"" )
DeserializeSuccessful deserializedState ->
deserializedState
|> " + rootModuleNameBeforeLowering + $@".interfaceToHost_processEvent hostEvent
|> Tuple.mapFirst DeserializeSuccessful
interfaceToHost_serializeState = jsonEncodeState >> Json.Encode.encode 0
interfaceToHost_deserializeState = deserializeState
-- Support function-level dead code elimination (https://elm-lang.org/blog/small-assets-without-the-headache) Elm code needed to inform the Elm compiler about our entry points.
main : Program Int State String
main =
Platform.worker
{{ init = \_ -> ( interfaceToHost_initState, Cmd.none )
, update =
\event stateBefore ->
interfaceToHost_processEvent event (stateBefore |> interfaceToHost_serializeState |> interfaceToHost_deserializeState) |> Tuple.mapSecond (always Cmd.none)
, subscriptions = \_ -> Sub.none
}}
-- Inlined helpers -->
{{-| Turn a `Result e a` to an `a`, by applying the conversion
function specified to the `e`.
-}}
result_Extra_Extract : (e -> a) -> Result e a -> a
result_Extra_Extract f x =
case x of
Ok a ->
a
Err e ->
f e
-- Remember and communicate errors from state deserialization -->
jsonEncodeState : State -> Json.Encode.Value
jsonEncodeState state =
case state of
DeserializeFailed error ->
[ ( ""Interface_DeserializeFailed"", [ ( ""error"", error |> Json.Encode.string ) ] |> Json.Encode.object ) ] |> Json.Encode.object
DeserializeSuccessful deserializedState ->
deserializedState |> jsonEncodeDeserializedState
deserializeState : String -> State
deserializeState serializedState =
serializedState
|> Json.Decode.decodeString jsonDecodeState
|> Result.mapError Json.Decode.errorToString
|> result_Extra_Extract DeserializeFailed
jsonDecodeState : Json.Decode.Decoder State
jsonDecodeState =
Json.Decode.oneOf
[ Json.Decode.field ""Interface_DeserializeFailed"" (Json.Decode.field ""error"" Json.Decode.string |> Json.Decode.map DeserializeFailed)
, jsonDecodeDeserializedState |> Json.Decode.map DeserializeSuccessful
]
";
static Lazy<JavaScriptEngineSwitcher.Core.IJsEngine> jsEngineToCompileElmApp = new Lazy<JavaScriptEngineSwitcher.Core.IJsEngine>(PrepareJsEngineToCompileElmApp);

static public JavaScriptEngineSwitcher.Core.IJsEngine PrepareJsEngineToCompileElmApp()
Expand Down
Loading

0 comments on commit da02778

Please sign in to comment.