@@ -2,6 +2,26 @@ import { Definition, Variant } from "./types.ts";
2
2
import { createMarkdownFromHelp } from "../markdown.ts" ;
3
3
import { Counter , regexIndexOf } from "../utils.ts" ;
4
4
5
+ type FnDef = {
6
+ restype : string ;
7
+ } ;
8
+
9
+ export const RESTYPE_MAP : Readonly < Record < string , string > > = {
10
+ any : "unknown" ,
11
+ blob : "unknown" ,
12
+ bool : "boolean" ,
13
+ boolean : "boolean" ,
14
+ channel : "unknown" ,
15
+ dict : "Record<string, unknown>" ,
16
+ float : "number" ,
17
+ funcref : "unknown" ,
18
+ job : "unknown" ,
19
+ list : "unknown[]" ,
20
+ none : "void" ,
21
+ number : "number" ,
22
+ string : "string" ,
23
+ } ;
24
+
5
25
/**
6
26
* Parse Vim/Neovim help.
7
27
*
@@ -17,7 +37,9 @@ import { Counter, regexIndexOf } from "../utils.ts";
17
37
*/
18
38
export function parse ( content : string ) : Definition [ ] {
19
39
// Remove modeline
20
- content = content . replace ( / \n v i m : [ ^ \n ] * \s * $ / , "" ) ;
40
+ content = content . replace ( / \n \s * v i m : [ ^ \n ] * : \s * \n / g, "\n---\n" ) ;
41
+
42
+ const fnDefs = parseFunctionList ( content ) ;
21
43
22
44
const definitions : Definition [ ] = [ ] ;
23
45
let last = - 1 ;
@@ -29,7 +51,7 @@ export function parse(content: string): Definition[] {
29
51
continue ;
30
52
}
31
53
const { block, start, end } = extractBlock ( content , index ) ;
32
- const definition = parseBlock ( fn , block ) ;
54
+ const definition = parseBlock ( fn , block , fnDefs . get ( fn ) ) ;
33
55
if ( definition ) {
34
56
definitions . push ( definition ) ;
35
57
last = end ;
@@ -88,7 +110,11 @@ function extractBlock(content: string, index: number): {
88
110
*
89
111
* This function parse content like above and return `Definition`.
90
112
*/
91
- function parseBlock ( fn : string , body : string ) : Definition | undefined {
113
+ function parseBlock (
114
+ fn : string ,
115
+ body : string ,
116
+ def ?: FnDef ,
117
+ ) : Definition | undefined {
92
118
// Separate vars/docs blocks
93
119
const reTags = / (?: [ \t ] + \* [ ^ * \s ] + \* ) + [ \t ] * $ / . source ;
94
120
const reArgs = / \( [ ^ ( ) ] * (?: \n [ \t ] [ ^ ( ) ] * ) ? \) / . source ;
@@ -115,9 +141,7 @@ function parseBlock(fn: string, body: string): Definition | undefined {
115
141
( ) => varsBlock . split ( "\n" ) . at ( - 1 ) ! . replaceAll ( / [ ^ \t ] / g, " " ) ,
116
142
) ;
117
143
118
- const vars = varsBlock . split ( "\n" )
119
- . map ( parseVariant )
120
- . filter ( < T > ( x : T ) : x is NonNullable < T > => ! ! x ) ;
144
+ const vars = varsBlock . split ( "\n" ) . map ( ( s ) => parseVariant ( s , def ) ) ;
121
145
const docs = createMarkdownFromHelp ( docsBlock ) ;
122
146
123
147
return { fn, docs, vars } ;
@@ -139,16 +163,27 @@ function parseBlock(fn: string, body: string): Definition | undefined {
139
163
*
140
164
* This function parse content like above and return `Variant`.
141
165
*/
142
- function parseVariant ( variant : string ) : Variant | undefined {
166
+ function parseVariant ( variant : string , def ?: FnDef ) : Variant {
143
167
// Extract {args} part from {variant}
144
- const m = variant . match ( / ^ \w + \( ( .+ ?) \) / ) ;
145
- if ( ! m ) {
146
- // The {variant} does not have {args}, probabliy it's not variant (ex. `strstr`)
147
- return undefined ;
168
+ const m = variant . match ( / ^ \w + \( (?< args > .* ?) \) / ) ?. groups ?? { } ;
169
+ const args = parseVariantArgs ( m . args ) ;
170
+
171
+ return {
172
+ args,
173
+ restype : "unknown" ,
174
+ ...def ,
175
+ } ;
176
+ }
177
+
178
+ function parseVariantArgs ( argsBody : string ) : Variant [ "args" ] {
179
+ if ( argsBody . length === 0 ) {
180
+ // The {variant} does not have {args}
181
+ return [ ] ;
148
182
}
149
- let optional = m [ 1 ] . startsWith ( "[" ) ;
183
+
184
+ let optional = argsBody . startsWith ( "[" ) ;
150
185
const counter = new Counter ( ) ;
151
- const args = m [ 1 ] . split ( "," ) . map ( ( t ) => {
186
+ const args = argsBody . split ( "," ) . map ( ( t ) => {
152
187
const name = t . replaceAll ( / [ { } \[ \] \s ] / g, "" ) ;
153
188
const spread = name . endsWith ( "..." ) ;
154
189
const arg = {
@@ -172,3 +207,43 @@ function parseVariant(variant: string): Variant | undefined {
172
207
} ) ;
173
208
return uniqueArgs ;
174
209
}
210
+
211
+ /**
212
+ * Extract function definitions from `builtin-function-list`.
213
+ *
214
+ * `builtin-function-list` block is constructed with following parts
215
+ *
216
+ * ```text
217
+ * fn restype
218
+ * ~~~~~~~~~~~~ ~~~~~~
219
+ * filewritable({file}) Number |TRUE| if {file} is a writable file
220
+ *
221
+ * fn restype
222
+ * ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
223
+ * filter({expr1}, {expr2}) List/Dict/Blob/String
224
+ * remove items from {expr1} where
225
+ * {expr2} is 0
226
+ * ```
227
+ *
228
+ * - `restype` may be preceded by TAB, space or newline.
229
+ * - `restype` may be splited by "/", ", " or " or ".
230
+ * - `restype` may not exist.
231
+ */
232
+ function parseFunctionList ( content : string ) : Map < string , FnDef > {
233
+ const s = content . match ( / \* b u i l t i n - f u n c t i o n - l i s t \* \s .* ?\n = = = / s) ?. [ 0 ] ?? "" ;
234
+ return new Map ( [
235
+ ...s . matchAll (
236
+ / ^ (?< fn > \w + ) \( .* ?\) \s + (?< restype > \w + (?: (?: \/ | , | o r ) \w + ) * ) / gms,
237
+ ) as Iterable < { groups : { fn : string ; restype : string } } > ,
238
+ ] . map ( ( { groups : { fn, restype } } ) => {
239
+ const restypes = restype . split ( / \/ | , | o r / g) . map ( ( t ) =>
240
+ RESTYPE_MAP [ t . toLowerCase ( ) ] ?? "unknown"
241
+ ) ;
242
+ return [
243
+ fn ,
244
+ {
245
+ restype : [ ...new Set ( restypes ) ] . join ( " | " ) ,
246
+ } ,
247
+ ] ;
248
+ } ) ) ;
249
+ }
0 commit comments