src/doc_commands: rewrite doc commands parser
[nit.git] / src / doc / doc_commands.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Parsing of commands understood by documentation tools.
16 #
17 # This can be through:
18 # * `nitx` commands like `code: MEntity::name`
19 # * `nitdoc` wikilinks like `[[doc: MEntity::name]]`
20 module doc_commands
21
22 #
23 class DocCommandParser
24
25 # List of allowed command names for this parser
26 var allowed_commands: Array[String] = [ "doc", "comment", "list", "param",
27 "return", "new", "call", "code", "graph"] is writable
28
29 # Parse `string` as a DocCommand
30 #
31 # Returns `null` if the string cannot be parsed.
32 #
33 # ~~~
34 # var parser = new DocCommandParser
35 #
36 # var command = parser.parse("comment: core::Array")
37 # assert command isa CommentCommand
38 # assert command.arg == "core::Array"
39 #
40 # command = parser.parse(":") # syntax error
41 # assert command == null
42 # assert parser.errors.not_empty
43 # ~~~
44 fun parse(string: String): nullable DocCommand do
45 var pos = 0
46 var tmp = new FlatBuffer
47 errors.clear
48
49 # Parse command name
50 pos = string.read_until(tmp, pos, ':')
51 var name = tmp.write_to_string.trim
52
53 # Check allowed commands
54 if name.is_empty then
55 error("empty command name", 0)
56 return null
57 end
58 if not allowed_commands.has(name) then
59 error("unknown command name", 0)
60 return null
61 end
62
63 # Build the command
64 var command = new_command(name, string)
65 if command == null then
66 error("unknown command name", 0)
67 return null
68 end
69
70 # Parse the argument
71 tmp.clear
72 pos = string.read_until(tmp, pos + 1, '|')
73 var arg = tmp.write_to_string.trim
74 if arg.is_empty then
75 error("empty command arg", pos)
76 return null
77 end
78 command.arg = arg
79
80 # Parse command options
81 while pos < string.length do
82 # Parse option name
83 tmp.clear
84 pos = string.read_until(tmp, pos + 1, ':', ',')
85 var oname = tmp.write_to_string.trim
86 var oval = ""
87 if oname.is_empty then break
88 # Parse option value
89 if pos < string.length and string[pos] == ':' then
90 tmp.clear
91 pos = string.read_until(tmp, pos + 1, ',')
92 oval = tmp.write_to_string.trim
93 end
94 command.opts[oname] = oval
95 # TODO Check options
96 end
97
98 return command
99 end
100
101 # Init a new DocCommand from its `name`
102 #
103 # You must redefine this method to add new custom commands.
104 fun new_command(name, string: String): nullable DocCommand do
105 if name == "doc" then return new ArticleCommand(string)
106 if name == "comment" then return new CommentCommand(string)
107 if name == "list" then return new ListCommand(string)
108 if name == "param" then return new ParamCommand(string)
109 if name == "return" then return new ReturnCommand(string)
110 if name == "new" then return new NewCommand(string)
111 if name == "call" then return new CallCommand(string)
112 if name == "code" then return new CodeCommand(string)
113 if name == "graph" then return new GraphCommand(string)
114 return null
115 end
116
117 # Errors and warnings from last call to `parse`
118 var errors = new Array[DocMessage]
119
120 # Generate an error
121 fun error(message: String, col: nullable Int) do
122 errors.add new DocMessage(1, message, col)
123 end
124
125 # Generate a warning
126 fun warning(message: String, col: nullable Int) do
127 errors.add new DocMessage(2, message, col)
128 end
129 end
130
131 # A message generated by the DocCommandParser
132 class DocMessage
133
134 # Message severity
135 #
136 # 1- Error
137 # 2- Warning
138 var level: Int
139
140 # Message explanatory string
141 var message: String
142
143 # Related column in original string if any
144 var col: nullable Int
145
146 redef fun to_s do
147 var str = new FlatBuffer
148 if level == 1 then
149 str.append "Error: "
150 else
151 str.append "Warning: "
152 end
153 str.append message
154 var col = self.col
155 if col != null then
156 str.append " (col: {col})"
157 end
158 return str.write_to_string
159 end
160 end
161
162 redef class Text
163 # Read `self` as raw text until `nend` and append it to the `out` buffer.
164 private fun read_until(out: FlatBuffer, start: Int, nend: Char...): Int do
165 var pos = start
166 while pos < length do
167 var c = self[pos]
168 var end_reached = false
169 for n in nend do
170 if c == n then
171 end_reached = true
172 break
173 end
174 end
175 if end_reached then break
176 out.add c
177 pos += 1
178 end
179 return pos
180 end
181 end
182
183 # A command aimed at a documentation tool like `nitdoc` or `nitx`.
184 #
185 # `DocCommand` are generally of the form `command: arg | opt1: val1, opt2: val2`.
186 abstract class DocCommand
187
188 # Original command string.
189 var string: String
190
191 # Command name.
192 var name: String is noinit
193
194 # Command arguments.
195 var arg: String is noinit, writable
196
197 # Command options.
198 var opts = new HashMap[String, String] is writable
199
200 redef fun to_s do
201 if opts.is_empty then
202 return "{name}: {arg}"
203 end
204 return "{name}: {arg} | {opts.join(", ", ": ")}"
205 end
206 end
207
208 # A `DocCommand` that includes the documentation article of a `MEntity`.
209 #
210 # Syntax: `doc: MEntity::name`.
211 class ArticleCommand
212 super DocCommand
213
214 redef var name = "doc"
215 end
216
217 # A `DocCommand` that includes the MDoc of a `MEntity`.
218 #
219 # Syntax: `comment: MEntity::name`.
220 class CommentCommand
221 super DocCommand
222
223 redef var name = "comment"
224 end
225
226 # A `DocCommand` that includes a list of something.
227 #
228 # Syntax: `list:kind: <arg>`.
229 class ListCommand
230 super DocCommand
231
232 redef var name = "list"
233 end
234
235 # A `DocCommand` that includes the list of methods tanking a `MType` as parameter.
236 #
237 # Syntax: `param: MType`.
238 class ParamCommand
239 super DocCommand
240
241 redef var name = "param"
242 end
243
244 # A `DocCommand` that includes the list of methods returning a `MType` as parameter.
245 #
246 # Syntax: `return: MType`.
247 class ReturnCommand
248 super DocCommand
249
250 redef var name = "return"
251 end
252
253 # A `DocCommand` that includes the list of methods creating new instances of a specific `MType`
254 #
255 # Syntax: `new: MType`.
256 class NewCommand
257 super DocCommand
258
259 redef var name = "new"
260 end
261
262 # A `DocCommand` that includes the list of methods calling a specific `MProperty`.
263 #
264 # Syntax: `call: MEntity::name`.
265 class CallCommand
266 super DocCommand
267
268 redef var name = "call"
269 end
270
271 # A `DocCommand` that includes the source code of a `MEntity`.
272 #
273 # Syntax:
274 # * `code: MEntity::name`
275 # * `./src/file.nit` to include source code from a file.
276 # * `./src/file.nit:1,2--3,4` to select code between positions.
277 class CodeCommand
278 super DocCommand
279
280 redef var name = "code"
281 end
282
283 # A `DocCommand` that display an graph for a `MEntity`.
284 #
285 # Syntax:
286 # * `graph: MEntity::name`
287 class GraphCommand
288 super DocCommand
289
290 redef var name = "graph"
291 end