xref: /vim-8.2.3635/src/vim9script.c (revision 3fb377fa)
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  * Handle an ":import" command and add the resulting imported_T to "gap", when
155  * not NULL, or script "import_sid" sn_imports.
156  * Returns a pointer to after the command or NULL in case of failure
157  */
158     char_u *
159 handle_import(char_u *arg_start, garray_T *gap, int import_sid)
160 {
161     char_u	*arg = arg_start;
162     char_u	*cmd_end;
163     char_u	*as_ptr = NULL;
164     char_u	*from_ptr;
165     int		as_len = 0;
166     int		ret = FAIL;
167     typval_T	tv;
168     int		sid = -1;
169     int		res;
170 
171     if (*arg == '{')
172     {
173 	// skip over {item} list
174 	while (*arg != NUL && *arg != '}')
175 	    ++arg;
176 	if (*arg == '}')
177 	    arg = skipwhite(arg + 1);
178     }
179     else
180     {
181 	if (*arg == '*')
182 	    arg = skipwhite(arg + 1);
183 	else
184 	{
185 	    while (eval_isnamec1(*arg))
186 		++arg;
187 	    arg = skipwhite(arg);
188 	}
189 	if (STRNCMP("as", arg, 2) == 0 && VIM_ISWHITE(arg[2]))
190 	{
191 	    // skip over "as Name "
192 	    arg = skipwhite(arg + 2);
193 	    as_ptr = arg;
194 	    while (eval_isnamec1(*arg))
195 		++arg;
196 	    as_len = (int)(arg - as_ptr);
197 	    arg = skipwhite(arg);
198 	}
199 	else if (*arg_start == '*')
200 	{
201 	    emsg(_("E1045: Missing \"as\" after *"));
202 	    return NULL;
203 	}
204     }
205     if (STRNCMP("from", arg, 4) != 0 || !VIM_ISWHITE(arg[4]))
206     {
207 	emsg(_("E1045: Missing \"from\""));
208 	return NULL;
209     }
210     from_ptr = arg;
211     arg = skipwhite(arg + 4);
212     tv.v_type = VAR_UNKNOWN;
213     // TODO: should we accept any expression?
214     if (*arg == '\'')
215 	ret = get_lit_string_tv(&arg, &tv, TRUE);
216     else if (*arg == '"')
217 	ret = get_string_tv(&arg, &tv, TRUE);
218     if (ret == FAIL || tv.vval.v_string == NULL || *tv.vval.v_string == NUL)
219     {
220 	emsg(_("E1045: Invalid string after \"from\""));
221 	return NULL;
222     }
223     cmd_end = arg;
224 
225     // find script tv.vval.v_string
226     if (*tv.vval.v_string == '.')
227     {
228 	size_t		len;
229 	scriptitem_T	*si = SCRIPT_ITEM(current_sctx.sc_sid);
230 	char_u		*tail = gettail(si->sn_name);
231 	char_u		*from_name;
232 
233 	// Relative to current script: "./name.vim", "../../name.vim".
234 	len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2;
235 	from_name = alloc((int)len);
236 	if (from_name == NULL)
237 	{
238 	    clear_tv(&tv);
239 	    return NULL;
240 	}
241 	vim_strncpy(from_name, si->sn_name, tail - si->sn_name);
242 	add_pathsep(from_name);
243 	STRCAT(from_name, tv.vval.v_string);
244 	simplify_filename(from_name);
245 
246 	res = do_source(from_name, FALSE, DOSO_NONE, &sid);
247 	vim_free(from_name);
248     }
249     else if (mch_isFullName(tv.vval.v_string))
250     {
251 	// Absolute path: "/tmp/name.vim"
252 	res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid);
253     }
254     else
255     {
256 	size_t	    len = 7 + STRLEN(tv.vval.v_string) + 1;
257 	char_u	    *from_name;
258 
259 	// Find file in "import" subdirs in 'runtimepath'.
260 	from_name = alloc((int)len);
261 	if (from_name == NULL)
262 	{
263 	    clear_tv(&tv);
264 	    return NULL;
265 	}
266 	vim_snprintf((char *)from_name, len, "import/%s", tv.vval.v_string);
267 	res = source_in_path(p_rtp, from_name, DIP_NOAFTER, &sid);
268 	vim_free(from_name);
269     }
270 
271     if (res == FAIL || sid <= 0)
272     {
273 	semsg(_("E1053: Could not import \"%s\""), tv.vval.v_string);
274 	clear_tv(&tv);
275 	return NULL;
276     }
277     clear_tv(&tv);
278 
279     if (*arg_start == '*')
280     {
281 	imported_T *imported = new_imported(gap != NULL ? gap
282 					: &SCRIPT_ITEM(import_sid)->sn_imports);
283 
284 	if (imported == NULL)
285 	    return NULL;
286 	imported->imp_name = vim_strnsave(as_ptr, as_len);
287 	imported->imp_sid = sid;
288 	imported->imp_all = TRUE;
289     }
290     else
291     {
292 	scriptitem_T *script = SCRIPT_ITEM(sid);
293 
294 	arg = arg_start;
295 	if (*arg == '{')
296 	    arg = skipwhite(arg + 1);
297 	for (;;)
298 	{
299 	    char_u	*name = arg;
300 	    int		name_len;
301 	    int		cc;
302 	    int		idx;
303 	    svar_T	*sv;
304 	    imported_T	*imported;
305 	    ufunc_T	*ufunc;
306 
307 	    // isolate one name
308 	    while (eval_isnamec1(*arg))
309 		++arg;
310 	    name_len = (int)(arg - name);
311 
312 	    // find name in "script"
313 	    // TODO: also find script-local user function
314 	    cc = *arg;
315 	    *arg = NUL;
316 	    idx = get_script_item_idx(sid, name, FALSE);
317 	    if (idx >= 0)
318 	    {
319 		sv = ((svar_T *)script->sn_var_vals.ga_data) + idx;
320 		if (!sv->sv_export)
321 		{
322 		    semsg(_("E1049: Item not exported in script: %s"), name);
323 		    *arg = cc;
324 		    return NULL;
325 		}
326 		ufunc = NULL;
327 	    }
328 	    else
329 	    {
330 		char_u	buffer[200];
331 		char_u	*funcname;
332 
333 		// it could be a user function.
334 		if (STRLEN(name) < sizeof(buffer) - 10)
335 		    funcname = buffer;
336 		else
337 		{
338 		    funcname = alloc(STRLEN(name) + 10);
339 		    if (funcname == NULL)
340 		    {
341 			*arg = cc;
342 			return NULL;
343 		    }
344 		}
345 		funcname[0] = K_SPECIAL;
346 		funcname[1] = KS_EXTRA;
347 		funcname[2] = (int)KE_SNR;
348 		sprintf((char *)funcname + 3, "%ld_%s", (long)sid, name);
349 		ufunc = find_func(funcname, NULL);
350 		if (funcname != buffer)
351 		    vim_free(funcname);
352 
353 		if (ufunc == NULL)
354 		{
355 		    semsg(_("E1048: Item not found in script: %s"), name);
356 		    *arg = cc;
357 		    return NULL;
358 		}
359 		sv = NULL;
360 	    }
361 	    *arg = cc;
362 	    arg = skipwhite(arg);
363 
364 	    imported = new_imported(gap != NULL ? gap
365 				       : &SCRIPT_ITEM(import_sid)->sn_imports);
366 	    if (imported == NULL)
367 		return NULL;
368 
369 	    // TODO: check for "as" following
370 	    // imported->imp_name = vim_strnsave(as_ptr, as_len);
371 	    imported->imp_name = vim_strnsave(name, name_len);
372 	    imported->imp_sid = sid;
373 	    if (idx >= 0)
374 	    {
375 		imported->imp_type = sv->sv_type;
376 		imported->imp_var_vals_idx = idx;
377 	    }
378 	    else
379 		imported->imp_funcname = ufunc->uf_name;
380 
381 	    arg = skipwhite(arg);
382 	    if (*arg_start != '{')
383 		break;
384 	    if (*arg == '}')
385 	    {
386 		arg = skipwhite(arg + 1);
387 		break;
388 	    }
389 
390 	    if (*arg != ',')
391 	    {
392 		emsg(_("E1046: Missing comma in import"));
393 		return NULL;
394 	    }
395 	    arg = skipwhite(arg + 1);
396 	}
397 	if (arg != from_ptr)
398 	{
399 	    emsg(_("E1047: syntax error in import"));
400 	    return NULL;
401 	}
402     }
403     return cmd_end;
404 }
405 
406 #endif // FEAT_EVAL
407