diff --git a/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmApp.elm b/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmApp.elm index dfb9108ea..d4d55809b 100644 --- a/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmApp.elm +++ b/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmApp.elm @@ -730,7 +730,7 @@ mapJsonConvertersModuleText { originalSourceModules, sourceDirs } ( sourceFiles, (\functionsToReplace -> let ( appFiles, { generatedModuleName, modulesToImport } ) = - mapAppFilesToSupportJsonConversion + mapAppFilesToSupportJsonConverters { generatedModuleNamePrefix = interfaceModuleName , sourceDirs = sourceDirs } @@ -777,7 +777,7 @@ mapJsonConvertersModuleText { originalSourceModules, sourceDirs } ( sourceFiles, |> Result.mapError (Elm.Syntax.Node.Node functionToReplace.declarationRange) ) (addImportsInElmModuleText - (List.map (Tuple.pair >> (|>) Nothing) modulesToImport) + (modulesToImport |> Set.toList |> List.map (Tuple.pair >> (|>) Nothing)) moduleText |> Result.mapError (Elm.Syntax.Node.Node (syntaxRangeCoveringCompleteString moduleText)) ) @@ -787,84 +787,48 @@ mapJsonConvertersModuleText { originalSourceModules, sourceDirs } ( sourceFiles, ) -mapAppFilesToSupportJsonConversion : +{-| Combines two transformations of the app files to support JSON converters: + + - Exposes the choice type tags from declaring modules where necessary to implement JSON decoders. + - Adds a module containing generated JSON conversion functions for the given list of type annotations and choice types. + +-} +mapAppFilesToSupportJsonConverters : { generatedModuleNamePrefix : List String , sourceDirs : SourceDirectories } -> List ElmTypeAnnotation -> Dict.Dict String ElmChoiceTypeStruct -> AppFiles - -> ( AppFiles, { generatedModuleName : List String, modulesToImport : List (List String) } ) -mapAppFilesToSupportJsonConversion { generatedModuleNamePrefix, sourceDirs } typeAnnotationsBeforeDeduplicating choiceTypes appFilesBefore = + -> ( AppFiles, { generatedModuleName : List String, modulesToImport : Set.Set (List String) } ) +mapAppFilesToSupportJsonConverters { generatedModuleNamePrefix, sourceDirs } typeAnnotationsBeforeDeduplicating choiceTypes appFilesBefore = let - modulesToImportForChoiceTypes = - choiceTypes - |> Dict.keys - |> List.map moduleNameFromTypeName - |> Set.fromList - |> Set.toList - |> List.map (String.split ".") + generatedFunctions = + buildJsonConverterFunctionsForMultipleTypes typeAnnotationsBeforeDeduplicating choiceTypes modulesToImport = - [ [ "Dict" ] - , [ "Set" ] - , [ "Array" ] - , [ "Json", "Decode" ] - , [ "Json", "Encode" ] - , [ "Bytes" ] - , [ "Bytes", "Decode" ] - , [ "Bytes", "Encode" ] - ] - ++ modulesToImportForChoiceTypes + generatedFunctions.generatedFunctions + |> List.map .modulesToImport + |> List.foldl Set.union Set.empty generatedModuleModulesToImport = - encodingModuleImportBase64 :: List.map (Tuple.pair >> (|>) Nothing) modulesToImport + encodingModuleImportBase64 + :: (modulesToImport + |> Set.toList + |> List.map (Tuple.pair >> (|>) Nothing) + ) appFilesAfterExposingChoiceTypesInModules = - modulesToImportForChoiceTypes + generatedFunctions.modulesToImportForChoiceTypes |> List.foldl (exposeAllInElmModuleInAppFiles sourceDirs) appFilesBefore - typeAnnotationsFunctions = - typeAnnotationsBeforeDeduplicating - |> listRemoveDuplicates - |> List.map buildJsonConverterFunctionsForTypeAnnotation - - typeAnnotationsFunctionsForGeneratedModule = - typeAnnotationsFunctions - |> List.concatMap - (\functionsForType -> - [ functionsForType.encodeFunction, functionsForType.decodeFunction ] - ) - |> List.map (\function -> { functionName = function.name, functionText = function.text }) - - dependenciesFunctions = - choiceTypes - |> Dict.toList - |> List.map - (\( choiceTypeName, choiceType ) -> - jsonConverterFunctionFromChoiceType - { choiceTypeName = choiceTypeName - , encodeValueExpression = jsonEncodeParamName - , typeArgLocalName = "type_arg" - } - choiceType - ) - |> List.concatMap - (\functionsForType -> - [ functionsForType.encodeFunction, functionsForType.decodeFunction ] - ) - |> List.map (\function -> { functionName = function.name, functionText = function.text }) - - functionsForGeneratedModule = - typeAnnotationsFunctionsForGeneratedModule ++ dependenciesFunctions ++ generalSupportingFunctionsTexts - generatedModuleTextWithoutModuleDeclaration = [ [ generatedModuleModulesToImport |> List.map importSyntaxTextFromModuleNameAndAlias |> List.sort |> String.join "\n" ] - , functionsForGeneratedModule |> List.map .functionText + , generatedFunctions.generatedFunctions |> List.map .functionText ] |> List.concat |> List.map String.trim @@ -895,11 +859,127 @@ mapAppFilesToSupportJsonConversion { generatedModuleNamePrefix, sourceDirs } typ in ( appFiles , { generatedModuleName = generatedModuleName - , modulesToImport = generatedModuleName :: modulesToImport + , modulesToImport = Set.insert generatedModuleName modulesToImport } ) +type alias GenerateFunctionsFromTypesConfig = + { generateFromTypeAnnotation : ElmTypeAnnotation -> List GenerateFunctionFromTypeResult + , generateFromChoiceType : ( String, ElmChoiceTypeStruct ) -> List GenerateFunctionFromTypeResult + } + + +type alias GenerateFunctionFromTypeResult = + { functionName : String + , functionText : String + , modulesToImport : Set.Set (List String) + } + + +buildJsonConverterFunctionsForMultipleTypes : + List ElmTypeAnnotation + -> Dict.Dict String ElmChoiceTypeStruct + -> + { generatedFunctions : List GenerateFunctionFromTypeResult + , modulesToImportForChoiceTypes : List (List String) + } +buildJsonConverterFunctionsForMultipleTypes typeAnnotations choiceTypes = + let + defaultModulesToImportForFunction = + [ [ "Dict" ] + , [ "Set" ] + , [ "Array" ] + , [ "Json", "Decode" ] + , [ "Json", "Encode" ] + , [ "Bytes" ] + , [ "Bytes", "Decode" ] + , [ "Bytes", "Encode" ] + ] + |> Set.fromList + + generatedFunctionsForTypes = + generateFunctionsForMultipleTypes + { generateFromTypeAnnotation = + buildJsonConverterFunctionsForTypeAnnotation + >> (\functionsForType -> + [ functionsForType.encodeFunction, functionsForType.decodeFunction ] + |> List.map + (\function -> + { functionName = function.name + , functionText = function.text + , modulesToImport = defaultModulesToImportForFunction + } + ) + ) + , generateFromChoiceType = + (\( choiceTypeName, choiceType ) -> + jsonConverterFunctionFromChoiceType + { choiceTypeName = choiceTypeName + , encodeValueExpression = jsonEncodeParamName + , typeArgLocalName = "type_arg" + } + choiceType + ) + >> (\functionsForType -> + [ functionsForType.encodeFunction, functionsForType.decodeFunction ] + |> List.map + (\function -> + { functionName = function.name + , functionText = function.text + , modulesToImport = defaultModulesToImportForFunction + } + ) + ) + } + typeAnnotations + choiceTypes + + generatedFunctions = + generatedFunctionsForTypes.generatedFunctions + ++ generalSupportingFunctionsTexts + in + { generatedFunctions = generatedFunctions + , modulesToImportForChoiceTypes = generatedFunctionsForTypes.modulesToImportForChoiceTypes + } + + +generateFunctionsForMultipleTypes : + GenerateFunctionsFromTypesConfig + -> List ElmTypeAnnotation + -> Dict.Dict String ElmChoiceTypeStruct + -> + { generatedFunctions : List GenerateFunctionFromTypeResult + , modulesToImportForChoiceTypes : List (List String) + } +generateFunctionsForMultipleTypes config typeAnnotationsBeforeDeduplicating choiceTypes = + let + modulesToImportForChoiceTypes = + choiceTypes + |> Dict.keys + |> List.map moduleNameFromTypeName + |> Set.fromList + |> Set.toList + |> List.map (String.split ".") + + generatedFunctionsFromTypeAnnotations = + typeAnnotationsBeforeDeduplicating + |> List.Extra.unique + |> List.concatMap config.generateFromTypeAnnotation + + generatedFunctionsFromChoiceTypes = + choiceTypes + |> Dict.toList + |> List.concatMap config.generateFromChoiceType + + generatedFunctions = + generatedFunctionsFromTypeAnnotations ++ generatedFunctionsFromChoiceTypes + in + { generatedFunctions = generatedFunctions + , modulesToImportForChoiceTypes = modulesToImportForChoiceTypes + } + + buildJsonConverterFunctionsForTypeAnnotation : ElmTypeAnnotation -> { encodeFunction : { name : String, text : String }, decodeFunction : { name : String, text : String } } @@ -2360,7 +2440,7 @@ type Declaration | ChoiceTypeDeclaration Elm.Syntax.Type.Type -generalSupportingFunctionsTexts : List { functionName : String, functionText : String } +generalSupportingFunctionsTexts : List GenerateFunctionFromTypeResult generalSupportingFunctionsTexts = (generalSupportingFunctionsTextsWithCommonNamePattern |> List.concatMap @@ -2423,6 +2503,13 @@ jsonDecodeSucceedWhenNotNull valueIfNotNull = )""" } ] + |> List.map + (\function -> + { functionName = function.functionName + , functionText = function.functionText + , modulesToImport = Set.empty + } + ) generalSupportingFunctionsTextsWithCommonNamePattern : @@ -4215,19 +4302,6 @@ resultCombineConcatenatingErrors = (Ok []) -listRemoveDuplicates : List a -> List a -listRemoveDuplicates = - List.foldr - (\item list -> - if list |> List.member item then - list - - else - item :: list - ) - [] - - importSyntaxTextFromModuleNameAndAlias : ( List String, Maybe String ) -> String importSyntaxTextFromModuleNameAndAlias ( moduleName, maybeAlias ) = "import " diff --git a/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmAppWithStateShim.elm b/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmAppWithStateShim.elm index 91bc40802..4d076bc30 100644 --- a/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmAppWithStateShim.elm +++ b/implement/elm-time/ElmTime/compile-elm-program/src/CompileElmAppWithStateShim.elm @@ -19,7 +19,7 @@ import CompileElmApp , filePathFromElmModuleName , importSyntaxTextFromModuleNameAndAlias , indentElmCodeLines - , mapAppFilesToSupportJsonConversion + , mapAppFilesToSupportJsonConverters , mapLocatedInSourceFiles , modulesToAddForBase64Coding , updateFileContentAtPath @@ -169,7 +169,7 @@ loweredForAppInStateManagementShim sourceDirs stateShimConfig config sourceFiles ( appFiles, generateSerializersResult ) = sourceFiles |> addModulesFromTextToAppFiles sourceDirs modulesToAdd - |> mapAppFilesToSupportJsonConversion + |> mapAppFilesToSupportJsonConverters { generatedModuleNamePrefix = config.interfaceToHostRootModuleName , sourceDirs = sourceDirs } @@ -177,7 +177,7 @@ loweredForAppInStateManagementShim sourceDirs stateShimConfig config sourceFiles jsonConvertedTypesDependencies modulesToImport = - generateSerializersResult.modulesToImport + Set.toList generateSerializersResult.modulesToImport ++ stateShimConfig.modulesToImport ++ List.map .moduleName (Dict.values supportingTypes.modules) diff --git a/implement/elm-time/Program.cs b/implement/elm-time/Program.cs index 2d1738eba..c55a27d4d 100644 --- a/implement/elm-time/Program.cs +++ b/implement/elm-time/Program.cs @@ -18,7 +18,7 @@ namespace ElmTime; public class Program { - public static string AppVersionId => "2023-08-11"; + public static string AppVersionId => "2023-08-12"; private static int AdminInterfaceDefaultPort => 4000; diff --git a/implement/elm-time/elm-time.csproj b/implement/elm-time/elm-time.csproj index 41e18511b..371821e27 100644 --- a/implement/elm-time/elm-time.csproj +++ b/implement/elm-time/elm-time.csproj @@ -5,8 +5,8 @@ net7.0 ElmTime elm-time - 2023.0811.0.0 - 2023.0811.0.0 + 2023.0812.0.0 + 2023.0812.0.0 enable true diff --git a/implement/example-apps/elm-editor/src/CompileElmApp.elm b/implement/example-apps/elm-editor/src/CompileElmApp.elm index dfb9108ea..d4d55809b 100644 --- a/implement/example-apps/elm-editor/src/CompileElmApp.elm +++ b/implement/example-apps/elm-editor/src/CompileElmApp.elm @@ -730,7 +730,7 @@ mapJsonConvertersModuleText { originalSourceModules, sourceDirs } ( sourceFiles, (\functionsToReplace -> let ( appFiles, { generatedModuleName, modulesToImport } ) = - mapAppFilesToSupportJsonConversion + mapAppFilesToSupportJsonConverters { generatedModuleNamePrefix = interfaceModuleName , sourceDirs = sourceDirs } @@ -777,7 +777,7 @@ mapJsonConvertersModuleText { originalSourceModules, sourceDirs } ( sourceFiles, |> Result.mapError (Elm.Syntax.Node.Node functionToReplace.declarationRange) ) (addImportsInElmModuleText - (List.map (Tuple.pair >> (|>) Nothing) modulesToImport) + (modulesToImport |> Set.toList |> List.map (Tuple.pair >> (|>) Nothing)) moduleText |> Result.mapError (Elm.Syntax.Node.Node (syntaxRangeCoveringCompleteString moduleText)) ) @@ -787,84 +787,48 @@ mapJsonConvertersModuleText { originalSourceModules, sourceDirs } ( sourceFiles, ) -mapAppFilesToSupportJsonConversion : +{-| Combines two transformations of the app files to support JSON converters: + + - Exposes the choice type tags from declaring modules where necessary to implement JSON decoders. + - Adds a module containing generated JSON conversion functions for the given list of type annotations and choice types. + +-} +mapAppFilesToSupportJsonConverters : { generatedModuleNamePrefix : List String , sourceDirs : SourceDirectories } -> List ElmTypeAnnotation -> Dict.Dict String ElmChoiceTypeStruct -> AppFiles - -> ( AppFiles, { generatedModuleName : List String, modulesToImport : List (List String) } ) -mapAppFilesToSupportJsonConversion { generatedModuleNamePrefix, sourceDirs } typeAnnotationsBeforeDeduplicating choiceTypes appFilesBefore = + -> ( AppFiles, { generatedModuleName : List String, modulesToImport : Set.Set (List String) } ) +mapAppFilesToSupportJsonConverters { generatedModuleNamePrefix, sourceDirs } typeAnnotationsBeforeDeduplicating choiceTypes appFilesBefore = let - modulesToImportForChoiceTypes = - choiceTypes - |> Dict.keys - |> List.map moduleNameFromTypeName - |> Set.fromList - |> Set.toList - |> List.map (String.split ".") + generatedFunctions = + buildJsonConverterFunctionsForMultipleTypes typeAnnotationsBeforeDeduplicating choiceTypes modulesToImport = - [ [ "Dict" ] - , [ "Set" ] - , [ "Array" ] - , [ "Json", "Decode" ] - , [ "Json", "Encode" ] - , [ "Bytes" ] - , [ "Bytes", "Decode" ] - , [ "Bytes", "Encode" ] - ] - ++ modulesToImportForChoiceTypes + generatedFunctions.generatedFunctions + |> List.map .modulesToImport + |> List.foldl Set.union Set.empty generatedModuleModulesToImport = - encodingModuleImportBase64 :: List.map (Tuple.pair >> (|>) Nothing) modulesToImport + encodingModuleImportBase64 + :: (modulesToImport + |> Set.toList + |> List.map (Tuple.pair >> (|>) Nothing) + ) appFilesAfterExposingChoiceTypesInModules = - modulesToImportForChoiceTypes + generatedFunctions.modulesToImportForChoiceTypes |> List.foldl (exposeAllInElmModuleInAppFiles sourceDirs) appFilesBefore - typeAnnotationsFunctions = - typeAnnotationsBeforeDeduplicating - |> listRemoveDuplicates - |> List.map buildJsonConverterFunctionsForTypeAnnotation - - typeAnnotationsFunctionsForGeneratedModule = - typeAnnotationsFunctions - |> List.concatMap - (\functionsForType -> - [ functionsForType.encodeFunction, functionsForType.decodeFunction ] - ) - |> List.map (\function -> { functionName = function.name, functionText = function.text }) - - dependenciesFunctions = - choiceTypes - |> Dict.toList - |> List.map - (\( choiceTypeName, choiceType ) -> - jsonConverterFunctionFromChoiceType - { choiceTypeName = choiceTypeName - , encodeValueExpression = jsonEncodeParamName - , typeArgLocalName = "type_arg" - } - choiceType - ) - |> List.concatMap - (\functionsForType -> - [ functionsForType.encodeFunction, functionsForType.decodeFunction ] - ) - |> List.map (\function -> { functionName = function.name, functionText = function.text }) - - functionsForGeneratedModule = - typeAnnotationsFunctionsForGeneratedModule ++ dependenciesFunctions ++ generalSupportingFunctionsTexts - generatedModuleTextWithoutModuleDeclaration = [ [ generatedModuleModulesToImport |> List.map importSyntaxTextFromModuleNameAndAlias |> List.sort |> String.join "\n" ] - , functionsForGeneratedModule |> List.map .functionText + , generatedFunctions.generatedFunctions |> List.map .functionText ] |> List.concat |> List.map String.trim @@ -895,11 +859,127 @@ mapAppFilesToSupportJsonConversion { generatedModuleNamePrefix, sourceDirs } typ in ( appFiles , { generatedModuleName = generatedModuleName - , modulesToImport = generatedModuleName :: modulesToImport + , modulesToImport = Set.insert generatedModuleName modulesToImport } ) +type alias GenerateFunctionsFromTypesConfig = + { generateFromTypeAnnotation : ElmTypeAnnotation -> List GenerateFunctionFromTypeResult + , generateFromChoiceType : ( String, ElmChoiceTypeStruct ) -> List GenerateFunctionFromTypeResult + } + + +type alias GenerateFunctionFromTypeResult = + { functionName : String + , functionText : String + , modulesToImport : Set.Set (List String) + } + + +buildJsonConverterFunctionsForMultipleTypes : + List ElmTypeAnnotation + -> Dict.Dict String ElmChoiceTypeStruct + -> + { generatedFunctions : List GenerateFunctionFromTypeResult + , modulesToImportForChoiceTypes : List (List String) + } +buildJsonConverterFunctionsForMultipleTypes typeAnnotations choiceTypes = + let + defaultModulesToImportForFunction = + [ [ "Dict" ] + , [ "Set" ] + , [ "Array" ] + , [ "Json", "Decode" ] + , [ "Json", "Encode" ] + , [ "Bytes" ] + , [ "Bytes", "Decode" ] + , [ "Bytes", "Encode" ] + ] + |> Set.fromList + + generatedFunctionsForTypes = + generateFunctionsForMultipleTypes + { generateFromTypeAnnotation = + buildJsonConverterFunctionsForTypeAnnotation + >> (\functionsForType -> + [ functionsForType.encodeFunction, functionsForType.decodeFunction ] + |> List.map + (\function -> + { functionName = function.name + , functionText = function.text + , modulesToImport = defaultModulesToImportForFunction + } + ) + ) + , generateFromChoiceType = + (\( choiceTypeName, choiceType ) -> + jsonConverterFunctionFromChoiceType + { choiceTypeName = choiceTypeName + , encodeValueExpression = jsonEncodeParamName + , typeArgLocalName = "type_arg" + } + choiceType + ) + >> (\functionsForType -> + [ functionsForType.encodeFunction, functionsForType.decodeFunction ] + |> List.map + (\function -> + { functionName = function.name + , functionText = function.text + , modulesToImport = defaultModulesToImportForFunction + } + ) + ) + } + typeAnnotations + choiceTypes + + generatedFunctions = + generatedFunctionsForTypes.generatedFunctions + ++ generalSupportingFunctionsTexts + in + { generatedFunctions = generatedFunctions + , modulesToImportForChoiceTypes = generatedFunctionsForTypes.modulesToImportForChoiceTypes + } + + +generateFunctionsForMultipleTypes : + GenerateFunctionsFromTypesConfig + -> List ElmTypeAnnotation + -> Dict.Dict String ElmChoiceTypeStruct + -> + { generatedFunctions : List GenerateFunctionFromTypeResult + , modulesToImportForChoiceTypes : List (List String) + } +generateFunctionsForMultipleTypes config typeAnnotationsBeforeDeduplicating choiceTypes = + let + modulesToImportForChoiceTypes = + choiceTypes + |> Dict.keys + |> List.map moduleNameFromTypeName + |> Set.fromList + |> Set.toList + |> List.map (String.split ".") + + generatedFunctionsFromTypeAnnotations = + typeAnnotationsBeforeDeduplicating + |> List.Extra.unique + |> List.concatMap config.generateFromTypeAnnotation + + generatedFunctionsFromChoiceTypes = + choiceTypes + |> Dict.toList + |> List.concatMap config.generateFromChoiceType + + generatedFunctions = + generatedFunctionsFromTypeAnnotations ++ generatedFunctionsFromChoiceTypes + in + { generatedFunctions = generatedFunctions + , modulesToImportForChoiceTypes = modulesToImportForChoiceTypes + } + + buildJsonConverterFunctionsForTypeAnnotation : ElmTypeAnnotation -> { encodeFunction : { name : String, text : String }, decodeFunction : { name : String, text : String } } @@ -2360,7 +2440,7 @@ type Declaration | ChoiceTypeDeclaration Elm.Syntax.Type.Type -generalSupportingFunctionsTexts : List { functionName : String, functionText : String } +generalSupportingFunctionsTexts : List GenerateFunctionFromTypeResult generalSupportingFunctionsTexts = (generalSupportingFunctionsTextsWithCommonNamePattern |> List.concatMap @@ -2423,6 +2503,13 @@ jsonDecodeSucceedWhenNotNull valueIfNotNull = )""" } ] + |> List.map + (\function -> + { functionName = function.functionName + , functionText = function.functionText + , modulesToImport = Set.empty + } + ) generalSupportingFunctionsTextsWithCommonNamePattern : @@ -4215,19 +4302,6 @@ resultCombineConcatenatingErrors = (Ok []) -listRemoveDuplicates : List a -> List a -listRemoveDuplicates = - List.foldr - (\item list -> - if list |> List.member item then - list - - else - item :: list - ) - [] - - importSyntaxTextFromModuleNameAndAlias : ( List String, Maybe String ) -> String importSyntaxTextFromModuleNameAndAlias ( moduleName, maybeAlias ) = "import " diff --git a/implement/example-apps/elm-editor/src/Frontend/Main.elm b/implement/example-apps/elm-editor/src/Frontend/Main.elm index 6557d3a7e..887042891 100644 --- a/implement/example-apps/elm-editor/src/Frontend/Main.elm +++ b/implement/example-apps/elm-editor/src/Frontend/Main.elm @@ -34,7 +34,7 @@ import File import File.Download import File.Select import FileTree -import FileTreeInWorkspace as FileTreeInWorkspace +import FileTreeInWorkspace import FontAwesome.Icon import FontAwesome.Solid import FontAwesome.Styles