Merge pull request #35 from dvdsgl/doc-render-refactor

Cleanup Doc and Renderer

Original commit 4a95cae60c9963125ce2d9a1082e9495f32af853
This commit is contained in:
David Siegel 2017-07-25 20:50:29 -07:00 коммит произвёл GitHub
Родитель 1b4155cf4a d1e73a434f
Коммит 5ad2d7df2b
5 изменённых файлов: 159 добавлений и 153 удалений

Просмотреть файл

@ -5,15 +5,13 @@ module CSharp
import Doc
import IRGraph
import Prelude
import Types
import Data.Char.Unicode (GeneralCategory(..), generalCategory, isLetter)
import Data.Foldable (find, for_, intercalate)
import Data.List (List, (:))
import Data.List as L
import Data.Map (Map)
import Data.Map as M
import Data.Maybe (Maybe(..), isNothing)
import Data.Maybe (Maybe(..))
import Data.Set (Set)
import Data.Set as S
import Data.String as Str
@ -23,42 +21,39 @@ import Partial.Unsafe (unsafePartial)
import Utils (removeElement)
type CSDoc = Doc Unit
forbiddenNames :: Array String
forbiddenNames = [ "Converter", "JsonConverter", "Type" ]
forbiddenNames = ["Converter", "JsonConverter", "Type"]
renderer :: Renderer
renderer =
{ name: "C#"
, aceMode: "csharp"
, extension: "cs"
, render: renderGraphToCSharp
, doc: csharpDoc
, transforms:
{ nameForClass
, unionName
, unionPredicate
, nextName: \s -> "Other" <> s
, forbiddenNames
}
}
renderGraphToCSharp :: IRGraph -> String
renderGraphToCSharp graph =
runDoc csharpDoc nameForClass unionName unionPredicate nextNameToTry (S.fromFoldable forbiddenNames) graph unit
where
unionPredicate =
case _ of
IRUnion ur ->
let s = unionToSet ur
in
if isNothing $ nullableFromSet s then
Just s
else
Nothing
unionPredicate :: IRType -> Maybe (Set IRType)
unionPredicate = case _ of
IRUnion ur ->
let s = unionToSet ur
in case nullableFromSet s of
Nothing -> Just s
_ -> Nothing
nameForClass (IRClassData { names }) =
csNameStyle $ combineNames names
nextNameToTry s =
"Other" <> s
_ -> Nothing
nameForClass :: IRClassData -> String
nameForClass (IRClassData { names }) = csNameStyle $ combineNames names
unionName :: List String -> String
unionName s =
s
# L.sort
L.sort s
<#> csNameStyle
# intercalate "Or"
@ -98,7 +93,7 @@ legalizeIdentifier str =
else
legalizeIdentifier ("_" <> str)
renderUnionToCSharp :: Set IRType -> CSDoc String
renderUnionToCSharp :: Set IRType -> Doc String
renderUnionToCSharp s =
case nullableFromSet s of
Just x -> do
@ -106,7 +101,7 @@ renderUnionToCSharp s =
pure if isValueType x then rendered <> "?" else rendered
Nothing -> lookupUnionName s
renderTypeToCSharp :: IRType -> CSDoc String
renderTypeToCSharp :: IRType -> Doc String
renderTypeToCSharp = case _ of
IRNothing -> pure "object"
IRNull -> pure "object"
@ -126,7 +121,7 @@ renderTypeToCSharp = case _ of
csNameStyle :: String -> String
csNameStyle = camelCase >>> capitalize >>> legalizeIdentifier
csharpDoc :: CSDoc Unit
csharpDoc :: Doc Unit
csharpDoc = do
line """// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
@ -160,7 +155,7 @@ stringIfTrue :: Boolean -> String -> String
stringIfTrue true s = s
stringIfTrue false _ = ""
renderJsonConverter :: CSDoc Unit
renderJsonConverter :: Doc Unit
renderJsonConverter = do
unionNames <- getUnionNames
let haveUnions = not $ M.isEmpty unionNames
@ -168,8 +163,7 @@ renderJsonConverter = do
line $ "public class Converter" <> stringIfTrue haveUnions ": JsonConverter"
line "{"
indent do
IRGraph { toplevel } <- getGraph
toplevelType <- renderTypeToCSharp toplevel
toplevelType <- getTopLevel >>= renderTypeToCSharp
line "// Loading helpers"
let converterParam = stringIfTrue haveUnions ", new Converter()"
line
@ -206,35 +200,32 @@ renderJsonConverter = do
line "public override bool CanWrite => false;"
line "}"
tokenCase :: String -> CSDoc Unit
tokenCase :: String -> Doc Unit
tokenCase tokenType =
line $ "case JsonToken." <> tokenType <> ":"
renderNullDeserializer :: Set IRType -> CSDoc Unit
renderNullDeserializer :: Set IRType -> Doc Unit
renderNullDeserializer types =
when (S.member IRNull types) do
tokenCase "Null"
indent do
line "break;"
unionFieldName :: IRType -> CSDoc String
unionFieldName t = do
graph <- getGraph
let typeName = typeNameForUnion graph t
pure $ csNameStyle typeName
unionFieldName :: IRType -> Doc String
unionFieldName t = csNameStyle <$> getTypeNameForUnion t
deserialize :: String -> String -> CSDoc Unit
deserialize :: String -> String -> Doc Unit
deserialize fieldName typeName = do
line $ fieldName <> " = serializer.Deserialize<" <> typeName <> ">(reader);"
line "break;"
deserializeType :: IRType -> CSDoc Unit
deserializeType :: IRType -> Doc Unit
deserializeType t = do
fieldName <- unionFieldName t
renderedType <- renderTypeToCSharp t
deserialize fieldName renderedType
renderPrimitiveDeserializer :: List String -> IRType -> Set IRType -> CSDoc Unit
renderPrimitiveDeserializer :: List String -> IRType -> Set IRType -> Doc Unit
renderPrimitiveDeserializer tokenTypes t types =
when (S.member t types) do
for_ tokenTypes \tokenType -> do
@ -242,7 +233,7 @@ renderPrimitiveDeserializer tokenTypes t types =
indent do
deserializeType t
renderDoubleDeserializer :: Set IRType -> CSDoc Unit
renderDoubleDeserializer :: Set IRType -> Doc Unit
renderDoubleDeserializer types =
when (S.member IRDouble types) do
unless (S.member IRInteger types) do
@ -251,7 +242,7 @@ renderDoubleDeserializer types =
indent do
deserializeType IRDouble
renderGenericDeserializer :: (IRType -> Boolean) -> String -> Set IRType -> CSDoc Unit
renderGenericDeserializer :: (IRType -> Boolean) -> String -> Set IRType -> Doc Unit
renderGenericDeserializer predicate tokenType types = unsafePartial $
case find predicate types of
Nothing -> pure unit
@ -260,7 +251,7 @@ renderGenericDeserializer predicate tokenType types = unsafePartial $
indent do
deserializeType t
renderCSharpUnion :: Set IRType -> CSDoc Unit
renderCSharpUnion :: Set IRType -> Doc Unit
renderCSharpUnion allTypes = do
name <- lookupUnionName allTypes
let { element: emptyOrNull, rest: nonNullTypes } = removeElement (_ == IRNull) allTypes
@ -295,7 +286,7 @@ renderCSharpUnion allTypes = do
line "}"
line "}"
renderCSharpClass :: IRClassData -> String -> CSDoc Unit
renderCSharpClass :: IRClassData -> String -> Doc Unit
renderCSharpClass (IRClassData { names, properties }) className = do
let propertyNames = transformNames csNameStyle ("Other" <> _) (S.singleton className) $ map (\n -> Tuple n n) $ M.keys properties
line $ "public class " <> className

Просмотреть файл

@ -1,12 +1,12 @@
module Doc
( Doc
, getGraph
, Renderer
, Transforms
, getTopLevel
, getClasses
, getClass
, getClassNames
, getUnions
, getUnionNames
, getRendererInfo
, lookupName
, lookupClassName
, lookupUnionName
@ -16,15 +16,17 @@ module Doc
, indent
-- Build Doc Unit with monad syntax, then render to string
, runDoc
, typeNameForUnion
, runRenderer
, getTypeNameForUnion
) where
import Prelude
import IR
import IRGraph
import Prelude
import Control.Monad.RWS (RWS, evalRWS, asks, gets, modify, tell)
import Data.Foldable (for_, intercalate, sequence_)
import Data.Foldable (for_)
import Data.List (List)
import Data.List as L
import Data.Map (Map)
@ -35,28 +37,53 @@ import Data.Set as S
import Data.String as String
import Data.Tuple (Tuple(..), snd)
type DocState = { indent :: Int }
type DocEnv r = { graph :: IRGraph, classNames :: Map Int String, unionNames :: Map (Set IRType) String, unions :: List (Set IRType), rendererInfo :: r }
newtype Doc r a = Doc (RWS (DocEnv r) String DocState a)
type Renderer =
{ name :: String
, extension :: String
, aceMode :: String
, doc :: Doc Unit
, transforms :: Transforms
}
derive newtype instance functorDoc :: Functor (Doc r)
derive newtype instance applyDoc :: Apply (Doc r)
derive newtype instance applicativeDoc :: Applicative (Doc r)
derive newtype instance bindDoc :: Bind (Doc r)
derive newtype instance monadDoc :: Monad (Doc r)
runDoc :: forall r a. Doc r a -> (IRClassData -> String) -> (List String -> String) -> (IRType -> Maybe (Set IRType)) -> (String -> String) -> (Set String) -> IRGraph -> r -> String
runDoc (Doc w) nameForClass unionName unionPredicate nextNameToTry forbiddenNames graph rendererInfo =
type Transforms =
{ nameForClass :: IRClassData -> String
, unionName :: List String -> String
, unionPredicate :: IRType -> Maybe (Set IRType)
, nextName :: String -> String
, forbiddenNames :: Array String
}
type DocState = { indent :: Int }
type DocEnv =
{ graph :: IRGraph
, classNames :: Map Int String
, unionNames :: Map (Set IRType) String
, unions :: List (Set IRType)
}
newtype Doc a = Doc (RWS DocEnv String DocState a)
derive newtype instance functorDoc :: Functor Doc
derive newtype instance applyDoc :: Apply Doc
derive newtype instance applicativeDoc :: Applicative Doc
derive newtype instance bindDoc :: Bind Doc
derive newtype instance monadDoc :: Monad Doc
runRenderer :: Renderer -> IRGraph -> String
runRenderer { doc, transforms } = runDoc doc transforms
runDoc :: forall a. Doc a -> Transforms -> IRGraph -> String
runDoc (Doc w) t graph =
let classes = classesInGraph graph
classNames = transformNames nameForClass nextNameToTry forbiddenNames classes
unions = L.fromFoldable $ filterTypes unionPredicate graph
forbiddenForUnions = S.union forbiddenNames $ S.fromFoldable $ M.values classNames
unionNames = transformNames nameForUnion nextNameToTry forbiddenForUnions $ map (\s -> Tuple s s) unions
forbidden = S.fromFoldable t.forbiddenNames
classNames = transformNames t.nameForClass t.nextName forbidden classes
unions = L.fromFoldable $ filterTypes t.unionPredicate graph
forbiddenForUnions = S.union forbidden $ S.fromFoldable $ M.values classNames
nameForUnion s = t.unionName $ map (typeNameForUnion graph) $ L.sort $ L.fromFoldable s
unionNames = transformNames nameForUnion t.nextName forbiddenForUnions $ map (\s -> Tuple s s) unions
in
evalRWS w { graph, classNames, unionNames, unions, rendererInfo } { indent: 0 } # snd
where
nameForUnion s =
unionName $ map (typeNameForUnion graph) $ L.sort $ L.fromFoldable s
evalRWS w { graph, classNames, unionNames, unions } { indent: 0 } # snd
typeNameForUnion :: IRGraph -> IRType -> String
typeNameForUnion graph = case _ of
@ -69,30 +96,36 @@ typeNameForUnion graph = case _ of
IRArray a -> typeNameForUnion graph a <> "_array"
IRClass i ->
let IRClassData { names } = getClassFromGraph graph i
in
combineNames names
in combineNames names
IRMap t -> typeNameForUnion graph t <> "_map"
IRUnion _ -> "union"
getGraph :: forall r. Doc r IRGraph
getTypeNameForUnion :: IRType -> Doc String
getTypeNameForUnion typ = do
g <- getGraph
pure $ typeNameForUnion g typ
getGraph :: Doc IRGraph
getGraph = Doc (asks _.graph)
getClassNames :: forall r. Doc r (Map Int String)
getTopLevel :: Doc IRType
getTopLevel = do
IRGraph { toplevel } <- getGraph
pure toplevel
getClassNames :: Doc (Map Int String)
getClassNames = Doc (asks _.classNames)
getUnions :: forall r. Doc r (List (Set IRType))
getUnions :: Doc (List (Set IRType))
getUnions = Doc (asks _.unions)
getUnionNames :: forall r. Doc r (Map (Set IRType) String)
getUnionNames :: Doc (Map (Set IRType) String)
getUnionNames = Doc (asks _.unionNames)
getRendererInfo :: forall r. Doc r r
getRendererInfo = Doc (asks _.rendererInfo)
getClasses :: forall r. Doc r (L.List (Tuple Int IRClassData))
getClasses :: Doc (L.List (Tuple Int IRClassData))
getClasses = classesInGraph <$> getGraph
getClass :: forall r. Int -> Doc r IRClassData
getClass :: Int -> Doc IRClassData
getClass i = do
graph <- getGraph
pure $ getClassFromGraph graph i
@ -101,18 +134,18 @@ lookupName :: forall a. Ord a => a -> Map a String -> String
lookupName original nameMap =
fromMaybe "NAME_NOT_PROCESSED" $ M.lookup original nameMap
lookupClassName :: forall r. Int -> Doc r String
lookupClassName :: Int -> Doc String
lookupClassName i = do
classNames <- getClassNames
pure $ lookupName i classNames
lookupUnionName :: forall r. Set IRType -> Doc r String
lookupUnionName :: Set IRType -> Doc String
lookupUnionName s = do
unionNames <- getUnionNames
pure $ lookupName s unionNames
-- Given a potentially multi-line string, render each line at the current indent level
line :: forall r. String -> Doc r Unit
line :: String -> Doc Unit
line s = do
indent <- Doc (gets _.indent)
let whitespace = times "\t" indent
@ -128,13 +161,13 @@ times s n | n < 1 = ""
times s 1 = s
times s n = s <> times s (n - 1)
string :: forall r. String -> Doc r Unit
string :: String -> Doc Unit
string = Doc <<< tell
blank :: forall r. Doc r Unit
blank :: Doc Unit
blank = string "\n"
indent :: forall r a. Doc r a -> Doc r a
indent :: forall a. Doc a -> Doc a
indent doc = do
Doc $ modify (\s -> { indent: s.indent + 1 })
a <- doc

Просмотреть файл

@ -5,7 +5,6 @@ module Golang
import Doc
import IRGraph
import Prelude
import Types
import Data.Array as A
import Data.Char.Unicode (isDigit, isLetter)
@ -20,37 +19,38 @@ import Data.String.Util (capitalize, camelCase, stringEscape)
import Data.Tuple (Tuple(..))
import Utils (mapM, removeElement)
-- data GoInfo = GoInfo { classNames :: Map Int String }
type GoDoc = Doc Unit
renderer :: Renderer
renderer =
{ name: "Go"
, aceMode: "golang"
, extension: "go"
, render: renderGraphToGolang
, doc: golangDoc
, transforms:
{ nameForClass
, unionName
, unionPredicate
, nextName: \s -> "Other" <> s
, forbiddenNames: []
}
}
renderGraphToGolang :: IRGraph -> String
renderGraphToGolang graph =
runDoc golangDoc nameForClass unionName unionPredicate nextNameToTry S.empty graph unit
where
unionPredicate =
case _ of
IRUnion ur ->
let s = unionToSet ur
in
if isNothing $ nullableFromSet s then
Just s
else
Nothing
unionPredicate :: IRType -> Maybe (Set IRType)
unionPredicate = case _ of
IRUnion ur ->
let s = unionToSet ur
in case nullableFromSet s of
Nothing -> Just s
_ -> Nothing
nameForClass (IRClassData { names }) = goNameStyle $ combineNames names
unionName components =
"OneOf" <> (goNameStyle $ intercalate "_" $ components)
nextNameToTry s =
"Other" <> s
_ -> Nothing
nameForClass :: IRClassData -> String
nameForClass (IRClassData { names }) = goNameStyle $ combineNames names
unionName :: L.List String -> String
unionName s =
L.sort s
<#> goNameStyle
# intercalate "Or"
isValueType :: IRType -> Boolean
isValueType IRInteger = true
@ -82,7 +82,7 @@ legalizeIdentifier str =
else
legalizeIdentifier ("_" <> str)
renderUnionToGolang :: Set IRType -> GoDoc String
renderUnionToGolang :: Set IRType -> Doc String
renderUnionToGolang s =
case nullableFromSet s of
Just x -> do
@ -90,7 +90,7 @@ renderUnionToGolang s =
pure if isValueType x then "*" <> rendered else rendered
Nothing -> lookupUnionName s
renderTypeToGolang :: IRType -> GoDoc String
renderTypeToGolang :: IRType -> Doc String
renderTypeToGolang = case _ of
IRNothing -> pure "interface{}"
IRNull -> pure "interface{}"
@ -110,7 +110,7 @@ renderTypeToGolang = case _ of
goNameStyle :: String -> String
goNameStyle = camelCase >>> capitalize >>> legalizeIdentifier
golangDoc :: GoDoc Unit
golangDoc :: Doc Unit
golangDoc = do
line "package main"
blank
@ -120,8 +120,7 @@ golangDoc = do
line "import \"errors\""
line "import \"encoding/json\""
blank
IRGraph { toplevel } <- getGraph
renderedToplevel <- renderTypeToGolang toplevel
renderedToplevel <- getTopLevel >>= renderTypeToGolang
line $ "type Root " <> renderedToplevel
blank
classes <- getClasses
@ -241,7 +240,7 @@ func marshalUnion(pi *int64, pf *float64, pb *bool, ps *string, haveArray bool,
renderGolangUnion types
blank
renderGolangType :: Int -> IRClassData -> GoDoc Unit
renderGolangType :: Int -> IRClassData -> Doc Unit
renderGolangType classIndex (IRClassData { names, properties }) = do
className <- lookupClassName classIndex
let propertyNames = transformNames goNameStyle ("Other" <> _) S.empty $ map (\n -> Tuple n n) $ Map.keys properties
@ -253,16 +252,13 @@ renderGolangType classIndex (IRClassData { names, properties }) = do
line $ csPropName <> " " <> rendered <> " `json:\"" <> (stringEscape pname) <> "\"`"
line "}"
unionFieldName :: IRType -> GoDoc String
unionFieldName t = do
graph <- getGraph
let typeName = typeNameForUnion graph t
pure $ goNameStyle typeName
unionFieldName :: IRType -> Doc String
unionFieldName t = goNameStyle <$> getTypeNameForUnion t
compoundPredicates :: Array (IRType -> Boolean)
compoundPredicates = [isArray, isClass, isMap]
renderGolangUnion :: Set IRType -> GoDoc Unit
renderGolangUnion :: Set IRType -> Doc Unit
renderGolangUnion allTypes = do
name <- lookupUnionName allTypes
let { element: emptyOrNull, rest: nonNullTypes } = removeElement (_ == IRNull) allTypes
@ -300,7 +296,7 @@ renderGolangUnion allTypes = do
line $ "return marshalUnion(" <> args <> ", " <> isNullableString <> ")"
line "}"
where
ifClass :: (String -> GoDoc Unit) -> GoDoc Unit
ifClass :: (String -> Doc Unit) -> Doc Unit
ifClass f =
let { element } = removeElement isClass allTypes
in
@ -317,12 +313,12 @@ renderGolangUnion allTypes = do
name <- unionFieldName t
line $ "x." <> name <> " = nil"
Nothing -> pure unit
makeArgs :: (IRType -> GoDoc String) -> ((IRType -> Boolean) -> GoDoc String) -> GoDoc String
makeArgs :: (IRType -> Doc String) -> ((IRType -> Boolean) -> Doc String) -> Doc String
makeArgs primitive compound = do
primitiveArgs <- mapM primitive $ L.fromFoldable [IRInteger, IRDouble, IRBool, IRString]
compoundArgs <- mapM compound $ L.fromFoldable compoundPredicates
pure $ intercalate ", " $ A.concat [A.fromFoldable primitiveArgs, A.fromFoldable compoundArgs]
memberArg :: String -> (String -> String) -> (IRType -> Boolean) -> GoDoc String
memberArg :: String -> (String -> String) -> (IRType -> Boolean) -> Doc String
memberArg notPresentValue renderPresent p =
let { element } = removeElement p allTypes
in
@ -335,9 +331,9 @@ renderGolangUnion allTypes = do
memberArg "nil" ("&x." <> _) (eq t)
compoundUnmarshalArg p =
memberArg "false, nil" ("true, &x." <> _) p
primitiveMarshalArg :: IRType -> GoDoc String
primitiveMarshalArg :: IRType -> Doc String
primitiveMarshalArg t =
memberArg "nil" ("x." <> _) (eq t)
compoundMarshalArg :: (IRType -> Boolean) -> GoDoc String
compoundMarshalArg :: (IRType -> Boolean) -> Doc String
compoundMarshalArg p =
memberArg "false, nil" (\n -> "x." <> n <> " != nil, x." <> n) p

Просмотреть файл

@ -4,7 +4,6 @@ import IR
import IRGraph
import Prelude
import Transformations
import Types
import CSharp as CSharp
import Data.Argonaut.Core (Json, foldJson)
@ -16,12 +15,13 @@ import Data.Set as S
import Data.StrMap as StrMap
import Data.String.Util (singular)
import Data.Tuple (Tuple(..))
import Doc as Doc
import Golang (renderer)
import Golang as Golang
import Utils (mapM)
renderers :: Array Renderer
renderers :: Array Doc.Renderer
renderers = [CSharp.renderer, Golang.renderer]
makeTypeFromJson :: String -> Json -> IR IRType
@ -50,13 +50,14 @@ makeTypeAndUnify name json = runIR do
replaceSimilarClasses
makeMaps
renderJson :: Renderer -> Json -> String
renderJson renderer =
makeTypeAndUnify "TopLevel"
>>> regatherClassNames
>>> renderer.render
renderJson :: Doc.Renderer -> Json -> String
renderJson renderer json =
json
# makeTypeAndUnify "TopLevel"
# regatherClassNames
# Doc.runRenderer renderer
renderJsonString :: Renderer -> String -> Either String String
renderJsonString :: Doc.Renderer -> String -> Either String String
renderJsonString renderer json =
jsonParser json
<#> renderJson renderer

Просмотреть файл

@ -1,15 +0,0 @@
module Types where
import Prelude
import Data.List as L
import IRGraph
import Doc
type Renderer = {
name :: String,
extension :: String,
aceMode :: String,
render :: IRGraph -> String
}