xref: /vim-8.2.3635/src/vim9script.c (revision bc93cebb)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * vim9script.c: :vim9script, :import, :export and friends
12  */
13 
14 #include "vim.h"
15 
16 #if defined(FEAT_EVAL) || defined(PROTO)
17 
18 #include "vim9.h"
19 
20 static char e_needs_vim9[] = N_("E1042: import/export can only be used in vim9script");
21 
22     int
23 in_vim9script(void)
24 {
25     // TODO: go up the stack?
26     return current_sctx.sc_version == SCRIPT_VERSION_VIM9;
27 }
28 
29 /*
30  * ":vim9script".
31  */
32     void
33 ex_vim9script(exarg_T *eap)
34 {
35     scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
36 
37     if (!getline_equal(eap->getline, eap->cookie, getsourceline))
38     {
39 	emsg(_("E1038: vim9script can only be used in a script"));
40 	return;
41     }
42     if (si->sn_had_command)
43     {
44 	emsg(_("E1039: vim9script must be the first command in a script"));
45 	return;
46     }
47     current_sctx.sc_version = SCRIPT_VERSION_VIM9;
48     si->sn_version = SCRIPT_VERSION_VIM9;
49     si->sn_had_command = TRUE;
50 
51     if (STRCMP(p_cpo, CPO_VIM) != 0)
52     {
53 	si->sn_save_cpo = p_cpo;
54 	p_cpo = vim_strsave((char_u *)CPO_VIM);
55     }
56 }
57 
58 /*
59  * ":export let Name: type"
60  * ":export const Name: type"
61  * ":export def Name(..."
62  * ":export class Name ..."
63  *
64  * ":export {Name, ...}"
65  */
66     void
67 ex_export(exarg_T *eap UNUSED)
68 {
69     if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
70     {
71 	emsg(_(e_needs_vim9));
72 	return;
73     }
74 
75     eap->cmd = eap->arg;
76     (void)find_ex_command(eap, NULL, lookup_scriptvar, NULL);
77     switch (eap->cmdidx)
78     {
79 	case CMD_let:
80 	case CMD_const:
81 	case CMD_def:
82 	// case CMD_class:
83 	    is_export = TRUE;
84 	    do_cmdline(eap->cmd, eap->getline, eap->cookie,
85 						DOCMD_VERBOSE + DOCMD_NOWAIT);
86 
87 	    // The command will reset "is_export" when exporting an item.
88 	    if (is_export)
89 	    {
90 		emsg(_("E1044: export with invalid argument"));
91 		is_export = FALSE;
92 	    }
93 	    break;
94 	default:
95 	    emsg(_("E1043: Invalid command after :export"));
96 	    break;
97     }
98 }
99 
100 /*
101  * Add a new imported item entry to the current script.
102  */
103     static imported_T *
104 new_imported(garray_T *gap)
105 {
106     if (ga_grow(gap, 1) == OK)
107 	return ((imported_T *)gap->ga_data + gap->ga_len++);
108     return NULL;
109 }
110 
111 /*
112  * Free all imported items in script "sid".
113  */
114     void
115 free_imports(int sid)
116 {
117     scriptitem_T    *si = SCRIPT_ITEM(sid);
118     int		    idx;
119 
120     for (idx = 0; idx < si->sn_imports.ga_len; ++idx)
121     {
122 	imported_T *imp = ((imported_T *)si->sn_imports.ga_data + idx);
123 
124 	vim_free(imp->imp_name);
125     }
126     ga_clear(&si->sn_imports);
127 }
128 
129 /*
130  * ":import Item from 'filename'"
131  * ":import Item as Alias from 'filename'"
132  * ":import {Item} from 'filename'".
133  * ":import {Item as Alias} from 'filename'"
134  * ":import {Item, Item} from 'filename'"
135  * ":import {Item, Item as Alias} from 'filename'"
136  *
137  * ":import * as Name from 'filename'"
138  */
139     void
140 ex_import(exarg_T *eap)
141 {
142     if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
143 	emsg(_(e_needs_vim9));
144     else
145     {
146 	char_u *cmd_end = handle_import(eap->arg, NULL, current_sctx.sc_sid);
147 
148 	if (cmd_end != NULL)
149 	    eap->nextcmd = check_nextcmd(cmd_end);
150     }
151 }
152 
153 /*
154  * Find an exported item in "sid" matching the name at "*argp".
155  * When it is a variable return the index.
156  * When it is a user function return "*ufunc".
157  * When not found returns -1 and "*ufunc" is NULL.
158  */
159     int
160 find_exported(
161 	int	    sid,
162 	char_u	    **argp,
163 	int	    *name_len,
164 	ufunc_T	    **ufunc,
165 	type_T	    **type)
166 {
167     char_u	*name = *argp;
168     char_u	*arg = *argp;
169     int		cc;
170     int		idx = -1;
171     svar_T	*sv;
172     scriptitem_T *script = SCRIPT_ITEM(sid);
173 
174     // isolate one name
175     while (eval_isnamec(*arg))
176 	++arg;
177     *name_len = (int)(arg - name);
178 
179     // find name in "script"
180     // TODO: also find script-local user function
181     cc = *arg;
182     *arg = NUL;
183     idx = get_script_item_idx(sid, name, FALSE);
184     if (idx >= 0)
185     {
186 	sv = ((svar_T *)script->sn_var_vals.ga_data) + idx;
187 	if (!sv->sv_export)
188 	{
189 	    semsg(_("E1049: Item not exported in script: %s"), name);
190 	    *arg = cc;
191 	    return -1;
192 	}
193 	*type = sv->sv_type;
194 	*ufunc = NULL;
195     }
196     else
197     {
198 	char_u	buffer[200];
199 	char_u	*funcname;
200 
201 	// it could be a user function.
202 	if (STRLEN(name) < sizeof(buffer) - 10)
203 	    funcname = buffer;
204 	else
205 	{
206 	    funcname = alloc(STRLEN(name) + 10);
207 	    if (funcname == NULL)
208 	    {
209 		*arg = cc;
210 		return -1;
211 	    }
212 	}
213 	funcname[0] = K_SPECIAL;
214 	funcname[1] = KS_EXTRA;
215 	funcname[2] = (int)KE_SNR;
216 	sprintf((char *)funcname + 3, "%ld_%s", (long)sid, name);
217 	*ufunc = find_func(funcname, NULL);
218 	if (funcname != buffer)
219 	    vim_free(funcname);
220 
221 	if (*ufunc == NULL)
222 	{
223 	    semsg(_("E1048: Item not found in script: %s"), name);
224 	    *arg = cc;
225 	    return -1;
226 	}
227     }
228     *arg = cc;
229     arg = skipwhite(arg);
230     *argp = arg;
231 
232     return idx;
233 }
234 
235 /*
236  * Handle an ":import" command and add the resulting imported_T to "gap", when
237  * not NULL, or script "import_sid" sn_imports.
238  * Returns a pointer to after the command or NULL in case of failure
239  */
240     char_u *
241 handle_import(char_u *arg_start, garray_T *gap, int import_sid)
242 {
243     char_u	*arg = arg_start;
244     char_u	*cmd_end;
245     char_u	*as_ptr = NULL;
246     char_u	*from_ptr;
247     int		as_len = 0;
248     int		ret = FAIL;
249     typval_T	tv;
250     int		sid = -1;
251     int		res;
252 
253     if (*arg == '{')
254     {
255 	// skip over {item} list
256 	while (*arg != NUL && *arg != '}')
257 	    ++arg;
258 	if (*arg == '}')
259 	    arg = skipwhite(arg + 1);
260     }
261     else
262     {
263 	if (*arg == '*')
264 	    arg = skipwhite(arg + 1);
265 	else if (eval_isnamec1(*arg))
266 	{
267 	    while (eval_isnamec(*arg))
268 		++arg;
269 	    arg = skipwhite(arg);
270 	}
271 	if (STRNCMP("as", arg, 2) == 0 && VIM_ISWHITE(arg[2]))
272 	{
273 	    // skip over "as Name "
274 	    arg = skipwhite(arg + 2);
275 	    as_ptr = arg;
276 	    if (eval_isnamec1(*arg))
277 		while (eval_isnamec(*arg))
278 		    ++arg;
279 	    as_len = (int)(arg - as_ptr);
280 	    arg = skipwhite(arg);
281 	}
282 	else if (*arg_start == '*')
283 	{
284 	    emsg(_("E1045: Missing \"as\" after *"));
285 	    return NULL;
286 	}
287     }
288     if (STRNCMP("from", arg, 4) != 0 || !VIM_ISWHITE(arg[4]))
289     {
290 	emsg(_("E1070: Missing \"from\""));
291 	return NULL;
292     }
293     from_ptr = arg;
294     arg = skipwhite(arg + 4);
295     tv.v_type = VAR_UNKNOWN;
296     // TODO: should we accept any expression?
297     if (*arg == '\'')
298 	ret = get_lit_string_tv(&arg, &tv, TRUE);
299     else if (*arg == '"')
300 	ret = get_string_tv(&arg, &tv, TRUE);
301     if (ret == FAIL || tv.vval.v_string == NULL || *tv.vval.v_string == NUL)
302     {
303 	emsg(_("E1071: Invalid string after \"from\""));
304 	return NULL;
305     }
306     cmd_end = arg;
307 
308     // find script tv.vval.v_string
309     if (*tv.vval.v_string == '.')
310     {
311 	size_t		len;
312 	scriptitem_T	*si = SCRIPT_ITEM(current_sctx.sc_sid);
313 	char_u		*tail = gettail(si->sn_name);
314 	char_u		*from_name;
315 
316 	// Relative to current script: "./name.vim", "../../name.vim".
317 	len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2;
318 	from_name = alloc((int)len);
319 	if (from_name == NULL)
320 	{
321 	    clear_tv(&tv);
322 	    return NULL;
323 	}
324 	vim_strncpy(from_name, si->sn_name, tail - si->sn_name);
325 	add_pathsep(from_name);
326 	STRCAT(from_name, tv.vval.v_string);
327 	simplify_filename(from_name);
328 
329 	res = do_source(from_name, FALSE, DOSO_NONE, &sid);
330 	vim_free(from_name);
331     }
332     else if (mch_isFullName(tv.vval.v_string))
333     {
334 	// Absolute path: "/tmp/name.vim"
335 	res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid);
336     }
337     else
338     {
339 	size_t	    len = 7 + STRLEN(tv.vval.v_string) + 1;
340 	char_u	    *from_name;
341 
342 	// Find file in "import" subdirs in 'runtimepath'.
343 	from_name = alloc((int)len);
344 	if (from_name == NULL)
345 	{
346 	    clear_tv(&tv);
347 	    return NULL;
348 	}
349 	vim_snprintf((char *)from_name, len, "import/%s", tv.vval.v_string);
350 	res = source_in_path(p_rtp, from_name, DIP_NOAFTER, &sid);
351 	vim_free(from_name);
352     }
353 
354     if (res == FAIL || sid <= 0)
355     {
356 	semsg(_("E1053: Could not import \"%s\""), tv.vval.v_string);
357 	clear_tv(&tv);
358 	return NULL;
359     }
360     clear_tv(&tv);
361 
362     if (*arg_start == '*')
363     {
364 	imported_T *imported = new_imported(gap != NULL ? gap
365 					: &SCRIPT_ITEM(import_sid)->sn_imports);
366 
367 	if (imported == NULL)
368 	    return NULL;
369 	imported->imp_name = vim_strnsave(as_ptr, as_len);
370 	imported->imp_sid = sid;
371 	imported->imp_all = TRUE;
372     }
373     else
374     {
375 	arg = arg_start;
376 	if (*arg == '{')
377 	    arg = skipwhite(arg + 1);
378 	for (;;)
379 	{
380 	    char_u	*name = arg;
381 	    int		name_len;
382 	    int		idx;
383 	    imported_T	*imported;
384 	    ufunc_T	*ufunc = NULL;
385 	    type_T	*type;
386 
387 	    idx = find_exported(sid, &arg, &name_len, &ufunc, &type);
388 
389 	    if (idx < 0 && ufunc == NULL)
390 		return NULL;
391 
392 	    imported = new_imported(gap != NULL ? gap
393 				       : &SCRIPT_ITEM(import_sid)->sn_imports);
394 	    if (imported == NULL)
395 		return NULL;
396 
397 	    // TODO: check for "as" following
398 	    // imported->imp_name = vim_strnsave(as_ptr, as_len);
399 	    imported->imp_name = vim_strnsave(name, name_len);
400 	    imported->imp_sid = sid;
401 	    if (idx >= 0)
402 	    {
403 		imported->imp_type = type;
404 		imported->imp_var_vals_idx = idx;
405 	    }
406 	    else
407 		imported->imp_funcname = ufunc->uf_name;
408 
409 	    arg = skipwhite(arg);
410 	    if (*arg_start != '{')
411 		break;
412 	    if (*arg == '}')
413 	    {
414 		arg = skipwhite(arg + 1);
415 		break;
416 	    }
417 
418 	    if (*arg != ',')
419 	    {
420 		emsg(_("E1046: Missing comma in import"));
421 		return NULL;
422 	    }
423 	    arg = skipwhite(arg + 1);
424 	}
425 	if (arg != from_ptr)
426 	{
427 	    // cannot happen, just in case the above has a flaw
428 	    emsg(_("E1047: syntax error in import"));
429 	    return NULL;
430 	}
431     }
432     return cmd_end;
433 }
434 
435 #endif // FEAT_EVAL
436