Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into wip/rpc

Trevor Elliott 9 лет назад
Родитель
Сommit
bc64ea1c55

+ 7 - 3
.travis.yml

@@ -1,6 +1,4 @@
 language: haskell
-
-
 before_install:
   - sudo add-apt-repository -y ppa:terry.guo/gcc-arm-embedded
   - sudo apt-get update -qq
@@ -11,15 +9,21 @@ before_install:
   - export TOWER_REPO=$PWD/tower
   - git clone https://github.com/galoisinc/ivory-tower-stm32
   - export BSP_REPO=$PWD/ivory-tower-stm32
+  - git clone https://github.com/aisamanra/s-cargot
+  - export SCARGOT_REPO=$PWD/s-cargot
 install:
+  - cabal install cabal-install
   - cabal install alex
   - cabal install happy
 script:
   - ghc --version
   - cabal --version
   - arm-none-eabi-gcc --version
-  - IVORY_REPO=$IVORY_REPO TOWER_REPO=$TOWER_REPO BSP_REPO=$BSP_REPO make create-sandbox
+  - SCARGOT_REPO=$SCARGOT_REPO IVORY_REPO=$IVORY_REPO TOWER_REPO=$TOWER_REPO BSP_REPO=$BSP_REPO make create-sandbox
   - IVORY_REPO=$IVORY_REPO TOWER_REPO=$TOWER_REPO BSP_REPO=$BSP_REPO make
   - IVORY_REPO=$IVORY_REPO TOWER_REPO=$TOWER_REPO BSP_REPO=$BSP_REPO make test
 ghc:
   - 7.8
+branches:
+  except:
+    - /^wip.*$/

+ 3 - 0
Makefile

@@ -1,4 +1,5 @@
 
+SCARGOT_REPO ?= ../s-cargot
 IVORY_REPO ?= ../ivory
 
 default:
@@ -6,6 +7,7 @@ default:
 
 create-sandbox:
 	cabal sandbox init
+	cabal sandbox add-source $(SCARGOT_REPO)
 	cabal sandbox add-source $(IVORY_REPO)/ivory-artifact
 	cabal install --dependencies-only
 
@@ -15,6 +17,7 @@ test: tower-backend-test
 
 haskell-backend-test:
 	cabal run gidl -- -b haskell \
+		--debug \
 		-i tests/example.idl \
 		-o tests/gidl-haskell-backend-test \
 		-p gidl-haskell-backend-test \

+ 5 - 3
gidl.cabal

@@ -12,6 +12,7 @@ data-files: support/ivory/Unpack.hs.template,
             support/ivory/CodeGen.hs.template,
             support/ivory/Makefile,
             support/tower/CodeGen.hs.template,
+            support/tower/Attr.hs.template,
             support/tower/default.conf,
             support/tower/Makefile,
             support/rpc/Base.hs.template,
@@ -38,7 +39,7 @@ library
                        Gidl.Backend.Rpc,
                        Gidl.Backend.Tower,
                        Gidl.Backend.Tower.Schema,
-                       Gidl.Backend.Tower.Interface
+                       Gidl.Backend.Tower.Server
 
   other-modules:       Paths_gidl
 
@@ -48,7 +49,9 @@ library
                        parsec,
                        pretty-show,
                        transformers,
-                       ivory-artifact
+                       ivory-artifact,
+                       s-cargot,
+                       text
   hs-source-dirs:      src
   default-language:    Haskell2010
   ghc-options:         -Wall
@@ -62,4 +65,3 @@ executable             gidl
 
   default-language:    Haskell2010
   ghc-options:         -Wall
-

+ 2 - 1
src/Gidl.hs

@@ -129,7 +129,8 @@ run = do
   opts <- parseOpts args
   idl <- readFile (idlpath opts)
   case parseDecls idl of
-    Left e -> print e >> exitFailure
+    Left e -> putStrLn ("Error parsing " ++ (idlpath opts) ++ ": " ++ e)
+              >> exitFailure
     Right (te, ie) -> do
       when (debug opts) $ do
         putStrLn (ppShow te)

+ 13 - 3
src/Gidl/Backend/Tower.hs

@@ -14,7 +14,7 @@ import Gidl.Backend.Cabal
 import Gidl.Backend.Ivory (dotwords, ivorySources)
 import Gidl.Backend.Ivory.Schema (ifModuleName)
 import Gidl.Backend.Tower.Schema
-import Gidl.Backend.Tower.Interface
+import Gidl.Backend.Tower.Server
 
 towerBackend :: TypeEnv -> InterfaceEnv -> String -> String -> [Artifact]
 towerBackend te ie pkgname namespace_raw =
@@ -26,7 +26,7 @@ towerBackend te ie pkgname namespace_raw =
   where
   namespace = dotwords namespace_raw
 
-  sources = isources ++ tsources
+  sources = isources ++ [ attrModule (namespace ++ ["Tower"]) ] ++ tsources
 
   tsources = towerSources ie (namespace ++ ["Tower"])
 
@@ -45,7 +45,8 @@ towerSources (InterfaceEnv ie) namespace = towerInterfaces
   towerInterfaces = concat
     [ [ schemaModule    ifnamespace i (producerSchema i)
       , schemaModule    ifnamespace i (consumerSchema i)
-      , interfaceModule ifnamespace i
+      , serverModule    ifnamespace i
+      , umbrellaModule  ifnamespace i
       ]
     | (_iname, i) <- ie ]
   ifnamespace = namespace ++ ["Interface"]
@@ -86,3 +87,12 @@ codegenTest (InterfaceEnv ie) modulepath =
       ++ " (snd c) >>= \\i -> "
       ++ (outputFuncName ((ifModuleName i) ++ schemaName))
       ++ " i >>= \\(_ :: ChanOutput (Array 80 (Stored Uint8))) -> return ()"
+
+
+attrModule :: [String] -> Artifact
+attrModule modulepath =
+  artifactPath (intercalate "/" modulepath) $
+  artifactCabalFileTemplate P.getDataDir fname
+    [("module_path", intercalate "." modulepath )]
+  where
+  fname = "support/tower/Attr.hs.template"

+ 0 - 54
src/Gidl/Backend/Tower/Interface.hs

@@ -1,54 +0,0 @@
-
-module Gidl.Backend.Tower.Interface where
-
-
-import Data.Monoid
-import Data.List (intercalate, nub)
-
-import Gidl.Types
-import Gidl.Interface
-import Gidl.Schema
-import Gidl.Backend.Ivory.Types
-import Gidl.Backend.Ivory.Schema (ifModuleName)
-import Ivory.Artifact
-import Text.PrettyPrint.Mainland
-
-interfaceModule :: [String] -> Interface -> Artifact
-interfaceModule modulepath ir =
-  artifactPath (intercalate "/" modulepath) $
-  artifactText (ifModuleName ir ++ ".hs") $
-  prettyLazyText 80 $
-  stack
-    [ text "{-# LANGUAGE DataKinds #-}"
-    , text "{-# LANGUAGE RankNTypes #-}"
-    , text "{-# LANGUAGE ScopedTypeVariables #-}"
-    , text "{-# OPTIONS_GHC -fno-warn-unused-imports #-}"
-    , empty
-    , text "module"
-      <+> im (ifModuleName ir)
-      <+> text "where"
-    , empty
-    , stack $ typeimports ++ extraimports
-    , empty
-    ]
-  where
-  rootpath = reverse . drop 2 . reverse
-  modAt path = mconcat (punctuate dot (map text path))
-  im mname = modAt (modulepath ++ [mname])
-  tm mname = modAt (rootpath modulepath ++ ["Ivory","Types", mname])
-
-  typeimports = map (importDecl tm)
-              $ nub
-              $ map importType
-              $ interfaceTypes ir
-
-  extraimports =
-    [ text "import" <+> modAt (rootpath modulepath ++ ["Ivory", "Types"])
-    , text "import" <+> im (ifModuleName ir) <> dot <> text "Producer"
-    , text "import" <+> im (ifModuleName ir) <> dot <> text "Consumer"
-    , text "import Ivory.Language"
-    , text "import Ivory.Stdlib"
-    , text "import Ivory.Tower"
-    , text "import Ivory.Serialize"
-    ]
-

+ 5 - 6
src/Gidl/Backend/Tower/Schema.hs

@@ -62,21 +62,20 @@ schemaDoc interfaceName (Schema schemaName schema) = stack
     [ text "-- Define" <+> text schemaName  <+> text "schema for"
         <+> text interfaceName <+> text "interface"
     , empty
-    , text "data" <+> constructor <+> text "c" <+> equals <+> constructor
+    , text "data" <+> constructor<+> equals <+> constructor
     , indent 2 $ encloseStack lbrace rbrace comma
         [ case t of
             PrimType VoidType -> accessorName n <+> colon <> colon
-                <+> text "c (Stored IBool)"
+                <+> text "ChanOutput (Stored IBool)"
             _ -> accessorName n <+> colon <> colon
-                    <+> text "c"
-                    <+> parens (text (typeIvoryType t))
+                <+> text "ChanOutput" <+> parens (text (typeIvoryType t))
         | (_, (Message n t)) <- schema
         ]
     , empty
     , text (inputFuncName typeName) <+> align
         (stack [ text ":: (ANat n)"
                , text "=> ChanOutput (Array n (Stored Uint8))"
-               , text "-> Tower e" <+> parens (constructor <+> text "ChanOutput")
+               , text "-> Tower e" <+> constructor
                ])
     , text (inputFuncName typeName) <+> text "frame_ch" <+> equals <+> text "do"
     , indent 2 $ stack
@@ -125,7 +124,7 @@ schemaDoc interfaceName (Schema schemaName schema) = stack
     , empty
     , text (outputFuncName typeName) <> align
         (stack [ text ":: (ANat n)"
-               , text "=>" <+> constructor <+> text "ChanOutput"
+               , text "=>" <+> constructor
                , text "-> Tower e (ChanOutput (Array n (Stored Uint8)))"
                ])
     , text (outputFuncName typeName) <+> text "a" <+> equals <+> text "do"

+ 211 - 0
src/Gidl/Backend/Tower/Server.hs

@@ -0,0 +1,211 @@
+
+module Gidl.Backend.Tower.Server where
+
+
+import Data.Monoid
+import Data.List (intercalate)
+
+import Gidl.Interface
+import Gidl.Schema
+import Gidl.Backend.Ivory.Types
+import Gidl.Backend.Ivory.Schema (ifModuleName)
+import Ivory.Artifact
+import Text.PrettyPrint.Mainland
+
+umbrellaModule :: [String] -> Interface -> Artifact
+umbrellaModule modulepath i =
+  artifactPath (intercalate "/" modulepath) $
+  artifactText (ifModuleName i ++ ".hs") $
+  prettyLazyText 80 $
+  stack
+    [ text "module" <+> mname
+    , indent 2 $ encloseStack lparen (rparen <+> text "where") comma
+        [ text "module" <+> im "Producer"
+        , text "module" <+> im "Consumer"
+        , text "module" <+> im "Server"
+        ]
+    , text "import" <+> im "Producer"
+    , text "import" <+> im "Consumer"
+    , text "import" <+> im "Server"
+    ]
+  where
+  modAt path = mconcat (punctuate dot (map text path))
+  mname = modAt (modulepath ++ [ifModuleName i])
+  im m = modAt (modulepath ++ [ifModuleName i, m])
+
+serverModule :: [String] -> Interface -> Artifact
+serverModule modulepath i =
+  artifactPath (intercalate "/" (modulepath ++ [ifModuleName i])) $
+  artifactText "Server.hs" $
+  prettyLazyText 80 $
+  stack
+    [ text "{-# LANGUAGE DataKinds #-}"
+    , text "{-# LANGUAGE RankNTypes #-}"
+    , text "{-# LANGUAGE ScopedTypeVariables #-}"
+    , text "{-# LANGUAGE KindSignatures #-}"
+    , text "{-# LANGUAGE RecordWildCards #-}"
+    , text "{-# OPTIONS_GHC -fno-warn-unused-imports #-}"
+    , text "{-# OPTIONS_GHC -fno-warn-unused-matches #-}"
+    , empty
+    , text "module"
+      <+> im "Server"
+      <+> text "where"
+    , empty
+    , stack imports
+    , empty
+    , attrsDataType i
+    , empty
+    , attrsTowerConstructor i
+    , empty
+    , attrsInitializer i
+    , empty
+    , streamsDataType i
+    , empty
+    , streamsTowerConstructor i
+    , empty
+    , interfaceServer i
+    ]
+  where
+  rootpath = reverse . drop 2 . reverse
+  modAt path = mconcat (punctuate dot (map text path))
+  im mname = modAt (modulepath ++ [ifModuleName i, mname])
+
+  imports =
+    [ text "import" <+> modAt (rootpath modulepath ++ ["Tower", "Attr"])
+    , text "import" <+> im "Producer"
+    , text "import" <+> im "Consumer"
+    , text "import Ivory.Language"
+    , text "import Ivory.Tower"
+    ]
+
+
+attrsDataType :: Interface -> Doc
+attrsDataType i = text "data" <+> constructor <+> text "(p :: Area * -> *) ="
+               </> indent 2 constructor
+               </> indent 4 body
+  where
+  constructor = text (ifModuleName i) <> text "Attrs"
+  body = encloseStack lbrace rbrace comma
+    [ text n <+> colon <> colon <+> text "p"
+                 <+> parens (text (typeIvoryType t))
+    | (aname, AttrMethod _ t)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ]
+
+attrsTowerConstructor :: Interface -> Doc
+attrsTowerConstructor i = typesig </> decl </> indent 2 body </> indent 2 ret
+  where
+  constructor = text (ifModuleName i) <> text "Attrs"
+  typesig = text "tower" <> constructor <+> colon <> colon
+    <+> constructor <+> text "Init"
+    <+> text "->"
+    <+> text "Tower e" <+> parens (constructor <+> text "Attr")
+  decl = text "tower" <> constructor <+> text "ivals = do"
+  body = stack
+    [ text n <> text "_p <- towerAttr"
+       <+> dquotes (text aname)
+       <+> parens (text n <+> text "ivals")
+    | (aname, AttrMethod _ _)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ]
+  ret = text "return" <+> constructor <+> encloseStack lbrace rbrace comma
+    [ text n <+> equals <+> text n <> text "_p"
+    | (aname, AttrMethod _ _)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ]
+
+attrsInitializer :: Interface -> Doc
+attrsInitializer i = typesig </> decl </> indent 2 body
+  where
+  constructor = text (ifModuleName i) <> text "Attrs"
+  typesig = text "init" <> constructor <+> colon <> colon
+            <+> constructor <+> text "Init"
+  decl = text "init" <> constructor <+> equals <+> constructor
+  body = encloseStack lbrace rbrace comma
+    [ text n <+> equals <+> text "izero"
+    | (aname, AttrMethod _ _)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ]
+
+streamsDataType :: Interface -> Doc
+streamsDataType i = text "data" <+> constructor <+> text "(p :: Area * -> *) ="
+               </> indent 2 constructor
+               </> indent 4 body
+  where
+  constructor = text (ifModuleName i) <> text "Streams"
+  body = encloseStack lbrace rbrace comma
+    [ text n <+> colon <> colon <+> text "p"
+                 <+> parens (text (typeIvoryType t))
+    | (aname, StreamMethod _ t)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ]
+
+streamsTowerConstructor :: Interface -> Doc
+streamsTowerConstructor i = typesig </> decl </> indent 2 body </> indent 2 ret
+  where
+  constructor = text (ifModuleName i) <> text "Streams"
+  typesig = text "tower" <> constructor <+> colon <> colon
+    <+> text "Tower e"
+    <+> parens (constructor <+> text "ChanInput" <> comma
+                <+> constructor <+> text "ChanOutput")
+  decl = text "tower" <> constructor <+> text "= do"
+  body = stack
+    [ text n <> text "_c <- channel"
+    | (aname, StreamMethod _ _)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ]
+  ret = text "return" <+> encloseStack lparen rparen comma
+    [ mkstream "fst", mkstream "snd"]
+  mkstream acc = constructor </> indent 2 (encloseStack lbrace rbrace comma
+    [ text n <+> equals <+> text acc <+> text n <> text "_c"
+    | (aname, StreamMethod _ _)  <- interfaceMethods i
+    , let n = userEnumValueName aname
+    ])
+
+
+interfaceServer :: Interface -> Doc
+interfaceServer i =
+  stack [typedef, decl, indent 2 body, indent 2 ret]
+  where
+  constructor postfix = text (ifModuleName i) <> text postfix
+  fname =  text "tower" <> constructor "Server"
+  typedef = fname <+> align (stack
+      [ guardEmptySchema (consumerSchema i)
+                         (text "::" <+> constructor "Consumer")
+                         (text ":: -- no consumer schema")
+      , guardEmptySchema (consumerSchema i) (text "->") (text "  ")
+            <+> constructor "Attrs Attr"
+      , text "->" <+> constructor "Streams ChanOutput"
+      , text "->" <+> text "Tower e"
+             <+> guardEmptySchema (producerSchema i)
+                                  (constructor "Producer")
+                                  (text "()")
+      ])
+  decl = fname <+> guardEmptySchema (consumerSchema i)
+                                    (constructor "Consumer{..}")
+                                    empty
+               <+> constructor "Attrs{..}"
+               <+> constructor "Streams{..}"
+               <+> equals <+> text "do"
+  body = stack [ methodBody (text (userEnumValueName n)) m
+               | (n,m) <- interfaceMethods i ]
+  ret = text "return" <+> guardEmptySchema (producerSchema i)
+                                           (constructor "Producer{..}")
+                                           (text "()")
+
+  methodBody n (StreamMethod _ _) =
+    text "let" <+> n <> text "Producer" <+> equals <+> n
+  methodBody n (AttrMethod Read _) =
+    n <> text "ValProducer" <+> text "<- readableAttrServer"
+      <+> n <+> n <> text "GetConsumer"
+  methodBody n (AttrMethod Write _) =
+    text "writableAttrServer" <+> n <+> n <> text "SetConsumer"
+  methodBody n (AttrMethod ReadWrite _) =
+    n <> text "ValProducer" <+> text "<- readwritableAttrServer"
+      <+> n <+> n <> text "GetConsumer" <+> n <> text "SetConsumer"
+
+
+guardEmptySchema :: Schema -> Doc -> Doc -> Doc
+guardEmptySchema (Schema _ []) _ d = d
+guardEmptySchema (Schema _ _) d _ = d
+

+ 321 - 237
src/Gidl/Parse.hs

@@ -1,242 +1,326 @@
-
-module Gidl.Parse where
-
-import Data.List
-import Data.Functor.Identity
-import Control.Monad
-import Text.Parsec.Prim
-import Text.Parsec.Char
-import Text.Parsec.Token
-import Text.Parsec.Combinator
-import Text.Parsec.Language
-import Text.Parsec.Error
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
+
+module Gidl.Parse (parseDecls) where
+
+import           Control.Applicative ((<$>), (<*>))
+import           Control.Monad ((>=>))
+import           Data.List (partition, group, intercalate)
+import           Data.SCargot.Comments (withHaskellComments)
+import           Data.SCargot.General ( SExprSpec
+                                      , convertSpec
+                                      , decode
+                                      , encodeOne
+                                      , asWellFormed
+                                      )
+import           Data.SCargot.HaskLike (HaskLikeAtom(..), haskLikeSpec)
+import           Data.SCargot.Repr.WellFormed
+import           Data.Text (unpack, pack)
 
 import Gidl.Types
 import Gidl.Interface
 
-type Parser u a = ParsecT String u Identity a
-type ParseEnv = (TypeEnv, InterfaceEnv)
-
-emptyParseEnv :: ParseEnv
-emptyParseEnv = (emptyTypeEnv, emptyInterfaceEnv)
-
-getTypeEnv :: Parser ParseEnv TypeEnv
-getTypeEnv = fmap fst getState
-
-getInterfaceEnv :: Parser ParseEnv InterfaceEnv
-getInterfaceEnv = fmap snd getState
-
-setTypeEnv :: TypeEnv -> Parser ParseEnv ()
-setTypeEnv te = do
-  (_, ie) <- getState
-  setState (te, ie)
-
-setInterfaceEnv :: InterfaceEnv -> Parser ParseEnv ()
-setInterfaceEnv ie = do
-  (te, _) <- getState
-  setState (te, ie)
-
----
-
-lexer :: GenTokenParser String u Identity
-lexer = makeTokenParser haskellDef
-
-tWhiteSpace :: Parser u ()
-tWhiteSpace = whiteSpace lexer
-
-tInteger :: Parser u Integer
-tInteger = (integer lexer) <?> "integer"
-
-tNatural :: Parser u Integer
-tNatural = do
-  i <- tInteger
-  case i < 0 of
-    True -> fail "expected positive integer"
-    False -> return i
-
-tFloat :: Parser u Double
-tFloat = (float lexer) <?> "floating point number"
-
-tString :: Parser u String
-tString = (stringLiteral lexer) <?> "string"
-
-tSymbol :: Parser u String
-tSymbol = (many1 $ noneOf "()\" \t\n\r") <?> "symbol"
-
-tIdentifier :: String -> Parser u ()
-tIdentifier i = do
-  s <- tSymbol
-  case s == i of
-    True -> return ()
-    False -> fail ("expected identifier " ++ i)
-
-tList :: Parser u a -> Parser u a
-tList c = do
-  tWhiteSpace
-  void $ char '('
-  tWhiteSpace
-  r <- c
-  tWhiteSpace
-  void $ char ')'
-  return r
-  <?> "list"
-
-
-tPair :: Parser u a
-      -> Parser u b
-      -> Parser u (a, b)
-tPair a b = tList $ do
-  ra <- a
-  tWhiteSpace
-  rb <- b
-  return (ra, rb)
-
-tKnownPrimType  :: Parser ParseEnv PrimType
-tKnownPrimType  = do
-  t <- tKnownType
-  case t of
-    PrimType p -> return p
-    StructType n _ -> fail ("expected a known primitive type name, got " ++ n)
-
-tKnownType :: Parser ParseEnv Type
-tKnownType = do
-  s <- tSymbol
-  te <- getTypeEnv
-  case lookupTypeName s te of
-    Just t -> return t
-    Nothing -> fail ("expected a known type name, got " ++ s)
-
-tStructRow :: Parser ParseEnv (Identifier, PrimType)
-tStructRow = tPair tSymbol tKnownPrimType
-  <?> "struct row"
-
-tStructBody :: Parser ParseEnv [(Identifier, PrimType)]
-tStructBody = tList (many1 (tWhiteSpace >> tStructRow))
-  <?> "struct body"
-
-tStructDecl :: Parser ParseEnv (TypeName, Type)
-tStructDecl = tList $ do
-  tIdentifier "def-struct"
-  tWhiteSpace
-  n <- tSymbol
-  b <- tStructBody
-  return (n, StructType n b)
-
-defineType :: (TypeName, Type) -> Parser ParseEnv ()
-defineType (tn, t) = do
-  te <- getTypeEnv
-  case lookupTypeName tn te of
-    Just _ -> fail ("type named '" ++ tn ++ "' already exists")
-    Nothing -> setTypeEnv (insertType tn t te)
-
-defineInterface :: Interface -> Parser ParseEnv ()
-defineInterface i = do
-  ie <- getInterfaceEnv
-  case lookupInterface ina ie of
-    Just _ -> fail ("interface named '" ++ ina ++ "' already exists")
-    Nothing -> setInterfaceEnv (insertInterface i ie)
-  where (Interface ina _ _) = i
-tNewtypeDecl :: Parser ParseEnv (TypeName, Type)
-tNewtypeDecl = tList $ do
-  tIdentifier "def-newtype"
-  tWhiteSpace
-  n <- tSymbol
-  tWhiteSpace
-  c <- tKnownPrimType
-  return (n, PrimType (Newtype n c))
-
-tEnumDecl :: Parser ParseEnv (TypeName, Type)
-tEnumDecl = tList $ do
-  tIdentifier "def-enum"
-  tWhiteSpace
-  n <- tSymbol
-  w  <- optionMaybe (try tInteger)
-  width <- case w of
-    Nothing -> return Bits32
-    Just 8 -> return  Bits8
-    Just 16 -> return Bits16
-    Just 32 -> return Bits32
-    Just 64 -> return Bits64
-    _ -> fail "Expected enum bit size to be 8, 16, 32, or 64"
-
-  vs <- tList $ many1 $ tPair tSymbol tNatural
-  when (not_unique (map fst vs)) $
-    fail "enum keys were not unique"
-  when (not_unique (map snd vs)) $
-    fail "enum values were not unique"
-  -- XXX make it possible to implicitly assign numbers
-  return (n, PrimType (EnumType n width vs))
-
-not_unique :: (Eq a) => [a] -> Bool
-not_unique l = nub l /= l
-
-tPermission :: Parser a Perm
-tPermission = do
-  s <- tSymbol
-  case s of
-    "read"      -> return Read
-    "r"         -> return Read
-    "write"     -> return Write
-    "w"         -> return Write
-    "readwrite" -> return ReadWrite
-    "rw"        -> return ReadWrite
-    _           -> fail "expected permission"
-
-tInterfaceMethod :: Parser ParseEnv (MethodName, Method)
-tInterfaceMethod = tList $ do
-  n <- tSymbol
-  m <- choice [ try tAttr, try tStream ]
-  return (n, m)
+-- We parse into this abstract structure before converting it to the
+-- structures Gidl uses elsewhere. That way, we can separate our
+-- parsing and our checking.
+data Decl
+  = NewtypeDecl Identifier Identifier
+  | EnumDecl (Identifier, Bits) [(Identifier, Integer)]
+  | StructDecl Identifier [(Identifier, Identifier)]
+  | InterfaceDecl Identifier [Identifier] [(Identifier, MethodDecl)]
+    deriving (Eq, Show)
+
+data MethodDecl
+  = AttrDecl Perm Identifier
+  | StreamDecl Integer Identifier
+    deriving (Eq, Show)
+
+unlessEmpty :: [a] -> (a -> String) -> Either String ()
+unlessEmpty [] _ = return ()
+unlessEmpty as msg  = throw (intercalate ";\n" (map msg as))
+
+duplicated :: (Eq a) => [a] -> [a]
+duplicated as = map (\a -> a !! 0) $ filter (\a -> length a > 1) $ group as
+
+-- Here's a function to convert those decls.
+toEnv :: [Decl] -> Either String (TypeEnv, InterfaceEnv)
+toEnv decls = do
+  unlessEmpty (duplicated typeNames)
+      (\n -> "Type named '" ++ n ++ "' declared multiple times")
+  unlessEmpty (filter (\t -> elem t (map fst builtins)) typeNames)
+      (\n -> "Builtin type named '" ++ n ++ "' cannot be redeclared")
+  unlessEmpty (duplicated interfaceNames)
+      (\n -> "Interface named '" ++ n ++ "' declared multiple times")
+
+  typs <- mapM (getTypePair . getName) typDs
+  ifcs <- mapM getIfacePair interfaceNames
+  return (TypeEnv typs, InterfaceEnv ifcs)
+  where (typDs, ifcDs) = partition isTypeDecl decls
+
+        builtins = let TypeEnv bs = baseTypeEnv in bs
+
+        typeNames = map getName typDs
+        interfaceNames = map getName ifcDs
+
+        typMap = [(getName d, toType d) | d <- typDs] ++
+                 [(n, return (n, t)) | (n, t) <- builtins ]
+        ifcMap = [(getName i, toInterface i) | i <- ifcDs]
+
+        -- this is gross because I'm trying to make sure declarations
+        -- can happen in any order. XXX: prevent recursion!
+        getType n = snd `fmap` getTypePair n
+        getTypePair n = case lookup n typMap of
+            Just (Right t) -> return t
+            Just (Left l)  -> Left l
+            Nothing        -> throw ("Unknown primitive type: " ++ n)
+
+        getIface n = snd `fmap` getIfacePair n
+        getIfacePair n = case lookup n ifcMap of
+          Just (Right i) -> return i
+          Just (Left l)  -> Left l
+          Nothing        -> throw ("Unknown interface: " ++ n)
+
+        getPrimType n = do
+          t <- getType n
+          case t of
+            PrimType t' -> return t'
+            _ -> throw ("Expected primitive type but got " ++ show t)
+
+        -- converts a decl to an actual type
+        toType (NewtypeDecl n t) = do
+          t' <- getPrimType t
+          return (n, PrimType (Newtype n t'))
+        toType (EnumDecl (n, s) ts) = do
+          unlessEmpty (duplicated (map fst ts))
+              (\i -> "Enum identifier '" ++ i
+                  ++ "' repeated in declaration of 'Enum " ++ n ++ "'")
+          unlessEmpty (duplicated (map snd ts))
+              (\i -> "Enum value '" ++ (show i)
+                  ++ "' repeated in declaration of 'Enum " ++ n ++ "'")
+          return (n, PrimType (EnumType n s ts))
+        toType (StructDecl n ss) = do
+          ps <- mapM (getPrimType . snd) ss
+          return (n, StructType n (zip (map fst ss) ps))
+        toType _ = error "[unreachable]"
+
+        toMethod (n, AttrDecl perm t) = do
+          t' <- getType t
+          return (n, AttrMethod perm t')
+        toMethod (n, StreamDecl rate t) = do
+          t' <- getType t
+          return (n, StreamMethod rate t')
+
+        toInterface (InterfaceDecl n is ms) = do
+          ms' <- mapM toMethod ms
+          is' <- mapM getIface is
+          return (n, Interface n is' ms')
+        toInterface _ = error "[unreachable]"
+
+        getName (NewtypeDecl n _)     = n
+        getName (EnumDecl (n, _) _)   = n
+        getName (StructDecl n _)      = n
+        getName (InterfaceDecl n _ _) = n
+
+        isTypeDecl InterfaceDecl {} = False
+        isTypeDecl _                = True
+
+parseDecls :: String -> Either String (TypeEnv, InterfaceEnv)
+parseDecls = return . pack >=> decode gidlSpec >=> toEnv
+
+gidlSpec :: SExprSpec HaskLikeAtom Decl
+gidlSpec
+  = withHaskellComments
+  $ convertSpec tDecl ppDecl
+  $ asWellFormed haskLikeSpec
+
+-- utility aliases and helpers
+type Parse a = WellFormedSExpr HaskLikeAtom -> Either String a
+
+throw :: String -> Either String a
+throw = Left
+
+infix 9 /?/
+(/?/) :: Either String a -> String -> Either String a
+Left msg /?/ ctx = throw (msg ++ "\n  in parsing " ++ ctx)
+r        /?/ _   = r
+
+infix 9 `asErr`
+asErr :: Either String a -> String -> Either String a
+Left _ `asErr` msg = throw msg
+r      `asErr` _   = r
+
+seShow :: WellFormedSExpr HaskLikeAtom -> String
+seShow sx = "`" ++ unpack (encodeOne (asWellFormed haskLikeSpec) sx) ++ "`"
+
+atShow :: WellFormedSExpr HaskLikeAtom -> String
+atShow e = go e ++ " " ++ seShow e
   where
-  tAttr = tList $ do
-    tIdentifier "attr"
-    tWhiteSpace
-    p <- tPermission
-    tWhiteSpace
-    tn <- tKnownType
-    return (AttrMethod p tn)
-  tStream = tList $ do
-    tIdentifier "stream"
-    tWhiteSpace
-    r <- tInteger
-    tWhiteSpace
-    tn <- tKnownType
-    return (StreamMethod r tn)
-
-tKnownInterface :: Parser ParseEnv Interface
-tKnownInterface  = do
-  n <- tSymbol
-  ie <- getInterfaceEnv
-  case lookupInterface n ie of
-    Just i -> return i
-    Nothing -> fail ("expected a known interface name, got " ++ n)
-
-tInterfaceDecl :: Parser ParseEnv Interface
-tInterfaceDecl = tList $ do
-  tIdentifier "def-interface"
-  tWhiteSpace
-  n <- tSymbol
-  tWhiteSpace
-  ms <- tList (many1 tInterfaceMethod)
-  when (not_unique (map fst ms)) $
-    fail "expected unique interface method names"
-  tWhiteSpace
-  ps <- optionMaybe (tList (many1 tKnownInterface))
-  -- XXX require the ms not shadow names in inherited interfaces
-  case ps of
-    Just p -> return (Interface  n p ms)
-    Nothing -> return (Interface n [] ms)
-
-tDecls :: Parser ParseEnv ParseEnv
-tDecls = do
-  _ <- many (choice [ try tStructDecl    >>= defineType
-                    , try tNewtypeDecl   >>= defineType
-                    , try tEnumDecl      >>= defineType
-                    , try tInterfaceDecl >>= defineInterface
-                    ])
-  tWhiteSpace >> eof
-  getState
-
-parseDecls :: String -> Either ParseError ParseEnv
-parseDecls s = runP tDecls emptyParseEnv "" s
-
+    go (A (HSInt _))    = "int"
+    go (A (HSString _)) = "string"
+    go (A (HSIdent _))  = "identifier"
+    go (A (HSFloat _))  = "float"
+    go (L _)            = "list"
+    go _                = "??"
+
+-- basic parsing of things (these might show up in s-cargot
+--  proper eventually?)
+
+tSymbol :: Parse String
+tSymbol e = asAtom go e `asErr`
+              ("Expected identifier; got " ++ atShow e)
+  where go (HSIdent i) = return (unpack i)
+        go sx          = throw ("Expected identifier; got " ++ atShow (A sx))
+
+tType :: Parse String
+tType = tSymbol
+
+tInteger :: Parse Integer
+tInteger e = asAtom go e `asErr` ("Expected integer; got " ++ atShow e)
+  where go (HSInt n) = return n
+        go sx        = throw ("Expected integer; got " ++ atShow (A sx))
+
+-- some parsing of gidl-specific types
+tBits :: Parse Bits
+tBits = tInteger >=> tWidth
+
+tWidth :: Integer -> Either String Bits
+tWidth 8  = return Bits8
+tWidth 16 = return Bits16
+tWidth 32 = return Bits32
+tWidth 64 = return Bits64
+tWidth _  = throw "Expected enum bit size to be 8, 16, 32, or 64"
+
+tPermission :: Parse Perm
+tPermission e = asAtom go e `asErr` ("unknown permission: " ++ seShow e)
+  where go token
+          | token == "read"      || token == "r"  = return Read
+          | token == "write"     || token == "w"  = return Write
+          | token == "readwrite" || token == "rw" = return ReadWrite
+          | otherwise = throw "error"
+
+-- newtypes
+tNewtypeDecl :: Parse Decl
+tNewtypeDecl = asList go
+ where go ["def-newtype",name,typ] =
+         NewtypeDecl <$> tSymbol name /?/ "newtype name"
+                     <*> tSymbol typ  /?/ "newtype type"
+       go _ = throw "wrong number of elements"
+
+-- structs
+tStructDecl :: Parse Decl
+tStructDecl = asList go
+  where go ("def-struct":name:body) =
+            StructDecl
+              <$> (tSymbol name         /?/ "struct name")
+              <*> (mapM tStructRow body /?/
+                     ("struct body " ++ seShow (L (A "..." : body))))
+        go _ = throw "invalid struct decl"
+
+tStructRow :: Parse (Identifier, Identifier)
+tStructRow sx =
+  fromPair tSymbol tType sx /?/ ("struct row " ++ seShow sx)
+
+-- enums
+tEnumDecl :: Parse Decl
+tEnumDecl = asList go
+  where go ("def-enum" : name : body) =
+          EnumDecl <$> tEnumName name      /?/ "enum name"
+                   <*> mapM tEnumBody body
+        go _ = throw "invalid enum decl"
+
+tEnumName :: Parse (Identifier, Bits)
+tEnumName (L [name, size]) = (,) <$> tSymbol name <*> tBits size
+tEnumName name             = (,) <$> tSymbol name <*> return Bits32
+
+tEnumBody :: Parse (Identifier, Integer)
+tEnumBody e =
+  fromPair tSymbol tInteger e /?/ ("enum constructor " ++ seShow e)
+
+-- interface declarations
+tInterfaceDecl :: Parse Decl
+tInterfaceDecl = asList go
+  where go ("def-interface":name:parents:body) =
+          InterfaceDecl
+            <$> tSymbol name                  /?/ "interface name"
+            <*> asList (mapM tSymbol) parents
+                  /?/ ("interface parents " ++ seShow parents)
+            <*> mapM tInterfaceMethod body
+        go _ = throw "invalid interface decl"
+
+tInterfaceMethod :: Parse (Identifier, MethodDecl)
+tInterfaceMethod e =
+  fromPair tSymbol (asList go) e /?/ ("interface method " ++ seShow e)
+  where go ["attr",   p, t] = AttrDecl   <$> tPermission p <*> tType t
+        go ["stream", n, t] = StreamDecl <$> tInteger n    <*> tType t
+        go (x:_) = throw ("unknown interface type: " ++ seShow x)
+        go []    = throw "empty interface decl"
+
+-- declarations in general
+tDecl :: Parse Decl
+tDecl ls@(L ("def-struct" : _)) =
+  tStructDecl ls /?/ ("struct " ++ showHead ls)
+tDecl ls@(L ("def-newtype" : _)) =
+  tNewtypeDecl ls /?/ ("newtype " ++ showHead ls)
+tDecl ls@(L ("def-enum" : _)) =
+  tEnumDecl ls /?/ ("enum " ++ showHead ls)
+tDecl ls@(L ("def-interface" : _)) =
+  tInterfaceDecl ls /?/ ("interface " ++ showHead ls)
+tDecl (L (A name : _)) =
+  throw ("unknown declaration type: " ++ seShow (A name))
+tDecl item = do
+  throw ("invalid top-level item " ++ seShow item)
+
+showHead :: WellFormedSExpr HaskLikeAtom -> String
+showHead (L (a:b:_)) = seShow (L [a,b,"..."])
+showHead sx          = seShow sx
+
+-- For now, no pretty-printing (but it will come soon!)
+ident :: Identifier -> WellFormedSExpr HaskLikeAtom
+ident = A . HSIdent . pack
+
+int :: Integer -> WellFormedSExpr HaskLikeAtom
+int = A . HSInt
+
+ppBits :: Bits -> WellFormedSExpr HaskLikeAtom
+ppBits Bits8  = A (HSInt 8)
+ppBits Bits16 = A (HSInt 16)
+ppBits Bits32 = A (HSInt 32)
+ppBits Bits64 = A (HSInt 64)
+
+ppDecl :: Decl -> WellFormedSExpr HaskLikeAtom
+ppDecl (NewtypeDecl name typ) =
+  L ["def-newtype", ident name, ident typ ]
+ppDecl (EnumDecl (name, Bits32) fields) =
+  L ( "def-enum"
+    : ident name
+    : [ L [ ident a, int b ]
+       | (a, b) <- fields
+       ]
+    )
+ppDecl (EnumDecl (name, bits) fields) =
+  L ( "def-enum"
+    : L [ ident name, ppBits bits ]
+    : [ L [ ident a, int b ]
+      | (a, b) <- fields
+      ]
+    )
+ppDecl (StructDecl name fields) =
+  L ( "def-struct"
+    : ident name
+    : [ L [ident a, ident b ]
+      | (a, b) <- fields
+      ]
+    )
+ppDecl (InterfaceDecl name parents methods) =
+  L ( "def-interface"
+    : ident name
+    : L (map ident parents)
+    : map go methods
+    ) where go (n, m) = L [ ident n, ppMethod m ]
+
+ppMethod :: MethodDecl -> WellFormedSExpr HaskLikeAtom
+ppMethod (StreamDecl rate name) = L [ "stream", int rate, ident name ]
+ppMethod (AttrDecl perm name) = L [ "attr", ppPerm perm, ident name ]
+  where ppPerm Read      = "r"
+        ppPerm Write     = "w"
+        ppPerm ReadWrite = "rw"

+ 138 - 0
support/tower/Attr.hs.template

@@ -0,0 +1,138 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module $module_path$.Attr where
+
+import Ivory.Language
+import Ivory.Tower
+
+data AttrWriter a =
+  AttrWriter
+    { aw_chan :: ChanInput a
+    , aw_name :: String
+    }
+
+data AttrReader a =
+  AttrReader
+    { ar_chan :: ChanOutput a
+    , ar_name :: String
+    , ar_ival :: Init a
+    }
+
+data Attr a =
+  Attr
+    { attr_writer :: AttrWriter a
+    , attr_reader :: AttrReader a
+    }
+
+attrReaderState :: (IvoryArea a, IvoryZero a)
+                => AttrReader a -> Monitor e (Ref Global a)
+attrReaderState ar@AttrReader{..} = do
+  s <- stateInit ar_name ar_ival
+  attrReaderHandler ar \$ do
+    callback \$ \\v -> refCopy s v
+  return s
+
+attrReaderHandler :: (IvoryArea a, IvoryZero a)
+                  => AttrReader a -> Handler a e () -> Monitor e ()
+attrReaderHandler AttrReader{..} k =
+  handler ar_chan (ar_name ++ "_update") k
+
+attrWriterEmitter :: (IvoryArea a, IvoryZero a) 
+                  => AttrWriter a -> Handler b e (Emitter a)
+attrWriterEmitter AttrWriter{..} = emitter aw_chan 1
+
+towerAttr :: (IvoryArea a) => String -> Init a -> Tower e (Attr a)
+towerAttr n i = do
+  c <- channel
+  return Attr
+    { attr_writer = AttrWriter
+      { aw_chan = fst c
+      , aw_name = n
+      }
+    , attr_reader = AttrReader
+      { ar_chan = snd c
+      , ar_name = n
+      , ar_ival = i
+      }
+    }
+
+class AttrNamed p where
+  attrName :: (IvoryArea a) => p a -> String
+
+instance AttrNamed AttrReader where
+  attrName = ar_name
+
+instance AttrNamed AttrWriter where
+  attrName = aw_name
+
+instance AttrNamed Attr where
+  attrName = attrName . attr_reader
+
+class AttrReadable p where
+  attrState   :: (IvoryArea a, IvoryZero a) => p a -> Monitor e (Ref Global a)
+  attrHandler :: (IvoryArea a, IvoryZero a) => p a -> Handler a e () -> Monitor e ()
+
+instance AttrReadable AttrReader where
+  attrState = attrReaderState
+  attrHandler = attrReaderHandler
+
+instance AttrReadable Attr where
+  attrState = attrReaderState . attr_reader
+  attrHandler p k = attrReaderHandler (attr_reader p) k
+
+class AttrWritable p where
+  attrEmitter :: (IvoryArea a, IvoryZero a) => p a -> Handler b e (Emitter a)
+
+instance AttrWritable AttrWriter where
+  attrEmitter = attrWriterEmitter
+
+instance AttrWritable Attr where
+  attrEmitter = attrWriterEmitter . attr_writer
+
+
+readableAttrServer :: (IvoryArea a, IvoryZero a)
+                   => Attr a
+                   -> ChanOutput (Stored IBool)
+                   -> Tower e (ChanOutput a)
+readableAttrServer p get = do
+  val <- channel
+  monitor (named "Server") \$ do
+    s <- attrState p
+    handler get (named "Get") \$ do
+      e <- emitter (fst val) 1
+      callback \$ const \$ emit e (constRef s)
+  return (snd val)
+  where
+  named n = attrName p ++ n
+
+writableAttrServer :: (IvoryArea a, IvoryZero a)
+                   => Attr a
+                   -> ChanOutput a
+                   -> Tower e ()
+writableAttrServer p set = do
+  monitor (named "Server") \$ do
+    handler set (named "Set") \$ do
+      e <- attrEmitter p
+      callback \$ \\v -> emit e v
+  where
+  named n = attrName p ++ n
+
+readwritableAttrServer :: (IvoryArea a, IvoryZero a)
+                       => Attr a
+                       -> ChanOutput (Stored IBool)
+                       -> ChanOutput a
+                       -> Tower e (ChanOutput a)
+readwritableAttrServer p get set = do
+  val <- channel
+  monitor (named "Server") \$ do
+    s <- attrState p
+    handler set (named "Set") \$ do
+      e <- attrEmitter p
+      callback \$ \\v -> emit e v
+    handler get (named "Get") \$ do
+      e <- emitter (fst val) 1
+      callback \$ const \$ emit e (constRef s)
+  return (snd val)
+  where
+  named n = attrName p ++ n

+ 1 - 1
tests/Test.hs

@@ -24,7 +24,7 @@ test :: FilePath -> IO ()
 test f = do
   c <- readFile f
   case parseDecls c of
-    Left e -> print e
+    Left e -> putStrLn e
     Right (te@(TypeEnv te'), ie@(InterfaceEnv ie')) -> do
       {-
       forM_ te' $ \(tn, t) -> do

+ 19 - 23
tests/example.idl

@@ -1,22 +1,20 @@
-
 (def-newtype time_micros_t sint64_t)
 
 -- comments should be haskell style, because we're suing parsec's haskell lexer
 
 -- enums default to 32 bit width if you don't really care
 (def-enum mode_t
- ((stabilize 0)
-  (auto 1)))
+ (stabilize 0)
+ (auto 1))
 
 -- or you can specify a width
-(def-enum armed_t 
- 8
- ((disarmed 0)
-  (armed 1)))
+(def-enum (armed_t 8)
+ (disarmed 0)
+ (armed 1))
 
 (def-struct heartbeat_t
- ((time time_micros_t)
-  (armed armed_t)))
+ (time time_micros_t)
+ (armed armed_t))
 
 (def-newtype lat_t sint32_t)
 (def-newtype lon_t sint32_t)
@@ -31,15 +29,15 @@
 (def-newtype meters_t float_t)
 
 (def-struct coordinate_t
- ((lat lat_t)
-  (lon lon_t)
-  (alt meters_t)))
+ (lat lat_t)
+ (lon lon_t)
+ (alt meters_t))
 
 (def-struct timed_coord_t
-  ((lat lat_t)
-   (lon lon_t)
-   (alt meters_t)
-   (time time_micros_t)))
+  (lat lat_t)
+  (lon lon_t)
+  (alt meters_t)
+  (time time_micros_t))
 
 -- Todo: the following interface syntax and semantics are a strawman.
 -- Interfaces have methods that are either streams or attrs.
@@ -48,16 +46,15 @@
 -- to zero). they also implicitly define an attr $(steamname)-stream-rate,
 -- which permits changing the stream rate at runtime.
 
-(def-interface vehicle_i
- ((heartbeat (stream 10 heartbeat_t))))
+(def-interface vehicle_i ()
+ (heartbeat (stream 10 heartbeat_t)))
 
 -- Interfaces implement java-style inheritance. No shadowing of inherited method
 -- names permitted.
 
-(def-interface controllable_vehicle_i
-  ((current_waypoint (attr read      coordinate_t))
-   (next_waypoint    (attr readwrite timed_coord_t)))
-  (vehicle_i)) -- Inherits from interface vehicle
+(def-interface controllable_vehicle_i (vehicle_i) -- Inherits from interface vehicle
+  (current_waypoint (attr read      coordinate_t))
+  (next_waypoint    (attr readwrite timed_coord_t)))
 
 -- The idea here is that, when negotiating a gidl connection, the client can
 -- specify or negotiate what interface is supported.
@@ -68,4 +65,3 @@
 -- This allows us to specify various kinds of vehicles might exist without
 -- repeating ourselves, and provides a way to
 -- manage new functionality without breaking legacy code.
-