Commit fddbce4d authored by Kenton Varda's avatar Kenton Varda

Implement imports.

parent 525d723b
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define CAPNPROTO_PRIVATE #define CAPNPROTO_PRIVATE
#include "test.capnp.h" #include "test-import.capnp.h"
#include "message.h" #include "message.h"
#include "logging.h" #include "logging.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
...@@ -301,6 +301,15 @@ TEST(Encoding, NestedTypes) { ...@@ -301,6 +301,15 @@ TEST(Encoding, NestedTypes) {
EXPECT_EQ(TestNestedTypes::NestedStruct::NestedEnum::QUUX, nested.getInnerNestedEnum()); EXPECT_EQ(TestNestedTypes::NestedStruct::NestedEnum::QUUX, nested.getInnerNestedEnum());
} }
TEST(Encoding, Imports) {
// Also just testing the generated code.
MallocMessageBuilder builder;
TestImport::Builder root = builder.getRoot<TestImport>();
initTestMessage(root.initField());
checkTestMessage(root.asReader().getField());
}
} // namespace } // namespace
} // namespace internal } // namespace internal
} // namespace capnproto } // namespace capnproto
...@@ -721,6 +721,20 @@ compileFile name decls importMap = ...@@ -721,6 +721,20 @@ compileFile name decls importMap =
dedup :: Ord a => [a] -> [a] dedup :: Ord a => [a] -> [a]
dedup = Set.toList . Set.fromList dedup = Set.toList . Set.fromList
emptyFileDesc filename = FileDesc
{ fileName = filename
, fileImports = []
, fileAliases = []
, fileConstants = []
, fileEnums = []
, fileStructs = []
, fileInterfaces = []
, fileOptions = Map.empty
, fileMemberMap = Map.empty
, fileImportMap = Map.empty
, fileStatements = []
}
parseAndCompileFile :: Monad m parseAndCompileFile :: Monad m
=> FilePath -- Name of this file. => FilePath -- Name of this file.
-> String -- Content of this file. -> String -- Content of this file.
...@@ -733,7 +747,7 @@ parseAndCompileFile filename text importCallback = do ...@@ -733,7 +747,7 @@ parseAndCompileFile filename text importCallback = do
result <- importCallback name result <- importCallback name
case result of case result of
Left desc -> return (succeed (name, desc)) Left desc -> return (succeed (name, desc))
Right err -> return Right err -> return $ recover (name, emptyFileDesc name)
(makeError pos (printf "Couldn't import \"%s\": %s" name err)) (makeError pos (printf "Couldn't import \"%s\": %s" name err))
importStatuses <- mapM doImport importNames importStatuses <- mapM doImport importNames
......
...@@ -294,6 +294,15 @@ typeContext parent desc = mkStrContext context where ...@@ -294,6 +294,15 @@ typeContext parent desc = mkStrContext context where
_ -> muNull _ -> muNull
context s = parent s context s = parent s
importContext parent ('/':filename) = mkStrContext context where
context "importFilename" = MuVariable filename
context "importIsSystem" = MuBool True
context s = parent s
importContext parent filename = mkStrContext context where
context "importFilename" = MuVariable filename
context "importIsSystem" = MuBool False
context s = parent s
fileContext desc = mkStrContext context where fileContext desc = mkStrContext context where
flattenedMembers = flattenTypes $ catMaybes $ Map.elems $ fileMemberMap desc flattenedMembers = flattenTypes $ catMaybes $ Map.elems $ fileMemberMap desc
...@@ -304,6 +313,7 @@ fileContext desc = mkStrContext context where ...@@ -304,6 +313,7 @@ fileContext desc = mkStrContext context where
context "fileNamespaces" = MuList [] -- TODO context "fileNamespaces" = MuList [] -- TODO
context "fileEnums" = MuList $ map (enumContext context) $ fileEnums desc context "fileEnums" = MuList $ map (enumContext context) $ fileEnums desc
context "fileTypes" = MuList $ map (typeContext context) flattenedMembers context "fileTypes" = MuList $ map (typeContext context) flattenedMembers
context "fileImports" = MuList $ map (importContext context) $ Map.keys $ fileImportMap desc
context s = error ("Template variable not defined: " ++ s) context s = error ("Template variable not defined: " ++ s)
headerTemplate :: String headerTemplate :: String
......
...@@ -27,8 +27,8 @@ import System.Environment ...@@ -27,8 +27,8 @@ import System.Environment
import System.Console.GetOpt import System.Console.GetOpt
import System.Exit(exitFailure, exitSuccess) import System.Exit(exitFailure, exitSuccess)
import System.IO(hPutStr, stderr) import System.IO(hPutStr, stderr)
import System.FilePath(takeDirectory, combine) import System.FilePath(takeDirectory)
import System.Directory(createDirectoryIfMissing, doesDirectoryExist) import System.Directory(createDirectoryIfMissing, doesDirectoryExist, doesFileExist)
import Control.Monad import Control.Monad
import Control.Monad.IO.Class(liftIO) import Control.Monad.IO.Class(liftIO)
import Control.Exception(IOException, catch) import Control.Exception(IOException, catch)
...@@ -50,14 +50,17 @@ type GeneratorFn = FileDesc -> IO [(FilePath, LZ.ByteString)] ...@@ -50,14 +50,17 @@ type GeneratorFn = FileDesc -> IO [(FilePath, LZ.ByteString)]
generatorFns = Map.fromList [ ("c++", generateCxx) ] generatorFns = Map.fromList [ ("c++", generateCxx) ]
data Opt = OutputOpt String (Maybe GeneratorFn) FilePath data Opt = SearchPathOpt FilePath
| OutputOpt String (Maybe GeneratorFn) FilePath
| VerboseOpt | VerboseOpt
| HelpOpt | HelpOpt
main :: IO () main :: IO ()
main = do main = do
let optionDescs = let optionDescs =
[ Option "o" ["output"] (ReqArg parseOutputArg "LANG[:DIR]") [ Option "I" ["import-path"] (ReqArg SearchPathOpt "DIR")
"Search DIR for absolute imports."
, Option "o" ["output"] (ReqArg parseOutputArg "LANG[:DIR]")
("Generate output for language LANG\n\ ("Generate output for language LANG\n\
\to directory DIR (default: current\n\ \to directory DIR (default: current\n\
\directory). LANG may be any of:\n\ \directory). LANG may be any of:\n\
...@@ -92,6 +95,7 @@ main = do ...@@ -92,6 +95,7 @@ main = do
let isVerbose = not $ null [opt | opt@VerboseOpt <- options] let isVerbose = not $ null [opt | opt@VerboseOpt <- options]
let outputs = [(fn, dir) | OutputOpt _ (Just fn) dir <- options] let outputs = [(fn, dir) | OutputOpt _ (Just fn) dir <- options]
let searchPath = [dir | SearchPathOpt dir <- options]
let verifyDirectoryExists dir = do let verifyDirectoryExists dir = do
exists <- doesDirectoryExist dir exists <- doesDirectoryExist dir
...@@ -101,7 +105,8 @@ main = do ...@@ -101,7 +105,8 @@ main = do
mapM_ verifyDirectoryExists [dir | (_, dir) <- outputs] mapM_ verifyDirectoryExists [dir | (_, dir) <- outputs]
CompilerState failed _ <- CompilerState failed _ <-
execStateT (mapM_ (handleFile outputs isVerbose) files) (CompilerState False Map.empty) execStateT (mapM_ (handleFile outputs isVerbose searchPath) files)
(CompilerState False Map.empty)
when failed exitFailure when failed exitFailure
parseOutputArg :: String -> Opt parseOutputArg :: String -> Opt
...@@ -109,13 +114,56 @@ parseOutputArg str = case List.elemIndex ':' str of ...@@ -109,13 +114,56 @@ parseOutputArg str = case List.elemIndex ':' str of
Just i -> let (lang, _:dir) = splitAt i str in OutputOpt lang (Map.lookup lang generatorFns) dir Just i -> let (lang, _:dir) = splitAt i str in OutputOpt lang (Map.lookup lang generatorFns) dir
Nothing -> OutputOpt str (Map.lookup str generatorFns) "." Nothing -> OutputOpt str (Map.lookup str generatorFns) "."
-- As always, here I am, writing my own path manipulation routines, because the ones in the
-- standard lib don't do what I want.
canonicalizePath :: [String] -> [String]
-- An empty string anywhere other than the beginning must be caused by multiple consecutive /'s.
canonicalizePath (a:"":rest) = canonicalizePath (a:rest)
-- An empty string at the beginning means this is an absolute path.
canonicalizePath ("":rest) = "":canonicalizePath rest
-- "." is redundant.
canonicalizePath (".":rest) = canonicalizePath rest
-- ".." at the beginning of the path refers to the parent of the root directory. Arguably this
-- is illegal but let's at least make sure that "../../foo" doesn't canonicalize to "foo"!
canonicalizePath ("..":rest) = "..":canonicalizePath rest
-- ".." cancels out the previous path component. Technically this does NOT match what the OS would
-- do in the presence of symlinks: `foo/bar/..` is NOT `foo` if `bar` is a symlink. But, in
-- practice, the user almost certainly wants symlinks to behave exactly the same as if the
-- directory had been copied into place.
canonicalizePath (_:"..":rest) = canonicalizePath rest
-- In all other cases, just proceed on.
canonicalizePath (a:rest) = a:canonicalizePath rest
-- All done.
canonicalizePath [] = []
splitPath = loop [] where
loop part ('/':text) = List.reverse part : loop [] text
loop part (c:text) = loop (c:part) text
loop part [] = [List.reverse part]
relativePath from searchPath relative = let
splitFrom = canonicalizePath $ splitPath from
splitRelative = canonicalizePath $ splitPath relative
splitSearchPath = map splitPath searchPath
-- TODO: Should we explicitly disallow "/../foo"?
resultPath = if head splitRelative == ""
then map (++ tail splitRelative) splitSearchPath
else [canonicalizePath (init splitFrom ++ splitRelative)]
in map (List.intercalate "/") resultPath
firstExisting :: [FilePath] -> IO (Maybe FilePath)
firstExisting paths = do
bools <- mapM doesFileExist paths
let existing = [path | (True, path) <- zip bools paths]
return (if null existing then Nothing else Just (head existing))
data ImportState = ImportInProgress | ImportFailed | ImportSucceeded FileDesc data ImportState = ImportInProgress | ImportFailed | ImportSucceeded FileDesc
type ImportStateMap = Map.Map String ImportState type ImportStateMap = Map.Map String ImportState
data CompilerState = CompilerState Bool ImportStateMap data CompilerState = CompilerState Bool ImportStateMap
type CompilerMonad a = StateT CompilerState IO a type CompilerMonad a = StateT CompilerState IO a
importFile :: Bool -> FilePath -> CompilerMonad (Either FileDesc String) importFile :: Bool -> [FilePath] -> FilePath -> CompilerMonad (Either FileDesc String)
importFile isVerbose filename = do importFile isVerbose searchPath filename = do
fileState <- state (\s@(CompilerState f m) -> case Map.lookup filename m of fileState <- state (\s@(CompilerState f m) -> case Map.lookup filename m of
d@Nothing -> (d, CompilerState f (Map.insert filename ImportInProgress m)) d@Nothing -> (d, CompilerState f (Map.insert filename ImportInProgress m))
d -> (d, s)) d -> (d, s))
...@@ -125,24 +173,27 @@ importFile isVerbose filename = do ...@@ -125,24 +173,27 @@ importFile isVerbose filename = do
Just ImportInProgress -> return $ Right "File cyclically imports itself." Just ImportInProgress -> return $ Right "File cyclically imports itself."
Just (ImportSucceeded d) -> return $ Left d Just (ImportSucceeded d) -> return $ Left d
Nothing -> do Nothing -> do
result <- readAndParseFile isVerbose filename result <- readAndParseFile isVerbose searchPath filename
modify (\(CompilerState f m) -> case result of modify (\(CompilerState f m) -> case result of
Left desc -> CompilerState f (Map.insert filename (ImportSucceeded desc) m) Left desc -> CompilerState f (Map.insert filename (ImportSucceeded desc) m)
Right _ -> CompilerState True (Map.insert filename ImportFailed m)) Right _ -> CompilerState True (Map.insert filename ImportFailed m))
return result return result
readAndParseFile isVerbose filename = do readAndParseFile isVerbose searchPath filename = do
textOrError <- liftIO $ catch (fmap Left $ readFile filename) textOrError <- liftIO $ catch (fmap Left $ readFile filename)
(\ex -> return $ Right $ show (ex :: IOException)) (\ex -> return $ Right $ show (ex :: IOException))
case textOrError of case textOrError of
Right err -> return $ Right err Right err -> return $ Right err
Left text -> parseFile isVerbose filename text Left text -> parseFile isVerbose searchPath filename text
parseFile isVerbose filename text = do parseFile isVerbose searchPath filename text = do
let importCallback name = do let importCallback name = do
let path = tail $ combine ('/':filename) name let candidates = relativePath filename searchPath name
importFile isVerbose path maybePath <- liftIO $ firstExisting candidates
case maybePath of
Nothing -> return $ Right "File not found."
Just path -> importFile isVerbose searchPath path
status <- parseAndCompileFile filename text importCallback status <- parseAndCompileFile filename text importCallback
case status of case status of
...@@ -156,9 +207,9 @@ parseFile isVerbose filename text = do ...@@ -156,9 +207,9 @@ parseFile isVerbose filename text = do
liftIO $ mapM_ printError (List.sortBy compareErrors e) liftIO $ mapM_ printError (List.sortBy compareErrors e)
return $ Right "File contained errors." return $ Right "File contained errors."
handleFile :: [(GeneratorFn, FilePath)] -> Bool -> FilePath -> CompilerMonad () handleFile :: [(GeneratorFn, FilePath)] -> Bool -> [FilePath] -> FilePath -> CompilerMonad ()
handleFile outputs isVerbose filename = do handleFile outputs isVerbose searchPath filename = do
result <- importFile isVerbose filename result <- importFile isVerbose searchPath filename
case result of case result of
Right _ -> return () Right _ -> return ()
......
...@@ -25,7 +25,6 @@ module Parser (parseFile) where ...@@ -25,7 +25,6 @@ module Parser (parseFile) where
import Data.Generics import Data.Generics
import Text.Parsec hiding (tokens) import Text.Parsec hiding (tokens)
import Text.Parsec.Error(newErrorMessage, Message(Message))
import Token import Token
import Grammar import Grammar
import Lexer (lexer) import Lexer (lexer)
...@@ -290,10 +289,6 @@ extractErrors :: Either ParseError (a, [ParseError]) -> [ParseError] ...@@ -290,10 +289,6 @@ extractErrors :: Either ParseError (a, [ParseError]) -> [ParseError]
extractErrors (Left err) = [err] extractErrors (Left err) = [err]
extractErrors (Right (_, errors)) = errors extractErrors (Right (_, errors)) = errors
failNonFatal :: SourcePos -> String -> TokenParser ()
failNonFatal pos msg = modifyState (newError:) where
newError = newErrorMessage (Message msg) pos
parseList parser items = do parseList parser items = do
let results = map (parseCollectingErrors parser) items let results = map (parseCollectingErrors parser) items
modifyState (\old -> concat (old:map extractErrors results)) modifyState (\old -> concat (old:map extractErrors results))
......
...@@ -25,10 +25,19 @@ ...@@ -25,10 +25,19 @@
Template for generated C++ header files. Template for generated C++ header files.
}}// Generated code, DO NOT EDIT }}// Generated code, DO NOT EDIT
#include <capnproto/generated-header-support.h>
#ifndef {{fileIncludeGuard}} #ifndef {{fileIncludeGuard}}
#define {{fileIncludeGuard}} #define {{fileIncludeGuard}}
#include <capnproto/generated-header-support.h>
{{#fileImports}}
{{#importIsSystem}}
#include <{{importFilename}}.h>
{{/importIsSystem}}
{{^importIsSystem}}
#include "{{importFilename}}.h"
{{/importIsSystem}}
{{/fileImports}}
{{#fileNamespaces}} {{#fileNamespaces}}
namespace {{namespaceName}} { namespace {{namespaceName}} {
{{/fileNamespaces}} {{/fileNamespaces}}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment