diff options
Diffstat (limited to 'style-generator/src')
-rw-r--r-- | style-generator/src/Color.elm | 376 | ||||
-rw-r--r-- | style-generator/src/Decoder.elm | 776 | ||||
-rw-r--r-- | style-generator/src/Main.elm | 138 | ||||
-rw-r--r-- | style-generator/src/Writer.elm | 640 |
4 files changed, 1930 insertions, 0 deletions
diff --git a/style-generator/src/Color.elm b/style-generator/src/Color.elm new file mode 100644 index 0000000..b4e8b73 --- /dev/null +++ b/style-generator/src/Color.elm @@ -0,0 +1,376 @@ +module Color exposing (parse) + +import Bitwise exposing (shiftLeftBy) +import Parser exposing ((|.), (|=), Parser, backtrackable, end, keyword, oneOf, spaces, succeed, symbol) + + +type alias Color = + { r : Int, g : Int, b : Int, a : Float } + + +parser : Parser Color +parser = + oneOf + [ keywords + , hsla + + -- , rgba + -- , hex + ] + |. end + + +fromHSLA hue sat light alpha = + let + ( h, s, l ) = + ( toFloat hue / 360, toFloat sat / 100, toFloat light / 100 ) + + m2 = + if l <= 0.5 then + l * (s + 1) + else + l + s - l * s + + m1 = + l * 2 - m2 + + r = + hueToRgb (h + 1 / 3) + + g = + hueToRgb h + + b = + hueToRgb (h - 1 / 3) + + hueToRgb h__ = + let + h_ = + if h__ < 0 then + h__ + 1 + else if h__ > 1 then + h__ - 1 + else + h__ + in + if h_ * 6 < 1 then + m1 + (m2 - m1) * h_ * 6 + else if h_ * 2 < 1 then + m2 + else if h_ * 3 < 2 then + m1 + (m2 - m1) * (2 / 3 - h_) * 6 + else + m1 + in + Color (r * 255 |> floor) (g * 255 |> floor) (b * 255 |> floor) alpha + + +hsla : Parser Color +hsla = + succeed fromHSLA + |. oneOf [ keyword "hsla", keyword "hsl" ] + |. symbol "(" + |= angle + |. spaces + |. symbol "," + |. spaces + |= percentage + |. spaces + |. symbol "," + |. spaces + |= percentage + |= oneOf + [ succeed identity + |. symbol "," + |. spaces + |= Parser.float + , succeed 1 + ] + |. symbol ")" + + +angle = + Parser.map round Parser.float + + +percentage = + Parser.map round Parser.float + |. symbol "%" + + +fromHexString : String -> Parser Color +fromHexString hexString = + case String.toList hexString of + [ '#', r, g, b ] -> + fromHex8 ( r, r ) ( g, g ) ( b, b ) ( 'f', 'f' ) + + [ r, g, b ] -> + fromHex8 ( r, r ) ( g, g ) ( b, b ) ( 'f', 'f' ) + + [ '#', r, g, b, a ] -> + fromHex8 ( r, r ) ( g, g ) ( b, b ) ( a, a ) + + [ r, g, b, a ] -> + fromHex8 ( r, r ) ( g, g ) ( b, b ) ( a, a ) + + [ '#', r1, r2, g1, g2, b1, b2 ] -> + fromHex8 ( r1, r2 ) ( g1, g2 ) ( b1, b2 ) ( 'f', 'f' ) + + [ r1, r2, g1, g2, b1, b2 ] -> + fromHex8 ( r1, r2 ) ( g1, g2 ) ( b1, b2 ) ( 'f', 'f' ) + + [ '#', r1, r2, g1, g2, b1, b2, a1, a2 ] -> + fromHex8 ( r1, r2 ) ( g1, g2 ) ( b1, b2 ) ( a1, a2 ) + + [ r1, r2, g1, g2, b1, b2, a1, a2 ] -> + fromHex8 ( r1, r2 ) ( g1, g2 ) ( b1, b2 ) ( a1, a2 ) + + _ -> + Parser.problem "Invalid color" + + +maybeToParser : Maybe a -> Parser a +maybeToParser aMaybe = + case aMaybe of + Just a -> + succeed a + + Nothing -> + Parser.problem "something went wrong" + + +fromHex8 : ( Char, Char ) -> ( Char, Char ) -> ( Char, Char ) -> ( Char, Char ) -> Parser Color +fromHex8 ( r1, r2 ) ( g1, g2 ) ( b1, b2 ) ( a1, a2 ) = + Maybe.map4 + (\r g b a -> + Color + r + g + b + (toFloat a / 255) + ) + (hex2ToInt r1 r2) + (hex2ToInt g1 g2) + (hex2ToInt b1 b2) + (hex2ToInt a1 a2) + |> maybeToParser + + +hex2ToInt : Char -> Char -> Maybe Int +hex2ToInt c1 c2 = + Maybe.map2 (\v1 v2 -> shiftLeftBy 4 v1 + v2) (hexToInt c1) (hexToInt c2) + + +hexToInt : Char -> Maybe Int +hexToInt char = + case Char.toLower char of + '0' -> + Just 0 + + '1' -> + Just 1 + + '2' -> + Just 2 + + '3' -> + Just 3 + + '4' -> + Just 4 + + '5' -> + Just 5 + + '6' -> + Just 6 + + '7' -> + Just 7 + + '8' -> + Just 8 + + '9' -> + Just 9 + + 'a' -> + Just 10 + + 'b' -> + Just 11 + + 'c' -> + Just 12 + + 'd' -> + Just 13 + + 'e' -> + Just 14 + + 'f' -> + Just 15 + + _ -> + Nothing + + +keywords : Parser Color +keywords = + oneOf + [ fromHexString "#000000" |. keyword "black" + , fromHexString "#c0c0c0" |. keyword "silver" + , fromHexString "#808080" |. keyword "gray" + , fromHexString "#ffffff" |. keyword "white" + , fromHexString "#800000" |. keyword "maroon" + , fromHexString "#ff0000" |. keyword "red" + , fromHexString "#800080" |. keyword "purple" + , fromHexString "#ff00ff" |. keyword "fuchsia" + , fromHexString "#008000" |. keyword "green" + , fromHexString "#00ff00" |. keyword "lime" + , fromHexString "#808000" |. keyword "olive" + , fromHexString "#ffff00" |. keyword "yellow" + , fromHexString "#000080" |. keyword "navy" + , fromHexString "#0000ff" |. keyword "blue" + , fromHexString "#008080" |. keyword "teal" + , fromHexString "#00ffff" |. keyword "aqua" + , fromHexString "#ffa500" |. keyword "orange" + , fromHexString "#f0f8ff" |. keyword "aliceblue" + , fromHexString "#faebd7" |. keyword "antiquewhite" + , fromHexString "#7fffd4" |. keyword "aquamarine" + , fromHexString "#f0ffff" |. keyword "azure" + , fromHexString "#f5f5dc" |. keyword "beige" + , fromHexString "#ffe4c4" |. keyword "bisque" + , fromHexString "#ffebcd" |. keyword "blanchedalmond" + , fromHexString "#8a2be2" |. keyword "blueviolet" + , fromHexString "#a52a2a" |. keyword "brown" + , fromHexString "#deb887" |. keyword "burlywood" + , fromHexString "#5f9ea0" |. keyword "cadetblue" + , fromHexString "#7fff00" |. keyword "chartreuse" + , fromHexString "#d2691e" |. keyword "chocolate" + , fromHexString "#ff7f50" |. keyword "coral" + , fromHexString "#6495ed" |. keyword "cornflowerblue" + , fromHexString "#fff8dc" |. keyword "cornsilk" + , fromHexString "#dc143c" |. keyword "crimson" + , fromHexString "#00ffff" |. keyword "cyan" + , fromHexString "#00008b" |. keyword "darkblue" + , fromHexString "#008b8b" |. keyword "darkcyan" + , fromHexString "#b8860b" |. keyword "darkgoldenrod" + , fromHexString "#a9a9a9" |. keyword "darkgray" + , fromHexString "#006400" |. keyword "darkgreen" + , fromHexString "#a9a9a9" |. keyword "darkgrey" + , fromHexString "#bdb76b" |. keyword "darkkhaki" + , fromHexString "#8b008b" |. keyword "darkmagenta" + , fromHexString "#556b2f" |. keyword "darkolivegreen" + , fromHexString "#ff8c00" |. keyword "darkorange" + , fromHexString "#9932cc" |. keyword "darkorchid" + , fromHexString "#8b0000" |. keyword "darkred" + , fromHexString "#e9967a" |. keyword "darksalmon" + , fromHexString "#8fbc8f" |. keyword "darkseagreen" + , fromHexString "#483d8b" |. keyword "darkslateblue" + , fromHexString "#2f4f4f" |. keyword "darkslategray" + , fromHexString "#2f4f4f" |. keyword "darkslategrey" + , fromHexString "#00ced1" |. keyword "darkturquoise" + , fromHexString "#9400d3" |. keyword "darkviolet" + , fromHexString "#ff1493" |. keyword "deeppink" + , fromHexString "#00bfff" |. keyword "deepskyblue" + , fromHexString "#696969" |. keyword "dimgray" + , fromHexString "#696969" |. keyword "dimgrey" + , fromHexString "#1e90ff" |. keyword "dodgerblue" + , fromHexString "#b22222" |. keyword "firebrick" + , fromHexString "#fffaf0" |. keyword "floralwhite" + , fromHexString "#228b22" |. keyword "forestgreen" + , fromHexString "#dcdcdc" |. keyword "gainsboro" + , fromHexString "#f8f8ff" |. keyword "ghostwhite" + , fromHexString "#ffd700" |. keyword "gold" + , fromHexString "#daa520" |. keyword "goldenrod" + , fromHexString "#adff2f" |. keyword "greenyellow" + , fromHexString "#808080" |. keyword "grey" + , fromHexString "#f0fff0" |. keyword "honeydew" + , fromHexString "#ff69b4" |. keyword "hotpink" + , fromHexString "#cd5c5c" |. keyword "indianred" + , fromHexString "#4b0082" |. keyword "indigo" + , fromHexString "#fffff0" |. keyword "ivory" + , fromHexString "#f0e68c" |. keyword "khaki" + , fromHexString "#e6e6fa" |. keyword "lavender" + , fromHexString "#fff0f5" |. keyword "lavenderblush" + , fromHexString "#7cfc00" |. keyword "lawngreen" + , fromHexString "#fffacd" |. keyword "lemonchiffon" + , fromHexString "#add8e6" |. keyword "lightblue" + , fromHexString "#f08080" |. keyword "lightcoral" + , fromHexString "#e0ffff" |. keyword "lightcyan" + , fromHexString "#fafad2" |. keyword "lightgoldenrodyellow" + , fromHexString "#d3d3d3" |. keyword "lightgray" + , fromHexString "#90ee90" |. keyword "lightgreen" + , fromHexString "#d3d3d3" |. keyword "lightgrey" + , fromHexString "#ffb6c1" |. keyword "lightpink" + , fromHexString "#ffa07a" |. keyword "lightsalmon" + , fromHexString "#20b2aa" |. keyword "lightseagreen" + , fromHexString "#87cefa" |. keyword "lightskyblue" + , fromHexString "#778899" |. keyword "lightslategray" + , fromHexString "#778899" |. keyword "lightslategrey" + , fromHexString "#b0c4de" |. keyword "lightsteelblue" + , fromHexString "#ffffe0" |. keyword "lightyellow" + , fromHexString "#32cd32" |. keyword "limegreen" + , fromHexString "#faf0e6" |. keyword "linen" + , fromHexString "#ff00ff" |. keyword "magenta" + , fromHexString "#66cdaa" |. keyword "mediumaquamarine" + , fromHexString "#0000cd" |. keyword "mediumblue" + , fromHexString "#ba55d3" |. keyword "mediumorchid" + , fromHexString "#9370db" |. keyword "mediumpurple" + , fromHexString "#3cb371" |. keyword "mediumseagreen" + , fromHexString "#7b68ee" |. keyword "mediumslateblue" + , fromHexString "#00fa9a" |. keyword "mediumspringgreen" + , fromHexString "#48d1cc" |. keyword "mediumturquoise" + , fromHexString "#c71585" |. keyword "mediumvioletred" + , fromHexString "#191970" |. keyword "midnightblue" + , fromHexString "#f5fffa" |. keyword "mintcream" + , fromHexString "#ffe4e1" |. keyword "mistyrose" + , fromHexString "#ffe4b5" |. keyword "moccasin" + , fromHexString "#ffdead" |. keyword "navajowhite" + , fromHexString "#fdf5e6" |. keyword "oldlace" + , fromHexString "#6b8e23" |. keyword "olivedrab" + , fromHexString "#ff4500" |. keyword "orangered" + , fromHexString "#da70d6" |. keyword "orchid" + , fromHexString "#eee8aa" |. keyword "palegoldenrod" + , fromHexString "#98fb98" |. keyword "palegreen" + , fromHexString "#afeeee" |. keyword "paleturquoise" + , fromHexString "#db7093" |. keyword "palevioletred" + , fromHexString "#ffefd5" |. keyword "papayawhip" + , fromHexString "#ffdab9" |. keyword "peachpuff" + , fromHexString "#cd853f" |. keyword "peru" + , fromHexString "#ffc0cb" |. keyword "pink" + , fromHexString "#dda0dd" |. keyword "plum" + , fromHexString "#b0e0e6" |. keyword "powderblue" + , fromHexString "#bc8f8f" |. keyword "rosybrown" + , fromHexString "#4169e1" |. keyword "royalblue" + , fromHexString "#8b4513" |. keyword "saddlebrown" + , fromHexString "#fa8072" |. keyword "salmon" + , fromHexString "#f4a460" |. keyword "sandybrown" + , fromHexString "#2e8b57" |. keyword "seagreen" + , fromHexString "#fff5ee" |. keyword "seashell" + , fromHexString "#a0522d" |. keyword "sienna" + , fromHexString "#87ceeb" |. keyword "skyblue" + , fromHexString "#6a5acd" |. keyword "slateblue" + , fromHexString "#708090" |. keyword "slategray" + , fromHexString "#708090" |. keyword "slategrey" + , fromHexString "#fffafa" |. keyword "snow" + , fromHexString "#00ff7f" |. keyword "springgreen" + , fromHexString "#4682b4" |. keyword "steelblue" + , fromHexString "#d2b48c" |. keyword "tan" + , fromHexString "#d8bfd8" |. keyword "thistle" + , fromHexString "#ff6347" |. keyword "tomato" + , fromHexString "#40e0d0" |. keyword "turquoise" + , fromHexString "#ee82ee" |. keyword "violet" + , fromHexString "#f5deb3" |. keyword "wheat" + , fromHexString "#f5f5f5" |. keyword "whitesmoke" + , fromHexString "#9acd32" |. keyword "yellowgreen" + , fromHexString "#663399" |. keyword "rebeccapurple" + , succeed (Color 0 0 0 0) |. keyword "transparent" + ] + + +parse : String -> Result String Color +parse string = + Parser.run parser string |> Result.mapError Parser.deadEndsToString diff --git a/style-generator/src/Decoder.elm b/style-generator/src/Decoder.elm new file mode 100644 index 0000000..65dc0a2 --- /dev/null +++ b/style-generator/src/Decoder.elm @@ -0,0 +1,776 @@ +module Decoder exposing (styleCode) + +import Color +import Elm.Syntax.Declaration exposing (Declaration(..)) +import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) +import Elm.Syntax.Expression exposing (Expression(..), RecordSetter) +import Elm.Syntax.Infix exposing (InfixDirection(..)) +import Elm.Syntax.Module exposing (Module(..)) +import Elm.Syntax.Node exposing (Node(..)) +import Elm.Syntax.Pattern +import Elm.Syntax.Range exposing (emptyRange) +import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..)) +import Json.Decode as D exposing (Decoder) +import Json.Encode +import String.Case exposing (toCamelCaseLower) +import Writer + + +node = + Node emptyRange + + +wrapNodes = + List.map node + + +styleCode : Decoder String +styleCode = + D.map (file >> Writer.writeFile >> Writer.write) style + + +declarations styleDec = + [ FunctionDeclaration + { documentation = Nothing + , signature = + Just + (node + { name = node "style" + , typeAnnotation = node (Typed (node ( [], "Style" )) []) + } + ) + , declaration = + node + { name = node "style" + , arguments = [] + , expression = + node <| + Application <| + wrapNodes + [ FunctionOrValue [] "Style" + , RecordExpr styleDec + ] + } + } + ] + + +file styleDec = + { moduleDefinition = + node + (NormalModule + { moduleName = node [ "Style" ] + , exposingList = node (Explicit [ node (FunctionExpose "style") ]) + } + ) + , imports = + [ node + { moduleName = node [ "Mapbox", "Style" ] + , moduleAlias = Just (node [ "Style" ]) + , exposingList = Just (Explicit [ node (TypeExpose { name = "Style", open = Just emptyRange }) ]) + } + , node + { moduleName = node [ "Mapbox", "Source" ] + , moduleAlias = Just (node [ "Source" ]) + , exposingList = Nothing + } + , node + { moduleName = node [ "Mapbox", "Layer" ] + , moduleAlias = Just (node [ "Layer" ]) + , exposingList = Nothing + } + , node + { moduleName = node [ "Mapbox", "Expression" ] + , moduleAlias = Just (node [ "E" ]) + , exposingList = Just (Explicit [ node (FunctionExpose "str"), node (FunctionExpose "float"), node (FunctionExpose "int"), node (FunctionExpose "true"), node (FunctionExpose "false") ]) + } + ] + , declarations = List.map node (declarations styleDec) + , comments = [] + } + + +style : Decoder (List (Node RecordSetter)) +style = + D.map5 + (\transition light layers sources misc -> + [ node ( node "transition", transition ) + , node ( node "light", light ) + , node ( node "layers", layers ) + , node ( node "sources", sources ) + , node ( node "misc", misc ) + ] + ) + (D.oneOf + [ D.field "transition" decodeTransition + , valueDecoder "Style" "defaultTransition" + ] + ) + (D.oneOf + [ D.field "light" decodeLight + , valueDecoder "Style" "defaultLight" + ] + ) + (D.field "layers" decodeLayers) + (D.field "sources" decodeSources) + decodeMisc + + +decodeTransition : Decoder (Node Expression) +decodeTransition = + D.map2 + (\duration delay -> + node + (RecordExpr + [ node ( node "duration", node (Integer duration) ) + , node ( node "delay", node (Integer delay) ) + ] + ) + ) + (D.oneOf [ D.field "duration" D.int, D.succeed 300 ]) + (D.oneOf [ D.field "delay" D.int, D.succeed 0 ]) + + +decodeLight : Decoder (Node Expression) +decodeLight = + valueDecoder "Style" "defaultLight" + + +addBogusRange index (Node _ v) = + Node { start = { row = index, column = 0 }, end = { row = index + 1, column = 0 } } v + + +decodeLayers : Decoder (Node Expression) +decodeLayers = + D.list decodeLayer + |> D.map (\layers -> node (ListExpr (List.indexedMap addBogusRange layers))) + + +layerDecodeHelp t = + D.map3 (\id source attrs -> call "Layer" t [ str id, str source, list attrs ]) (D.field "id" D.string) (D.field "source" D.string) decodeAttrs + + +decodeLayer : Decoder (Node Expression) +decodeLayer = + D.field "type" D.string + |> D.andThen + (\t -> + case t of + "background" -> + D.map2 (\id attrs -> call "Layer" "background" [ str id, list attrs ]) (D.field "id" D.string) decodeAttrs + + "fill" -> + layerDecodeHelp "fill" + + "symbol" -> + layerDecodeHelp "symbol" + + "line" -> + layerDecodeHelp "line" + + "raster" -> + layerDecodeHelp "raster" + + "circle" -> + layerDecodeHelp "circle" + + "fill-extrusion" -> + layerDecodeHelp "fillExtrusion" + + "heatmap" -> + layerDecodeHelp "heatmap" + + "hillshade" -> + layerDecodeHelp "hillshade" + + other -> + D.fail ("Layer type " ++ t ++ " not supported") + ) + + +decodeAttrs : Decoder (List (Node Expression)) +decodeAttrs = + D.map3 (\top paint layout -> top ++ paint ++ layout) (D.keyValuePairs D.value) (D.field "paint" (D.keyValuePairs D.value)) (D.field "layout" (D.keyValuePairs D.value)) + |> D.andThen + (List.filterMap + (\( attrName, attrValue ) -> + case attrName of + "id" -> + Nothing + + "type" -> + Nothing + + "source" -> + Nothing + + "paint" -> + Nothing + + "layout" -> + Nothing + + "source-layer" -> + decodeAttr "sourceLayer" (D.map str D.string) attrValue + + "minzoom" -> + decodeAttr "minzoom" (D.map float D.float) attrValue + + "maxzoom" -> + decodeAttr "maxzoom" (D.map float D.float) attrValue + + "filter" -> + decodeAttr "filter" (D.oneOf [ decodeLegacyFilter, decodeValue ]) attrValue + + other -> + decodeAttr (toCamelCaseLower attrName) decodeValue attrValue + ) + >> combine + ) + |> D.map (List.indexedMap addBogusRange) + + +decodeAttr : String -> Decoder (Node Expression) -> D.Value -> Maybe (Decoder (Node Expression)) +decodeAttr attrName expressionNodeDecoder attrValue = + Just + (D.decodeValue expressionNodeDecoder attrValue + |> resultToDecoder + |> D.map (\v -> call "Layer" (toCamelCaseLower attrName) [ v ]) + ) + + +resultToDecoder : Result D.Error a -> Decoder a +resultToDecoder res = + case res of + Ok a -> + D.succeed a + + Err e -> + D.fail (D.errorToString e) + + +decodeBool : Decoder (Node Expression) +decodeBool = + D.bool + |> D.map + (\b -> + if b then + evalue "true" + else + evalue "false" + ) + + +decodeValue : Decoder (Node Expression) +decodeValue = + D.oneOf + [ D.string |> D.map makeConstant + , decodeBool + , D.float |> D.map (Floatable >> node >> ecall "float") + , D.int |> D.map (Integer >> node >> ecall "int") + , D.index 0 D.string |> D.andThen decodeExpression + , todo + ] + |> D.map (ParenthesizedExpression >> node) + + +makeConstant s = + case s of + "map" -> + value "E" "anchorMap" + + "viewport" -> + value "E" "anchorViewport" + + "auto" -> + value "E" "anchorAuto" + + "center" -> + value "E" "positionCenter" + + "left" -> + value "E" "positionLeft" + + "right" -> + value "E" "positionRight" + + "top" -> + value "E" "positionTop" + + "bottom" -> + value "E" "positionBottom" + + "topRight" -> + value "E" "positionTopRight" + + "topLeft" -> + value "E" "positionTopLeft" + + "bottomLeft" -> + value "E" "positionBottomLeft" + + "bottomRight" -> + value "E" "positionBottomRight" + + "none" -> + value "E" "textFitNone" + + "width" -> + value "E" "textFitWidth" + + "height" -> + value "E" "textFitHeight" + + "both" -> + value "E" "textFitBoth" + + "butt" -> + value "E" "lineCapButt" + + "round" -> + value "E" "lineCapRound" + + "square" -> + value "E" "lineCapSquare" + + "bevel" -> + value "E" "lineJoinBevel" + + "miter" -> + value "E" "lineJoinMiter" + + "point" -> + value "E" "symbolPlacementPoint" + + "line-center" -> + value "E" "symbolPlacementLineCenter" + + "line" -> + value "E" "symbolPlacementLine" + + "uppercase" -> + value "E" "textTransformUppercase" + + "lowercase" -> + value "E" "textTransformLowercase" + + "linear" -> + value "E" "rasterResamplingLinear" + + "nearest" -> + value "E" "rasterResamplingNearest" + + _ -> + case Color.parse s of + Ok { r, g, b, a } -> + call "E" "rgba" [ integer r, integer g, integer b, float a ] + + Err err -> + str s |> ecall "str" + + + +-- legacy filter + + +decodeLegacyFilter : Decoder (Node Expression) +decodeLegacyFilter = + let + decodeProp = + D.index 1 D.string + |> D.map + (\prop -> + case prop of + "$type" -> + value "E" "geometryType" + + "$id" -> + value "E" "id" + + _ -> + call "E" "getProperty" [ ecall "str" (str prop) ] + ) + + decodeVal = + D.index 2 <| + D.oneOf + [ D.map (str >> ecall "str") D.string + , D.map (float >> ecall "float") D.float + , decodeBool + ] + + decodeVals = + D.list <| + D.oneOf + [ D.map (str >> ecall "str") D.string + , D.map (float >> ecall "float") D.float + , decodeBool + ] + in + D.index 0 D.string + |> D.andThen + (\filter -> + case filter of + "all" -> + decodeTail decodeLegacyFilter |> D.map (\filters -> call "E" "all" [ list filters ]) + + "any" -> + decodeTail decodeLegacyFilter |> D.map (\filters -> call "E" "any" [ list filters ]) + + "none" -> + decodeTail decodeLegacyFilter |> D.map (\filters -> call "E" "all" [ list (List.map (\f -> call "E" "not" [ f ]) filters) ]) + + "has" -> + D.index 1 D.string |> D.map (\prop -> call "E" "hasProperty" [ ecall "str" (str prop) ]) + + "!has" -> + D.index 1 D.string |> D.map (\prop -> call "E" "not" [ call "E" "hasProperty" [ ecall "str" (str prop) ] ]) + + "==" -> + D.map2 (\prop val -> pipelineCall "E" "isEqual" [ prop, val ]) decodeProp decodeVal + + "!=" -> + D.map2 (\prop val -> pipelineCall "E" "notEqual" [ prop, val ]) decodeProp decodeVal + + ">" -> + D.map2 (\prop val -> pipelineCall "E" "greaterThan" [ prop, val ]) decodeProp decodeVal + + ">=" -> + D.map2 (\prop val -> pipelineCall "E" "greaterThanOrEqual" [ prop, val ]) decodeProp decodeVal + + "<" -> + D.map2 (\prop val -> pipelineCall "E" "lessThan" [ prop, val ]) decodeProp decodeVal + + "<=" -> + D.map2 (\prop val -> pipelineCall "E" "lessThanOrEqual" [ prop, val ]) decodeProp decodeVal + + "in" -> + D.map2 + (\prop values -> + List.drop 2 values + |> List.map (\v -> pipelineCall "E" "isEqual" [ prop, v ]) + |> list + |> List.singleton + |> call "E" "any" + ) + decodeProp + decodeVals + + "!in" -> + D.map2 + (\prop values -> + List.drop 2 values + |> List.map (\v -> pipelineCall "E" "notEqual" [ prop, v ]) + |> list + |> List.singleton + |> call "E" "all" + ) + decodeProp + decodeVals + + _ -> + D.fail "not actually a legacy filter" + ) + + + +-- Expressions + + +decodeTail : Decoder a -> Decoder (List a) +decodeTail itemDecoder = + D.list D.value + |> D.andThen + (\l -> + case l of + [] -> + D.fail "Can't get tail of empty" + + head :: t -> + List.map (subdecode itemDecoder) t |> combine + ) + + +subdecode : Decoder a -> D.Value -> Decoder a +subdecode d v = + D.decodeValue d v |> resultToDecoder + + +decodeMatch : Bool -> any -> Decoder (Node Expression) +decodeMatch isString _ = + decodeTail D.value + |> D.andThen + (\args -> + case args of + [] -> + todo + + head :: tail -> + D.map2 + (\cond rest -> + parens + (node + (OperatorApplication "|>" + Right + cond + (call "E" + (if isString then + "matchesStr" + else + "matchesFloat" + ) + rest + ) + ) + ) + ) + (subdecode decodeValue head) + (organizeArgs + (if isString then + D.map str D.string + else + D.map float D.float + ) + [] + tail + ) + ) + + +organizeArgs : Decoder (Node Expression) -> List (Decoder (Node Expression)) -> List D.Value -> Decoder (List (Node Expression)) +organizeArgs inpDec accu args = + case args of + [] -> + combine [ D.map list (List.reverse accu |> combine) ] + + [ default ] -> + combine [ D.map list (List.reverse accu |> combine), subdecode decodeValue default ] + + a :: b :: rest -> + let + newAccu = + D.map2 + (\inp out -> + parens (node (TupledExpression [ inp, out ])) + ) + (subdecode inpDec a) + (subdecode decodeValue b) + :: accu + in + organizeArgs inpDec newAccu rest + + +decodeExpression : String -> Decoder (Node Expression) +decodeExpression funName = + case funName of + "literal" -> + D.index 1 + (D.oneOf + [ D.list D.string |> D.map (\strs -> call "E" "strings" [ list (List.map str strs) ]) + , D.list D.float |> D.map (\floats -> call "E" "floats" [ list (List.map float floats) ]) + ] + ) + + "match" -> + D.oneOf + [ D.index 2 D.string |> D.andThen (decodeMatch True) + , D.index 2 D.float |> D.andThen (decodeMatch False) + ] + + "exponential" -> + D.map (\base -> call "E" "Exponential" [ float base ]) (D.index 1 D.float) + + "interpolate" -> + D.map3 + (\interpolation options input -> + pipelineCall "E" "interpolate" (input :: interpolation :: options) + ) + (D.index 1 decodeValue) + (decodeTail D.value |> D.map (List.drop 2) |> D.andThen (organizeArgs (D.map float D.float) [])) + (D.index 2 decodeValue) + + "step" -> + D.map3 + (\inp def stps -> + pipelineCall "E" "step" (inp :: def :: stps) + ) + (D.index 1 decodeValue) + (D.index 2 decodeValue) + (decodeTail D.value |> D.map (List.drop 2) |> D.andThen (organizeArgs (D.map float D.float) [])) + + _ -> + let + fallback = + decodeTail decodeValue + |> D.map + (\arguments -> + case funName of + "==" -> + pipelineCall "E" "isEqual" arguments + + "!=" -> + pipelineCall "E" "notEqual" arguments + + "!has" -> + todoExpr "!has is not supported" + + "!in" -> + todoExpr "!in is not supported" + + "in" -> + todoExpr "in is not supported" + + ">=" -> + pipelineCall "E" "greaterThanOrEqual" arguments + + "<=" -> + pipelineCall "E" "lessThanOrEqual" arguments + + "concat" -> + pipelineCall "E" "append" arguments + + "linear" -> + call "E" "Linear" arguments + + "rgb" -> + call "E" "makeRGBColor" arguments + + "rgba" -> + call "E" "makeRGBAColor" arguments + + "to-rgba" -> + call "E" "rgbaChannels" arguments + + "-" -> + pipelineCall "E" "minus" arguments + + "*" -> + pipelineCall "E" "multiply" arguments + + "+" -> + pipelineCall "E" "plus" arguments + + "/" -> + pipelineCall "E" "divideBy" arguments + + "%" -> + pipe |