packages feed

thrift-compiler-0.1.0.1: Thrift/Compiler/Pretty.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

module Thrift.Compiler.Pretty
  ( renderTypeError
  , renderTypeErrorPlain
  , renderType
  , ppHeader, ppName, ppType, ppText
  ) where

import Text.PrettyPrint hiding ((<>))
import Data.Some
import Data.Text (Text)
import qualified Data.Text as Text

import Thrift.Compiler.Parser
import Thrift.Compiler.Plugin
import Thrift.Compiler.Typechecker.Monad
import Thrift.Compiler.Types

renderType :: Typecheckable l => Type l t -> String
renderType = render . ppType

renderTypeError :: Typecheckable l => TypeError l -> String
renderTypeError = render . ppTypeError

renderTypeErrorPlain :: Typecheckable l => TypeError l -> String
renderTypeErrorPlain = render . ppTypeErrorPlain

ppTypeError :: Typecheckable l => TypeError l -> Doc
ppTypeError (TypeError loc msg) = "" $$ hang (ppHeader loc) 1 (ppErrorMsg msg)
ppTypeError EmptyInput = "" $$ red "Error: no input files specified"
ppTypeError (CyclicModules ms) =
  "" $$ hang (red "Error: cycle in modules:") 1
    (sep (punctuate "," (map (text . thriftPath) ms)))

ppTypeErrorPlain :: Typecheckable l => TypeError l -> Doc
ppTypeErrorPlain (TypeError loc msg) =
  ppHeaderPlain loc <+> ppErrorMsg msg
ppTypeErrorPlain EmptyInput = "Error: no input files specified"
ppTypeErrorPlain (CyclicModules ms) =
  "Error: cycle in modules:" <+>
    sep (punctuate "," (map (text . thriftPath) ms))

ppHeader :: Loc -> Doc
ppHeader loc = red (ppHeaderPlain loc)

ppHeaderPlain :: Loc -> Doc
ppHeaderPlain Loc{..} =
  hcat $ map (<> ":") [ text locFile, int locStartLine, int locStartCol ]

ppErrorMsg :: Typecheckable l => ErrorMsg l -> Doc
ppErrorMsg (CyclicTypes decls) =
 "cycle in types:" <+> sep (punctuate "," (foldr getName [] decls))
    where
      getName (D_Typedef Typedef{..}) ns = ppText tdName : ns
      getName (D_Struct Struct{..} )  ns = ppText structName : ns
      getName (D_Union Union{..})     ns = ppText unionName : ns
      getName (D_Enum Enum{..})       ns = ppText enumName : ns
      getName D_Const{}               ns = ns
      getName D_Service{}             ns = ns
      getName D_Interaction{}             ns = ns
ppErrorMsg (CyclicServices ss) =
  "cycle in service hierarchy:" <+> sep (punctuate "," (map getName ss))
    where
      getName Service{..} = quotes $ ppText serviceName
ppErrorMsg (UnknownType name)  = "unknown type" <+> quotes (ppName_ name)
ppErrorMsg (UnknownConst name) = "unknown constant" <+> quotes (ppName_ name)
ppErrorMsg (UnknownService name) = "unknown service" <+> quotes (ppName_ name)
ppErrorMsg (UnknownField name) = "unknown field" <+> quotes (ppText name)
ppErrorMsg (MissingField name) = "missing field" <+> quotes (ppText name)
ppErrorMsg (InvalidFieldId name fid) =
  hang "invalid field id:" 1 $
    "field" <+> quotes (ppText name) <+> "has id" <+> quotes (int fid) $$
    "field ids must be unique and non-zero"
ppErrorMsg (InvalidField val)  =
  hang "invalid field name:" 1 $
    "struct fields must be string literals" $$
    "got" <+> quotes (ppConst val)
ppErrorMsg (InvalidUnion name n) =
  hang ("invalid union literal for type" <+> (quotes (ppName name) <> ":")) 1 $
    "unions must contain exactly 1 field" $$
    "got" <+> int n <+> "fields"
ppErrorMsg (EmptyUnion name) =
  hang ("invalid union declaration" <+> (quotes (ppText name) <> ":")) 1 $
    "unions must contain at least one alternative" $$
    "try using an empty enum"
ppErrorMsg (InvalidThrows ty name) =
  hang ("invalid throws declaration" <+> (quotes (ppText name) <> ":")) 1 $
    "all fields in a throws clause must be exceptions" $$
    "but got type" <+> quotes (ppType ty)
ppErrorMsg (LiteralMismatch ty val) =
  hang "type mismatch:" 1 $
    "expected value of type" <+> quotes (ppType ty) $$
    "but got" <+> quotes (ppConst val)
ppErrorMsg (IdentMismatch expect actual name) =
  hang "type mismatch:" 1 $
    "expected value of type" <+> quotes (ppType expect) $$
    "but got identifier" <+> quotes (ppName_ name) <+>
      "of type" <+> quotes (ppType actual)
ppErrorMsg (AnnotationMismatch place ann) =
  hang "annotation mismatch:" 1 $
    "cannot use annotation" <+> quotes (ppAnnotation ann) $$
    "in" <+> ppPlacement place
ppErrorMsg (DuplicateName name) =
  "multiple definitions of" <+> quotes (ppText name)
ppErrorMsg (DuplicateEnumVal enum names val) =
  hang "duplicate enum value" 1 $
    "enum" <+> quotes (ppText enum) <+>
      "has multiple constructors with value" <+> quotes (int val) $$
    "constructors are" <+> hcat (map (quotes . ppText) names)
ppErrorMsg (TypeMismatch ty1 ty2) =
    hang "type mismatch:" 1 $
    "expected type" <+> quotes (ppType ty1) $$
    "but got type" <+> quotes (ppType ty2)
ppErrorMsg (NotDefinedBeforeUsed ty) =
    "type" <+> quotes (ppType ty) <+> "must be defined before it is used"
ppErrorMsg (UnknownEnumValue name) =
    "no value found for enum" <+> quotes (ppName_ name)
ppErrorMsg (MultipleEnumValues name) =
    "ambiguous values found for enum" <+> quotes (ppName_ name)

red :: Doc -> Doc
red doc = zeroWidthText "\ESC[31;1m" <> doc <> zeroWidthText "\ESC[0m"

ppPlacement :: Typecheckable l => AnnotationPlacement l -> Doc
ppPlacement (AnnType ty) = "type" <+> ppType ty
ppPlacement AnnField = "field"
ppPlacement AnnStruct = "struct"
ppPlacement AnnUnion = "union"
ppPlacement AnnTypedef = "typedef"
ppPlacement AnnEnum = "enum"
ppPlacement AnnPriority = "priority"

ppType :: Typecheckable l => Type l t -> Doc
ppType I8   = "byte"
ppType I16  = "i16"
ppType I32  = "i32"
ppType I64  = "i64"
ppType TDouble = "double"
ppType TFloat  = "float"
ppType TBool   = "bool"
ppType TText   = "string"
ppType TBytes  = "binary"
ppType (TSet u)     = hcat [ "set<", ppType u, ">" ]
ppType (THashSet u) = hcat [ "hash_set<", ppType u, ">" ]
ppType (TList u)    = hcat [ "list<", ppType u, ">" ]
ppType (TMap k v) = hcat [ "map<", ppType k, ", ", ppType v, ">" ]
ppType (THashMap k v) = hcat [ "hash_map<", ppType k, ", ", ppType v, ">" ]
ppType (TTypedef name _ _loc) = ppName name
ppType (TNewtype name _ _loc) = ppName name
ppType (TStruct name _loc)    = ppName name
ppType (TException name _loc) = ppName name
ppType (TUnion name _loc)     = ppName name
ppType (TEnum name _loc _)      = ppName name
ppType (TSpecial ty) = case backTranslateType ty of
  (Some u, name) -> ppType u <+> parens (ppText name)

ppName :: Name -> Doc
ppName Name{..} = ppName_ sourceName

ppName_ :: Name_ s -> Doc
ppName_ (UName name) = text $ Text.unpack name
ppName_ (QName m name) =
    hcat [ ppText m, ".", ppText name ]

ppText :: Text -> Doc
ppText = text . Text.unpack

ppConst :: UntypedConst a -> Doc
ppConst (UntypedConst _ c) = ppConstVal c

ppConstVal :: ConstVal a -> Doc
ppConstVal (IntConst _ i)    = ppText i
ppConstVal (DoubleConst _ d) = ppText d
ppConstVal (StringConst s qt) = addQuotes $ text $ Text.unpack s
  where
    addQuotes = case qt of
      DoubleQuote -> doubleQuotes
      SingleQuote -> quotes
ppConstVal (IdConst x)     = text $ Text.unpack x
ppConstVal ListConst{..} =
  brackets $ sep $ punctuate "," $ map (ppConst . leElem) lvElems
ppConstVal MapConst{..} = braces $ sep $ punctuate "," $ map pp mvElems
  where
    pp ListElem{ leElem = MapPair{..} } =
      hsep [ ppConst mpKey, ":", ppConst mpVal ]
ppConstVal StructConst{..} = braces $ sep $ punctuate "," $ map pp svElems
  where
    pp ListElem{ leElem = StructPair{..} } =
      hsep [ ppText spKey, "=", ppConst spVal ]
ppConstVal (BoolConst b)
  | b         = text "true"
  | otherwise = text "false"

ppAnnotation :: Annotation a -> Doc
ppAnnotation SimpleAnn{..} = parens $ ppText saTag
ppAnnotation ValueAnn{..} = parens $
  ppText vaTag <+> "=" <+>
  case vaVal of
    TextAnn txt _ -> doubleQuotes (ppText txt)
    IntAnn x _ -> int x