Skip to content

Commit

Permalink
Port legacy translations to use elm-syntax-dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
mdevlamynck committed Sep 11, 2024
1 parent 2db3fb2 commit 36dca8d
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 652 deletions.
242 changes: 18 additions & 224 deletions lib/src/Elm.elm
Original file line number Diff line number Diff line change
@@ -1,228 +1,18 @@
module Elm exposing
( Module(..), Function(..), Arg(..), Expr(..)
, renderElmModule
, normalizeModuleName, normalizeFunctionName
, keywords, quote
( normalizeModuleName, normalizeFunctionName
, keywords, stringConcat
)

{-| Module defining a simplified AST of elm code along with a render to string function.
# AST
@docs Module, Function, Arg, Expr
# Rendering
@docs renderElmModule
# Utils
{-| Extra helpers to build elm code.
@docs normalizeModuleName, normalizeFunctionName
# Misc
@docs keywords, quote
-}

import Char exposing (isAlpha)
import Dict exposing (Dict)
import Elm.CodeGen as Gen exposing (Expression)
import String.Extra exposing (toSentenceCase)
import StringUtil exposing (indent, splitOn)


{-| An elm module with its name and the functions it defines.
-}
type Module
= Module String (List Function)


{-| An elm function with its name, the list of arguments, the return type and its body.
-}
type Function
= Function String (List Arg) String Expr


{-| An elm function argument, containing both the name and the type.
Can either be a Primitive with name and type or a Record with the name and type of each field in the record.
-}
type Arg
= Primitive String String
| Record (Dict String String)


{-| An elm expression.
Can either be a Ifs (a if / else if / else block) with the expressions for the test condition and the body or
can be a Case with the variable to match on and a list of values to match with and body pairs or
can be an Expr with the expression.
-}
type Expr
= Ifs (List ( Expr, Expr ))
| Case String (List ( String, Expr ))
| LetIn (List ( String, Expr )) Expr
| Expr String


{-| Renders a whole module to string.
-}
renderElmModule : Module -> String
renderElmModule (Module name body) =
let
renderedBody =
List.map renderElmFunction body
in
(("module " ++ name ++ " exposing (..)") :: renderedBody)
|> String.join "\n\n\n"


{-| Renders a function to string.
-}
renderElmFunction : Function -> String
renderElmFunction (Function name args returnType body) =
let
renderedArgs =
(List.map renderElmType args ++ [ returnType ])
|> String.join " -> "

annotation =
name ++ " : " ++ renderedArgs

definition =
(name :: List.map renderElmParam args ++ [ "=" ])
|> String.join " "
in
[ annotation, definition, indent (renderElmExpr body) ]
|> String.join "\n"


{-| Renders a type to string.
-}
renderElmType : Arg -> String
renderElmType arg =
case arg of
Primitive typeName _ ->
typeName

Record types ->
let
renderTypes =
Dict.toList
>> List.map
(\( argName, typeName ) ->
argName ++ " : " ++ typeName
)
>> String.join ", "
in
"{ " ++ renderTypes types ++ " }"


{-| Renders a parameter name to string, intended to be used when rendering a function's arguments.
-}
renderElmParam : Arg -> String
renderElmParam arg =
case arg of
Primitive _ param ->
param

Record _ ->
"params_"


{-| Renders an expression to string.
-}
renderElmExpr : Expr -> String
renderElmExpr expr =
case expr of
Ifs alternatives ->
case alternatives of
-- Don't render condition when there is only one branch
[ ( Expr _, Expr subExpr ) ] ->
subExpr

( Expr cond, Expr headExpr ) :: tail ->
([ "if " ++ cond ++ " then"
, indent headExpr
, ""
]
|> String.join "\n"
)
++ renderElseIf tail

_ ->
""

Case matched variants ->
("case " ++ matched ++ " of\n")
++ indent (renderElmCaseVariants variants)

LetIn vars body ->
if List.isEmpty vars then
renderElmExpr body

else
"let\n"
++ indent
(vars
|> List.map renderLetVar
|> String.join "\n"
)
++ "in\n"
++ renderElmExpr body

Expr body ->
body


renderLetVar : ( String, Expr ) -> String
renderLetVar ( name, body ) =
(name ++ " =\n")
++ (indent <| renderElmExpr body ++ "\n")


{-| Recursive function rendering the else if and else parts of an Ifs.
-}
renderElseIf : List ( Expr, Expr ) -> String
renderElseIf alternatives =
case alternatives of
[ ( Expr _, Expr expr ) ] ->
[ "else"
, indent expr
]
|> String.join "\n"

( Expr cond, Expr expr ) :: tail ->
([ "else if " ++ cond ++ " then"
, indent expr
, ""
]
|> String.join "\n"
)
++ renderElseIf tail

_ ->
""


renderElmCaseVariants : List ( String, Expr ) -> String
renderElmCaseVariants variants =
List.map renderElmCaseVariant variants
|> String.join "\n\n"


{-| Renders a match arm.
-}
renderElmCaseVariant : ( String, Expr ) -> String
renderElmCaseVariant ( match, body ) =
(match ++ " ->\n")
++ indent (renderElmExpr body)
import StringUtil exposing (splitOn)


{-| Formats a string to match elm rules on module name.
Expand Down Expand Up @@ -276,17 +66,21 @@ replaceMatches predicate replacement =
>> String.fromList


quote : String -> String
quote string =
if String.contains "\n" string || String.contains "\\" string || String.contains "\"" string then
"\"\"\"" ++ string ++ "\"\"\""

else
"\"" ++ string ++ "\""


{-| List of reserved elm keywords.
-}
keywords : List String
keywords =
[ "if", "then", "else", "case", "of", "let", "in", "type", "module", "where", "import", "exposing", "as", "port" ]


stringConcat : List Expression -> Expression
stringConcat strings =
case List.filter ((/=) (Gen.string "")) strings of
[] ->
Gen.string ""

one :: [] ->
one

first :: rest ->
Gen.binOpChain first Gen.append rest
4 changes: 2 additions & 2 deletions lib/src/Routing/Transpiler.elm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Routing.Transpiler exposing (Command, transpileToElm)
-}

import Dict exposing (Dict)
import Elm exposing (normalizeFunctionName)
import Elm as Gen exposing (normalizeFunctionName)
import Elm.CodeGen as Gen exposing (Declaration, Expression, Import, TypeAnnotation)
import Elm.Pretty as Gen
import Json.Decode exposing (Decoder, decodeString, dict, errorToString, oneOf, string, succeed)
Expand Down Expand Up @@ -189,7 +189,7 @@ recordField chunk =

chunksToElm : String -> Routing -> Expression
chunksToElm urlPrefix routing =
Gen.binOpChain (Gen.string urlPrefix) Gen.append (List.map chunkToElm routing)
Gen.stringConcat (Gen.string urlPrefix :: List.map chunkToElm routing)


chunkToElm : Path -> Expression
Expand Down
11 changes: 8 additions & 3 deletions lib/src/StringUtil.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module StringUtil exposing
( indent, splitOn
, addEmptyLineAtEnd, trimEmptyLines, unindent
, addEmptyLinesAtEnd, addOneEmptyLineAtEnd, trimEmptyLines, unindent
)

{-| Extra tools on strings.
Expand Down Expand Up @@ -116,11 +116,16 @@ trimEmptyLines text =
|> String.join "\n"


addEmptyLineAtEnd : String -> String
addEmptyLineAtEnd text =
addOneEmptyLineAtEnd : String -> String
addOneEmptyLineAtEnd text =
text ++ "\n"


addEmptyLinesAtEnd : Int -> String -> String
addEmptyLinesAtEnd n text =
text ++ (String.concat <| List.repeat n "\n")


trimList : (a -> Bool) -> List a -> List a
trimList predicate list =
list
Expand Down
9 changes: 4 additions & 5 deletions lib/src/Translation/IntlIcu/Transpiler.elm
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Translation.IntlIcu.Transpiler exposing (parseTranslation, translationToElm)

import Dict
import Elm exposing (..)
import Elm.CodeGen as Gen exposing (Declaration, Expression, Import, TypeAnnotation)
import Result
import Translation.IntlIcu.Data exposing (..)
import Translation.IntlIcu.Parser as Parser
Expand All @@ -22,10 +21,10 @@ parseTranslation ( name, message ) =

{-| Turns a translation into an elm function.
-}
translationToElm : Translation -> Function
translationToElm : Translation -> Declaration
translationToElm translation =
Function translation.name (extractArguments translation.content) "String" <|
chunksToElm translation.content
Gen.funDecl Nothing Nothing "" [] <|
Gen.string ""


extractArguments : Chunks -> List Arg
Expand Down
Loading

0 comments on commit 36dca8d

Please sign in to comment.