{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE RankNTypes #-} module Text.Docvim.AST where import Control.Lens.Fold import Control.Lens.Getter import Control.Lens.Plated import Data.Char import Data.Data import Data.Data.Lens import Data.Monoid data Node -- Roots = Project [Node] -- list of translation units (files) | Unit [Node] -- translation unit (file) -- To remove any node from the tree, we can just replace it with this. | Empty -- VimL nodes | FunctionDeclaration { functionBang :: Bool , functionName :: String , functionArguments :: ArgumentList , functionAttributes :: [String] , functionBody :: [Node] } | LetStatement { letLexpr :: String , letValue :: String } | LexprStatement { lexprBang :: Bool , lexprExpr :: String } | LwindowStatement { lwindowHeight :: Maybe Int } | UnletStatement { unletBang :: Bool , unletBody :: String } | GenericStatement String -- ^ For the stuff we only crudely parse for now. -- Docvim nodes: "block-level" elements | DocBlock [Node] | Paragraph [Node] | LinkTargets [String] | List [Node] | ListItem [Node] | Blockquote [Node] | Fenced [String] | Separator -- Docvim nodes: "phrasing content" elements | Plaintext String | BreakTag | Link String | Code String | Whitespace -- Docvim nodes: annotations | PluginAnnotation Name Description | FunctionsAnnotation | FunctionAnnotation Name -- not sure if I will want more here | IndentAnnotation | DedentAnnotation | CommandsAnnotation | CommandAnnotation Name (Maybe Parameters) | FooterAnnotation | MappingsAnnotation | MappingAnnotation Name | OptionsAnnotation | OptionAnnotation Name Type (Maybe Default) | HeadingAnnotation String | SubheadingAnnotation String -- Docvim nodes: synthesized nodes | TOC [String] deriving (Data, Eq, Show, Typeable) -- The VimScript (VimL) grammar is embodied in the implementation of -- https://github.com/vim/vim/blob/master/src/eval.c; there is no formal -- specification for it, and there are many ambiguities that can only be -- resolved at runtime. We aim to parse a loose subset. -- TODO: deal with bar | -- note that `function X() |` does not work, and `endf` must be on a line -- of its own too (not a syntax error to do `| endf`, but it doesn't work -- , so you need to add another `endf`, which will blow up at runtime. -- TODO: validate name = CapitalLetter or s:foo or auto#loaded data ArgumentList = ArgumentList [Argument] deriving (Data, Eq, Show, Typeable) data Argument = Argument String deriving (Data, Eq, Show, Typeable) instance Plated Node type Default = String type Description = String type Name = String type Type = String type Parameters = String -- | Walks an AST node calling the supplied visitor function. -- -- This is an in-order traversal. -- -- For example, to implement a visitor which counts all nodes: -- -- > import Data.Monoid -- > count = getSum $ walk (\_ -> 1) (Sum 0) tree -- -- For comparison, here is a (probably inefficient) alternative way using -- `Control.Lens.Plated` instead of `walk`: -- -- > import Control.Lens.Operators -- > import Control.Lens.Plated -- > import Data.Data.Lens -- > count = length $ tree ^.. cosmosOf uniplate -- -- Another example; accumulating `SubheadingAnnotation` nodes into a list: -- -- > accumulator node@(SubheadingAnnotation _) = [node] -- > accumulator _ = [] -- skip everything else -- > nodes = walk accumulator [] tree -- -- Again, for comparison, the same without `walk`, this time using a list -- comprehension: -- -- > import Control.Lens.Operators -- > import Control.Lens.Plated -- > import Data.Data.Lens -- > [n | n@(SubheadingAnnotation _) <- tree ^.. cosmosOf uniplate] -- walk :: Monoid a => (Node -> a) -> a -> Node -> a walk f = foldlOf (cosmosOf uniplate . to f) (<>) -- TODO: consider making it possible for `f` to return `Nothing` to -- short-circuit traversal, or `Just a` to continue with the current `mappend` -- behavior. -- | Sanitizes a link target similar to the way that GitHub does: -- -- - Downcase. -- - Filter, keeping only letter, number, space, hyphen. -- - Change spaces to hyphens. -- - Uniquify by appending "-1", "-2", "-3" etc (not yet implemented). -- -- We use this both for generating GitHub friendly link targets, and for -- auto-generating new link targets for use inside Vim help files. -- -- Source: https://gist.github.com/asabaylus/3071099#gistcomment-1593627 sanitizeAnchor :: String -> String sanitizeAnchor = hyphenate . keepValid . downcase where hyphenate = map spaceToHyphen spaceToHyphen c = if c == ' ' then '-' else c keepValid = filter (`elem` (['a'..'z'] ++ ['0'..'9'] ++ " -")) downcase = map toLower invalidNode :: forall t. t invalidNode = error "Invalid Node type"