From dc7c366f3aae65ee691010b08f37acfb26e0742b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Mon, 20 Dec 2021 15:04:29 +0000 Subject: patch 8.2.3860: Vim9: codecov struggles with the file size Problem: Vim9: codecov struggles with the file size. Solution: Split vim9compile.c into four files. --- src/Make_ami.mak | 3 + src/Make_cyg_ming.mak | 9 + src/Make_mvc.mak | 12 + src/Make_vms.mms | 20 +- src/Makefile | 52 +- src/proto.h | 5 + src/proto/vim9cmds.pro | 33 + src/proto/vim9compile.pro | 24 +- src/proto/vim9expr.pro | 17 + src/proto/vim9instr.pro | 72 + src/version.c | 2 + src/vim9.h | 208 +- src/vim9cmds.c | 2274 ++++++++++ src/vim9compile.c | 10591 +++++++------------------------------------- src/vim9execute.c | 8 +- src/vim9expr.c | 2893 ++++++++++++ src/vim9instr.c | 2208 +++++++++ src/vim9script.c | 3 +- 18 files changed, 9356 insertions(+), 9078 deletions(-) create mode 100644 src/proto/vim9cmds.pro create mode 100644 src/proto/vim9expr.pro create mode 100644 src/proto/vim9instr.pro create mode 100644 src/vim9cmds.c create mode 100644 src/vim9expr.c create mode 100644 src/vim9instr.c (limited to 'src') diff --git a/src/Make_ami.mak b/src/Make_ami.mak index 12f7fe5571..dd674dc616 100644 --- a/src/Make_ami.mak +++ b/src/Make_ami.mak @@ -180,8 +180,11 @@ SRC += \ userfunc.c \ version.c \ viminfo.c \ + vim9cmds.c \ vim9compile.c \ vim9execute.c \ + vim9expr.c \ + vim9instr.c \ vim9script.c \ vim9type.c \ window.c \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak index fe04cb57ac..d9a50d243b 100644 --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -825,8 +825,11 @@ OBJ = \ $(OUTDIR)/usercmd.o \ $(OUTDIR)/userfunc.o \ $(OUTDIR)/version.o \ + $(OUTDIR)/vim9cmds.o \ $(OUTDIR)/vim9compile.o \ $(OUTDIR)/vim9execute.o \ + $(OUTDIR)/vim9expr.o \ + $(OUTDIR)/vim9instr.o \ $(OUTDIR)/vim9script.o \ $(OUTDIR)/vim9type.o \ $(OUTDIR)/viminfo.o \ @@ -1203,10 +1206,16 @@ $(OUTDIR)/netbeans.o: netbeans.c $(INCL) version.h $(OUTDIR)/version.o: version.c $(INCL) version.h +$(OUTDIR)/vim9cmds.o: vim9cmds.c $(INCL) version.h + $(OUTDIR)/vim9compile.o: vim9compile.c $(INCL) version.h $(OUTDIR)/vim9execute.o: vim9execute.c $(INCL) version.h +$(OUTDIR)/vim9expr.o: vim9expr.c $(INCL) version.h + +$(OUTDIR)/vim9instr.o: vim9instr.c $(INCL) version.h + $(OUTDIR)/vim9script.o: vim9script.c $(INCL) version.h $(OUTDIR)/vim9type.o: vim9type.c $(INCL) version.h diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index 2417d77270..f2a45a0af8 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -842,8 +842,11 @@ OBJ = \ $(OUTDIR)\undo.obj \ $(OUTDIR)\usercmd.obj \ $(OUTDIR)\userfunc.obj \ + $(OUTDIR)\vim9cmds.obj \ $(OUTDIR)\vim9compile.obj \ $(OUTDIR)\vim9execute.obj \ + $(OUTDIR)\vim9expr.obj \ + $(OUTDIR)\vim9instr.obj \ $(OUTDIR)\vim9script.obj \ $(OUTDIR)\vim9type.obj \ $(OUTDIR)\viminfo.obj \ @@ -1834,10 +1837,16 @@ $(OUTDIR)/userfunc.obj: $(OUTDIR) userfunc.c $(INCL) $(OUTDIR)/version.obj: $(OUTDIR) version.c $(INCL) version.h +$(OUTDIR)/vim9cmds.obj: $(OUTDIR) vim9cmds.c $(INCL) + $(OUTDIR)/vim9compile.obj: $(OUTDIR) vim9compile.c $(INCL) $(OUTDIR)/vim9execute.obj: $(OUTDIR) vim9execute.c $(INCL) +$(OUTDIR)/vim9expr.obj: $(OUTDIR) vim9expr.c $(INCL) + +$(OUTDIR)/vim9instr.obj: $(OUTDIR) vim9instr.c $(INCL) + $(OUTDIR)/vim9script.obj: $(OUTDIR) vim9script.c $(INCL) $(OUTDIR)/vim9type.obj: $(OUTDIR) vim9type.c $(INCL) @@ -2041,8 +2050,11 @@ proto.h: \ proto/undo.pro \ proto/usercmd.pro \ proto/userfunc.pro \ + proto/vim9cmds.pro \ proto/vim9compile.pro \ proto/vim9execute.pro \ + proto/vim9expr.pro \ + proto/vim9instr.pro \ proto/vim9script.pro \ proto/vim9type.pro \ proto/viminfo.pro \ diff --git a/src/Make_vms.mms b/src/Make_vms.mms index ab5be44905..54aa4c3a72 100644 --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -2,7 +2,7 @@ # Makefile for Vim on OpenVMS # # Maintainer: Zoltan Arpadffy -# Last change: 2021 Nov 19 +# Last change: 2021 Dec 20 # # This script has been tested on VMS 6.2 to 8.4 on DEC Alpha, VAX and IA64 # with MMS and MMK @@ -410,8 +410,11 @@ SRC = \ usercmd.c \ userfunc.c \ version.c \ + vim9cmds.c \ vim9compile.c \ vim9execute.c \ + vim9expr.c \ + vim9instr.c \ vim9script.c \ vim9type.c \ viminfo.c \ @@ -531,8 +534,11 @@ OBJ = \ usercmd.obj \ userfunc.obj \ version.obj \ + vim9cmds.obj \ vim9compile.obj \ vim9execute.obj \ + vim9expr.obj \ + vim9instr.obj \ vim9script.obj \ vim9type.obj \ viminfo.obj \ @@ -1112,6 +1118,10 @@ viminfo.obj : viminfo.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ errors.h globals.h version.h +vim9cmds.obj : vim9cmds.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + errors.h globals.h version.h vim9compile.obj : vim9compile.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ @@ -1120,6 +1130,14 @@ vim9execute.obj : vim9execute.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ errors.h globals.h version.h +vim9expr.obj : vim9expr.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + errors.h globals.h version.h +vim9instr.obj : vim9instr.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + errors.h globals.h version.h vim9script.obj : vim9script.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ diff --git a/src/Makefile b/src/Makefile index 8e87d1d7c9..4acc370d09 100644 --- a/src/Makefile +++ b/src/Makefile @@ -368,7 +368,7 @@ CClink = $(CC) #CONF_OPT_GUI = --enable-gui=gtk2 --disable-gtktest #CONF_OPT_GUI = --enable-gui=gnome2 #CONF_OPT_GUI = --enable-gui=gnome2 --disable-gtktest -#CONF_OPT_GUI = --enable-gui=gtk3 +CONF_OPT_GUI = --enable-gui=gtk3 #CONF_OPT_GUI = --enable-gui=gtk3 --disable-gtktest #CONF_OPT_GUI = --enable-gui=motif #CONF_OPT_GUI = --enable-gui=motif --with-motif-lib="-static -lXm -shared" @@ -404,7 +404,7 @@ CClink = $(CC) # Use --with-luajit if you want to use LuaJIT instead of Lua. # Set PATH environment variable to find lua or luajit executable. # This requires at least "normal" features, "tiny" and "small" don't work. -#CONF_OPT_LUA = --enable-luainterp +CONF_OPT_LUA = --enable-luainterp #CONF_OPT_LUA = --enable-luainterp=dynamic #CONF_OPT_LUA = --enable-luainterp --with-luajit #CONF_OPT_LUA = --enable-luainterp=dynamic --with-luajit @@ -433,7 +433,7 @@ CClink = $(CC) # When you get an error for a missing "perl.exp" file, try creating an empty # one: "touch perl.exp". # This requires at least "normal" features, "tiny" and "small" don't work. -#CONF_OPT_PERL = --enable-perlinterp +CONF_OPT_PERL = --enable-perlinterp #CONF_OPT_PERL = --enable-perlinterp=dynamic # PYTHON @@ -453,7 +453,7 @@ CClink = $(CC) #CONF_OPT_PYTHON = --enable-pythoninterp #CONF_OPT_PYTHON = --enable-pythoninterp --with-python-command=python2.7 #CONF_OPT_PYTHON = --enable-pythoninterp=dynamic -#CONF_OPT_PYTHON3 = --enable-python3interp +CONF_OPT_PYTHON3 = --enable-python3interp #CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.6 #CONF_OPT_PYTHON3 = --enable-python3interp=dynamic @@ -462,7 +462,7 @@ CClink = $(CC) # First one for static linking, second one for loading when used. # Debian package is "ruby-dev". # This requires at least "normal" features, "tiny" and "small" don't work. -#CONF_OPT_RUBY = --enable-rubyinterp +CONF_OPT_RUBY = --enable-rubyinterp #CONF_OPT_RUBY = --enable-rubyinterp=dynamic #CONF_OPT_RUBY = --enable-rubyinterp --with-ruby-command=ruby1.9.1 @@ -470,13 +470,13 @@ CClink = $(CC) # Uncomment this when you want to include the Tcl interface. # First one is for static linking, second one for dynamic loading. # Debian package is "tcl-dev". -#CONF_OPT_TCL = --enable-tclinterp +CONF_OPT_TCL = --enable-tclinterp #CONF_OPT_TCL = --enable-tclinterp=dynamic #CONF_OPT_TCL = --enable-tclinterp --with-tclsh=tclsh8.4 # CSCOPE # Uncomment this when you want to include the Cscope interface. -#CONF_OPT_CSCOPE = --enable-cscope +CONF_OPT_CSCOPE = --enable-cscope # NETBEANS - NetBeans interface. Only works with Motif, GTK, and gnome. # Motif version must have XPM libraries (see |netbeans-xpm|). @@ -548,7 +548,7 @@ CClink = $(CC) #CONF_OPT_FEAT = --with-features=small #CONF_OPT_FEAT = --with-features=normal #CONF_OPT_FEAT = --with-features=big -#CONF_OPT_FEAT = --with-features=huge +CONF_OPT_FEAT = --with-features=huge # COMPILED BY - For including a specific e-mail address for ":version". #CONF_OPT_COMPBY = "--with-compiledby=John Doe " @@ -623,7 +623,7 @@ CClink = $(CC) # Note: If you use -Wextra and get warnings in GTK code about function # parameters, you can add -Wno-cast-function-type (but not with clang) #CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wunreachable-code -Wno-cast-function-type -Wno-deprecated-declarations -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -#CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wunreachable-code -Wno-deprecated-declarations -D_REENTRANT -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 +CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wunreachable-code -Wno-deprecated-declarations -D_REENTRANT -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 # Add -Wpedantic to find // comments and other C99 constructs. # Better disable Perl and Python to avoid a lot of warnings. #CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wpedantic -Wunreachable-code -Wunused-result -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 @@ -727,12 +727,12 @@ SANITIZER_LIBS = $(SANITIZER_CFLAGS) # Configuration is in the .ccmalloc or ~/.ccmalloc file. # Doesn't work very well, since memory linked to from global variables # (in libraries) is also marked as leaked memory. -#LEAK_CFLAGS = -DEXITFREE +LEAK_CFLAGS = -DEXITFREE #LEAK_LIBS = -lccmalloc # Uncomment this line to have Vim call abort() when an internal error is # detected. Useful when using a tool to find errors. -#ABORT_CFLAGS = -DABORT_ON_INTERNAL_ERROR +ABORT_CFLAGS = -DABORT_ON_INTERNAL_ERROR #################################################### ### Specific systems, check if yours is listed ### {{{ @@ -1695,8 +1695,11 @@ BASIC_SRC = \ usercmd.c \ userfunc.c \ version.c \ + vim9cmds.c, \ vim9compile.c \ vim9execute.c \ + vim9expr.c, \ + vim9instr.c, \ vim9script.c \ vim9type.c \ viminfo.c \ @@ -1848,8 +1851,11 @@ OBJ_COMMON = \ objects/usercmd.o \ objects/userfunc.o \ objects/version.o \ + objects/vim9cmds.o \ objects/vim9compile.o \ objects/vim9execute.o \ + objects/vim9expr.o \ + objects/vim9instr.o \ objects/vim9script.o \ objects/vim9type.o \ objects/viminfo.o \ @@ -2034,8 +2040,11 @@ PRO_AUTO = \ usercmd.pro \ userfunc.pro \ version.pro \ + vim9cmds.pro \ vim9compile.pro \ vim9execute.pro \ + vim9expr.pro \ + vim9instr.pro \ vim9script.pro \ vim9type.pro \ viminfo.pro \ @@ -3575,12 +3584,21 @@ objects/usercmd.o: usercmd.c objects/userfunc.o: userfunc.c $(CCC) -o $@ userfunc.c +objects/vim9cmds.o: vim9cmds.c + $(CCC) -o $@ vim9cmds.c + objects/vim9compile.o: vim9compile.c $(CCC) -o $@ vim9compile.c objects/vim9execute.o: vim9execute.c $(CCC) -o $@ vim9execute.c +objects/vim9expr.o: vim9expr.c + $(CCC) -o $@ vim9expr.c + +objects/vim9instr.o: vim9instr.c + $(CCC) -o $@ vim9instr.c + objects/vim9script.o: vim9script.c $(CCC) -o $@ vim9script.c @@ -4137,6 +4155,10 @@ objects/version.o: version.c vim.h protodef.h auto/config.h feature.h os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h errors.h version.h +objects/vim9cmds.o: vim9cmds.c vim.h protodef.h auto/config.h feature.h \ + os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h errors.h vim9.h objects/vim9compile.o: vim9compile.c vim.h protodef.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ @@ -4145,6 +4167,14 @@ objects/vim9execute.o: vim9execute.c vim.h protodef.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h errors.h vim9.h +objects/vim9expr.o: vim9expr.c vim.h protodef.h auto/config.h feature.h \ + os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h errors.h vim9.h +objects/vim9instr.o: vim9instr.c vim.h protodef.h auto/config.h feature.h \ + os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h errors.h vim9.h objects/vim9script.o: vim9script.c vim.h protodef.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/proto.h b/src/proto.h index 6c26aee38c..d31629e0e1 100644 --- a/src/proto.h +++ b/src/proto.h @@ -212,8 +212,13 @@ void mbyte_im_set_active(int active_arg); # include "version.pro" # include "vim9script.pro" # ifdef FEAT_EVAL +// include vim9.h here, the types defined there are used by function arguments. +# include "vim9.h" +# include "vim9cmds.pro" # include "vim9compile.pro" # include "vim9execute.pro" +# include "vim9expr.pro" +# include "vim9instr.pro" # include "vim9type.pro" # endif # include "window.pro" diff --git a/src/proto/vim9cmds.pro b/src/proto/vim9cmds.pro new file mode 100644 index 0000000000..7529d63d62 --- /dev/null +++ b/src/proto/vim9cmds.pro @@ -0,0 +1,33 @@ +/* vim9cmds.c */ +void free_locals(cctx_T *cctx); +int check_vim9_unlet(char_u *name); +char_u *compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx); +void drop_scope(cctx_T *cctx); +char_u *compile_if(char_u *arg, cctx_T *cctx); +char_u *compile_elseif(char_u *arg, cctx_T *cctx); +char_u *compile_else(char_u *arg, cctx_T *cctx); +char_u *compile_endif(char_u *arg, cctx_T *cctx); +char_u *compile_for(char_u *arg_start, cctx_T *cctx); +char_u *compile_endfor(char_u *arg, cctx_T *cctx); +char_u *compile_while(char_u *arg, cctx_T *cctx); +char_u *compile_endwhile(char_u *arg, cctx_T *cctx); +char_u *compile_continue(char_u *arg, cctx_T *cctx); +char_u *compile_break(char_u *arg, cctx_T *cctx); +char_u *compile_block(char_u *arg, cctx_T *cctx); +void compile_endblock(cctx_T *cctx); +char_u *compile_try(char_u *arg, cctx_T *cctx); +char_u *compile_catch(char_u *arg, cctx_T *cctx); +char_u *compile_finally(char_u *arg, cctx_T *cctx); +char_u *compile_endtry(char_u *arg, cctx_T *cctx); +char_u *compile_throw(char_u *arg, cctx_T *cctx); +char_u *compile_eval(char_u *arg, cctx_T *cctx); +char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx); +char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx); +char_u *compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx); +char_u *compile_script(char_u *line, cctx_T *cctx); +char_u *compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx); +char_u *compile_redir(char_u *line, exarg_T *eap, cctx_T *cctx); +char_u *compile_cexpr(char_u *line, exarg_T *eap, cctx_T *cctx); +char_u *compile_return(char_u *arg, int check_return_type, int legacy, cctx_T *cctx); +int check_global_and_subst(char_u *cmd, char_u *arg); +/* vim: set ft=c : */ diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro index 5910c67d6c..33290d09e7 100644 --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -1,26 +1,30 @@ /* vim9compile.c */ +int lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx); +int arg_exists(char_u *name, size_t len, int *idxp, type_T **type, int *gen_load_outer, cctx_T *cctx); +int script_is_vim9(void); +int script_var_exists(char_u *name, size_t len, cctx_T *cctx); int check_defined(char_u *p, size_t len, cctx_T *cctx, int is_arg); -int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2); int need_type(type_T *actual, type_T *expected, int offset, int arg_idx, cctx_T *cctx, int silent, int actual_is_const); -int func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type); +lvar_T *reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type); int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); imported_T *find_imported_in_script(char_u *name, size_t len, int sid); +char_u *may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp); char_u *peek_next_line_from_context(cctx_T *cctx); char_u *next_line_from_context(cctx_T *cctx, int skip_comment); -char_u *to_name_end(char_u *arg, int use_namespace); -char_u *to_name_const_end(char_u *arg); -int get_lambda_tv_and_compile(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); -exprtype_T get_compare_type(char_u *p, int *len, int *type_is); -void error_white_both(char_u *op, int len); +int may_get_next_line(char_u *whitep, char_u **arg, cctx_T *cctx); +int may_get_next_line_error(char_u *whitep, char_u **arg, cctx_T *cctx); void fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx); +int func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type); int assignment_len(char_u *p, int *heredoc); void vim9_declare_error(char_u *name); -int check_vim9_unlet(char_u *name); -int check_global_and_subst(char_u *cmd, char_u *arg); +int get_var_dest(char_u *name, assign_dest_T *dest, int cmdidx, int *option_scope, int *vimvaridx, type_T **type, cctx_T *cctx); +int compile_lhs(char_u *var_start, lhs_T *lhs, int cmdidx, int heredoc, int oplen, cctx_T *cctx); +int compile_assign_lhs(char_u *var_start, lhs_T *lhs, int cmdidx, int is_decl, int heredoc, int oplen, cctx_T *cctx); +int compile_load_lhs_with_index(lhs_T *lhs, char_u *var_start, cctx_T *cctx); +int compile_assign_unlet(char_u *var_start, lhs_T *lhs, int is_assign, type_T *rhs_type, cctx_T *cctx); int compile_def_function(ufunc_T *ufunc, int check_return_type, compiletype_T compile_type, cctx_T *outer_cctx); void set_function_type(ufunc_T *ufunc); -void delete_instr(isn_T *isn); void unlink_def_function(ufunc_T *ufunc); void link_def_function(ufunc_T *ufunc); void free_def_functions(void); diff --git a/src/proto/vim9expr.pro b/src/proto/vim9expr.pro new file mode 100644 index 0000000000..aa8450902d --- /dev/null +++ b/src/proto/vim9expr.pro @@ -0,0 +1,17 @@ +/* vim9expr.c */ +int generate_ppconst(cctx_T *cctx, ppconst_T *ppconst); +void clear_ppconst(ppconst_T *ppconst); +int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx); +int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end, int error); +int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error); +char_u *to_name_end(char_u *arg, int use_namespace); +char_u *to_name_const_end(char_u *arg); +int get_lambda_tv_and_compile(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); +exprtype_T get_compare_type(char_u *p, int *len, int *type_is); +void skip_expr_cctx(char_u **arg, cctx_T *cctx); +int bool_on_stack(cctx_T *cctx); +void error_white_both(char_u *op, int len); +int compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst); +int compile_expr0_ext(char_u **arg, cctx_T *cctx, int *is_const); +int compile_expr0(char_u **arg, cctx_T *cctx); +/* vim: set ft=c : */ diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro new file mode 100644 index 0000000000..4df3af0018 --- /dev/null +++ b/src/proto/vim9instr.pro @@ -0,0 +1,72 @@ +/* vim9instr.c */ +isn_T *generate_instr(cctx_T *cctx, isntype_T isn_type); +isn_T *generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop); +isn_T *generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type); +isn_T *generate_instr_debug(cctx_T *cctx); +int may_generate_2STRING(int offset, int tolerant, cctx_T *cctx); +int generate_add_instr(cctx_T *cctx, vartype_T vartype, type_T *type1, type_T *type2, exprtype_T expr_type); +vartype_T operator_type(type_T *type1, type_T *type2); +int generate_two_op(cctx_T *cctx, char_u *op); +int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2); +int generate_COMPARE(cctx_T *cctx, exprtype_T exprtype, int ic); +int generate_2BOOL(cctx_T *cctx, int invert, int offset); +int generate_COND2BOOL(cctx_T *cctx); +int generate_TYPECHECK(cctx_T *cctx, type_T *expected, int offset, int argidx); +int generate_SETTYPE(cctx_T *cctx, type_T *expected); +int generate_tv_PUSH(cctx_T *cctx, typval_T *tv); +int generate_PUSHNR(cctx_T *cctx, varnumber_T number); +int generate_PUSHBOOL(cctx_T *cctx, varnumber_T number); +int generate_PUSHSPEC(cctx_T *cctx, varnumber_T number); +int generate_PUSHF(cctx_T *cctx, float_T fnumber); +int generate_PUSHS(cctx_T *cctx, char_u **str); +int generate_PUSHCHANNEL(cctx_T *cctx, channel_T *channel); +int generate_PUSHJOB(cctx_T *cctx, job_T *job); +int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob); +int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type); +int generate_GETITEM(cctx_T *cctx, int index, int with_op); +int generate_SLICE(cctx_T *cctx, int count); +int generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK); +int generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name); +int generate_STOREOUTER(cctx_T *cctx, int idx, int level); +int generate_STORENR(cctx_T *cctx, int idx, varnumber_T value); +int generate_STOREOPT(cctx_T *cctx, isntype_T isn_type, char_u *name, int opt_flags); +int generate_LOAD(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name, type_T *type); +int generate_LOADOUTER(cctx_T *cctx, int idx, int nesting, type_T *type); +int generate_LOADV(cctx_T *cctx, char_u *name, int error); +int generate_UNLET(cctx_T *cctx, isntype_T isn_type, char_u *name, int forceit); +int generate_LOCKCONST(cctx_T *cctx); +int generate_OLDSCRIPT(cctx_T *cctx, isntype_T isn_type, char_u *name, int sid, type_T *type); +int generate_VIM9SCRIPT(cctx_T *cctx, isntype_T isn_type, int sid, int idx, type_T *type); +int generate_NEWLIST(cctx_T *cctx, int count); +int generate_NEWDICT(cctx_T *cctx, int count); +int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc); +int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name); +int generate_DEF(cctx_T *cctx, char_u *name, size_t len); +int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where); +int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off); +int generate_FOR(cctx_T *cctx, int loop_idx); +int generate_TRYCONT(cctx_T *cctx, int levels, int where); +int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call); +int generate_LISTAPPEND(cctx_T *cctx); +int generate_BLOBAPPEND(cctx_T *cctx); +int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount); +int generate_UCALL(cctx_T *cctx, char_u *name, int argcount); +int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top); +int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len); +int generate_ECHO(cctx_T *cctx, int with_white, int count); +int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count); +int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum); +int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line); +int generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str); +int generate_LEGACY_EVAL(cctx_T *cctx, char_u *line); +int generate_EXECCONCAT(cctx_T *cctx, int count); +int generate_RANGE(cctx_T *cctx, char_u *range); +int generate_UNPACK(cctx_T *cctx, int var_count, int semicolon); +int generate_cmdmods(cctx_T *cctx, cmdmod_T *cmod); +int generate_undo_cmdmods(cctx_T *cctx); +int generate_store_var(cctx_T *cctx, assign_dest_T dest, int opt_flags, int vimvaridx, int scriptvar_idx, int scriptvar_sid, type_T *type, char_u *name); +int generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count); +void may_generate_prof_end(cctx_T *cctx, int prof_lnum); +void delete_instr(isn_T *isn); +void clear_instr_ga(garray_T *gap); +/* vim: set ft=c : */ diff --git a/src/version.c b/src/version.c index 38c9e0098d..9c24b95f40 100644 --- a/src/version.c +++ b/src/version.c @@ -749,6 +749,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 3860, /**/ 3859, /**/ diff --git a/src/vim9.h b/src/vim9.h index 99b952fe48..8a243ba7e9 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -11,6 +11,10 @@ * vim9.h: types and globals used for Vim9 script. */ +#ifdef VMS +# include +#endif + typedef enum { ISN_EXEC, // execute Ex command line isn_arg.string ISN_EXECCONCAT, // execute Ex command from isn_arg.number items on stack @@ -502,14 +506,7 @@ struct dfunc_S { #define STACK_FRAME_SIZE 6 -#ifdef DEFINE_VIM9_GLOBALS -// Functions defined with :def are stored in this growarray. -// They are never removed, so that they can be found by index. -// Deleted functions have the df_deleted flag set. -garray_T def_functions = {0, 0, sizeof(dfunc_T), 50, NULL}; -#else extern garray_T def_functions; -#endif // Used for "lnum" when a range is to be taken from the stack. #define LNUM_VARIABLE_RANGE -999 @@ -531,3 +528,200 @@ extern garray_T def_functions; ? (dfunc)->df_instr_debug \ : (dfunc)->df_instr) #endif + +// Structure passed between the compile_expr* functions to keep track of +// constants that have been parsed but for which no code was produced yet. If +// possible expressions on these constants are applied at compile time. If +// that is not possible, the code to push the constants needs to be generated +// before other instructions. +// Using 50 should be more than enough of 5 levels of (). +#define PPSIZE 50 +typedef struct { + typval_T pp_tv[PPSIZE]; // stack of ppconst constants + int pp_used; // active entries in pp_tv[] + int pp_is_const; // all generated code was constants, used for a + // list or dict with constant members +} ppconst_T; + +// values for ctx_skip +typedef enum { + SKIP_NOT, // condition is a constant, produce code + SKIP_YES, // condition is a constant, do NOT produce code + SKIP_UNKNOWN // condition is not a constant, produce code +} skip_T; + +/* + * Chain of jump instructions where the end label needs to be set. + */ +typedef struct endlabel_S endlabel_T; +struct endlabel_S { + endlabel_T *el_next; // chain end_label locations + int el_end_label; // instruction idx where to set end +}; + +/* + * info specific for the scope of :if / elseif / else + */ +typedef struct { + int is_seen_else; + int is_seen_skip_not; // a block was unconditionally executed + int is_had_return; // every block ends in :return + int is_if_label; // instruction idx at IF or ELSEIF + endlabel_T *is_end_label; // instructions to set end label +} ifscope_T; + +/* + * info specific for the scope of :while + */ +typedef struct { + int ws_top_label; // instruction idx at WHILE + endlabel_T *ws_end_label; // instructions to set end +} whilescope_T; + +/* + * info specific for the scope of :for + */ +typedef struct { + int fs_top_label; // instruction idx at FOR + endlabel_T *fs_end_label; // break instructions +} forscope_T; + +/* + * info specific for the scope of :try + */ +typedef struct { + int ts_try_label; // instruction idx at TRY + endlabel_T *ts_end_label; // jump to :finally or :endtry + int ts_catch_label; // instruction idx of last CATCH + int ts_caught_all; // "catch" without argument encountered +} tryscope_T; + +typedef enum { + NO_SCOPE, + IF_SCOPE, + WHILE_SCOPE, + FOR_SCOPE, + TRY_SCOPE, + BLOCK_SCOPE +} scopetype_T; + +/* + * Info for one scope, pointed to by "ctx_scope". + */ +typedef struct scope_S scope_T; +struct scope_S { + scope_T *se_outer; // scope containing this one + scopetype_T se_type; + int se_local_count; // ctx_locals.ga_len before scope + skip_T se_skip_save; // ctx_skip before the block + union { + ifscope_T se_if; + whilescope_T se_while; + forscope_T se_for; + tryscope_T se_try; + } se_u; +}; + +/* + * Entry for "ctx_locals". Used for arguments and local variables. + */ +typedef struct { + char_u *lv_name; + type_T *lv_type; + int lv_idx; // index of the variable on the stack + int lv_from_outer; // nesting level, using ctx_outer scope + int lv_const; // when TRUE cannot be assigned to + int lv_arg; // when TRUE this is an argument +} lvar_T; + +// Destination for an assignment or ":unlet" with an index. +typedef enum { + dest_local, + dest_option, + dest_func_option, + dest_env, + dest_global, + dest_buffer, + dest_window, + dest_tab, + dest_vimvar, + dest_script, + dest_reg, + dest_expr, +} assign_dest_T; + +// Used by compile_lhs() to store information about the LHS of an assignment +// and one argument of ":unlet" with an index. +typedef struct { + assign_dest_T lhs_dest; // type of destination + + char_u *lhs_name; // allocated name excluding the last + // "[expr]" or ".name". + size_t lhs_varlen; // length of the variable without + // "[expr]" or ".name" + char_u *lhs_whole; // allocated name including the last + // "[expr]" or ".name" for :redir + size_t lhs_varlen_total; // length of the variable including + // any "[expr]" or ".name" + char_u *lhs_dest_end; // end of the destination, including + // "[expr]" or ".name". + char_u *lhs_end; // end including any type + + int lhs_has_index; // has "[expr]" or ".name" + + int lhs_new_local; // create new local variable + int lhs_opt_flags; // for when destination is an option + int lhs_vimvaridx; // for when destination is a v:var + + lvar_T lhs_local_lvar; // used for existing local destination + lvar_T lhs_arg_lvar; // used for argument destination + lvar_T *lhs_lvar; // points to destination lvar + int lhs_scriptvar_sid; + int lhs_scriptvar_idx; + + int lhs_has_type; // type was specified + type_T *lhs_type; + type_T *lhs_member_type; + + int lhs_append; // used by ISN_REDIREND +} lhs_T; + +/* + * Context for compiling lines of Vim script. + * Stores info about the local variables and condition stack. + */ +struct cctx_S { + ufunc_T *ctx_ufunc; // current function + int ctx_lnum; // line number in current function + char_u *ctx_line_start; // start of current line or NULL + garray_T ctx_instr; // generated instructions + + int ctx_prev_lnum; // line number below previous command, for + // debugging + + compiletype_T ctx_compile_type; + + garray_T ctx_locals; // currently visible local variables + + int ctx_has_closure; // set to one if a closures was created in + // the function + + garray_T ctx_imports; // imported items + + skip_T ctx_skip; + scope_T *ctx_scope; // current scope, NULL at toplevel + int ctx_had_return; // last seen statement was "return" + + cctx_T *ctx_outer; // outer scope for lambda or nested + // function + int ctx_outer_used; // var in ctx_outer was used + + garray_T ctx_type_stack; // type of each item on the stack + garray_T *ctx_type_list; // list of pointers to allocated types + + int ctx_has_cmdmod; // ISN_CMDMOD was generated + + lhs_T ctx_redir_lhs; // LHS for ":redir => var", valid when + // lhs_name is not NULL +}; + diff --git a/src/vim9cmds.c b/src/vim9cmds.c new file mode 100644 index 0000000000..fe9ead2750 --- /dev/null +++ b/src/vim9cmds.c @@ -0,0 +1,2274 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * vim9cmds.c: Dealing with commands of a compiled function + */ + +#define USING_FLOAT_STUFF +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +// When not generating protos this is included in proto.h +#ifdef PROTO +# include "vim9.h" +#endif + +/* + * Get the index of the current instruction. + * This compensates for a preceding ISN_CMDMOD and ISN_PROF_START. + */ + static int +current_instr_idx(cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + int idx = instr->ga_len; + + while (idx > 0) + { + if (cctx->ctx_has_cmdmod && ((isn_T *)instr->ga_data)[idx - 1] + .isn_type == ISN_CMDMOD) + { + --idx; + continue; + } +#ifdef FEAT_PROFILE + if (((isn_T *)instr->ga_data)[idx - 1].isn_type == ISN_PROF_START) + { + --idx; + continue; + } +#endif + if (((isn_T *)instr->ga_data)[idx - 1].isn_type == ISN_DEBUG) + { + --idx; + continue; + } + break; + } + return idx; +} +/* + * Remove local variables above "new_top". + */ + static void +unwind_locals(cctx_T *cctx, int new_top) +{ + if (cctx->ctx_locals.ga_len > new_top) + { + int idx; + lvar_T *lvar; + + for (idx = new_top; idx < cctx->ctx_locals.ga_len; ++idx) + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + vim_free(lvar->lv_name); + } + } + cctx->ctx_locals.ga_len = new_top; +} + +/* + * Free all local variables. + */ + void +free_locals(cctx_T *cctx) +{ + unwind_locals(cctx, 0); + ga_clear(&cctx->ctx_locals); +} + + +/* + * Check if "name" can be "unlet". + */ + int +check_vim9_unlet(char_u *name) +{ + if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL) + { + // "unlet s:var" is allowed in legacy script. + if (*name == 's' && !script_is_vim9()) + return OK; + semsg(_(e_cannot_unlet_str), name); + return FAIL; + } + return OK; +} + +/* + * Callback passed to ex_unletlock(). + */ + static int +compile_unlet( + lval_T *lvp, + char_u *name_end, + exarg_T *eap, + int deep UNUSED, + void *coookie) +{ + cctx_T *cctx = coookie; + char_u *p = lvp->ll_name; + int cc = *name_end; + int ret = OK; + + if (cctx->ctx_skip == SKIP_YES) + return OK; + + *name_end = NUL; + if (*p == '$') + { + // :unlet $ENV_VAR + ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit); + } + else if (vim_strchr(p, '.') != NULL || vim_strchr(p, '[') != NULL) + { + lhs_T lhs; + + // This is similar to assigning: lookup the list/dict, compile the + // idx/key. Then instead of storing the value unlet the item. + // unlet {list}[idx] + // unlet {dict}[key] dict.key + // + // Figure out the LHS type and other properties. + // + ret = compile_lhs(p, &lhs, CMD_unlet, FALSE, 0, cctx); + + // : unlet an indexed item + if (!lhs.lhs_has_index) + { + iemsg("called compile_lhs() without an index"); + ret = FAIL; + } + else + { + // Use the info in "lhs" to unlet the item at the index in the + // list or dict. + ret = compile_assign_unlet(p, &lhs, FALSE, &t_void, cctx); + } + + vim_free(lhs.lhs_name); + } + else if (check_vim9_unlet(p) == FAIL) + { + ret = FAIL; + } + else + { + // Normal name. Only supports g:, w:, t: and b: namespaces. + ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit); + } + + *name_end = cc; + return ret; +} + +/* + * Callback passed to ex_unletlock(). + */ + static int +compile_lock_unlock( + lval_T *lvp, + char_u *name_end, + exarg_T *eap, + int deep UNUSED, + void *coookie) +{ + cctx_T *cctx = coookie; + int cc = *name_end; + char_u *p = lvp->ll_name; + int ret = OK; + size_t len; + char_u *buf; + isntype_T isn = ISN_EXEC; + + if (cctx->ctx_skip == SKIP_YES) + return OK; + + // Cannot use :lockvar and :unlockvar on local variables. + if (p[1] != ':') + { + char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START); + + if (lookup_local(p, end - p, NULL, cctx) == OK) + { + char_u *s = p; + + if (*end != '.' && *end != '[') + { + emsg(_(e_cannot_lock_unlock_local_variable)); + return FAIL; + } + + // For "d.member" put the local variable on the stack, it will be + // passed to ex_lockvar() indirectly. + if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL) + return FAIL; + isn = ISN_LOCKUNLOCK; + } + } + + // Checking is done at runtime. + *name_end = NUL; + len = name_end - p + 20; + buf = alloc(len); + if (buf == NULL) + ret = FAIL; + else + { + vim_snprintf((char *)buf, len, "%s %s", + eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar", + p); + ret = generate_EXEC_copy(cctx, isn, buf); + + vim_free(buf); + *name_end = cc; + } + return ret; +} + +/* + * compile "unlet var", "lock var" and "unlock var" + * "arg" points to "var". + */ + char_u * +compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx) +{ + ex_unletlock(eap, arg, 0, GLV_NO_AUTOLOAD | GLV_COMPILING, + eap->cmdidx == CMD_unlet ? compile_unlet : compile_lock_unlock, + cctx); + return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd; +} + +/* + * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". + */ + static int +compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); + + if (endlabel == NULL) + return FAIL; + endlabel->el_next = *el; + *el = endlabel; + endlabel->el_end_label = instr->ga_len; + + generate_JUMP(cctx, when, 0); + return OK; +} + + static void +compile_fill_jump_to_end(endlabel_T **el, int jump_where, cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + + while (*el != NULL) + { + endlabel_T *cur = (*el); + isn_T *isn; + + isn = ((isn_T *)instr->ga_data) + cur->el_end_label; + isn->isn_arg.jump.jump_where = jump_where; + *el = cur->el_next; + vim_free(cur); + } +} + + static void +compile_free_jump_to_end(endlabel_T **el) +{ + while (*el != NULL) + { + endlabel_T *cur = (*el); + + *el = cur->el_next; + vim_free(cur); + } +} + +/* + * Create a new scope and set up the generic items. + */ + static scope_T * +new_scope(cctx_T *cctx, scopetype_T type) +{ + scope_T *scope = ALLOC_CLEAR_ONE(scope_T); + + if (scope == NULL) + return NULL; + scope->se_outer = cctx->ctx_scope; + cctx->ctx_scope = scope; + scope->se_type = type; + scope->se_local_count = cctx->ctx_locals.ga_len; + return scope; +} + +/* + * Free the current scope and go back to the outer scope. + */ + void +drop_scope(cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + + if (scope == NULL) + { + iemsg("calling drop_scope() without a scope"); + return; + } + cctx->ctx_scope = scope->se_outer; + switch (scope->se_type) + { + case IF_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_if.is_end_label); break; + case FOR_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_for.fs_end_label); break; + case WHILE_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_while.ws_end_label); break; + case TRY_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_try.ts_end_label); break; + case NO_SCOPE: + case BLOCK_SCOPE: + break; + } + vim_free(scope); +} + + static int +misplaced_cmdmod(cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + + if (cctx->ctx_has_cmdmod + && ((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type + == ISN_CMDMOD) + { + emsg(_(e_misplaced_command_modifier)); + return TRUE; + } + return FALSE; +} + +/* + * compile "if expr" + * + * "if expr" Produces instructions: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE end + * ... body ... + * end: + * + * "if expr | else" Produces instructions: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE else + * ... body ... + * JUMP_ALWAYS end + * else: + * ... body ... + * end: + * + * "if expr1 | elseif expr2 | else" Produces instructions: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE elseif + * ... body ... + * JUMP_ALWAYS end + * elseif: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE else + * ... body ... + * JUMP_ALWAYS end + * else: + * ... body ... + * end: + */ + char_u * +compile_if(char_u *arg, cctx_T *cctx) +{ + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + int instr_count = instr->ga_len; + scope_T *scope; + skip_T skip_save = cctx->ctx_skip; + ppconst_T ppconst; + + CLEAR_FIELD(ppconst); + if (compile_expr1(&p, cctx, &ppconst) == FAIL) + { + clear_ppconst(&ppconst); + return NULL; + } + if (!ends_excmd2(arg, skipwhite(p))) + { + semsg(_(e_trailing_arg), p); + return NULL; + } + if (cctx->ctx_skip == SKIP_YES) + clear_ppconst(&ppconst); + else if (instr->ga_len == instr_count && ppconst.pp_used == 1) + { + int error = FALSE; + int v; + + // The expression results in a constant. + v = tv_get_bool_chk(&ppconst.pp_tv[0], &error); + clear_ppconst(&ppconst); + if (error) + return NULL; + cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES; + } + else + { + // Not a constant, generate instructions for the expression. + cctx->ctx_skip = SKIP_UNKNOWN; + if (generate_ppconst(cctx, &ppconst) == FAIL) + return NULL; + if (bool_on_stack(cctx) == FAIL) + return NULL; + } + + // CMDMOD_REV must come before the jump + generate_undo_cmdmods(cctx); + + scope = new_scope(cctx, IF_SCOPE); + if (scope == NULL) + return NULL; + scope->se_skip_save = skip_save; + // "is_had_return" will be reset if any block does not end in :return + scope->se_u.se_if.is_had_return = TRUE; + + if (cctx->ctx_skip == SKIP_UNKNOWN) + { + // "where" is set when ":elseif", "else" or ":endif" is found + scope->se_u.se_if.is_if_label = instr->ga_len; + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + } + else + scope->se_u.se_if.is_if_label = -1; + +#ifdef FEAT_PROFILE + if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES + && skip_save != SKIP_YES) + { + // generated a profile start, need to generate a profile end, since it + // won't be done after returning + cctx->ctx_skip = SKIP_NOT; + generate_instr(cctx, ISN_PROF_END); + cctx->ctx_skip = SKIP_YES; + } +#endif + + return p; +} + + char_u * +compile_elseif(char_u *arg, cctx_T *cctx) +{ + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + int instr_count; + isn_T *isn; + scope_T *scope = cctx->ctx_scope; + ppconst_T ppconst; + skip_T save_skip = cctx->ctx_skip; + + if (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_elseif_without_if)); + return NULL; + } + unwind_locals(cctx, scope->se_local_count); + if (!cctx->ctx_had_return) + scope->se_u.se_if.is_had_return = FALSE; + + if (cctx->ctx_skip == SKIP_NOT) + { + // previous block was executed, this one and following will not + cctx->ctx_skip = SKIP_YES; + scope->se_u.se_if.is_seen_skip_not = TRUE; + } + if (scope->se_u.se_if.is_seen_skip_not) + { + // A previous block was executed, skip over expression and bail out. + // Do not count the "elseif" for profiling and cmdmod + instr->ga_len = current_instr_idx(cctx); + + skip_expr_cctx(&p, cctx); + return p; + } + + if (cctx->ctx_skip == SKIP_UNKNOWN) + { + int moved_cmdmod = FALSE; + int saved_debug = FALSE; + isn_T debug_isn; + + // Move any CMDMOD instruction to after the jump + if (((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type == ISN_CMDMOD) + { + if (GA_GROW_FAILS(instr, 1)) + return NULL; + ((isn_T *)instr->ga_data)[instr->ga_len] = + ((isn_T *)instr->ga_data)[instr->ga_len - 1]; + --instr->ga_len; + moved_cmdmod = TRUE; + } + + // Remove the already generated ISN_DEBUG, it is written below the + // ISN_FOR instruction. + if (cctx->ctx_compile_type == CT_DEBUG && instr->ga_len > 0 + && ((isn_T *)instr->ga_data)[instr->ga_len - 1] + .isn_type == ISN_DEBUG) + { + --instr->ga_len; + debug_isn = ((isn_T *)instr->ga_data)[instr->ga_len]; + saved_debug = TRUE; + } + + if (compile_jump_to_end(&scope->se_u.se_if.is_end_label, + JUMP_ALWAYS, cctx) == FAIL) + return NULL; + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + + if (moved_cmdmod) + ++instr->ga_len; + + if (saved_debug) + { + // move the debug instruction here + if (GA_GROW_FAILS(instr, 1)) + return NULL; + ((isn_T *)instr->ga_data)[instr->ga_len] = debug_isn; + ++instr->ga_len; + } + } + + // compile "expr"; if we know it evaluates to FALSE skip the block + CLEAR_FIELD(ppconst); + if (cctx->ctx_skip == SKIP_YES) + { + cctx->ctx_skip = SKIP_UNKNOWN; +#ifdef FEAT_PROFILE + if (cctx->ctx_compile_type == CT_PROFILE) + // the previous block was skipped, need to profile this line + generate_instr(cctx, ISN_PROF_START); +#endif + if (cctx->ctx_compile_type == CT_DEBUG) + // the previous block was skipped, may want to debug this line + generate_instr_debug(cctx); + } + + instr_count = instr->ga_len; + if (compile_expr1(&p, cctx, &ppconst) == FAIL) + { + clear_ppconst(&ppconst); + return NULL; + } + cctx->ctx_skip = save_skip; + if (!ends_excmd2(arg, skipwhite(p))) + { + clear_ppconst(&ppconst); + semsg(_(e_trailing_arg), p); + return NULL; + } + if (scope->se_skip_save == SKIP_YES) + clear_ppconst(&ppconst); + else if (instr->ga_len == instr_count && ppconst.pp_used == 1) + { + int error = FALSE; + int v; + + // The expression result is a constant. + v = tv_get_bool_chk(&ppconst.pp_tv[0], &error); + if (error) + { + clear_ppconst(&ppconst); + return NULL; + } + cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES; + clear_ppconst(&ppconst); + scope->se_u.se_if.is_if_label = -1; + } + else + { + // Not a constant, generate instructions for the expression. + cctx->ctx_skip = SKIP_UNKNOWN; + if (generate_ppconst(cctx, &ppconst) == FAIL) + return NULL; + if (bool_on_stack(cctx) == FAIL) + return NULL; + + // CMDMOD_REV must come before the jump + generate_undo_cmdmods(cctx); + + // "where" is set when ":elseif", "else" or ":endif" is found + scope->se_u.se_if.is_if_label = instr->ga_len; + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + } + + return p; +} + + char_u * +compile_else(char_u *arg, cctx_T *cctx) +{ + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + scope_T *scope = cctx->ctx_scope; + + if (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_else_without_if)); + return NULL; + } + unwind_locals(cctx, scope->se_local_count); + if (!cctx->ctx_had_return) + scope->se_u.se_if.is_had_return = FALSE; + scope->se_u.se_if.is_seen_else = TRUE; + +#ifdef FEAT_PROFILE + if (cctx->ctx_compile_type == CT_PROFILE) + { + if (cctx->ctx_skip == SKIP_NOT + && ((isn_T *)instr->ga_data)[instr->ga_len - 1] + .isn_type == ISN_PROF_START) + // the previous block was executed, do not count "else" for + // profiling + --instr->ga_len; + if (cctx->ctx_skip == SKIP_YES && !scope->se_u.se_if.is_seen_skip_not) + { + // the previous block was not executed, this one will, do count the + // "else" for profiling + cctx->ctx_skip = SKIP_NOT; + generate_instr(cctx, ISN_PROF_END); + generate_instr(cctx, ISN_PROF_START); + cctx->ctx_skip = SKIP_YES; + } + } +#endif + + if (!scope->se_u.se_if.is_seen_skip_not && scope->se_skip_save != SKIP_YES) + { + // jump from previous block to the end, unless the else block is empty + if (cctx->ctx_skip == SKIP_UNKNOWN) + { + if (!cctx->ctx_had_return + && compile_jump_to_end(&scope->se_u.se_if.is_end_label, + JUMP_ALWAYS, cctx) == FAIL) + return NULL; + } + + if (cctx->ctx_skip == SKIP_UNKNOWN) + { + if (scope->se_u.se_if.is_if_label >= 0) + { + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + scope->se_u.se_if.is_if_label = -1; + } + } + + if (cctx->ctx_skip != SKIP_UNKNOWN) + cctx->ctx_skip = cctx->ctx_skip == SKIP_YES ? SKIP_NOT : SKIP_YES; + } + + return p; +} + + char_u * +compile_endif(char_u *arg, cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + ifscope_T *ifscope; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + + if (misplaced_cmdmod(cctx)) + return NULL; + + if (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_endif_without_if)); + return NULL; + } + ifscope = &scope->se_u.se_if; + unwind_locals(cctx, scope->se_local_count); + if (!cctx->ctx_had_return) + ifscope->is_had_return = FALSE; + + if (scope->se_u.se_if.is_if_label >= 0) + { + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + // Fill in the "end" label in jumps at the end of the blocks. + compile_fill_jump_to_end(&ifscope->is_end_label, instr->ga_len, cctx); + +#ifdef FEAT_PROFILE + // even when skipping we count the endif as executed, unless the block it's + // in is skipped + if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES + && scope->se_skip_save != SKIP_YES) + { + cctx->ctx_skip = SKIP_NOT; + generate_instr(cctx, ISN_PROF_START); + } +#endif + cctx->ctx_skip = scope->se_skip_save; + + // If all the blocks end in :return and there is an :else then the + // had_return flag is set. + cctx->ctx_had_return = ifscope->is_had_return && ifscope->is_seen_else; + + drop_scope(cctx); + return arg; +} + +/* + * Compile "for var in expr": + * + * Produces instructions: + * PUSHNR -1 + * STORE loop-idx Set index to -1 + * EVAL expr result of "expr" on top of stack + * top: FOR loop-idx, end Increment index, use list on bottom of stack + * - if beyond end, jump to "end" + * - otherwise get item from list and push it + * STORE var Store item in "var" + * ... body ... + * JUMP top Jump back to repeat + * end: DROP Drop the result of "expr" + * + * Compile "for [var1, var2] in expr" - as above, but instead of "STORE var": + * UNPACK 2 Split item in 2 + * STORE var1 Store item in "var1" + * STORE var2 Store item in "var2" + */ + char_u * +compile_for(char_u *arg_start, cctx_T *cctx) +{ + char_u *arg; + char_u *arg_end; + char_u *name = NULL; + char_u *p; + char_u *wp; + int var_count = 0; + int var_list = FALSE; + int semicolon = FALSE; + size_t varlen; + garray_T *stack = &cctx->ctx_type_stack; + garray_T *instr = &cctx->ctx_instr; + scope_T *scope; + lvar_T *loop_lvar; // loop iteration variable + lvar_T *var_lvar; // variable for "var" + type_T *vartype; + type_T *item_type = &t_any; + int idx; + int prev_lnum = cctx->ctx_prev_lnum; + + p = skip_var_list(arg_start, TRUE, &var_count, &semicolon, FALSE); + if (p == NULL) + return NULL; + if (var_count == 0) + var_count = 1; + else + var_list = TRUE; // can also be a list of one variable + + // consume "in" + wp = p; + if (may_get_next_line_error(wp, &p, cctx) == FAIL) + return NULL; + if (STRNCMP(p, "in", 2) != 0 || !IS_WHITE_OR_NUL(p[2])) + { + if (*p == ':' && wp != p) + semsg(_(e_no_white_space_allowed_before_colon_str), p); + else + emsg(_(e_missing_in)); + return NULL; + } + wp = p + 2; + if (may_get_next_line_error(wp, &p, cctx) == FAIL) + return NULL; + + // Remove the already generated ISN_DEBUG, it is written below the ISN_FOR + // instruction. + if (cctx->ctx_compile_type == CT_DEBUG && instr->ga_len > 0 + && ((isn_T *)instr->ga_data)[instr->ga_len - 1] + .isn_type == ISN_DEBUG) + { + --instr->ga_len; + prev_lnum = ((isn_T *)instr->ga_data)[instr->ga_len] + .isn_arg.debug.dbg_break_lnum; + } + + scope = new_scope(cctx, FOR_SCOPE); + if (scope == NULL) + return NULL; + + // Reserve a variable to store the loop iteration counter and initialize it + // to -1. + loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); + if (loop_lvar == NULL) + { + // out of memory + drop_scope(cctx); + return NULL; + } + generate_STORENR(cctx, loop_lvar->lv_idx, -1); + + // compile "expr", it remains on the stack until "endfor" + arg = p; + if (compile_expr0(&arg, cctx) == FAIL) + { + drop_scope(cctx); + return NULL; + } + arg_end = arg; + + if (cctx->ctx_skip != SKIP_YES) + { + // If we know the type of "var" and it is a not a supported type we can + // give an error now. + vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (vartype->tt_type != VAR_LIST && vartype->tt_type != VAR_STRING + && vartype->tt_type != VAR_BLOB && vartype->tt_type != VAR_ANY) + { + semsg(_(e_for_loop_on_str_not_supported), + vartype_name(vartype->tt_type)); + drop_scope(cctx); + return NULL; + } + + if (vartype->tt_type == VAR_STRING) + item_type = &t_string; + else if (vartype->tt_type == VAR_BLOB) + item_type = &t_number; + else if (vartype->tt_type == VAR_LIST + && vartype->tt_member->tt_type != VAR_ANY) + { + if (!var_list) + item_type = vartype->tt_member; + else if (vartype->tt_member->tt_type == VAR_LIST + && vartype->tt_member->tt_member->tt_type != VAR_ANY) + item_type = vartype->tt_member->tt_member; + } + + // CMDMOD_REV must come before the FOR instruction. + generate_undo_cmdmods(cctx); + + // "for_end" is set when ":endfor" is found + scope->se_u.se_for.fs_top_label = current_instr_idx(cctx); + + generate_FOR(cctx, loop_lvar->lv_idx); + + arg = arg_start; + if (var_list) + { + generate_UNPACK(cctx, var_count, semicolon); + arg = skipwhite(arg + 1); // skip white after '[' + + // the list item is replaced by a number of items + if (GA_GROW_FAILS(stack, var_count - 1)) + { + drop_scope(cctx); + return NULL; + } + --stack->ga_len; + for (idx = 0; idx < var_count; ++idx) + { + ((type_T **)stack->ga_data)[stack->ga_len] = + (semicolon && idx == 0) ? vartype : item_type; + ++stack->ga_len; + } + } + + for (idx = 0; idx < var_count; ++idx) + { + assign_dest_T dest = dest_local; + int opt_flags = 0; + int vimvaridx = -1; + type_T *type = &t_any; + type_T *lhs_type = &t_any; + where_T where = WHERE_INIT; + + p = skip_var_one(arg, FALSE); + varlen = p - arg; + name = vim_strnsave(arg, varlen); + if (name == NULL) + goto failed; + if (*p == ':') + { + p = skipwhite(p + 1); + lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE); + } + + if (get_var_dest(name, &dest, CMD_for, &opt_flags, + &vimvaridx, &type, cctx) == FAIL) + goto failed; + if (dest != dest_local) + { + if (generate_store_var(cctx, dest, opt_flags, vimvaridx, + 0, 0, type, name) == FAIL) + goto failed; + } + else if (varlen == 1 && *arg == '_') + { + // Assigning to "_": drop the value. + if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL) + goto failed; + } + else + { + // Script var is not supported. + if (STRNCMP(name, "s:", 2) == 0) + { + emsg(_(e_cannot_use_script_variable_in_for_loop)); + goto failed; + } + + if (!valid_varname(arg, (int)varlen, FALSE)) + goto failed; + if (lookup_local(arg, varlen, NULL, cctx) == OK) + { + semsg(_(e_variable_already_declared), arg); + goto failed; + } + + // Reserve a variable to store "var". + where.wt_index = var_list ? idx + 1 : 0; + where.wt_variable = TRUE; + if (lhs_type == &t_any) + lhs_type = item_type; + else if (item_type != &t_unknown + && (item_type == &t_any + ? need_type(item_type, lhs_type, + -1, 0, cctx, FALSE, FALSE) + : check_type(lhs_type, item_type, TRUE, where)) + == FAIL) + goto failed; + var_lvar = reserve_local(cctx, arg, varlen, TRUE, lhs_type); + if (var_lvar == NULL) + // out of memory or used as an argument + goto failed; + + if (semicolon && idx == var_count - 1) + var_lvar->lv_type = vartype; + else + var_lvar->lv_type = item_type; + generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL); + } + + if (*p == ',' || *p == ';') + ++p; + arg = skipwhite(p); + vim_free(name); + } + + if (cctx->ctx_compile_type == CT_DEBUG) + { + int save_prev_lnum = cctx->ctx_prev_lnum; + + // Add ISN_DEBUG here, so that the loop variables can be inspected. + // Use the prev_lnum from the ISN_DEBUG instruction removed above. + cctx->ctx_prev_lnum = prev_lnum; + generate_instr_debug(cctx); + cctx->ctx_prev_lnum = save_prev_lnum; + } + } + + return arg_end; + +failed: + vim_free(name); + drop_scope(cctx); + return NULL; +} + +/* + * compile "endfor" + */ + char_u * +compile_endfor(char_u *arg, cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + scope_T *scope = cctx->ctx_scope; + forscope_T *forscope; + isn_T *isn; + + if (misplaced_cmdmod(cctx)) + return NULL; + + if (scope == NULL || scope->se_type != FOR_SCOPE) + { + emsg(_(e_for)); + return NULL; + } + forscope = &scope->se_u.se_for; + cctx->ctx_scope = scope->se_outer; + if (cctx->ctx_skip != SKIP_YES) + { + unwind_locals(cctx, scope->se_local_count); + + // At end of ":for" scope jump back to the FOR instruction. + generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label); + + // Fill in the "end" label in the FOR statement so it can jump here. + isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label; + isn->isn_arg.forloop.for_end = instr->ga_len; + + // Fill in the "end" label any BREAK statements + compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx); + + // Below the ":for" scope drop the "expr" list from the stack. + if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL) + return NULL; + } + + vim_free(scope); + + return arg; +} + +/* + * compile "while expr" + * + * Produces instructions: + * top: EVAL expr Push result of "expr" + * JUMP_IF_FALSE end jump if false + * ... body ... + * JUMP top Jump back to repeat + * end: + * + */ + char_u * +compile_while(char_u *arg, cctx_T *cctx) +{ + char_u *p = arg; + scope_T *scope; + + scope = new_scope(cctx, WHILE_SCOPE); + if (scope == NULL) + return NULL; + + // "endwhile" jumps back here, one before when profiling or using cmdmods + scope->se_u.se_while.ws_top_label = current_instr_idx(cctx); + + // compile "expr" + if (compile_expr0(&p, cctx) == FAIL) + return NULL; + + if (!ends_excmd2(arg, skipwhite(p))) + { + semsg(_(e_trailing_arg), p); + return NULL; + } + + if (cctx->ctx_skip != SKIP_YES) + { + if (bool_on_stack(cctx) == FAIL) + return FAIL; + + // CMDMOD_REV must come before the jump + generate_undo_cmdmods(cctx); + + // "while_end" is set when ":endwhile" is found + if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label, + JUMP_IF_FALSE, cctx) == FAIL) + return FAIL; + } + + return p; +} + +/* + * compile "endwhile" + */ + char_u * +compile_endwhile(char_u *arg, cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + garray_T *instr = &cctx->ctx_instr; + + if (misplaced_cmdmod(cctx)) + return NULL; + if (scope == NULL || scope->se_type != WHILE_SCOPE) + { + emsg(_(e_while)); + return NULL; + } + cctx->ctx_scope = scope->se_outer; + if (cctx->ctx_skip != SKIP_YES) + { + unwind_locals(cctx, scope->se_local_count); + +#ifdef FEAT_PROFILE + // count the endwhile before jumping + may_generate_prof_end(cctx, cctx->ctx_lnum); +#endif + + // At end of ":for" scope jump back to the FOR instruction. + generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label); + + // Fill in the "end" label in the WHILE statement so it can jump here. + // And in any jumps for ":break" + compile_fill_jump_to_end(&scope->se_u.se_while.ws_end_label, + instr->ga_len, cctx); + } + + vim_free(scope); + + return arg; +} + +/* + * compile "continue" + */ + char_u * +compile_continue(char_u *arg, cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + int try_scopes = 0; + int loop_label; + + for (;;) + { + if (scope == NULL) + { + emsg(_(e_continue)); + return NULL; + } + if (scope->se_type == FOR_SCOPE) + { + loop_label = scope->se_u.se_for.fs_top_label; + break; + } + if (scope->se_type == WHILE_SCOPE) + { + loop_label = scope->se_u.se_while.ws_top_label; + break; + } + if (scope->se_type == TRY_SCOPE) + ++try_scopes; + scope = scope->se_outer; + } + + if (try_scopes > 0) + // Inside one or more try/catch blocks we first need to jump to the + // "finally" or "endtry" to cleanup. + generate_TRYCONT(cctx, try_scopes, loop_label); + else + // Jump back to the FOR or WHILE instruction. + generate_JUMP(cctx, JUMP_ALWAYS, loop_label); + + return arg; +} + +/* + * compile "break" + */ + char_u * +compile_break(char_u *arg, cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + endlabel_T **el; + + for (;;) + { + if (scope == NULL) + { + emsg(_(e_break)); + return NULL; + } + if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE) + break; + scope = scope->se_outer; + } + + // Jump to the end of the FOR or WHILE loop. + if (scope->se_type == FOR_SCOPE) + el = &scope->se_u.se_for.fs_end_label; + else + el = &scope->se_u.se_while.ws_end_label; + if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL) + return FAIL; + + return arg; +} + +/* + * compile "{" start of block + */ + char_u * +compile_block(char_u *arg, cctx_T *cctx) +{ + if (new_scope(cctx, BLOCK_SCOPE) == NULL) + return NULL; + return skipwhite(arg + 1); +} + +/* + * compile end of block: drop one scope + */ + void +compile_endblock(cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + + cctx->ctx_scope = scope->se_outer; + unwind_locals(cctx, scope->se_local_count); + vim_free(scope); +} + +/* + * Compile "try". + * Creates a new scope for the try-endtry, pointing to the first catch and + * finally. + * Creates another scope for the "try" block itself. + * TRY instruction sets up exception handling at runtime. + * + * "try" + * TRY -> catch1, -> finally push trystack entry + * ... try block + * "throw {exception}" + * EVAL {exception} + * THROW create exception + * ... try block + * " catch {expr}" + * JUMP -> finally + * catch1: PUSH exception + * EVAL {expr} + * MATCH + * JUMP nomatch -> catch2 + * CATCH remove exception + * ... catch block + * " catch" + * JUMP -> finally + * catch2: CATCH remove exception + * ... catch block + * " finally" + * finally: + * ... finally block + * " endtry" + * ENDTRY pop trystack entry, may rethrow + */ + char_u * +compile_try(char_u *arg, cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + scope_T *try_scope; + scope_T *scope; + + if (misplaced_cmdmod(cctx)) + return NULL; + + // scope that holds the jumps that go to catch/finally/endtry + try_scope = new_scope(cctx, TRY_SCOPE); + if (try_scope == NULL) + return NULL; + + if (cctx->ctx_skip != SKIP_YES) + { + isn_T *isn; + + // "try_catch" is set when the first ":catch" is found or when no catch + // is found and ":finally" is found. + // "try_finally" is set wh