]> git.wincent.com - docvim.git/blob - lib/Text/Docvim/AST.hs
882d9cf02b28cef7fb48e0a05445a316379f4a92
[docvim.git] / lib / Text / Docvim / AST.hs
1 {-# LANGUAGE DeriveDataTypeable #-}
2
3 module Text.Docvim.AST where
4
5 import Control.Lens.Fold
6 import Control.Lens.Getter
7 import Control.Lens.Plated
8 import Data.Char
9 import Data.Data
10 import Data.Data.Lens
11 import Data.Monoid
12
13 data Node
14     -- Roots
15     = Project [Node] -- list of translation units (files)
16     | Unit [Node] -- translation unit (file)
17
18     -- To remove any node from the tree, we can just replace it with this.
19     | Empty
20
21     -- VimL nodes
22     | FunctionDeclaration { functionBang :: Bool
23                           , functionName :: String
24                           , functionArguments :: ArgumentList
25                           , functionAttributes :: [String]
26                           , functionBody :: [Node]
27                           }
28     | LetStatement { letLexpr :: String
29                     , letValue :: String
30                     }
31     | LexprStatement { lexprBang :: Bool
32                       , lexprExpr :: String
33                       }
34     | LwindowStatement { lwindowHeight :: Maybe Int }
35     | UnletStatement { unletBang :: Bool
36                       , unletBody :: String
37                       }
38     | GenericStatement String -- ^ For the stuff we only crudely parse for now.
39
40     -- Docvim nodes: "block-level" elements
41     | DocBlock [Node]
42     | Paragraph [Node]
43     | LinkTargets [String]
44     | List [Node]
45     | ListItem [Node]
46     | Blockquote [Node]
47     | Fenced [String]
48     | Separator
49
50     -- Docvim nodes: "phrasing content" elements
51     | Plaintext String
52     | BreakTag
53     | Link String
54     | Code String
55     | Whitespace
56
57     -- Docvim nodes: annotations
58     | PluginAnnotation Name Description
59     | FunctionsAnnotation
60     | FunctionAnnotation Name -- not sure if I will want more here
61     | IndentAnnotation
62     | DedentAnnotation
63     | CommandsAnnotation
64     | CommandAnnotation Name (Maybe Parameters)
65     | FooterAnnotation
66     | MappingsAnnotation
67     | MappingAnnotation Name
68     | OptionsAnnotation
69     | OptionAnnotation Name Type (Maybe Default)
70     | HeadingAnnotation String
71     | SubheadingAnnotation String
72
73     -- Docvim nodes: synthesized nodes
74     | TOC [String]
75   deriving (Data, Eq, Show, Typeable)
76
77 -- The VimScript (VimL) grammar is embodied in the implementation of
78 -- https://github.com/vim/vim/blob/master/src/eval.c; there is no formal
79 -- specification for it, and there are many ambiguities that can only be
80 -- resolved at runtime. We aim to parse a loose subset.
81
82 -- TODO: deal with bar |
83 --       note that `function X() |` does not work, and `endf` must be on a line
84 --       of its own too (not a syntax error to do `| endf`, but it doesn't work
85 --       , so you need to add another `endf`, which will blow up at runtime.
86 -- TODO: validate name = CapitalLetter or s:foo or auto#loaded
87
88 data ArgumentList = ArgumentList [Argument]
89   deriving (Data, Eq, Show, Typeable)
90
91 data Argument = Argument String
92   deriving (Data, Eq, Show, Typeable)
93
94 instance Plated Node
95
96 type Default = String
97 type Description = String
98 type Name = String
99 type Type = String
100 type Parameters = String
101
102 -- | Walks an AST node calling the supplied visitor function.
103 --
104 -- This is an in-order traversal.
105 --
106 -- For example, to implement a visitor which counts all nodes:
107 --
108 -- >  import Data.Monoid
109 -- >  count = getSum $ walk (\_ -> 1) (Sum 0) tree
110 --
111 -- For comparison, here is a (probably inefficient) alternative way using
112 -- `Control.Lens.Plated` instead of `walk`:
113 --
114 -- > import Control.Lens.Operators
115 -- > import Control.Lens.Plated
116 -- > import Data.Data.Lens
117 -- > count = length $ tree ^.. cosmosOf uniplate
118 --
119 -- Another example; accumulating `SubheadingAnnotation` nodes into a list:
120 --
121 -- >  accumulator node@(SubheadingAnnotation _) = [node]
122 -- >  accumulator _ = [] -- skip everything else
123 -- >  nodes = walk accumulator [] tree
124 --
125 -- Again, for comparison, the same without `walk`, this time using a list
126 -- comprehension:
127 --
128 -- > import Control.Lens.Operators
129 -- > import Control.Lens.Plated
130 -- > import Data.Data.Lens
131 -- > [n | n@(SubheadingAnnotation _) <- tree ^.. cosmosOf uniplate]
132 --
133 walk :: Monoid a => (Node -> a) -> a -> Node -> a
134 walk f = foldlOf (cosmosOf uniplate . to f) (<>)
135 -- TODO: consider making it possible for `f` to return `Nothing` to
136 -- short-circuit traversal, or `Just a` to continue with the current `mappend`
137 -- behavior.
138
139 -- | Sanitizes a link target similar to the way that GitHub does:
140 --
141 --    - Downcase.
142 --    - Filter, keeping only letter, number, space, hyphen.
143 --    - Change spaces to hyphens.
144 --    - Uniquify by appending "-1", "-2", "-3" etc (not yet implemented).
145 --
146 -- We use this both for generating GitHub friendly link targets, and for
147 -- auto-generating new link targets for use inside Vim help files.
148 --
149 -- Source: https://gist.github.com/asabaylus/3071099#gistcomment-1593627
150 sanitizeAnchor :: String -> String
151 sanitizeAnchor = hyphenate . keepValid . downcase
152   where
153     hyphenate = map spaceToHyphen
154     spaceToHyphen c = if c == ' ' then '-' else c
155     keepValid = filter (`elem` (['a'..'z'] ++ ['0'..'9'] ++ " -"))
156     downcase = map toLower
157
158 invalidNode = error "Invalid Node type"