cmake-syntax (empty) → 0.1.0.0
raw patch · 8 files changed
+385/−0 lines, 8 filesdep +basedep +bytestringdep +cmake-syntaxsetup-changed
Dependencies added: base, bytestring, cmake-syntax, hspec, raw-strings-qq, trifecta
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- README.md +8/−0
- Setup.hs +2/−0
- cmake-syntax.cabal +61/−0
- src/Language/CMake/AST.hs +36/−0
- src/Language/CMake/Parser.hs +83/−0
- test/Spec.hs +160/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Changelog for cmake-syntax++## v0.1.0.0++Initial release
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Georg Rudoy (c) 2019++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Georg Rudoy nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,8 @@+# cmake-syntax++[![Build Status][travis-badge]][travis]++A simple parser for the CMake files.++[travis]: <https://travis-ci.org/0xd34df00d/can-i-haz>+[travis-badge]: <https://travis-ci.org/0xd34df00d/can-i-haz.svg?branch=master>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cmake-syntax.cabal view
@@ -0,0 +1,61 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 26bcc743c2398b409fec017473b905cf77d41b0b8ec9abe5cf6b0ae91e461e2c++name: cmake-syntax+version: 0.1.0.0+synopsis: Parser for the CMake syntax (CMakeLists.txt and .cmake files)+description: Please see the README on GitHub at <https://github.com/0xd34df00d/cmake-syntax#readme>+category: Language+homepage: https://github.com/0xd34df00d/cmake-syntax#readme+bug-reports: https://github.com/0xd34df00d/cmake-syntax/issues+author: Georg Rudoy+maintainer: 0xd34df00d@gmail.com+copyright: 2019 Georg Rudoy+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/0xd34df00d/cmake-syntax++library+ exposed-modules:+ Language.CMake.AST+ Language.CMake.Parser+ other-modules:+ Paths_cmake_syntax+ hs-source-dirs:+ src+ ghc-options: -Wall+ build-depends:+ base >=4.7 && <5+ , bytestring+ , raw-strings-qq+ , trifecta+ default-language: Haskell2010++test-suite cmake-syntax-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_cmake_syntax+ hs-source-dirs:+ test+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , bytestring+ , cmake-syntax+ , hspec+ , raw-strings-qq+ , trifecta+ default-language: Haskell2010
+ src/Language/CMake/AST.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Language.CMake.AST where++import qualified Data.ByteString.Char8 as BS+import Data.String++newtype File = File { fileElements :: [FileElement] } deriving (Eq, Show)++data FileElement+ = CommandElement { commandInvocation :: CommandInvocation }+ | NonCommandElement+ deriving (Eq, Show)++data CommandInvocation = CommandInvocation+ { commandId :: BS.ByteString+ , commandArgs :: [Argument]+ }+ deriving (Eq, Show)++data LiteralElem+ = LiteralString { literalString :: BS.ByteString }+ | VariableReference { variableName :: BS.ByteString }+ deriving (Eq, Show)++newtype Literal = Literal { literalParts :: [LiteralElem] }+ deriving (Eq, Show, Semigroup, Monoid)++instance IsString Literal where+ fromString = Literal . pure . LiteralString . BS.pack++newtype Argument = Argument { argumentLiteral :: Literal }+ deriving (Eq, Show, Semigroup, Monoid)++instance IsString Argument where+ fromString = Argument . fromString
+ src/Language/CMake/Parser.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE RecordWildCards, QuasiQuotes #-}++module Language.CMake.Parser+( fileParser+) where++import qualified Data.ByteString.Char8 as BS+import Control.Applicative+import Data.Char+import Data.Functor+import Data.Maybe+import Data.String+import Text.RawString.QQ+import Text.Trifecta++import Language.CMake.AST hiding(commandInvocation)++fileParser :: Parser File+fileParser = File <$> many fileElement <* eof++fileElement :: Parser FileElement+fileElement = try (many (try bracketComment <|> spaceNonLF) *> lineEnding $> NonCommandElement)+ <|> (CommandElement <$> commandInvocation) <* many spaceNonLF <* lineEnding+ where spaceNonLF = void $ satisfy $ \c -> isSpace c && c /= '\n'++lineEnding :: Parser ()+lineEnding = skipOptional lineComment >> void newline++commandInvocation :: Parser CommandInvocation+commandInvocation = do+ spaces+ commandId <- BS.pack <$> some ('A' ~~ 'Z' <|> 'a' ~~ 'z' <|> char '_' <|> '0' ~~ '9')+ spaces+ commandArgs <- between (char '(') (char ')') arguments+ pure CommandInvocation { .. }+ where (~~) = satisfyRange++arguments :: Parser [Argument]+arguments = do+ arg <- maybeToList <$> optional argument+ rest <- concat <$> many separatedArguments+ pure $ arg <> rest++separatedArguments :: Parser [Argument]+separatedArguments = maybeToList <$> try (some separation *> optional argument)+ <|> many separation *> between (char '(') (char ')') arguments++separation :: Parser ()+separation = void space <|> lineEnding++argument :: Parser Argument+argument = bracketArgument <|> quotedArgument <|> unquotedArgument++bracketArgument :: Parser Argument+bracketArgument = do+ opening <- char '[' *> many (char '=') <* char '['+ let closing = string $ "]" <> opening <> "]"+ fromString <$> anyChar `manyTill` try closing++quotedArgument :: Parser Argument+quotedArgument = fromString . catMaybes <$> many quotedElement `surroundedBy` char '"'++quotedElement :: Parser (Maybe Char)+quotedElement = Just <$> (noneOf [r|\"|] <|> try escapeSequence)+ <|> (char '\\' *> newline $> Nothing)++unquotedArgument :: Parser Argument+unquotedArgument = fromString <$> some (satisfy unElem <|> escapeSequence)+ where unElem c = c `notElem` [r|()#\"|] && not (isSpace c)++escapeSequence :: Parser Char+escapeSequence = char '\\' >> (self <|> encoded)+ where+ self = oneOf [r|()#" \$@^;|]+ encoded = char 't' $> '\t'+ <|> char 'r' $> '\r'+ <|> char 'n' $> '\n'++bracketComment :: Parser ()+bracketComment = char '#' >> void bracketArgument++lineComment :: Parser ()+lineComment = char '#' >> void (many $ notChar '\n')
+ test/Spec.hs view
@@ -0,0 +1,160 @@+{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++import Data.Char+import Test.Hspec+import Text.RawString.QQ+import Text.Trifecta.Parser+import Text.Trifecta.Result++import Language.CMake.AST+import Language.CMake.Parser++instance Eq a => Eq (Result a) where+ Success a == Success b = a == b+ Failure _ == Failure _ = True+ _ == _ = False++(~~>) :: HasCallStack => String -> File -> Expectation+(~~>) str res = parseString fileParser mempty (str <> "\n") `shouldBe` Success res++dropLeading :: String -> String+dropLeading str = init $ unlines $ l : (drop numSpaces <$> init ls)+ where+ (l:ls) = lines str+ numSpaces = length $ takeWhile isSpace $ last ls++main :: IO ()+main = hspec $ do+ describe "Parsing simple commands" $ do+ it "without args"+ $ [r|add_executable()|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" [] ]+ it "with leading spaces"+ $ [r| add_executable()|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" [] ]+ it "empty line"+ $ [r||]+ ~~> File [ NonCommandElement ]+ it "empty line with spaces"+ $ " \t "+ ~~> File [ NonCommandElement ]+ describe "Parsing a sequence of commands" $ do+ it "without args"+ $ [r|add_executable()+ add_library()|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" []+ , CommandElement $ CommandInvocation "add_library" []+ ]+ it "with empty lines"+ $ [r|add_executable()++ add_library()|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" []+ , NonCommandElement+ , CommandElement $ CommandInvocation "add_library" []+ ]+ describe "Parsing comments" $ do+ it "parses single whole-line comment"+ $ [r|# this is a comment|]+ ~~> File [NonCommandElement]+ it "parses multiple whole-line comments"+ $ [r|# this is a comment+ # and this is a continuation|]+ ~~> File [NonCommandElement, NonCommandElement]+ it "parses end-of-line comments"+ $ [r|add_library() # this is a comment|]+ ~~> File [CommandElement $ CommandInvocation "add_library" []]+ describe "Bracket comments" $ do+ it "parses simplest empty bracket comments"+ $ [r|#[[]]|]+ ~~> File [NonCommandElement]+ it "parses zero-eq bracket comments with text"+ $ [r|#[[ foobar ]]|]+ ~~> File [NonCommandElement]+ it "parses non-zero-eq bracket comments with text"+ $ [r|#[==[ foobar ]==]|]+ ~~> File [NonCommandElement]+ it "parses non-zero-eq bracket comments with nested eqs (less)"+ $ [r|#[==[ ]=] ]==]|]+ ~~> File [NonCommandElement]+ it "parses non-zero-eq bracket comments with nested eqs (more)"+ $ [r|#[==[ ]===] ]==]|]+ ~~> File [NonCommandElement]+ it "parses multilinenon-zero-eq bracket comments"+ $ [r|#[==[ foobar+ ]=]+ fdsafsdahljkrew hjkfdshafkjlashjk+ ]===]+ ]==]|]+ ~~> File [NonCommandElement]+ describe "Parsing simple command arguments" $ do+ it "parses command with one arg"+ $ [r|add_executable(foo)|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo"] ]+ it "parses command with several simple args"+ $ [r|add_executable(foo bar baz)|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo", "bar", "baz"] ]+ describe "Parsing bracket command arguments" $ do+ it "parses command with bracket arguments"+ $ dropLeading+ [r|add_executable([==[+ This is a bracket argument+ ]==])+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is a bracket argument\n"] ]+ it "parses command with multiline bracket arguments"+ $ dropLeading+ [r|add_executable([==[+ This is a+ bracket+ argument+ ]==])+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is a\nbracket\nargument\n"] ]+ it "does not escape in bracket arguments"+ $ dropLeading+ [r|add_executable([==[+ This is \t \; \n a \- bracket argument \meh+ ]==])+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is \\t \\; \\n a \\- bracket argument \\meh\n"] ]+ it "does not expand variables in bracket arguments"+ $ dropLeading+ [r|add_executable([==[+ This is a ${bracket} argument+ ]==])+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is a ${bracket} argument\n"] ]+ describe "Parsing quoted command arguments" $ do+ it "parses command with one arg"+ $ [r|add_executable("foo")|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo"] ]+ it "parses command with multple args"+ $ [r|add_executable("foo" "bar")|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo", "bar"] ]+ it "does escaping"+ $ [r|add_executable("foo \n \; bar")|]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo \n ; bar"] ]+ it "parses command with multiline arg"+ $ dropLeading+ [r|add_executable("foo+ bar+ baz")+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo\nbar\nbaz"] ]+ it "parses command with multiline arg and final line feed"+ $ dropLeading+ [r|add_executable("foo+ bar+ baz+ ")+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo\nbar\nbaz\n"] ]+ it "parses command with single-line arg spanning multiple lines"+ $ dropLeading+ [r|add_executable("foo \+ bar \+ baz")+ |]+ ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo bar baz"] ]