@@ -2,6 +2,26 @@ import { Definition, Variant } from "./types.ts";
22import { createMarkdownFromHelp } from "../markdown.ts" ;
33import { Counter , regexIndexOf } from "../utils.ts" ;
44
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+
525/**
626 * Parse Vim/Neovim help.
727 *
@@ -17,7 +37,9 @@ import { Counter, regexIndexOf } from "../utils.ts";
1737 */
1838export function parse ( content : string ) : Definition [ ] {
1939 // 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 ) ;
2143
2244 const definitions : Definition [ ] = [ ] ;
2345 let last = - 1 ;
@@ -29,7 +51,7 @@ export function parse(content: string): Definition[] {
2951 continue ;
3052 }
3153 const { block, start, end } = extractBlock ( content , index ) ;
32- const definition = parseBlock ( fn , block ) ;
54+ const definition = parseBlock ( fn , block , fnDefs . get ( fn ) ) ;
3355 if ( definition ) {
3456 definitions . push ( definition ) ;
3557 last = end ;
@@ -88,7 +110,11 @@ function extractBlock(content: string, index: number): {
88110 *
89111 * This function parse content like above and return `Definition`.
90112 */
91- function parseBlock ( fn : string , body : string ) : Definition | undefined {
113+ function parseBlock (
114+ fn : string ,
115+ body : string ,
116+ def ?: FnDef ,
117+ ) : Definition | undefined {
92118 // Separate vars/docs blocks
93119 const reTags = / (?: [ \t ] + \* [ ^ * \s ] + \* ) + [ \t ] * $ / . source ;
94120 const reArgs = / \( [ ^ ( ) ] * (?: \n [ \t ] [ ^ ( ) ] * ) ? \) / . source ;
@@ -115,9 +141,7 @@ function parseBlock(fn: string, body: string): Definition | undefined {
115141 ( ) => varsBlock . split ( "\n" ) . at ( - 1 ) ! . replaceAll ( / [ ^ \t ] / g, " " ) ,
116142 ) ;
117143
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 ) ) ;
121145 const docs = createMarkdownFromHelp ( docsBlock ) ;
122146
123147 return { fn, docs, vars } ;
@@ -139,16 +163,27 @@ function parseBlock(fn: string, body: string): Definition | undefined {
139163 *
140164 * This function parse content like above and return `Variant`.
141165 */
142- function parseVariant ( variant : string ) : Variant | undefined {
166+ function parseVariant ( variant : string , def ?: FnDef ) : Variant {
143167 // 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 [ ] ;
148182 }
149- let optional = m [ 1 ] . startsWith ( "[" ) ;
183+
184+ let optional = argsBody . startsWith ( "[" ) ;
150185 const counter = new Counter ( ) ;
151- const args = m [ 1 ] . split ( "," ) . map ( ( t ) => {
186+ const args = argsBody . split ( "," ) . map ( ( t ) => {
152187 const name = t . replaceAll ( / [ { } \[ \] \s ] / g, "" ) ;
153188 const spread = name . endsWith ( "..." ) ;
154189 const arg = {
@@ -172,3 +207,43 @@ function parseVariant(variant: string): Variant | undefined {
172207 } ) ;
173208 return uniqueArgs ;
174209}
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