diff options
Diffstat (limited to 'generic/Lcompile.c')
-rw-r--r-- | generic/Lcompile.c | 8167 |
1 files changed, 8167 insertions, 0 deletions
diff --git a/generic/Lcompile.c b/generic/Lcompile.c new file mode 100644 index 0000000..c1e5973 --- /dev/null +++ b/generic/Lcompile.c @@ -0,0 +1,8167 @@ +/* + * Copyright (c) 2006-2009 BitMover, Inc. + */ +#include <stdio.h> +#include <stdarg.h> +#include <setjmp.h> +#include "tclInt.h" +#include "tclIO.h" +#include "tclCompile.h" +#include "tclRegexp.h" +#include "Lcompile.h" +#include "Lgrammar.h" + +/* Used by compile_spawn_system(). */ +enum { + SYSTEM_ARGV = 0x00000001, + SYSTEM_IN_STRING = 0x00000002, + SYSTEM_IN_ARRAY = 0x00000004, + SYSTEM_IN_FILENAME = 0x00000008, + SYSTEM_IN_HANDLE = 0x00000010, + SYSTEM_OUT_STRING = 0x00000020, + SYSTEM_OUT_ARRAY = 0x00000040, + SYSTEM_OUT_FILENAME = 0x00000080, + SYSTEM_OUT_HANDLE = 0x00000100, + SYSTEM_ERR_STRING = 0x00000200, + SYSTEM_ERR_ARRAY = 0x00000400, + SYSTEM_ERR_FILENAME = 0x00000800, + SYSTEM_ERR_HANDLE = 0x00001000, + SYSTEM_BACKGROUND = 0x00002000, +}; + +/* + * As of March 2009, we use a bit in the Tcl_Obj structure to + * represent when an object has the L undefined value. This avoids + * the problems we had when Tcl would shimmer undef away into another + * type, making it look defined. But we also need an undef object, as + * the value of array, hash, and struct members when they dynamically + * are brought into life. This is also the value of the "undef" + * pre-defined constant. We create one object of this type and dup it + * whenever undef is requested. + */ + +private void +undef_freeInternalRep(Tcl_Obj *o) +{ +} + +/* + * Return an error if someone tries to convert something to undef + * type. + */ +private int +undef_setFromAny(Tcl_Interp *interp, Tcl_Obj *o) +{ + Tcl_SetObjResult(interp, + Tcl_NewStringObj("cannot convert to undefined value", + -1)); + return (TCL_ERROR); +} + +/* + * Get a pointer to the "undefined" object pointer, allocating it the + * first time it is needed. Keep the refCount high because we want + * the one-and-only undef object to never be freed. + */ +Tcl_Obj ** +L_undefObjPtrPtr() +{ + static Tcl_Obj *undef_obj = NULL; + + unless (undef_obj) { + undef_obj = Tcl_NewObj(); + undef_obj->bytes = tclEmptyStringRep; + undef_obj->typePtr = &L_undefType; + undef_obj->undef = 1; + undef_obj->refCount = 1234; // arbitrary; to be recognizable + } + ASSERT(undef_obj->undef); + return (&undef_obj); +} + +int +L_isUndef(Tcl_Obj *o) +{ + return (o->undef); +} + +Tcl_ObjType L_undefType = { + "undef", + undef_freeInternalRep, + NULL, + NULL, + undef_setFromAny +}; + +/* Returned by re_kind. */ +typedef enum { + RE_NOT_AN_RE = 0x0001, + RE_CONST = 0x0002, + RE_GLOB = 0x0004, + RE_SIMPLE = 0x0008, + RE_COMPLEX = 0x0010, + RE_NEEDS_EVAL = 0x0020, +} ReKind; + +/* Used by tmp_* API. */ +typedef enum { + TMP_REUSE, + TMP_UNSET, +} TmpKind; + +/* + * Lists of allowable attributes in #pragma, _attribute, and cmd-line + * options. Each array must end with a NULL. + */ +char *L_attrs_attribute[] = { + "dis", + "fnhook", + "fntrace", + "trace_depth", + NULL +}; +char *L_attrs_cmdLine[] = { + "L", + "dis", + "fnhook", + "fntrace", + "line", + "lineadj", + "norun", + "nowarn", + "poly", + "trace_depth", + "trace_files", + "trace_funcs", + "trace_out", + "trace_script", + "warn_undefined_fns", + "version", + NULL +}; +char *L_attrs_pragma[] = { + "dis", + "fnhook", + "fntrace", + "line", + "lineadj", + "norun", + "nowarn", + "poly", + "trace_depth", + "warn_undefined_fns", + NULL +}; +char *L_attrs_Lhtml[] = { + "line", + "lineadj", +}; + +/* The next two functions are generated by flex. */ +extern void *L__scan_bytes (const char *bytes, int len); +extern void L__delete_buffer(void *buf); + +private int ast_compile(void *ast); +private void ast_free(Ast *ast_list); +private char *basenm(char *s); +private int compile_abs(Expr *expr); +private int compile_assert(Expr *expr); +private void compile_assign(Expr *expr); +private void compile_assignComposite(Expr *expr); +private void compile_assignFromStack(Expr *lhs, Type *rhs_type, Expr *expr, + int flags); +private int compile_binOp(Expr *expr, Expr_f flags); +private void compile_block(Block *block); +private void compile_break(Stmt *stmt); +private int compile_cast(Expr *expr, Expr_f flags); +private int compile_catch(Expr *expr); +private void compile_clsDecl(ClsDecl *class); +private int compile_clsDeref(Expr *expr, Expr_f flags); +private int compile_clsInstDeref(Expr *expr, Expr_f flags); +private void compile_condition(Expr *cond); +private void compile_continue(Stmt *stmt); +private void compile_defined(Expr *expr); +private int compile_die(Expr *expr); +private void compile_do(Loop *loop); +private void compile_eq_stack(Expr *expr, Type *type); +private void compile_for_while(Loop *loop); +private int compile_idxOp(Expr *expr, Expr_f flags); +private int compile_idxOp2(Expr *expr, Expr_f flags); +private int compile_expr(Expr *expr, Expr_f flags); +private int compile_exprs(Expr *expr, Expr_f flags); +private int compile_fnCall(Expr *expr); +private void compile_fnDecl(FnDecl *fun, Decl_f flags); +private void compile_fnDecls(FnDecl *fun, Decl_f flags); +private void compile_foreach(ForEach *loop); +private void compile_foreachAngle(ForEach *loop); +private void compile_foreachArray(ForEach *loop); +private void compile_foreachHash(ForEach *loop); +private void compile_foreachString(ForEach *loop); +private void compile_goto(Stmt *stmt); +private int compile_here(Expr *expr); +private void compile_ifUnless(Cond *cond); +private void compile_incdec(Expr *expr); +private int compile_insert_unshift(Expr *expr); +private int compile_join(Expr *expr); +private int compile_keys(Expr *expr); +private void compile_label(Stmt *stmt); +private int compile_length(Expr *expr); +private void compile_loop(Loop *loop); +private int compile_min_max(Expr *expr); +private int compile_fnParms(VarDecl *decl); +private int compile_popen(Expr *expr); +private int compile_pop_shift(Expr *expr); +private int compile_push(Expr *expr); +private void compile_reMatch(Expr *re); +private int compile_read(Expr *expr); +private int compile_rename(Expr *expr); +private void compile_return(Stmt *stmt); +private int compile_script(Tcl_Obj *scriptObj, Tcl_Obj *nameObj); +private void compile_shortCircuit(Expr *expr); +private int compile_sort(Expr *expr); +private int compile_spawn_system(Expr *expr); +private int compile_split(Expr *expr); +private void compile_stmt(Stmt *stmt); +private void compile_stmts(Stmt *stmt); +private void compile_switch(Switch *sw); +private void compile_switch_fast(Switch *sw); +private void compile_switch_slow(Switch *sw); +private int compile_trinOp(Expr *expr); +private int compile_trace_script(char *script); +private void compile_trycatch(Stmt *stmt); +private void compile_twiddle(Expr *expr); +private void compile_twiddleSubst(Expr *expr); +private int compile_typeof(Expr *expr); +private int compile_undef(Expr *expr); +private int compile_unOp(Expr *expr); +private int compile_var(Expr *expr, Expr_f flags); +private void compile_varDecl(VarDecl *decl); +private void compile_varDecls(VarDecl *decls); +private int compile_warn(Expr *expr); +private int compile_write(Expr *expr); +private void copyout_parms(Expr *actuals); +private Tcl_Obj *do_getline(Tcl_Interp *interp, Tcl_Channel chan); +private void emit_globalUpvar(Sym *sym); +private void emit_instrForLOp(Expr *expr, Type *type); +private void emit_jmp_back(TclJumpType jmp_type, int offset); +private Jmp *emit_jmp_fwd(int op, Jmp *next); +private void fixup_jmps(Jmp **jumps); +private int fnCallBegin(); +private void fnCallEnd(int lev); +private int fnInArgList(); +private Frame *frame_find(Frame_f flags); +private char *frame_name(void); +private void frame_pop(void); +private void frame_push(void *node, char *name, Frame_f flags); +private void frame_resumeBody(); +private void frame_resumePrologue(); +private char *get_text(Expr *expr); +private int has_END(Expr *expr); +private void init_predefined(); +private Type *iscallbyname(VarDecl *formal); +private int ispatternfn(char *name, Expr **foo, Expr **Foo_star, + Expr **opts, int *nopts); +private Label *label_lookup(Stmt *stmt, Label_f flags); +private Expr *mkId(char *name); +private int parse_options(int objc, Tcl_Obj **objv, char *allowed[]); +private int parse_script(char *str, Ast **L_ast, Tcl_Obj *nameObj); +private void proc_mkArg(Proc *proc, VarDecl *decl); +private int push_index(Expr *expr, int flags); +private int push_parms(Expr *actuals, VarDecl *formals); +private int push_regexpModifiers(Expr *regexp); +private ReKind re_kind(Expr *re, Tcl_DString *ds); +private int re_submatchCnt(Expr *re); +private VarDecl *struct_lookupMember(Type *t, Expr *idx, int *offset); +private Sym *sym_mk(char *name, Type *t, Decl_f flags); +private Sym *sym_lookup(Expr *id, Expr_f flags); +private Sym *sym_store(VarDecl *decl); +private Tmp *tmp_get(TmpKind kind); +private void tmp_free(Tmp *tmp); +private void tmp_freeAll(Tmp *tmp); +private void track_cmd(int codeOffset, void *node); +private void type_free(Type *type_list); +private int typeck_spawn(Expr *in, Expr *out, Expr *err); +private int typeck_system(Expr *in, Expr *out, Expr *err); + +Linterp *L; // per-interp L state +Type *L_int; // pre-defined types +Type *L_float; +Type *L_string; +Type *L_void; +Type *L_var; +Type *L_poly; +Type *L_widget; + +/* + * L built-in functions. + */ +static struct { + char *name; + int (*fn)(Expr *); +} builtins[] = { + { "abs", compile_abs }, + { "assert", compile_assert }, + { "catch", compile_catch }, + { "die", compile_die }, + { "here", compile_here }, + { "insert", compile_insert_unshift }, + { "join", compile_join }, + { "keys", compile_keys }, + { "length", compile_length }, + { "max", compile_min_max }, + { "min", compile_min_max }, + { "popen", compile_popen }, + { "pop", compile_pop_shift }, + { "push", compile_push }, + { "read", compile_read }, + { "rename", compile_rename }, + { "shift", compile_pop_shift }, + { "sort", compile_sort }, + { "split", compile_split }, + { "spawn", compile_spawn_system }, + { "system", compile_spawn_system }, + { "typeof", compile_typeof }, + { "undef", compile_undef }, + { "unshift", compile_insert_unshift }, + { "warn", compile_warn }, + { "write", compile_write }, +}; + +/* + * L compiler entry point. + */ +int +Tcl_LObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *CONST objv[]) +{ + char *s; + int argc, ret; + Tcl_Obj **argvList; + + /* Extract the L state from the interp. */ + L = Tcl_GetAssocData(interp, "L", NULL); + + /* + * Verify that lib L was loaded. L fails badly if lib L isn't + * there, and this catches cases where the user overrides the + * Tcl library path. + */ + unless (Tcl_GetVar(L->interp, "::L_libl_initted", 0)) { + Tcl_SetResult(L->interp, "fatal -- libl.tcl not found", 0); + return (TCL_ERROR); + } + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?options? l-program"); + return (TCL_ERROR); + } + + /* Parse options from both the Tcl L command and the tclsh cmd line. */ + L->errs = NULL; + L->options = Tcl_NewDictObj(); + ret = parse_options(objc-1, (Tcl_Obj **)(objv+1), L_attrs_cmdLine); + unless (ret == TCL_OK) { + Tcl_SetObjResult(interp, L->errs); + return (ret); + } + if (L->global->tclsh_argv && + Tcl_ListObjGetElements(L->interp, L->global->tclsh_argv, &argc, + &argvList) == TCL_OK) { + ret = parse_options(argc-1, argvList+1, L_attrs_cmdLine); + unless (ret == TCL_OK) { + Tcl_SetObjResult(interp, L->errs); + return (ret); + } + } + + /* L_synerr() longjmps back here on a parser syntax error. */ + if (setjmp(L->jmp)) { + Tcl_SetObjResult(interp, L->errs); + return (TCL_ERROR); + } + + /* + * If a function-tracing script was specified in + * --trace_script or L_TRACE_SCRIPT (takes precedence), + * compile that (once) but only after libL has been compiled. + */ + unless (hash_get(L->options, "trace_script_compiled")) { + if ((s = getenv("L_TRACE_SCRIPT"))) { + hash_put(L->options, "trace_script", s); + } + if ((s = hash_get(L->options, "trace_script")) && + Tcl_GetVar(L->interp, "::L_libl_done", 0)) { + hash_put(L->options, "trace_script_compiled", "yes"); + s = ckstrdup(s); + ret = compile_trace_script(s); + if (ret != TCL_OK) return (ret); + } + } + + /* + * Propagate some cmd-line options to env variables for lib L. + * Pre-existing env variables take precedence. + */ + if ((s = hash_get(L->options, "trace_funcs"))) { + unless (getenv("L_TRACE_FUNCS")) { + s = cksprintf("L_TRACE_FUNCS=%s", s); + putenv(s); + } + } + if ((s = hash_get(L->options, "trace_files"))) { + unless (getenv("L_TRACE_FILES")) { + s = cksprintf("L_TRACE_FILES=%s", s); + putenv(s); + } + } + if ((s = hash_get(L->options, "trace_out"))) { + unless (getenv("L_TRACE_OUT")) { + s = cksprintf("L_TRACE_OUT=%s", s); + putenv(s); + } + } + if ((s = hash_get(L->options, "fnhook"))) { + unless (getenv("L_TRACE_HOOK")) { + s = cksprintf("L_TRACE_HOOK=%s", s); + putenv(s); + } + } + if ((s = hash_get(L->options, "fntrace"))) { + unless (getenv("L_TRACE_ALL")) { + s = cksprintf("L_TRACE_ALL=%s", s); + putenv(s); + } + } + if ((s = hash_get(L->options, "dis"))) { + unless (getenv("L_DISASSEMBLE")) { + s = cksprintf("L_DISASSEMBLE=%s", s); + putenv(s); + } + } + + /* This allows the old comparison-op syntax (eq ne lt le gt ge). */ + if (getenv("_L_ALLOW_EQ_OPS")) { + hash_put(L->options, "allow_eq_ops", "yes"); + } + + return (compile_script(objv[objc-1], ((Interp *)L->interp)->scriptFile)); +} + +private int +compile_trace_script(char *script) +{ + int len, ret; + Tcl_Channel chan; + Tcl_Obj *nameObj, *scriptObj; + + len = strlen(script); + if ((len > 3) && (script[len-2] == '.') && (script[len-1] == 'l')) { + nameObj = Tcl_NewStringObj(script, -1); + Tcl_IncrRefCount(nameObj); + chan = Tcl_FSOpenFileChannel(L->interp, nameObj, "r", 0644); + unless (chan) return (TCL_ERROR); + scriptObj = Tcl_NewObj(); + Tcl_IncrRefCount(scriptObj); + ret = Tcl_ReadChars(chan, scriptObj, -1, 0); + Tcl_Close(L->interp, chan); + if (ret < 0) { + Tcl_DecrRefCount(scriptObj); + return (TCL_ERROR); + } + } else { + nameObj = Tcl_NewStringObj("L_TRACE_SCRIPT", -1); + scriptObj = Tcl_ObjPrintf( + "void L_fn_hook(_argused int pre, _argused poly av[], " + "_argused poly ret) { %s ;}", + script); + hash_put(L->options, "fnhook", "L_fn_hook"); + Tcl_IncrRefCount(nameObj); + } + ret = compile_script(scriptObj, nameObj); + Tcl_DecrRefCount(nameObj); + return (ret); +} + +private int +compile_script(Tcl_Obj *scriptObj, Tcl_Obj *nameObj) +{ + int ret; + Ast *ast; +#ifdef TCL_COMPILE_DEBUG + char *s; +#endif + + L->script = Tcl_NewObj(); + Tcl_IncrRefCount(L->script); + L->script_len = 0; + + ret = parse_script(TclGetString(scriptObj), &ast, nameObj); + + if ((ret == TCL_OK) && ast) { + ret = ast_compile(ast); + } + +#ifdef TCL_COMPILE_DEBUG + if ((s = getenv("L_TRACE_BYTECODES"))) { + extern int tclTraceExec; + tclTraceExec = atoi(s); + } +#endif + return (ret); +} + +/* + * Parse key=val (where =val is optional and is replaced by "yes" if + * omitted) and add to the L->options hash. Strip any leading -'s from + * key so that -key and --key both work. Replace all other -'s with _'s + * so that --trace-files becomes trace_files. + */ +private int +parse_options(int objc, Tcl_Obj **objv, char *allowed[]) +{ + int i, ret = TCL_OK; + char *key, *newkey, *p, *val; + char **q; + + for (i = 0; i < objc; ++i) { + key = Tcl_GetString(objv[i]); + unless (key[0] == '-') break; + /* Look for key=val */ + val = strchr(key, '='); + if (val) { + *val = 0; + } + newkey = ckalloc(strlen(key)+1); + /* Skip past all leading -'s in the key */ + while (*key == '-') ++key; + /* Now copy except replace all other -'s with _ */ + for (p = newkey; *key; ++key, ++p) { + *p = *key; + if (*p == '-') *p = '_'; + } + *p = 0; + key = newkey; + for (q = allowed; *q; ++q) { + if (!strcmp(key, *q)) break; + } + unless (*q) { + L_errf(NULL, "illegal option '%s'", + Tcl_GetString(objv[i])); + ret = TCL_ERROR; + } + if (val) { + hash_put(L->options, key, val+1); + *val = '='; + } else { + hash_put(L->options, key, "yes"); + } + } + return (ret); +} + +/* + * Parse an L script into an AST. Parsing and compiling are broken into two + * stages in order to support an interactive mode that parses many times + * before finally compiling. + */ +private int +parse_script(char *str, Ast **ast_p, Tcl_Obj *nameObj) +{ + char *prepend, *s; + void *lex_buffer; + + L_typeck_init(); + + if (nameObj) { + L->file = ckstrdup(Tcl_GetString(nameObj)); + L->dir = L_dirname(L->file); + } else { + char *cwd = getcwd(NULL, 0); + L->file = ckstrdup("<stdin>"); + L->dir = ckstrdup(cwd); + free(cwd); + } + + /* + * Calculate the starting line # from the --line and --lineadj + * cmd-line options and inject a #line directive at the start + * of the source code. This communicates the file-relative + * line # to code elsewhere that prints run-time error + * messages. + */ + if ((s = getenv("_L_LINE"))) { + L->line = strtoul(s, NULL, 10); + } else { + if ((s = hash_get(L->options, "line"))) { + L->line = atoi(s); + } else { + L->line = 1; + } + if ((s = hash_get(L->options, "lineadj"))) { + L->line += atoi(s); + } + } + prepend = cksprintf("#line %d\n", L->line); + str = cksprintf("%s%s", prepend, str); + + L->token_off = 0; + L->prev_token_off = 0; + L->prev_token_len = 0; + L->errs = NULL; + L_lex_start(); + lex_buffer = (void *)L__scan_bytes(str, strlen(str)); + + L_parse(); + ASSERT(ast_p); + *ast_p = L->ast; + + L__delete_buffer(lex_buffer); + ckfree(str); + ckfree(prepend); + + if (L->errs) { + Tcl_SetObjResult(L->interp, L->errs); + return (TCL_ERROR); + } + return (TCL_OK); +} + +/* Compile an L AST into Tcl ByteCodes. The envPtr may be NULL. */ +private int +ast_compile(void *ast) +{ + int ret = TCL_OK; + TopLev *toplev; + static int ctr = 0; + + ASSERT(((Ast *)ast)->type == L_NODE_TOPLEVEL); + + L->toplev = cksprintf("%d%%l_toplevel", ctr++); + + init_predefined(); // set the L pre-defined identifiers + + /* + * Two frames get pushed, one for private globals that exist + * at file scope, and one for the top-level code. See the + * comment in sym_store(). + */ + frame_push(NULL, NULL, SCRIPT|SEARCH); + frame_push(NULL, L->toplev, FUNC|TOPLEV|SKIP); + + /* + * Before compiling, enter prototypes for all functions into + * the global symbol table. + */ + for (toplev = (TopLev *)ast; toplev; toplev = toplev->next) { + switch (toplev->kind) { + case L_TOPLEVEL_FUN: + compile_fnDecl(toplev->u.fun, FN_PROTO_ONLY); + break; + default: + break; + } + } + + for (toplev = (TopLev *)ast; toplev; toplev = toplev->next) { + switch (toplev->kind) { + case L_TOPLEVEL_CLASS: + compile_clsDecl(toplev->u.class); + break; + case L_TOPLEVEL_FUN: + compile_fnDecl(toplev->u.fun, FN_PROTO_AND_BODY); + break; + case L_TOPLEVEL_GLOBAL: + compile_varDecls(toplev->u.global); + break; + case L_TOPLEVEL_STMT: + compile_stmts(toplev->u.stmt); + break; + default: + L_bomb("Unexpected toplevel stmt type %d", toplev->kind); + } + } + + /* If main() was defined, emit a %%call_main_if_defined call. */ + if (sym_lookup(mkId("main"), L_NOWARN)) { + if (hash_get(L->options, "warn_undefined_fns")) { + push_lit("%%check_L_fns"); + emit_invoke(1); + } + push_lit("%%call_main_if_defined"); + emit_invoke(1); + } + + push_lit(""); + TclEmitOpcode(INST_DONE, L->frame->envPtr); + frame_pop(); + frame_pop(); + + if (L->errs) { + Tcl_SetObjResult(L->interp, L->errs); + return (TCL_ERROR); + } + + if (hash_get(L->options, "norun") || (L->err && !getenv("_L_TEST"))) { + /* Still check for undefined functions if requested. */ + if (hash_get(L->options, "warn_undefined_fns") && + sym_lookup(mkId("main"), L_NOWARN)) { + if (L->frame->envPtr) { + push_lit("%%check_L_fns"); + emit_invoke(1); + } else { + Tcl_Eval(L->interp, "%%check_L_fns"); + } + } + return (TCL_OK); + } + + /* Invoke the top-level code that was just compiled. */ + if (L->frame->envPtr) { + push_lit("LtraceInit"); + emit_invoke(1); + push_lit(L->toplev); + emit_invoke(1); + } else { + if (Tcl_GetVar(L->interp, "::L_libl_done", 0)) { + ret = Tcl_Eval(L->interp, "LtraceInit"); + } + if (ret == TCL_OK) ret = Tcl_Eval(L->interp, L->toplev); + } + return (ret); +} + +private void +init_predefined() +{ +#define SET_INT(name, val) \ + Tcl_SetVar2Ex(L->interp, (name), NULL, Tcl_NewIntObj(val), \ + TCL_GLOBAL_ONLY) + + /* + * These are flags used by compile_spawn_system() when + * compiling calls to libl.tcl's system_(). Pre-define them as L + * variables so that system_() in lib L can see their values. + */ + SET_INT("SYSTEM_ARGV__", SYSTEM_ARGV); + SET_INT("SYSTEM_IN_STRING__", SYSTEM_IN_STRING); + SET_INT("SYSTEM_IN_ARRAY__", SYSTEM_IN_ARRAY); + SET_INT("SYSTEM_IN_FILENAME__", SYSTEM_IN_FILENAME); + SET_INT("SYSTEM_IN_HANDLE__", SYSTEM_IN_HANDLE); + SET_INT("SYSTEM_OUT_STRING__", SYSTEM_OUT_STRING); + SET_INT("SYSTEM_OUT_ARRAY__", SYSTEM_OUT_ARRAY); + SET_INT("SYSTEM_OUT_FILENAME__", SYSTEM_OUT_FILENAME); + SET_INT("SYSTEM_OUT_HANDLE__", SYSTEM_OUT_HANDLE); + SET_INT("SYSTEM_ERR_STRING__", SYSTEM_ERR_STRING); + SET_INT("SYSTEM_ERR_ARRAY__", SYSTEM_ERR_ARRAY); + SET_INT("SYSTEM_ERR_FILENAME__", SYSTEM_ERR_FILENAME); + SET_INT("SYSTEM_ERR_HANDLE__", SYSTEM_ERR_HANDLE); + SET_INT("SYSTEM_BACKGROUND__", SYSTEM_BACKGROUND); + +#undef SET_INT +} + +private void +compile_clsDecl(ClsDecl *clsdecl) +{ + ASSERT(clsdecl->constructors); + ASSERT(clsdecl->destructors); + + /* + * A class creates two scopes, one for the class symbols and + * the other for its top-level code (class variable + * initializers). See the comments in sym_store(). The class + * symtab is persisted so it can be later retrieved from the + * class type to support obj->var or classname->var lookups. + */ + frame_push(NULL, NULL, CLS_OUTER|SEARCH|KEEPSYMS); + clsdecl->symtab = L->frame->symtab; + frame_push(NULL, NULL, CLS_TOPLEV|SKIP); + L->frame->clsdecl = clsdecl; + + frame_resumePrologue(); + push_lit("::namespace"); + push_lit("eval"); + push_litf("::L::_class_%s", clsdecl->decl->id->str); + push_lit("variable __num 0"); + emit_invoke(4); + emit_pop(); + frame_resumeBody(); + + compile_varDecls(clsdecl->clsvars); + /* Process function decls first, then compile the bodies. */ + compile_fnDecls(clsdecl->fns, FN_PROTO_ONLY); + compile_fnDecls(clsdecl->constructors, FN_PROTO_ONLY); + compile_fnDecls(clsdecl->destructors, FN_PROTO_ONLY); + compile_fnDecls(clsdecl->constructors, FN_PROTO_AND_BODY); + compile_fnDecls(clsdecl->destructors, FN_PROTO_AND_BODY); + compile_fnDecls(clsdecl->fns, FN_PROTO_AND_BODY); + + frame_pop(); + frame_pop(); +} + +/* + * Take an expr list consisting of + * + * id like the arg to "#pragma fntrace" + * id=constant like the arg to "#pragma fnhook=myhook" + * + * and add hash entries to the given hash. The id's here aren't taken + * as variables, but the name of the id itself is used, to avoid + * making the programmer put everything inside quotes. This is used + * for #pragmas and function attributes. + */ +void +L_compile_attributes(Tcl_Obj *hash, Expr *expr, char *allowed[]) +{ + Expr *arg; + char *key, *val; + char **p; + + ASSERT(hash); + for (arg = expr; arg; arg = arg->next) { + if (arg->kind == L_EXPR_ID) { + key = arg->str; + val = "yes"; + } else if ((arg->kind == L_EXPR_BINOP) && + (arg->op == L_OP_EQUALS)) { + key = arg->a->str; + val = arg->b->str; + unless (isconst(arg->b) || (arg->b->kind == L_EXPR_ID)) { + L_errf(arg, + "non-constant value for attribute %s", + key); + } + } else { + L_errf(arg, "illegal attribute; not id or id=constant"); + continue; + } + for (p = allowed; *p; ++p) { + if (!strcmp(key, *p)) break; + } + unless (*p) { + L_errf(expr, "illegal attribute '%s'", key); + } else { + hash_put(hash, key, val); + } + } +} + +private void +compile_fnDecls(FnDecl *fun, Decl_f flags) +{ + for (; fun; fun = fun->next) { + compile_fnDecl(fun, flags); + } +} + +private void +compile_fnDecl(FnDecl *fun, Decl_f flags) +{ + int i; + VarDecl *decl = fun->decl; + char *name = decl->id->str; + char *clsname = NULL; + ClsDecl *clsdecl = NULL; + Sym *self_sym = NULL; + Sym *sym; + + flags |= decl->flags; + + ASSERT(fun && decl); + ASSERT(!(flags & SCOPE_LOCAL)); + ASSERT(flags & (SCOPE_CLASS | SCOPE_GLOBAL | SCOPE_SCRIPT)); + ASSERT(flags & (DECL_FN | DECL_CLASS_FN)); + // DECL_CLASS_FN ==> DECL_PUBLIC | DECL_PRIVATE + ASSERT(!(flags & DECL_CLASS_FN) || + (flags & (DECL_PUBLIC | DECL_PRIVATE))); + ASSERT(flags & (FN_PROTO_ONLY | FN_PROTO_AND_BODY)); + + /* + * Sort out the possible error cases: + * + * - main() declared with wrong types for formals + * - name illegal + * - name already declared as a variable + * - proto already declared and doesn't match this decl + * - this decl declares function body but body already declared + */ + if (!strcmp(name, "main")) L_typeck_main(decl); + if (name[0] == '_') { + L_errf(decl->id, "function names cannot begin with _"); + } + if (!strcmp(name, "END")) { + L_errf(decl->id, "cannot use END for function name"); + } else if (!strcmp(name, "undef")) { + L_errf(decl->id, "cannot use undef for function name"); + } + for (i = 0; i < sizeof(builtins)/sizeof(builtins[0]); ++i) { + if (!strcmp(builtins[i].name, name)) { + L_errf(decl->id, + "function '%s' conflicts with built-in", + name); + return; + } + } + sym = sym_lookup(decl->id, L_NOWARN|L_NOTUSED); + if (sym) { + unless (sym->kind & L_SYM_FN) { + L_errf(fun, "%s already declared as a variable",name); + return; + } else if ((sym->kind & L_SYM_FNBODY) && fun->body) { + L_errf(fun, "function %s already declared", name); + return; + } else unless (L_typeck_same(decl->type, sym->type)) { + L_errf(fun, "does not match other declaration of %s", + name); + return; + } + } else { + sym = sym_store(decl); + unless (sym) return; + } + + /* Check arg and return types for legality. */ + L_typeck_declType(decl); + + if (!fun->body || (flags & FN_PROTO_ONLY)) return; + + /* + * Add this function's attributes to the hash of all declared + * functions in L->fn_decls which is put into the Tcl global + * variable L_fnsDeclared, for use by the function-tracing + * subsystem code in libl.tcl when tracing is enabled. + */ + L_compile_attributes(fun->attrs, decl->attrs, L_attrs_attribute); + if (flags & FN_PROTO_AND_BODY) { + Tcl_Obj *key; + Var *arrayPtr, *varPtr; + + /* + * L->fn_decls can get out of date when the L code in + * lib L writes to L_fnsDeclared, so grab the latest. + */ + varPtr = TclLookupVar(L->interp, + "L_fnsDeclared", + NULL, + TCL_GLOBAL_ONLY, + NULL, + 0, + 0, + &arrayPtr); + if (L->fn_decls != varPtr->value.objPtr) { + L->fn_decls = varPtr->value.objPtr; + } + + hash_put(fun->attrs, "name", name); + hash_put(fun->attrs, "file", basenm(fun->node.loc.file)); + if (Tcl_IsShared(L->fn_decls)) { + L->fn_decls = Tcl_DuplicateObj(L->fn_decls); + Tcl_SetVar2Ex(L->interp, "L_fnsDeclared", NULL, + L->fn_decls, TCL_GLOBAL_ONLY); + } + key = Tcl_NewStringObj(sym->tclname, -1); + Tcl_IncrRefCount(key); + Tcl_DictObjPut(L->interp, L->fn_decls, key, fun->attrs); + Tcl_DecrRefCount(key); + } + + frame_push(fun, sym->tclname, FUNC|SEARCH); + sym->kind |= L_SYM_FNBODY; + L->frame->block = (Ast *)fun; + + compile_fnParms(decl); + + /* Gather class decl and name, for class member functions. */ + clsdecl = fun->decl->clsdecl; + if (clsdecl) clsname = clsdecl->decl->id->str; + + /* + * For private class member fns and the constructor, declare + * the local variable "self". For public member fns, lookup + * "self" which is required to be the first parameter (and is + * added by compile_fnParms if not present). + */ + if (isClsConstructor(decl) || isClsFnPrivate(decl)) { + self_sym = sym_mk("self", + clsdecl->decl->type, + SCOPE_LOCAL | DECL_LOCAL_VAR); + ASSERT(self_sym && self_sym->idx >= 0); + self_sym->used_p = TRUE; + } else if (isClsFnPublic(decl)) { + self_sym = sym_lookup(mkId("self"), L_NOWARN); + ASSERT(self_sym && self_sym->idx >= 0); + } + + /* + * For a constructor, before compiling the user's + * constructor body, emit code to increment the class instance + * #, set "self" to the namespace name of the class instance, + * create the namespace, then compile the instance-variable + * initializers. Basically this: + * + * incrStkImm ::L::_class_<cls_name>::__num + * set self ::L::_instance_<cls_name>${__num} + * namespace eval $self {} + * ...instance variable initializers... + * ...user's constructor body... + */ + if (isClsConstructor(decl)) { + frame_resumePrologue(); + ASSERT(clsdecl && clsname && self_sym); + push_litf("::L::_class_%s::__num", clsname); + TclEmitInstInt1(INST_INCR_STK_IMM, 1, L->frame->envPtr); + emit_pop(); + push_lit("::namespace"); + push_lit("eval"); + push_litf("::L::_instance_%s", clsname); + push_litf("::L::_class_%s::__num", clsname); + TclEmitOpcode(INST_LOAD_STK, L->frame->envPtr); + TclEmitInstInt1(INST_STR_CONCAT1, 2, L->frame->envPtr); + emit_store_scalar(self_sym->idx); + push_lit(""); + emit_invoke(4); + emit_pop(); + frame_resumeBody(); + compile_varDecls(clsdecl->instvars); + } + + /* + * For private member functions, upvar "self" to the "self" in + * the calling frame. This works because only other class member + * functions can call private member functions, and they have "self". + */ + if (isClsFnPrivate(decl)) { + frame_resumePrologue(); + push_lit("1"); + push_lit("self"); + TclEmitInstInt4(INST_UPVAR, self_sym->idx, L->frame->envPtr); + emit_pop(); + frame_resumeBody(); + } + + L->enclosing_func = fun; + L->enclosing_func_frame = L->frame; + compile_block(fun->body); + L->enclosing_func = NULL; + L->enclosing_func_frame = NULL; + + /* + * Emit a "fall off the end" implicit return for void + * functions. Class constructors return the value of "self". + * Non-void functions throw an exception if you fall + * off the end. + */ + if (isClsConstructor(decl)) { + emit_load_scalar(self_sym->idx); + } else if (isvoidtype(decl->type->base_type)) { + push_lit(""); + } else { + push_lit("::throw"); + push_lit("{FUNCTION NO-RETURN-VALUE " + "{no value returned from function}}"); + push_lit("no value returned from function"); + emit_invoke(3); + } + + /* + * Fix-up the return jmps so that all return stmts jump to here. + * The return value will already be on the run-time stack. + */ + fixup_jmps(&L->frame->ret_jmps); + + /* + * For class destructor, delete the instance namespace. + */ + if (isClsDestructor(decl)) { + ASSERT(self_sym); + push_lit("::namespace"); + push_lit("delete"); + emit_load_scalar(self_sym->idx); + emit_invoke(3); + emit_pop(); + } + + TclEmitOpcode(INST_DONE, L->frame->envPtr); + + frame_pop(); +} + +/* + * Push a semantic-stack frame. If flags & FUNC, start a new proc + * too. To support the delayed generation of proc prologue code, we + * allocate two CompileEnv's, one for the proc body and one for its + * prologue. You switch between the two with frame_resumePrologue() + * and frame_resumeBody(). A jump is emitted at the head of the proc + * that jumps to the end, and when the proc is done being compiled, + * the prologue code is emitted at the end along with a jump back. + * This provides a way to lazily output proc initialization code, such + * as the upvars for accessing globals and class variables. + */ +private void +frame_push(void *node, char *name, Frame_f flags) +{ + Frame *frame; + Proc *proc; + CompileEnv *bodyEnvPtr, *prologueEnvPtr; + + frame = (Frame *)ckalloc(sizeof(Frame)); + memset(frame, 0, sizeof(*frame)); + frame->flags = flags; + frame->symtab = (Tcl_HashTable *)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(frame->symtab, TCL_STRING_KEYS); + frame->labeltab = (Tcl_HashTable *)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(frame->labeltab, TCL_STRING_KEYS); + frame->prevFrame = L->frame; + L->frame = frame; + + unless (frame->flags & FUNC) { + frame->block = node; + if (frame->prevFrame) { + frame->envPtr = frame->prevFrame->envPtr; + frame->bodyEnvPtr = frame->prevFrame->bodyEnvPtr; + frame->prologueEnvPtr = frame->prevFrame->prologueEnvPtr; + } + return; + } + + bodyEnvPtr = (CompileEnv *)ckalloc(sizeof(CompileEnv)); + prologueEnvPtr = (CompileEnv *)ckalloc(sizeof(CompileEnv)); + frame->bodyEnvPtr = bodyEnvPtr; + frame->prologueEnvPtr = prologueEnvPtr; + frame->envPtr = bodyEnvPtr; + + proc = (Proc *)ckalloc(sizeof(Proc)); + proc->iPtr = (struct Interp *)L->interp; + proc->refCount = 1; + proc->numArgs = 0; + proc->numCompiledLocals = 0; + proc->firstLocalPtr = NULL; + proc->lastLocalPtr = NULL; + proc->bodyPtr = Tcl_NewObj(); + Tcl_IncrRefCount(proc->bodyPtr); + TclInitCompileEnv(L->interp, bodyEnvPtr, TclGetString(L->script), + L->script_len, NULL, 0); + bodyEnvPtr->procPtr = proc; + + TclInitCompileEnv(L->interp, prologueEnvPtr, NULL, 0, NULL, 0); + + frame->proc = proc; + frame->name = name; + + /* + * Emit a jump to what will eventually be the prologue code + * (output by frame_pop()). + */ + frame->end_jmp = emit_jmp_fwd(INST_JUMP4, NULL); + frame->proc_top = currOffset(frame->envPtr); +} + +private void +frame_resumePrologue() +{ + L->frame->envPtr = L->frame->prologueEnvPtr; +} + +private void +frame_resumeBody() +{ + L->frame->envPtr = L->frame->bodyEnvPtr; +} + +private void +frame_pop() +{ + int off; + Frame *frame = L->frame; + Proc *proc = frame->proc; + Sym *sym; + Label *label; + ByteCode *codePtr; + Tcl_HashEntry *hPtr; + Tcl_HashSearch hSearch; + + /* + * Emit proc prologue code and the jump back to the head of + * the proc. Splice in any code in the frame->prologueEnvPtr + * CompileEnv. This is dependent on CompileEnv details. + */ + if (frame->flags & FUNC) { + CompileEnv *body = frame->bodyEnvPtr; + CompileEnv *prologue = frame->prologueEnvPtr; + int len = prologue->codeNext - prologue->codeStart; + + ASSERT(frame->envPtr == frame->bodyEnvPtr); + + fixup_jmps(&frame->end_jmp); + while ((body->codeNext + len) >= body->codeEnd) { + TclExpandCodeArray(body); + } + memcpy(body->codeNext, prologue->codeStart, len); + body->codeNext += len; + if (prologue->maxStackDepth > body->maxStackDepth) { + body->maxStackDepth = prologue->maxStackDepth; + } + off = currOffset(frame->envPtr); + TclEmitInstInt4(INST_JUMP4, frame->proc_top-off, frame->envPtr); + } + + /* + * Check for unused local symbols, and free the frame's symbol table. + */ + for (hPtr = Tcl_FirstHashEntry(frame->symtab, &hSearch); + hPtr != NULL; + hPtr = Tcl_NextHashEntry(&hSearch)) { + sym = (Sym *)Tcl_GetHashValue(hPtr); + unless (sym->used_p || !(sym->kind & L_SYM_LVAR) || + (sym->decl->flags & DECL_ARGUSED)) { + L_warnf(sym->decl, "%s unused", sym->name); + } + unless (frame->flags & KEEPSYMS) { + ckfree(sym->name); + ckfree(sym->tclname); + ckfree((char *)sym); + } + } + unless (frame->flags & KEEPSYMS) { + Tcl_DeleteHashTable(frame->symtab); + ckfree((char *)frame->symtab); + } + + /* + * Check for unresolved labels, and free the frame's label table. + */ + for (hPtr = Tcl_FirstHashEntry(frame->labeltab, &hSearch); + hPtr != NULL; + hPtr = Tcl_NextHashEntry(&hSearch)) { + label = (Label *)Tcl_GetHashValue(hPtr); + unless (label->offset >= 0) { + L_err("label %s referenced but not defined", + label->name); + } + ckfree((char *)label); + } + Tcl_DeleteHashTable(frame->labeltab); + ckfree((char *)frame->labeltab); + + /* + * Create the Tcl command and free the old frame. + */ + if (frame->flags & FUNC) { + TclInitByteCodeObj(proc->bodyPtr, frame->envPtr); + proc->cmdPtr = (Command *)Tcl_CreateObjCommand(L->interp, + frame->name, + TclObjInterpProc, + (ClientData)proc, + TclProcDeleteProc); + // Don't recompile on compileEpoch changes. + codePtr = (ByteCode *)proc->bodyPtr->internalRep.twoPtrValue.ptr1; + codePtr->flags |= TCL_BYTECODE_PRECOMPILED; + TclFreeCompileEnv(frame->bodyEnvPtr); + TclFreeCompileEnv(frame->prologueEnvPtr); + ckfree((char *)frame->bodyEnvPtr); + ckfree((char *)frame->prologueEnvPtr); + } + + L->frame = frame->prevFrame; + tmp_freeAll(frame->tmps); + ckfree((char *)frame); +} + +private Frame * +frame_find(Frame_f flags) +{ + Frame *f = L->frame; + + ASSERT(f); + while (f && !(f->flags & flags)) f = f->prevFrame; + return (f); +} + +private char * +frame_name() +{ + if (L->enclosing_func) { + return(L->enclosing_func->decl->id->str); + } else { + return(L->toplev); + } +} + +private void +compile_varInitializer(VarDecl *decl) +{ + int start_off = currOffset(L->frame->envPtr); + + unless (decl->initializer) { + decl->initializer = ast_mkBinOp(L_OP_EQUALS, + decl->id, + mkId("undef"), + decl->node.loc, + decl->node.loc); + } + compile_expr(decl->initializer, L_DISCARD); + track_cmd(start_off, decl); +} + +private void +compile_varDecl(VarDecl *decl) +{ + char *name; + Sym *sym; + + /* + * Process any declaration only once, but generate code for + * its initializers each time through here. This is for class + * constructors where the class instance variables get + * compiled once for each constructor. + */ + if (decl->flags & DECL_DONE) { + compile_varInitializer(decl); + return; + } + decl->flags |= DECL_DONE; + + ASSERT(decl->id && decl->type); + + name = decl->id->str; + + unless (L_typeck_declType(decl)) return; + + if (decl->flags & DECL_LOCAL_VAR) { + if (name[0] == '_') { + L_errf(decl, + "local variable names cannot begin with _"); + } + if (decl->flags & (DECL_PRIVATE | DECL_PUBLIC)) { + L_errf(decl, + "public/private qualifiers illegal for locals"); + decl->flags &= ~(DECL_PRIVATE | DECL_PUBLIC); + } + } + if (!strcmp(name, "END")) { + L_errf(decl, "cannot use END for variable name"); + return; + } else if (!strcmp(name, "undef")) { + L_errf(decl, "cannot use undef for variable name"); + return; + } + if ((decl->type->kind == L_CLASS) && + !strcmp(name, decl->type->u.class.clsdecl->decl->id->str)) { + L_errf(decl, "cannot declare object with same name as class"); + } + + sym = sym_store(decl); + unless (sym) return; // bail if multiply declared + + if (decl->flags & DECL_EXTERN) { + if (decl->initializer) { + L_errf(decl, "extern initializers illegal"); + } + unless (L->frame->flags & TOPLEV) { + L_errf(decl, "externs legal only at global scope"); + } + sym->used_p = TRUE; // to suppress extraneous warning + return; + } + + compile_varInitializer(decl); + + /* Mark var as unused even though it was just initialized. */ + sym->used_p = FALSE; +} + +private void +compile_varDecls(VarDecl *decls) +{ + for (; decls; decls = decls->next) { + compile_varDecl(decls); + } +} + +private void +compile_stmt(Stmt *stmt) +{ + int start_off = currOffset(L->frame->envPtr); + + unless (stmt) return; + switch (stmt->kind) { + case L_STMT_BLOCK: + frame_push(stmt, NULL, SEARCH); + compile_block(stmt->u.block); + frame_pop(); + break; + case L_STMT_EXPR: + compile_exprs(stmt->u.expr, L_DISCARD); + break; + case L_STMT_COND: + compile_ifUnless(stmt->u.cond); + break; + case L_STMT_LOOP: + compile_loop(stmt->u.loop); + break; + case L_STMT_SWITCH: + compile_switch(stmt->u.swich); + break; + case L_STMT_FOREACH: + compile_foreach(stmt->u.foreach); + break; + case L_STMT_RETURN: + compile_return(stmt); + break; + case L_STMT_BREAK: + compile_break(stmt); + break; + case L_STMT_CONTINUE: + compile_continue(stmt); + break; + case L_STMT_LABEL: + compile_label(stmt); + break; + case L_STMT_GOTO: + compile_goto(stmt); + break; + case L_STMT_TRY: + compile_trycatch(stmt); + break; + default: + L_bomb("Malformed AST in compile_stmt"); + } + switch (stmt->kind) { + case L_STMT_BLOCK: + case L_STMT_COND: + case L_STMT_EXPR: + case L_STMT_TRY: + break; + default: + track_cmd(start_off, stmt); + break; + } +} + +private void +compile_stmts(Stmt *stmts) +{ + for (; stmts; stmts = stmts->next) { + compile_stmt(stmts); + } +} + +private void +compile_trycatch(Stmt *stmt) +{ + int range; + int msg_idx = -1; + Jmp *jmp; + Try *try = stmt->u.try; + Expr *msg = try->msg; + + if (msg) { + unless (msg->op == L_OP_ADDROF) { + L_errf(msg, "expected catch(&variable)"); + return; + } + compile_expr(msg, L_DISCARD); + if (msg->a->sym) { + msg_idx = msg->a->sym->idx; + } else { + L_errf(msg->a, "illegal operand to &"); + } + } + + range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, L->frame->envPtr); + TclEmitInstInt4(INST_BEGIN_CATCH4, range, L->frame->envPtr); + + /* + * Emit separate INST_END_CATCH's for the non-error and error + * paths so that a return can be done inside of a catch() + * clause -- the "try" is done when the body finishes without + * error or by the time the catch() is entered. + */ + + /* body */ + ExceptionRangeStarts(L->frame->envPtr, range); + compile_stmts(try->try); + ExceptionRangeEnds(L->frame->envPtr, range); + TclEmitOpcode(INST_END_CATCH, L->frame->envPtr); + jmp = emit_jmp_fwd(INST_JUMP4, 0); + + /* error case */ + ExceptionRangeTarget(L->frame->envPtr, range, catchOffset); + if (msg_idx != -1) { + TclEmitOpcode(INST_PUSH_RESULT, L->frame->envPtr); + TclEmitInstInt4(INST_STORE_SCALAR4, msg_idx, L->frame->envPtr); + TclEmitOpcode(INST_POP, L->frame->envPtr); + } + TclEmitOpcode(INST_END_CATCH, L->frame->envPtr); + compile_stmts(try->catch); + + /* out */ + fixup_jmps(&jmp); +} + +private void +compile_block(Block *block) +{ + compile_varDecls(block->decls); + compile_stmts(block->body); +} + +private void +compile_return(Stmt *stmt) +{ + VarDecl *decl; + Type *ret_type; + + /* Handle return from the top level. */ + unless (L->enclosing_func) { + if (stmt->u.expr) { + compile_expr(stmt->u.expr, L_PUSH_VAL); + } else { + push_lit(""); + } + TclEmitOpcode(INST_DONE, L->frame->envPtr); + return; + } + + decl = L->enclosing_func->decl; + ret_type = decl->type->base_type; + + if (isvoidtype(ret_type) && (stmt->u.expr)) { + L_errf(stmt, "void function cannot return value"); + compile_expr(stmt->u.expr, L_DISCARD); + } else if (stmt->u.expr) { + compile_expr(stmt->u.expr, L_PUSH_VAL); // return value + unless (L_typeck_compat(ret_type, stmt->u.expr->type)) { + L_errf(stmt, "incompatible return type"); + } + } else unless (isvoidtype(ret_type)) { + L_errf(stmt, "must specify return value"); + } else { + push_lit(""); // no return value -- push a "" + } + + /* Jmp to the function end where any necessary clean-up code is. */ + ASSERT(L->enclosing_func_frame); + L->enclosing_func_frame->ret_jmps = + emit_jmp_fwd(INST_JUMP4, L->enclosing_func_frame->ret_jmps); +} + +private void +proc_mkArg(Proc *proc, VarDecl *decl) +{ + int argnum; + char *name = decl->id->str; + CompiledLocal *local; + + argnum = proc->numArgs++; + ++proc->numCompiledLocals; + local = (CompiledLocal *)ckalloc(sizeof(CompiledLocal) - + sizeof(local->name) + + strlen(name) + 1); + if (proc->firstLocalPtr == NULL) { + proc->firstLocalPtr = local; + proc->lastLocalPtr = local; + } else { + proc->lastLocalPtr->nextPtr = local; + proc->lastLocalPtr = local; + } + local->nextPtr = NULL; + local->resolveInfo = NULL; + local->defValuePtr = NULL; + local->frameIndex = argnum; + local->nameLength = strlen(name); + strcpy(local->name, name); + + local->flags = VAR_ARGUMENT; + if (decl->flags & DECL_REST_ARG) local->flags |= VAR_IS_ARGS; + if (decl->flags & DECL_OPTIONAL) { + if (isnameoftype(decl->type)) { + local->defValuePtr = + Tcl_NewStringObj("::L_undef_ref_parm_", -1); + local->defValuePtr->undef = 1; + } else { + local->defValuePtr = *L_undefObjPtrPtr(); + } + Tcl_IncrRefCount(local->defValuePtr); + } +} + +/* + * Determine whether the parameter-passing mode for a formal parameter + * declaration is call-by-reference. Return NULL or the base type of + * the parameter (without the name-of). You get call-by-reference if + * the parameter was declared with & and is not a function pointer. + */ +private Type * +iscallbyname(VarDecl *formal) +{ + unless (formal) return (NULL); + if (formal->flags & DECL_REF) { + if (isfntype(formal->type->base_type)) { + return (NULL); + } else { + return (formal->type->base_type); + } + } + return (NULL); +} + +private int +compile_fnParms(VarDecl *decl) +{ + int n; + int name_parms = 0; + char *name; + Proc *proc = L->frame->envPtr->procPtr; + Expr *varId; + VarDecl *p, *varDecl; + Sym *parmSym, *varSym; + Type *type; + VarDecl *param = decl->type->u.func.formals; + + proc->numArgs = 0; + proc->numCompiledLocals = 0; + + /* + * Public class member fns (except constructor) must have "self" + * as the first arg and it must be of the class type. + */ + if (isClsFnPublic(decl) && !isClsConstructor(decl)) { + Type *clstype = decl->clsdecl->decl->type; + Expr *self_id; + VarDecl *self_decl; + unless (param && param->id && isid(param->id, "self")) { + L_errf(decl->id, "class public member function lacks " + "'self' as first arg"); + /* Add it so we can keep compiling. */ + self_id = mkId("self"); + self_decl = ast_mkVarDecl(clstype, self_id, + decl->node.loc, + decl->node.loc); + self_decl->flags = SCOPE_LOCAL | DECL_LOCAL_VAR; + self_decl->next = param; + param = self_decl; + } else unless (L_typeck_same(param->type, clstype)) { + L_errf(param, "'self' parameter must be of class type"); + } + } + + /* + * To handle call-by-name formals, make two passes through the + * formals list. In the first pass, mangle any formal name to + * "&name". In the second pass, for formals only, create a + * local "name" as an upvar to the variable one frame up whose + * name is passed in the arg. Note that the formal will have + * type "name-of <t>" and the local gets type <t>. This is + * needed since Tcl requires the locals to follow the args. + */ + for (p = param, n = 0; p; p = p->next, n++) { + unless (p->id) { + L_errf(p, "formal parameter #%d lacks a name", n+1); + name = cksprintf("unnamed-arg-%d", n+1); + p->id = mkId(name); + ckfree(name); + } + if (isClsConstructor(decl) && isid(p->id, "self")) { + L_errf(p, + "'self' parameter illegal in class constructor"); + continue; + } + if (isClsFnPrivate(decl) && isid(p->id, "self")) { + L_errf(p, + "'self' parameter illegal in private function"); + continue; + } + if ((p->flags & DECL_REST_ARG) && (p->next)) { + L_errf(p, "Rest parameter must be last"); + } + if ((p->flags & DECL_OPTIONAL) && (p->next)) { + L_errf(p, "_optional parameter must be last"); + } + if (typeis(p->type, "FMT") && + (!p->next || !(p->next->flags & DECL_REST_ARG))) { + L_errf(p, "rest argument must follow FMT"); + } + if (iscallbyname(p)) { + name = cksprintf("&%s", p->id->str); + ckfree(p->id->str); + p->id->str = name; + ++name_parms; + } + proc_mkArg(proc, p); + parmSym = sym_store(p); + unless (parmSym) continue; // multiple declaration + parmSym->idx = n; + /* Suppress unused warning for obj arg to class member fns. */ + if ((p == param) && + isClsFnPublic(decl) && !isClsConstructor(decl)) { + parmSym->used_p = TRUE; + } + } + /* For call by name, push a 1 the first time (arg to INST_UPVAR). */ + if (name_parms) push_lit("1"); + /* + * For each call-by-reference formal, we have + * "&var" - a fn parm that gets the name of the caller's actual parm + * "var" - a local upvar'd to this name, becomes alias for the actual + * The first was created above. Create the second one now. + */ + for (p = param; p; p = p->next) { + unless (type = iscallbyname(p)) continue; + + /* Lookup "&var". */ + parmSym = sym_lookup(p->id, L_NOWARN); + ASSERT(parmSym && (p->id->str[0] == '&')); + + /* Create "var". */ + varId = ast_mkId(p->id->str + 1, // point past the & + p->id->node.loc, + p->id->node.loc); + varDecl = ast_mkVarDecl(type, varId, p->node.loc, p->node.loc); + varDecl->flags = SCOPE_LOCAL | DECL_LOCAL_VAR | p->flags; + varDecl->node.loc.line = p->node.loc.line; + unless (varSym = sym_store(varDecl)) continue; // multiple decl + varSym->decl->refsym = parmSym; + emit_load_scalar(parmSym->idx); + TclEmitInstInt4(INST_UPVAR, varSym->idx, L->frame->envPtr); + } + /* Pop the 1 pushed for INST_UPVAR. */ + if (name_parms) emit_pop(); + return (n); +} + +private int +compile_rename(Expr *expr) +{ + int n; + + push_lit("frename_"); + n = compile_exprs(expr->b, L_PUSH_VAL); + unless (n == 2) { + L_errf(expr, "incorrect # args for rename"); + } + emit_invoke(3); + expr->type = L_int; + return (1); // stack effect +} + +private int +compile_split(Expr *expr) +{ + int n; + Expr *str = NULL, *lim = NULL, *sep = NULL; + Expr_f flags = 0; + + expr->type = L_poly; // for err return path + n = compile_exprs(expr->b, L_PUSH_VAL); + ASSERT(n > 0); // grammar ensures this + if (n > 3) { + L_errf(expr, "too many args to split"); + return (0); + } + switch (n) { + case 1: // split(<str>) + str = expr->b; + break; + case 2: // split(/re/, <str>) + sep = expr->b; + str = sep->next; + break; + case 3: // split(/re/, <str>, <lim>) + sep = expr->b; + str = sep->next; + lim = str->next; + break; + } + unless (istype(str, L_STRING|L_WIDGET|L_POLY)) { + L_errf(str, "expression to split must be string"); + } + if (sep) { + unless (isregexp(sep)) { + L_errf(sep, "split delimiter must be a " + "regular expression"); + } + if (sep->flags & ~(L_EXPR_RE_T | L_EXPR_RE_I)) { + L_errf(sep, "illegal regular expression modifier"); + } + flags |= L_SPLIT_RE | sep->flags; + } + if (lim) { + flags |= L_SPLIT_LIM; + unless (isint(lim)) { + L_errf(expr, "third arg to split must be integer"); + return (0); + } + } + TclEmitInstInt4(INST_L_SPLIT, flags, L->frame->envPtr); + TclAdjustStackDepth(n-1, L->frame->envPtr); + expr->type = type_mkArray(0, L_string); + return (1); // stack effect +} + +private int +compile_push(Expr *expr) +{ + int flags = 0, i, idx; + Expr *arg, *array; + Type *base_type; + Tmp *tmp; + + expr->type = L_void; + unless (expr->b && expr->b->next) { + L_errf(expr, "too few arguments to push"); + return (0); + } + unless (isaddrof(expr->b)) { + L_errf(expr, "first arg to push not an array reference (&)"); + return (0); + } + ASSERT(expr->b->a); + array = expr->b->a; + arg = expr->b->next; + compile_expr(array, L_PUSH_PTR | L_LVALUE); + unless (isarray(array) || ispoly(array)) { + L_errf(expr, + "first arg to push not an array reference (&)"); + return (0); + } + unless (array->sym) { + L_errf(expr, "invalid l-value in push"); + return (0); + } + idx = array->sym->idx; // local slot # for array + if (isarray(array)) { + base_type = array->type->base_type; + } else { + base_type = L_poly; + } + if (arg->next) { + /* Build up a list of the args to push. */ + tmp = tmp_get(TMP_REUSE); + push_lit(""); + emit_store_scalar(tmp->idx); + emit_pop(); + for (i = 2; arg; arg = arg->next, ++i) { + compile_expr(arg, L_PUSH_VAL); + /* We allow base_type or an array of base_type. */ + if (L_typeck_compat(base_type, arg->type)) { + flags = L_INSERT_ELT; + } else if (L_typeck_compat(array->type, arg->type)) { + flags = L_INSERT_LIST; + } else { + L_errf(expr, "arg #%d to push has type " + "incompatible with array", i); + } + push_lit("-1"); // -1 means append + TclEmitInstInt4(INST_L_LIST_INSERT, tmp->idx, + L->frame->envPtr); + TclEmitInt4(flags, L->frame->envPtr); + } + emit_load_scalar(tmp->idx); + tmp_free(tmp); + flags = L_INSERT_LIST; + } else { + compile_expr(arg, L_PUSH_VAL); + /* We allow base_type or an array of base_type. */ + if (L_typeck_compat(base_type, arg->type)) { + flags = L_INSERT_ELT; + } else if (L_typeck_compat(array->type, arg->type)) { + flags = L_INSERT_LIST; + } else { + L_errf(expr, "arg #2 to push has type " + "incompatible with array"); + } + } + if (array->flags & L_EXPR_DEEP) { + // deep-ptr rval + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + // rval deep-ptr + push_lit("-1"); // -1 means append + TclEmitInstInt4(INST_L_DEEP_WRITE, idx, + L->frame->envPtr); + TclEmitInt4(flags | L_DISCARD, L->frame->envPtr); + } else { + push_lit("-1"); // -1 means append + TclEmitInstInt4(INST_L_LIST_INSERT, idx, + L->frame->envPtr); + TclEmitInt4(flags, L->frame->envPtr); + } + return (0); // stack effect +} + +private int +compile_pop_shift(Expr *expr) +{ + int idx; + Expr *arg = NULL; + char *opNm = expr->a->str; + Expr *toDelete; + YYLTYPE loc; + + expr->type = L_poly; + unless (expr->b && !expr->b->next) { + L_errf(expr, "incorrect # arguments to %s", opNm); + return (0); + } + unless (isaddrof(expr->b)) { + L_errf(expr, "arg to %s not an array reference (&)", opNm); + return (0); + } + /* + * For pop, change arg from &arr to &arr[END] and then delete + * that element. For shift, use &arr[0]. + */ + ASSERT(expr->b->a); + loc = expr->b->a->node.loc; + if (!strcmp(opNm, "pop")) { + toDelete = mkId("END"); + } else { + toDelete = ast_mkConst(L_int, ckstrdup("0"), loc, loc); + } + arg = ast_mkBinOp(L_OP_ARRAY_INDEX, + expr->b->a, + toDelete, + loc, + loc); + expr->b->a = arg; + /* L_NEG_OK here permits indexing element -1 (array already empty). */ + compile_expr(arg, L_PUSH_PTR | L_DELETE | L_NEG_OK | L_LVALUE); + unless (isarray(arg->a) || ispoly(arg->a)) { + L_errf(expr, "arg to %s not an array reference (&)", opNm); + return (0); + } + unless (arg->sym) { + L_errf(expr, "invalid l-value in %s", opNm); + return (0); + } + idx = arg->sym->idx; // local slot # for array + TclEmitInstInt4(INST_L_DEEP_WRITE, idx, L->frame->envPtr); + TclEmitInt4(L_DELETE | L_PUSH_OLD, L->frame->envPtr); + TclAdjustStackDepth(1, L->frame->envPtr); + expr->type = arg->type; + return (1); // stack effect +} + +private int +compile_insert_unshift(Expr *expr) +{ + int flags, i, idx; + Expr *arg, *array, *index; + Type *base_type; + Tmp *argTmp = NULL, *idxTmp = NULL; + char *opNm = expr->a->str; + + /* + * Make unshift(arg1, arg2, ...) look like insert(arg1, "0", arg2, ...) + */ + if (!strcmp(opNm, "unshift")) { + if (expr->b) { + arg = ast_mkConst(L_int, ckstrdup("0"), expr->node.loc, + expr->node.loc); + arg->next = expr->b->next; + expr->b->next = arg; + } + i = 2; // where data args start + } else { + i = 3; // where data args start + } + + expr->type = L_void; + unless (expr->b && expr->b->next && expr->b->next->next) { + L_errf(expr, "too few arguments to %s", opNm); + return (0); + } + ASSERT(expr->b->a); + array = expr->b->a; + index = expr->b->next; + arg = expr->b->next->next; + unless (isaddrof(expr->b)) { + L_errf(expr, "first arg to %s not an array reference (&)", opNm); + return (0); + } + compile_expr(array, L_PUSH_PTR | L_LVALUE); + unless (isarray(array) || ispoly(array)) { + L_errf(expr, + "first arg to %s not an array reference (&)", opNm); + return (0); + } + unless (array->sym) { + L_errf(expr, "invalid l-value in %s", opNm); + return (0); + } + idx = array->sym->idx; // local slot # for array + if (isarray(array)) { + base_type = array->type->base_type; + } else { + base_type = L_poly; + } + + /* + * If >1 arg, concat them all into a temp and insert that. We + * can't just insert them one by one like we do in + * compile_push(), since that would insert them backwards. + * We could reverse the arg list, but building the temp is + * about as fast as re-indexing into the array for each element. + */ + if (arg->next) { + idxTmp = tmp_get(TMP_REUSE); + compile_expr(index, L_PUSH_VAL); + emit_store_scalar(idxTmp->idx); + emit_pop(); + unless (isint(index)) { + L_errf(expr, "second arg to %s not an int", opNm); + return (0); + } + argTmp = tmp_get(TMP_REUSE); + push_lit(""); + emit_store_scalar(argTmp->idx); + emit_pop(); + for (; arg; arg = arg->next, ++i) { + compile_expr(arg, L_PUSH_VAL); + /* For an arg, allow base_type or array of base_type. */ + unless (L_typeck_compat(base_type, arg->type) || + L_typeck_compat(array->type, arg->type)) { + L_errf(expr, "arg #%d to %s has type " + "incompatible with array", i, opNm); + } + if (isarray(arg) || islist(arg)) { + flags = L_INSERT_LIST; + } else { + flags = L_INSERT_ELT; + } + push_lit("-1"); // -1 means append + TclEmitInstInt4(INST_L_LIST_INSERT, argTmp->idx, + L->frame->envPtr); + TclEmitInt4(flags, L->frame->envPtr); + } + if (array->flags & L_EXPR_DEEP) { + emit_load_scalar(argTmp->idx); + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + emit_load_scalar(idxTmp->idx); + TclEmitInstInt4(INST_L_DEEP_WRITE, idx, + L->frame->envPtr); + TclEmitInt4(L_INSERT_LIST | L_DISCARD, + L->frame->envPtr); + } else { + emit_load_scalar(argTmp->idx); + emit_load_scalar(idxTmp->idx); + TclEmitInstInt4(INST_L_LIST_INSERT, idx, + L->frame->envPtr); + TclEmitInt4(L_INSERT_LIST, L->frame->envPtr); + } + } else { + compile_expr(arg, L_PUSH_VAL); + /* For the arg, we allow base_type or an array of base_type. */ + unless (L_typeck_compat(base_type, arg->type) || + L_typeck_compat(array->type, arg->type)) { + L_errf(expr, "arg #%d to %s has type incompatible " + "with array", i, opNm); + } + if (isarray(arg) || islist(arg)) { + flags = L_INSERT_LIST; + } else { + flags = L_INSERT_ELT; + } + if (array->flags & L_EXPR_DEEP) { + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + } + compile_expr(index, L_PUSH_VAL); + unless (isint(index)) { + L_errf(expr, "second arg to %s not an int", opNm); + return (0); + } + if (array->flags & L_EXPR_DEEP) { + TclEmitInstInt4(INST_L_DEEP_WRITE, idx, + L->frame->envPtr); + TclEmitInt4(flags | L_DISCARD, L->frame->envPtr); + } else { + TclEmitInstInt4(INST_L_LIST_INSERT, idx, + L->frame->envPtr); + TclEmitInt4(flags, L->frame->envPtr); + } + } + tmp_free(idxTmp); + tmp_free(argTmp); + return (0); // stack effect +} + +private void +compile_eq_stack(Expr *expr, Type *type) +{ + int i, top_off; + Tmp *itmp, *ltmp, *rtmp; + Jmp *out = NULL; + Jmp *out_false = NULL, *out_false2 = NULL, *out_true = NULL; + VarDecl *v; + + unless (type->kind & (L_ARRAY|L_STRUCT|L_HASH)) { + /* Scalar -- just need a single bytecode. */ + emit_instrForLOp(expr, type); + return; + } + + /* Put lhs and rhs into temps. */ + ltmp = tmp_get(TMP_REUSE); + rtmp = tmp_get(TMP_REUSE); + emit_store_scalar(rtmp->idx); + emit_pop(); + emit_store_scalar(ltmp->idx); + emit_pop(); + + switch (type->kind) { + case L_ARRAY: + itmp = tmp_get(TMP_UNSET); + /* + * if (length(lhs) != length(rhs)) goto out_false + * itmp = length(rhs) + * top_off: + * if (itmp == 0) goto out_true + * --itmp + * if (lhs[itmp] != rhs[itmp]) goto out_false + * goto top_off + * out_true: + * push 1 + * goto out + * out_false: + * push 0 + * out: + */ + emit_load_scalar(ltmp->idx); + TclEmitOpcode(INST_LIST_LENGTH, L->frame->envPtr); + emit_load_scalar(rtmp->idx); + TclEmitOpcode(INST_LIST_LENGTH, L->frame->envPtr); + emit_store_scalar(itmp->idx); + TclEmitOpcode(INST_EQ, L->frame->envPtr); + out_false = emit_jmp_fwd(INST_JUMP_FALSE4, out_false); + top_off = currOffset(L->frame->envPtr); + emit_load_scalar(itmp->idx); + out_true = emit_jmp_fwd(INST_JUMP_FALSE4, out_true); + TclEmitInstInt1(INST_INCR_SCALAR1_IMM, itmp->idx, + L->frame->envPtr); + TclEmitInt1(-1, L->frame->envPtr); + emit_pop(); + emit_load_scalar(ltmp->idx); + emit_load_scalar(itmp->idx); + TclEmitOpcode(INST_LIST_INDEX, L->frame->envPtr); + emit_load_scalar(rtmp->idx); + emit_load_scalar(itmp->idx); + TclEmitOpcode(INST_LIST_INDEX, L->frame->envPtr); + compile_eq_stack(expr, type->base_type); + out_false = emit_jmp_fwd(INST_JUMP_FALSE4, out_false); + emit_jmp_back(TCL_UNCONDITIONAL_JUMP, top_off); + fixup_jmps(&out_true); + push_lit("1"); + out = emit_jmp_fwd(INST_JUMP1, out); + fixup_jmps(&out_false); + push_lit("0"); + fixup_jmps(&out); + tmp_free(itmp); + break; + case L_STRUCT: + /* + * The structs are of compatible types, so we know + * they have the same number of members. Compare + * them one by one. + */ + i = 0; + for (v = type->u.struc.members; v; v = v->next) { + emit_load_scalar(ltmp->idx); + TclEmitInstInt4(INST_LIST_INDEX_IMM, i, + L->frame->envPtr); + emit_load_scalar(rtmp->idx); + TclEmitInstInt4(INST_LIST_INDEX_IMM, i, + L->frame->envPtr); + ++i; + compile_eq_stack(expr, v->type); + out_false = emit_jmp_fwd(INST_JUMP_FALSE4, out_false); + } + push_lit("1"); + out = emit_jmp_fwd(INST_JUMP1, out); + fixup_jmps(&out_false); + push_lit("0"); + fixup_jmps(&out); + break; + case L_HASH: + /* + * if (length(lhs) != length(rhs)) goto out_false2 + * if [dict first lhs] goto out_true + * top_off: + * // stack: val key (key is on top) + * unless [::dict exists rhs key] goto out_false + * unless [::dict get rhs key] == val goto out_false2 + * unless [dict next] goto top_off + * out_true: + * pop // pop key + * pop // pop val + * push 1 + * goto out + * out_false: + * pop // pop key + * pop // pop val + * out_false2: + * push 0 + * out: + */ + itmp = tmp_get(TMP_UNSET); + push_lit("::dict"); + push_lit("size"); + emit_load_scalar(ltmp->idx); + // ::dict size lhs + emit_invoke(3); + // <lhs-size> + push_lit("::dict"); + push_lit("size"); + emit_load_scalar(rtmp->idx); + // <lhs-size> ::dict size rhs + emit_invoke(3); + // <lhs-size> <rhs-size> + TclEmitOpcode(INST_EQ, L->frame->envPtr); + // <true/false> + out_false2 = emit_jmp_fwd(INST_JUMP_FALSE4, out_false2); + emit_load_scalar(ltmp->idx); + // lhs + TclEmitInstInt4(INST_DICT_FIRST, itmp->idx, L->frame->envPtr); + // <lhs-val> <lhs-key> <done-flag> + out_true = emit_jmp_fwd(INST_JUMP_TRUE4, out_true); + top_off = currOffset(L->frame->envPtr); + // <lhs-val> <lhs-key> + TclEmitOpcode(INST_DUP, L->frame->envPtr); + // <lhs-val> <lhs-key> <lhs-key> + push_lit("::dict"); + push_lit("exists"); + emit_load_scalar(rtmp->idx); + // <lhs-val> <lhs-key> <lhs-key> ::dict exists rhs + TclEmitInstInt1(INST_ROT, 3, L->frame->envPtr); + // <lhs-val> <lhs-key> ::dict exists rhs <lhs-key> + emit_invoke(4); + // <lhs-val> <lhs-key> <rhs-exists-flag> + out_false = emit_jmp_fwd(INST_JUMP_FALSE4, out_false); + // <lhs-val> <lhs-key> + push_lit("::dict"); + push_lit("get"); + emit_load_scalar(rtmp->idx); + // <lhs-val> <lhs-key> ::dict get rhs + TclEmitInstInt1(INST_ROT, 3, L->frame->envPtr); + // <lhs-val> ::dict get rhs <lhs-key> + emit_invoke(4); + // <lhs-val> <rhs-val> + compile_eq_stack(expr, type->base_type); + // <equals-flag> + out_false2 = emit_jmp_fwd(INST_JUMP_FALSE4, out_false2); + TclEmitInstInt4(INST_DICT_NEXT, itmp->idx, L->frame->envPtr); + // <lhs-val> <lhs-key> <done-flag> + emit_jmp_back(TCL_FALSE_JUMP, top_off); + fixup_jmps(&out_true); + // <lhs-val> <lhs-key> + emit_pop(); + emit_pop(); + push_lit("1"); + out = emit_jmp_fwd(INST_JUMP1, out); + // <lhs-val> <lhs-key> + fixup_jmps(&out_false); + emit_pop(); + emit_pop(); + fixup_jmps(&out_false2); + push_lit("0"); + fixup_jmps(&out); + tmp_free(itmp); + break; + default: ASSERT(0); + } + tmp_free(ltmp); + tmp_free(rtmp); +} + +private int +compile_keys(Expr *expr) +{ + int n; + + push_lit("::dict"); + push_lit("keys"); + n = compile_exprs(expr->b, L_PUSH_VAL); + unless (n == 1) { + L_errf(expr, "incorrect # args to keys"); + expr->type = L_poly; + return (0); // stack effect + } + unless (ishash(expr->b) || ispoly(expr->b)) { + L_errf(expr, "arg to keys is not a hash"); + expr->type = L_poly; + return (0); // stack effect + } + emit_invoke(3); + if (ispoly(expr->b)) { + expr->type = L_poly; + } else { + expr->type = type_mkArray(0, expr->b->type->u.hash.idx_type); + } + return (1); // stack effect +} + +private int +compile_length(Expr *expr) +{ + int n; + Jmp *jmp1, *jmp2; + + expr->type = L_int; + + n = compile_exprs(expr->b, L_PUSH_VAL); + unless (n == 1) { + L_errf(expr, "incorrect # args to length"); + return (0); // stack effect + } + if (isstring(expr->b) || iswidget(expr->b)) { + TclEmitOpcode(INST_STR_LEN, L->frame->envPtr); + } else if (isarray(expr->b) || islist(expr->b) || ispoly(expr->b)) { + TclEmitOpcode(INST_LIST_LENGTH, L->frame->envPtr); + } else if (ishash(expr->b)) { + /* + * <arg is on stack from above compile_exprs> + * dup + * l_defined + * jmpFalse 1 + * ::dict size (rot arg into place before the invoke) + * jmp 2 + * 1: pop + * push 0 + * 2: + */ + TclEmitOpcode(INST_DUP, L->frame->envPtr); + TclEmitOpcode(INST_L_DEFINED, L->frame->envPtr); + jmp1 = emit_jmp_fwd(INST_JUMP_FALSE1, NULL); + push_lit("::dict"); + push_lit("size"); + TclEmitInstInt1(INST_ROT, 2, L->frame->envPtr); + emit_invoke(3); + jmp2 = emit_jmp_fwd(INST_JUMP1, NULL); + fixup_jmps(&jmp1); + emit_pop(); + push_lit("0"); + fixup_jmps(&jmp2); + } else { + L_errf(expr, "arg to length has illegal type"); + } + return (1); // stack effect +} + +private int +compile_min_max(Expr *expr) +{ + push_litf("::tcl::mathfunc::%s", expr->a->str); + unless (compile_exprs(expr->b, L_PUSH_VAL) == 2) { + L_errf(expr, "incorrect # args to %s", expr->a->str); + expr->type = L_poly; + return (0); + } + L_typeck_expect(L_INT|L_FLOAT, expr->b, "in min/max"); + L_typeck_expect(L_INT|L_FLOAT, expr->b->next, "in min/max"); + emit_invoke(3); + if (isfloat(expr->b) || isfloat(expr->b->next)) { + expr->type = L_float; + } else { + expr->type = L_int; + } + return (1); // stack effect +} + +private int +compile_sort(Expr *expr) +{ + int custom_compar = 0, i, n; + Expr *e, *l; + Type *t; + + /* + * Do some gymnastics to get this on the run-time stack: + * ::lsort + * <all args except last one> + * -integer, -real, or -ascii depending on list type, unless + * the -compare option was given + * <last arg (the thing to be sorted)> + */ + + push_lit("::lsort"); + n = compile_exprs(expr->b, L_PUSH_VAL); + unless (n >= 1) { + L_errf(expr, "incorrect # args to sort"); + expr->type = L_poly; + return (0); // stack effect + } + /* See if there's a "-command" argument. */ + for (i = 0, l = expr->b; i < (n-1); ++i, l = l->next) { + unless (isconst(l) && l->str && !strcmp(l->str, "-command")) { + continue; + } + /* Type check the arg to -command. */ + e = l->next; + unless (e && (e->type->kind == L_NAMEOF) && + (e->type->base_type->kind == L_FUNCTION)) { + L_errf(e, "'command:' arg to sort must be &function"); + } + custom_compar = 1; + } + /* The last argument to sort must be an array, list, or poly. */ + if (isarray(l) || islist(l)) { + t = l->type->base_type; + } else if (ispoly(l)) { + t = L_poly; + } else { + L_errf(expr, "last arg to sort not an array or list"); + expr->type = L_poly; + return (0); // stack effect + } + unless (custom_compar) { + switch (t->kind) { + case L_INT: + push_lit("-integer"); + break; + case L_FLOAT: + push_lit("-real"); + break; + default: + push_lit("-ascii"); + break; + } + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + ++n; + } + if (n > 255) L_errf(expr, "sort cannot have >255 args"); + emit_invoke(n+1); + expr->type = type_mkArray(0, t); + return (1); // stack effect +} + +private int +compile_join(Expr *expr) +{ + Expr *array, *sep; + + expr->type = L_string; + push_lit("::join"); + unless ((sep=expr->b) && (array=sep->next) && !array->next) { + L_errf(expr, "incorrect # args to join"); + return (0); // stack effect + } + compile_expr(array, L_PUSH_VAL); + unless (isarray(array) || islist(array) || ispoly(array)) { + L_errf(expr, "second arg to join not an array or list"); + return (0); // stack effect + } + compile_expr(sep, L_PUSH_VAL); + unless (isstring(sep) || iswidget(sep) || ispoly(sep)) { + L_errf(expr, "first arg to join not a string"); + return (0); // stack effect + } + emit_invoke(3); + return (1); // stack effect +} + +private int +compile_abs(Expr *expr) +{ + int n; + + push_lit("::tcl::mathfunc::abs"); + n = compile_exprs(expr->b, L_PUSH_VAL); + unless (n == 1) { + L_errf(expr, "incorrect # args to abs"); + expr->type = L_poly; + return (0); + } + unless (isint(expr->b) || isfloat(expr->b) || ispoly(expr->b)) { + L_errf(expr, "must pass int or float to abs"); + } + emit_invoke(2); + expr->type = expr->b->type; + return (1); // stack effect +} + +private int +compile_assert(Expr *expr) +{ + Jmp *jmp; + char *cond_txt; + + expr->type = L_void; + unless (expr->b && !expr->b->next) { + L_errf(expr, "incorrect # args to assert"); + return (0); // stack effect + } + compile_condition(expr->b); + jmp = emit_jmp_fwd(INST_JUMP_TRUE4, NULL); + cond_txt = get_text(expr->b); + push_lit("die_"); + push_lit(frame_name()); + push_litf("%d", expr->node.loc.line); + push_litf("ASSERTION FAILED %s:%d: %s\n", expr->node.loc.file, + expr->node.loc.line, cond_txt); + emit_invoke(4); + emit_pop(); + ckfree(cond_txt); + fixup_jmps(&jmp); + return (0); // stack effect +} + +private int +compile_catch(Expr *expr) +{ + L_errf(expr, "catch() is reserved for try/catch; " + "use ::catch() for Tcl's catch"); + return (0); +} + +/* + * Change die(fmt, ...args) into die_(__FILE__, __LINE__, fmt, ...args) + */ +private int +compile_die(Expr *expr) +{ + Expr *arg; + + ckfree(expr->a->str); + expr->a->str = ckstrdup("die_"); + arg = ast_mkId("__FILE__", expr->node.loc, expr->node.loc); + arg->next = ast_mkId("__LINE__", expr->node.loc, expr->node.loc); + arg->next->next = expr->b; + expr->b = arg; + return (compile_expr(expr, L_PUSH_VAL)); +} + +/* + * Change warn(fmt, ...args) into warn_(__FILE__, __LINE__, fmt, ...args) + */ +private int +compile_warn(Expr *expr) +{ + Expr *arg; + + ckfree(expr->a->str); + expr->a->str = ckstrdup("warn_"); + arg = ast_mkId("__FILE__", expr->node.loc, expr->node.loc); + arg->next = ast_mkId("__LINE__", expr->node.loc, expr->node.loc); + arg->next->next = expr->b; + expr->b = arg; + return (compile_expr(expr, L_PUSH_VAL)); +} + +/* + * Change here() into here_(__FILE__, __LINE__, __FUNC__) + */ +private int +compile_here(Expr *expr) +{ + Expr *arg; + + if (expr->b) { + L_errf(expr, "here() takes no arguments"); + } + ckfree(expr->a->str); + expr->a->str = ckstrdup("here_"); + arg = ast_mkId("__FILE__", expr->node.loc, expr->node.loc); + arg->next = ast_mkId("__LINE__", expr->node.loc, expr->node.loc); + arg->next->next = ast_mkId("__FUNC__", expr->node.loc, expr->node.loc); + expr->b = arg; + return (compile_expr(expr, L_PUSH_VAL)); +} + +private int +compile_undef(Expr *expr) +{ + int n; + Expr *arg = expr->b; + + n = compile_exprs(arg, L_PUSH_PTR | L_DELETE | L_LVALUE); + unless (n == 1) { + L_errf(expr, "incorrect # args to undef"); + goto done; + } + unless (arg->sym) { + L_errf(expr, "illegal l-value in undef()"); + goto done; + } + if (((arg->op == L_OP_DOT) || (arg->op == L_OP_POINTS)) && + isstruct(arg->a)) { + L_errf(expr, "cannot undef() a struct field"); + goto done; + } + /* + * If arg is a deep dive, delete the hash or array element. + * If arg is a variable, treat undef(var) like var=undef. + */ + if (arg->flags & L_EXPR_DEEP) { + TclEmitInstInt4(INST_L_DEEP_WRITE, + arg->sym->idx, + L->frame->envPtr); + TclEmitInt4(L_DELETE | L_DISCARD, L->frame->envPtr); + } else { + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + emit_store_scalar(arg->sym->idx); + emit_pop(); + } + done: + expr->type = L_void; + return (0); // stack effect +} + +private int +compile_typeof(Expr *expr) +{ + Sym *sym; + + expr->type = L_string; + unless (expr->b->kind == L_EXPR_ID) { + L_errf(expr, "argument to typeof() not a variable"); + return (0); + } + sym = sym_lookup(expr->b, 0); + if (sym) { + if (sym->type->name) { + push_lit(sym->type->name); + } else { + push_lit(L_type_str(sym->type->kind)); + } + } + return (1); // stack effect +} + +private int +compile_read(Expr *expr) +{ + int n; + Expr *buf, *fd, *nbytes; + + expr->type = L_int; + push_lit("Lread_"); + n = compile_exprs(expr->b, L_PUSH_VAL); + unless ((n == 2) || (n == 3)) { + L_errf(expr, "incorrect # args to read()"); + return (0); + } + fd = expr->b; + unless (typeisf(fd, "FILE") || ispoly(fd)) { + L_errf(expr, "first arg to read() must have type FILE"); + return (0); + } + buf = fd->next; + unless (isaddrof(buf) && (isstring(buf->a) || ispoly(buf->a))) { + L_errf(expr, "second arg to read() must have type string&"); + return (0); + } + nbytes = buf->next; + if (nbytes) { + unless (isint(nbytes) || ispoly(nbytes)) { + L_errf(expr, "third arg to read() must have type int"); + return (0); + } + } + emit_invoke(n+1); + return (1); // stack effect +} + +private int +compile_write(Expr *expr) +{ + int n; + Expr *buf, *fd, *nbytes; + + expr->type = L_int; + push_lit("Lwrite_"); + n = compile_exprs(expr->b, L_PUSH_VAL); + unless (n == 3) { + L_errf(expr, "incorrect # args to write()"); + return (0); + } + fd = expr->b; + unless (typeisf(fd, "FILE") || ispoly(fd)) { + L_errf(expr, "first arg to write() must have type FILE"); + return (0); + } + buf = fd->next; + unless (isstring(buf) || iswidget(buf) || ispoly(buf)) { + L_errf(expr, "second arg to write() must have type string"); + return (0); + } + nbytes = buf->next; + unless (isint(nbytes) || ispoly(nbytes)) { + L_errf(expr, "third arg to write() must have type int"); + return (0); + } + emit_invoke(4); + return (1); // stack effect +} + +/* + * Allowable forms of system(): + * + * int system(string cmd) + * int system(string cmd, STATUS &s) + * int system(string argv[]) + * int system(string argv[], STATUS &s) + * int system(cmd | argv[], string in, string &out, string &err) + * int system(cmd | argv[], string in, string &out, string &err, STATUS &) + * int system(cmd | argv[], string[] in, string[] &out, string[] &err) + * int system(cmd | argv[], string[] in, string[] &out, string[] &err,STATUS &) + * int system(cmd | argv[], "input", "${outf}", "errors") + * int system(cmd | argv[], "input", "${outf}", "errors", STATUS &s) + * int system(cmd | argv[], FILE in, FILE out, FILE err); + * int system(cmd | argv[], FILE in, FILE out, FILE err, STATUS &s); + * + * and spawn(): + * + * int spawn(string cmd) + * int spawn(string cmd, STATUS &s) + * int spawn(string argv[]) + * int spawn(string argv[], STATUS &s) + * int spawn(cmd | argv[], string in, FILE out, FILE err) + * int spawn(cmd | argv[], string in, FILE out, FILE err, STATUS &s) + * int spawn(cmd | argv[], string[] in, FILE out, FILE err) + * int spawn(cmd | argv[], string[] in, FILE out, FILE err, STATUS &s) + * int spawn(cmd | argv[], "input", "${outf}", "errors") + * int spawn(cmd | argv[], "input", "${outf}", "errors", STATUS &s) + * int spawn(cmd | argv[], FILE in, FILE out, FILE err) + * int spawn(cmd | argv[], FILE in, FILE out, FILE err, STATUS &s) + * + * Convert these into a call to system_ or spawn_ that has exactly + * seven args, the last being flags indicating the number and type of + * what the user supplied. + */ + +private int +compile_spawn_system(Expr *expr) +{ + int flags = 0, n; + Expr *cmd; + Expr *err = NULL, *in = NULL, *out = NULL, *status = NULL; + enum { SYSTEM, SPAWN } kind; + + kind = isid(expr->a, "system") ? SYSTEM : SPAWN; + + push_lit("system_"); + n = compile_exprs(expr->b, L_PUSH_VAL); + + expr->type = L_poly; + cmd = expr->b; + switch (n) { + case 1: + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + break; + case 2: + status = cmd->next; + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + TclEmitInstInt1(INST_ROT, 3, L->frame->envPtr); + break; + case 4: + in = cmd->next; + out = in->next; + err = out->next; + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + break; + case 5: + in = cmd->next; + out = in->next; + err = out->next; + status = err->next; + break; + default: + L_errf(expr, "incorrect # args"); + return (0); + } + if (isstring(cmd) || ispoly(cmd)) { + } else if (isarrayof(cmd, L_STRING | L_POLY) || islist(cmd)) { + flags |= SYSTEM_ARGV; + } else { + L_errf(expr, "first arg must be string or string array"); + } + switch (kind) { + case SYSTEM: flags |= typeck_system(in, out, err); break; + case SPAWN: flags |= typeck_spawn(in, out, err); break; + } + if (status) { + Type *base_type = status->type->base_type; + unless (isid(status, "undef") || + (isnameoftype(status->type) && + (ispolytype(base_type) || typeis(base_type, "STATUS")))) { + L_errf(expr, "last arg must be of type STATUS &"); + return (0); + } + } + push_litf("0x%x", flags); + emit_invoke(7); + expr->type = L_int; + return (1); +} + +private int +typeck_spawn(Expr *in, Expr *out, Expr *err) +{ + int flags = 0; + + if (!in || isid(in, "undef")) { + } else if (typeisf(in, "FILE")) { + flags |= SYSTEM_IN_HANDLE; + } else if (isstring(in) && (isconst(in) || isinterp(in))) { + flags |= SYSTEM_IN_FILENAME; + } else if (isstring(in) || ispoly(in)) { + flags |= SYSTEM_IN_STRING; + } else if (isarrayof(in, L_STRING | L_POLY) || islist(in)) { + flags |= SYSTEM_IN_ARRAY; + } else { + L_errf(in, "second arg must be FILE, or " + "string constant/variable/array"); + } + if (!out || isid(out, "undef")) { + } else if (typeisf(out, "FILE")) { + flags |= SYSTEM_OUT_HANDLE; + } else if (isstring(out) && (isconst(out) || isinterp(out))) { + flags |= SYSTEM_OUT_FILENAME; + } else { + L_errf(out, "third arg must be FILE, or string constant"); + } + if (!err || isid(err, "undef")) { + } else if (typeisf(err, "FILE")) { + flags |= SYSTEM_ERR_HANDLE; + } else if (isstring(err) && (isconst(err) || isinterp(err))) { + flags |= SYSTEM_ERR_FILENAME; + } else { + L_errf(err, "fourth arg must be FILE, or string constant"); + } + + return (flags | SYSTEM_BACKGROUND); +} + +private int +typeck_system(Expr *in, Expr *out, Expr *err) +{ + int flags = 0; + + if (!in || isid(in, "undef")) { + } else if (typeisf(in, "FILE")) { + flags |= SYSTEM_IN_HANDLE; + } else if (isstring(in) && (isconst(in) || isinterp(in))) { + flags |= SYSTEM_IN_FILENAME; + } else if (isstring(in) || ispoly(in)) { + flags |= SYSTEM_IN_STRING; + } else if (isarrayof(in, L_STRING | L_POLY) || islist(in)) { + flags |= SYSTEM_IN_ARRAY; + } else { + L_errf(in, "second arg must be FILE, or " + "string constant/variable/array"); + } + if (!out || isid(out, "undef")) { + } else if (typeisf(out, "FILE")) { + flags |= SYSTEM_OUT_HANDLE; + } else if (isstring(out) && (isconst(out) || isinterp(out))) { + flags |= SYSTEM_OUT_FILENAME; + } else if (isaddrof(out) && (isstring(out->a) || ispoly(out->a))) { + flags |= SYSTEM_OUT_STRING; + } else if (isaddrof(out) && isarrayof(out->a, L_STRING | L_POLY)) { + flags |= SYSTEM_OUT_ARRAY; + } else { + L_errf(out, "third arg must be FILE, string " + "constant, or reference to string or string array"); + } + if (!err || isid(err, "undef")) { + } else if (typeisf(err, "FILE")) { + flags |= SYSTEM_ERR_HANDLE; + } else if (isstring(err) && (isconst(err) || isinterp(err))) { + flags |= SYSTEM_ERR_FILENAME; + } else if (isaddrof(err) && (isstring(err->a) || ispoly(err->a))) { + flags |= SYSTEM_ERR_STRING; + } else if (isaddrof(err) && isarrayof(err->a, L_STRING | L_POLY)) { + flags |= SYSTEM_ERR_ARRAY; + } else { + L_errf(err, "fourth arg must be FILE, string " + "constant, or reference to string or string array"); + } + + return (flags); +} + +private int +compile_popen(Expr *expr) +{ + int flags = 0, n; + Expr *cb, *cmd, *mode; + VarDecl *args; + Type *want; + YYLTYPE loc = { 0 }; + + push_lit("popen_"); + expr->type = L_poly; + + n = compile_exprs(expr->b, L_PUSH_VAL); + unless ((n == 2) || (n == 3)) { + L_errf(expr, "incorrect # args to popen"); + return (0); + } + cmd = expr->b; + mode = cmd->next; + cb = mode->next; + + if (isarrayof(cmd, L_STRING | L_POLY) || islist(cmd)) { + flags |= SYSTEM_ARGV; + } else unless (isstring(cmd) || ispoly(cmd)) { + L_errf(cmd, "first arg to popen must be string or string array"); + } + + L_typeck_expect(L_STRING, mode, "in second arg to popen"); + + // To typecheck the optional stderr-callback arg, build a + // type descriptor and let L_typeck_same() do the work. + if (cb) { + args = ast_mkVarDecl(L_string, NULL, loc, loc); + args->next = ast_mkVarDecl(L_string, NULL, loc, loc); + want = type_mkNameOf(type_mkFunc(L_void, args)); + unless (L_typeck_same(want, cb->type)) { + L_errf(cb, "illegal type for stderr callback"); + } + flags |= SYSTEM_OUT_HANDLE; + } else { + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + } + + push_litf("0x%x", flags); + emit_invoke(5); + expr->type = L_typedef_lookup("FILE"); + ASSERT(expr->type); + return (1); +} + +/* + * Return a copy of the source text for the given expression. Caller + * must free. + */ +private char * +get_text(Expr *expr) +{ + int beg = expr->node.loc.beg; + int end = expr->node.loc.end; + int len = end - beg; + char *s; + + s = ckalloc(len + 1); + strncpy(s, Tcl_GetString(L->script)+beg, len); + s[len] = 0; + return (s); +} + +/* + * Emit code to compute the value of the given expression. The flags + * say in what form the generated code should produce the value. The + * caller chooses these flags based on whether + * 1. expr will be read, written, or both; and whether + * 2. expr is a deep dive or something else (an object dereference, + * a variable, or an expression). + * The flags are bit-masks (see below) and can be combined. + * + * Passing in one of the pointer flags means that IF the expr is a + * deep dive, leave a deep-ptr to it and possibly also its value on + * the run-time stack. If the expr is not, evaluate it (so that + * expr->sym etc is valid) but don't push anything. You use this when + * expr is an l-value. + * + * Passing in L_PUSH_VAL and none of the pointer flags means that + * the expr's value is left on the stack. + * + * Passing in both L_PUSH_VAL and one of the pointer flags is done + * when the caller needs a deep-ptr if expr is a deep dive but + * just wants the value otherwise. You use this when expr is + * an l-value but you also need the r-value, such as when + * compiling ++/-- or =~. + * + * Passing in L_PUSH_NAME means the fully qualified name of the + * variable is left on the stack and is valid only for certain + * kinds of variables (globals, locals, class variables, or class + * instance variables). + * + * L_PUSH_VAL push value onto stack, unless deep dive and + * you also request a deep-ptr + * L_PUSH_PTR if deep dive, push deep-ptr onto stack + * L_PUSH_PTRVAL if deep dive, push deep-ptr then value onto stack + * L_PUSH_VALPTR if deep dive, push value then deep-ptr onto stack + * L_LVALUE if deep dive, create an un-shared copy for writing + * L_DISCARD evaluate expr then discard its value + * L_PUSH_NAME push fully qualified name of variable, not the value + */ +private int +compile_expr(Expr *expr, Expr_f flags) +{ + int n = 0; + int start_off = currOffset(L->frame->envPtr); + + ++L->expr_level; + + /* The compile_xxx returns indicate whether they pushed anything. */ + unless (expr) return (0); + switch (expr->kind) { + case L_EXPR_FUNCALL: + n = compile_fnCall(expr); + break; + case L_EXPR_CONST: + case L_EXPR_RE: + push_lit(expr->str); + n = 1; + break; + case L_EXPR_ID: + n = compile_var(expr, flags); + break; + case L_EXPR_UNOP: + n = compile_unOp(expr); + break; + case L_EXPR_BINOP: + n = compile_binOp(expr, flags); + break; + case L_EXPR_TRINOP: + n = compile_trinOp(expr); + break; + default: + L_bomb("Unknown expression type %d", expr->kind); + } + + /* + * Throw away the value if requested by the caller. This is done + * for expressions that are statements, in for-loop pre and + * post expressions, etc. + */ + if (flags & L_DISCARD) { + while (n--) emit_pop(); + } + + track_cmd(start_off, expr); + + --L->expr_level; + return (n); +} + +/* + * If a function-call name begins with a cap and has an _ inside, it + * looks like a pattern call. From a name like "Foo_barBazBlech" + * create Expr const nodes "foo", "Foo_*" and a linked list of Expr + * const nodes for "bar", "baz", and "blech". Note that the returned + * Expr's need not be freed explicitly since all AST nodes are + * deallocated by the compiler. + */ +private int +ispatternfn(char *name, Expr **foo, Expr **Foo_star, Expr **opts, int *nopts) +{ + int i; + char *buf, *p, *under; + Expr *e; + + unless ((name[0] >= 'A') && (name[0] <= 'Z') && + (p = strchr(name, '_')) && p[1]) { // _ cannot be last + return (FALSE); + } + + under = p; + *under = '\0'; + + /* Build foo from Foo_bar. */ + buf = cksprintf("%s", name); + buf[0] = tolower(buf[0]); + *foo = mkId(buf); + ckfree(buf); + + /* Build Foo_* from Foo_bar. */ + buf = cksprintf("%s_*", name); + *Foo_star = mkId(buf); + ckfree(buf); + + /* Build a list of bar,baz,blech nodes from barBazBlech. */ + ++p; + *opts = NULL; + *nopts = 0; + while (*p) { + YYLTYPE loc = { 0 }; + *p = tolower(*p); + buf = ckalloc(strlen(p) + 1); + for (i = 0; *p && !isupper(*p); ++p, ++i) { + buf[i] = *p; + } + buf[i] = 0; + e = ast_mkConst(L_string, buf, loc, loc); + APPEND_OR_SET(Expr, next, *opts, e); + ++(*nopts); + } + + *under = '_'; + + return (TRUE); +} + +/* + * Rules for compiling a function call like "foo(arg)": + * + * - If foo is a variable of type name-of function, assume it contains + * the name of the function to call. + * + * - Otherwise call foo. If foo isn't declared, that's OK, we just + * won't have a prototype to type-check against. + * + * For a function call like "Foo_bar(a,b,c)" or "Foo_barBazBlech(a,b,c)", + * where the name starts with [A-Z] and has an _ in it (except at the + * end), we have what's called a "pattern function". The "bar", "baz", + * and "blech" are the "options", and "a", "b", and "c" are the "arguments". + * + * - If Foo_bar happens to be a declared function, handle as above. + * + * - If the function Foo_* is defined, change the call to + * Foo_*(bar,baz,blech,a,b,c). + * + * - If "a" is not of widget type, change the call to + * foo(bar,baz,blech,a,b,c). + * + * - If "a" is a widget type, change the call to *a(bar,baz,blech,b,c) + * where *a means that the value of the argument "a" becomes the + * function name. + */ +private int +compile_fnCall(Expr *expr) +{ + int expand, i, level, nopts; + int num_parms = 0, typchk = FALSE; + char *name; + char *defchk = NULL; // name for definedness chk before main() runs + Expr *foo, *Foo_star, *opts, *p; + Sym *sym; + VarDecl *formals = NULL; + + ASSERT(expr->a->kind == L_EXPR_ID); + name = expr->a->str; + + /* Check for an (expand) in the arg list. */ + expand = 0; + for (p = expr->b; p; p = p->next) { + if (isexpand(p)) { + TclEmitOpcode(INST_EXPAND_START, L->frame->envPtr); + expand = 1; + break; + } + } + + /* + * Check for an L built-in function. XXX change the array to + * a hash if the number of built-ins grows much more. + */ + for (i = 0; i < sizeof(builtins)/sizeof(builtins[0]); ++i) { + if (!strcmp(builtins[i].name, name)) { + if (expand) { + L_errf(expr, "(expand) illegal with " + "this function"); + } + i = builtins[i].fn(expr); + /* Copy out hash/array elements passed by reference. */ + copyout_parms(expr->b); + return (i); + } + } + + level = fnCallBegin(); + sym = sym_lookup(expr->a, L_NOWARN); + + if (sym && isfntype(sym->type)) { + /* A regular call -- the name is the fn name. */ + push_lit(sym->tclname); + formals = sym->type->u.func.formals; + typchk = TRUE; + defchk = name; + expr->type = sym->type->base_type; + } else if (sym && (sym->type->kind == L_NAMEOF) && + (sym->type->base_type->kind == L_FUNCTION)) { + /* + * Name is a function "pointer". It holds the function + * name and its type is the function proto. + */ + emit_load_scalar(sym->idx); + formals = sym->type->base_type->u.func.formals; + typchk = TRUE; + expr->type = sym->type->base_type->base_type; + } else if (sym) { + /* Name is declared but isn't a function or fn pointer. */ + L_errf(expr, "'%s' is declared but not as a function", name); + expr->type = L_poly; + } else if (ispatternfn(name, &foo, &Foo_star, &opts, &nopts)) { + /* Pattern function. Figure out which kind. */ + if ((sym = sym_lookup(Foo_star, L_NOWARN))) { + /* Foo_* is defined -- compile Foo_*(opts,a,b,c). */ + push_lit(Foo_star->str); + APPEND(Expr, next, opts, expr->b); + expr->b = opts; + formals = sym->type->u.func.formals; + typchk = TRUE; + defchk = Foo_star->str; + expr->type = sym->type->base_type; + } else { + /* Push first arg, then check its type. */ + compile_expr(expr->b, L_PUSH_VAL); + if (!expr->b) { + /* No args, compile as foo(opts). */ + push_lit(foo->str); + num_parms = push_parms(opts, NULL); + defchk = foo->str; + } else if (iswidget(expr->b)) { + /* Compile as *a(opts,b,c). */ + APPEND(Expr, next, opts, expr->b->next); + expr->b = opts; + } else { + /* Compile as foo(opts,a,b,c). */ + // a + push_lit(foo->str); + num_parms = push_parms(opts, NULL); + ASSERT(num_parms == nopts); + // a foo <opts> + TclEmitInstInt1(isexpand(expr->b)? + INST_EXPAND_ROT : INST_ROT, + nopts + 1, + L->frame->envPtr); + // foo <opts> a + expr->b = expr->b->next; + ++num_parms; + defchk = foo->str; + } + expr->type = L_poly; + } + } else { + /* Call to an undeclared function. */ + push_lit(name); + expr->type = L_poly; + defchk = name; + } + num_parms += push_parms(expr->b, formals); + if (expand) { + emit_invoke_expanded(); + } else { + emit_invoke(num_parms+1); + } + + /* + * Handle the copy-out part of copy in/out parameters. + * These are any deep-dive expressions that are passed by reference. + */ + copyout_parms(expr->b); + + if (typchk) L_typeck_fncall(formals, expr); + fnCallEnd(level); + /* + * If the call is to a function name that is known now (e.g., + * not a function pointer), add it to the L->fn_calls list + * which is walked before main() is called to verify that the + * function exists. + */ + if (defchk) { + Tcl_Obj *nm = Tcl_NewStringObj(defchk, -1); + Tcl_Obj *val = Tcl_NewObj(); + Tcl_DictObjPut(L->interp, L->fn_calls, nm, val); + Tcl_SetVar2Ex(L->interp, "%%L_fnsCalled", NULL, L->fn_calls, + TCL_GLOBAL_ONLY); + } + return (1); // stack effect +} + +private void +copyout_parms(Expr *actuals) +{ + Expr *actual, *arg; + + /* + * Copy out any deep-dive expressions that were passed with &. + * For these, the actual's value was copied into a temp var + * and its name passed. Copy that temp back out. + */ + for (actual = actuals; actual; actual = actual->next) { + arg = actual->a; + unless (isaddrof(actual) && (arg->flags & L_SAVE_IDX)) { + continue; + } + emit_load_scalar(arg->u.deepdive.val->idx); + compile_assignFromStack(arg, arg->type, NULL, L_REUSE_IDX); + emit_pop(); + tmp_free(arg->u.deepdive.val); + arg->u.deepdive.val = NULL; + } +} + +private int +compile_var(Expr *expr, Expr_f flags) +{ + Sym *self, *sym; + + ASSERT(expr->kind == L_EXPR_ID); + + /* Check for pre-defined identifiers first. */ + if (isid(expr, "END")) { + TclEmitOpcode(INST_L_READ_SIZE, L->frame->envPtr); + unless ((L->idx_op == L_OP_ARRAY_INDEX) | + (L->idx_op == L_OP_ARRAY_SLICE)) { + L_errf(expr, + "END illegal outside of a string or array index"); + } + expr->type = L_int; + return (1); + } else if (isid(expr, "undef")) { + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + expr->type = L_poly; + return (1); + } else if (isid(expr, "__FILE__")) { + push_lit(expr->node.loc.file); + expr->type = L_string; + return (1); + } else if (isid(expr, "__LINE__")) { + push_litf("%d", expr->node.loc.line); + expr->type = L_int; + return (1); + } else if (isid(expr, "__FUNC__")) { + push_lit(frame_name()); + expr->type = L_string; + return (1); + } + + unless ((sym = sym_lookup(expr, flags))) { + // Undeclared variable. + expr->type = L_poly; + return (1); + } + expr->type = sym->type; + if (flags & L_PUSH_VAL) { + if (sym->kind & L_SYM_FN) { + L_errf(expr, "cannot use a function name as a value"); + } else { + emit_load_scalar(sym->idx); + } + return (1); + } else if (flags & L_PUSH_NAME) { + switch (canDeref(sym)) { + case DECL_GLOBAL_VAR: + if (sym->decl->flags & DECL_PRIVATE) { + push_litf("::_%s_%s", L->toplev, sym->name); + } else { + push_litf("::%s", sym->name); + } + break; + case DECL_LOCAL_VAR: + push_lit(sym->tclname); + break; + case DECL_FN: + push_lit(sym->tclname); + break; + case DECL_CLASS_VAR: + push_litf("::L::_class_%s::%s", + sym->decl->clsdecl->decl->id->str, + sym->name); + break; + case DECL_CLASS_INST_VAR: + self = sym_lookup(mkId("self"), L_NOWARN); + ASSERT(self); + emit_load_scalar(self->idx); + push_litf("::%s", sym->name); + TclEmitInstInt1(INST_STR_CONCAT1, 2, L->frame->envPtr); + break; + default: + ASSERT(0); + } + return (1); + } else { + /* Push nothing. */ + return (0); + } + /* Not reached. */ + ASSERT(0); + return (1); +} + +private int +compile_exprs(Expr *expr, Expr_f flags) +{ + int num_exprs; + + for (num_exprs = 0; expr; expr = expr->next, ++num_exprs) { + compile_expr(expr, flags); + } + return (num_exprs); +} + +/* + * Emit code to push the parameters to a function call and return the + * # pushed. Rules: + * + * - For two consecutive parms like "-foovariable, &foo", push "-foovariable" + * and then the name of "foo". This is legal only for globals, class + * variables, and class instance variables. + * + * - If undef is passed as a reference parameter, pass the name of the + * special variable L_undef_ref_parm_. Code in lib L sets read and + * write traces on this variable as a way to cause a run-time error + * upon access to it. + * + * - For everything else, push the value or name as indicated by whether + * the parm has the & operator; compile_expr() handles that. The type + * checker sorts out any mis-matches with the declared formals. + */ +private int +push_parms(Expr *actuals, VarDecl *formals) +{ + int i; + int widget_flag = FALSE; + int strlen_of_variable = strlen("variable"); + char *s; + Expr *a, *v; + Sym *sym; + + for (i = 0, a = actuals; a; a = a->next, ++i) { + if (isaddrof(a) && (a->a->kind == L_EXPR_ID) && + (sym = sym_lookup(a->a, L_NOWARN)) && + (sym->decl->flags & DECL_REF)) { + push_lit(sym->tclname); + a->type = type_mkNameOf(a->a->type); + } else if (isid(a, "undef") && + formals && isnameoftype(formals->type) && + !isfntype(formals->type->base_type)) { + push_lit("::L_undef_ref_parm_"); + a->type = L_poly; + } else { + compile_expr(a, L_PUSH_VAL); + } + if (widget_flag && isaddrof(a)) { + a->type = L_poly; + v = a->a; + /* can't use local vars or functions from a widget */ + if (v->sym && + ((v->sym->decl->flags & (DECL_LOCAL_VAR|DECL_FN)) || + !canDeref(v->sym))) { + L_errf(a, "illegal operand to &"); + } + } + s = a->str; + widget_flag = ((a->kind == L_EXPR_CONST) && + isstring(a) && + /* has at least the minimum length */ + (strlen(s) > strlen_of_variable) && + /* starts with '-' */ + (s[0] == '-') && + /* ends with "variable" */ + !strcmp("variable", s + (strlen(s) - strlen_of_variable))); + if (formals) formals = formals->next; + } + return (i); +} + +private int +compile_unOp(Expr *expr) +{ + switch (expr->op) { + case L_OP_BANG: + case L_OP_BITNOT: + if (expr->op == L_OP_BANG) { + compile_condition(expr->a); + } else { + compile_expr(expr->a, L_PUSH_VAL); + } + L_typeck_expect(L_INT, expr->a, "in unary ! or ~"); + emit_instrForLOp(expr, expr->type); + expr->type = expr->a->type; + break; + case L_OP_UPLUS: + case L_OP_UMINUS: + compile_expr(expr->a, L_PUSH_VAL); + L_typeck_expect(L_INT|L_FLOAT, expr->a, "in unary +/-"); + emit_instrForLOp(expr, expr->type); + expr->type = expr->a->type; + break; + case L_OP_DEFINED: + compile_defined(expr->a); + expr->type = L_int; + break; + case L_OP_ADDROF: + /* + * Compile &<expr>. For function names, regular + * variables, and class variables (&x, + * &classname->var, &obj->var), this is just the name + * of the Tcl variable. For a deep-dive expr, + * it's the name of a temp var that holds the value. + */ + compile_expr(expr->a, L_PUSH_NAME); + expr->type = type_mkNameOf(expr->a->type); + unless (expr->a->sym) { + L_errf(expr->a, "illegal operand to &"); + expr->type = L_poly; + } + break; + case L_OP_PLUSPLUS_PRE: + case L_OP_PLUSPLUS_POST: + case L_OP_MINUSMINUS_PRE: + case L_OP_MINUSMINUS_POST: + compile_incdec(expr); + expr->type = expr->a->type; + break; + case L_OP_EXPAND: + unless (fnInArgList()) { + L_errf(expr, "(expand) illegal in this context"); + } + compile_expr(expr->a, L_PUSH_VAL); + TclEmitInstInt4(INST_EXPAND_STKTOP, + L->frame->envPtr->currStackDepth, + L->frame->envPtr); + expr->type = L_poly; + break; + case L_OP_CMDSUBST: + push_lit("::backtick_"); + if (expr->a) { + compile_expr(expr->a, L_PUSH_VAL); + push_lit(expr->str); + TclEmitInstInt1(INST_STR_CONCAT1, 2, L->frame->envPtr); + } else { + push_lit(expr->str); + } + emit_invoke(2); + expr->type = L_string; + break; + case L_OP_FILE: + if (expr->a) { + push_lit("fgetline"); + compile_expr(expr->a, L_PUSH_VAL); + if (typeisf(expr->a, "FILE")) { + emit_invoke(2); + } else { + L_errf(expr->a, "expect FILE in <>"); + } + } else { + push_lit("angle_read_"); + emit_invoke(1); + } + expr->type = L_string; + break; + default: + L_bomb("Unknown unary operator %d", expr->op); + break; + } + return (1); // stack effect +} + +private int +compile_binOp(Expr *expr, Expr_f flags) +{ + int expand, level, n; + Type *type; + Expr *e; + + /* Return the net run-time stack effect (i.e., how much was pushed). */ + + switch (expr->op) { + case L_OP_EQUALS: + compile_assign(expr); + expr->type = expr->a->type; + return (1); + case L_OP_EQPLUS: + case L_OP_EQMINUS: + case L_OP_EQSTAR: + case L_OP_EQSLASH: + compile_assign(expr); + L_typeck_expect(L_INT|L_FLOAT, expr->a, + "in arithmetic assignment"); + expr->type = expr->a->type; + return (1); + case L_OP_EQPERC: + case L_OP_EQBITAND: + case L_OP_EQBITOR: + case L_OP_EQBITXOR: + case L_OP_EQLSHIFT: + case L_OP_EQRSHIFT: + compile_assign(expr); + L_typeck_expect(L_INT, expr->a, "in arithmetic assignment"); + expr->type = expr->a->type; + return (1); + case L_OP_EQDOT: + compile_assign(expr); + L_typeck_expect(L_STRING|L_WIDGET, expr->a, "in .="); + expr->type = expr->a->type; + return (1); + case L_OP_ANDAND: + case L_OP_OROR: + compile_shortCircuit(expr); + expr->type = L_int; + return (1); + case L_OP_STR_EQ: + case L_OP_STR_NE: + case L_OP_STR_GT: + case L_OP_STR_GE: + case L_OP_STR_LT: + case L_OP_STR_LE: + unless (hash_get(L->options, "allow_eq_ops")) { + L_errf(expr, "illegal comparison operator"); + } + /* Warn on things like "s eq undef". */ + if (isid(e=expr->a, "undef") || isid(e=expr->b, "undef")) { + L_errf(e, "undef illegal in comparison"); + } + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + L_typeck_expect(L_STRING|L_WIDGET, expr->a, + "in string comparison"); + L_typeck_expect(L_STRING|L_WIDGET, expr->b, + "in string comparison"); + emit_instrForLOp(expr, expr->type); + expr->type = L_int; + return (1); + case L_OP_EQUALEQUAL: + case L_OP_NOTEQUAL: + case L_OP_GREATER: + case L_OP_GREATEREQ: + case L_OP_LESSTHAN: + case L_OP_LESSTHANEQ: + expr->type = L_int; + /* Warn on things like "i == undef". */ + if (isid(e=expr->a, "undef") || isid(e=expr->b, "undef")) { + L_errf(e, "undef illegal in comparison"); + } + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + L_typeck_deny(L_VOID, expr->a); + L_typeck_deny(L_VOID, expr->b); + unless (L_typeck_compat(expr->a->type, expr->b->type) || + L_typeck_compat(expr->b->type, expr->a->type)) { + L_errf(expr, "incompatible types in comparison"); + return (0); + } + if (!isscalar(expr->a) && (expr->op != L_OP_EQUALEQUAL)) { + L_errf(expr, "only eq() allowed on non-scalar types"); + return (0); + } + compile_eq_stack(expr, expr->a->type); + return (1); // stack effect + case L_OP_PLUS: + case L_OP_MINUS: + case L_OP_STAR: + case L_OP_SLASH: + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + L_typeck_expect(L_INT|L_FLOAT, expr->a, + "in arithmetic operator"); + L_typeck_expect(L_INT|L_FLOAT, expr->b, + "in arithmetic operator"); + emit_instrForLOp(expr, expr->type); + if (isfloat(expr->a) || isfloat(expr->b)) { + expr->type = L_float; + } else { + expr->type = L_int; + } + return (1); + case L_OP_PERC: + case L_OP_BITAND: + case L_OP_BITOR: + case L_OP_BITXOR: + case L_OP_LSHIFT: + case L_OP_RSHIFT: + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + L_typeck_expect(L_INT, expr->a, "in arithmetic operator"); + L_typeck_expect(L_INT, expr->b, "in arithmetic operator"); + emit_instrForLOp(expr, expr->type); + expr->type = L_int; + return (1); + case L_OP_ARRAY_INDEX: + case L_OP_HASH_INDEX: + case L_OP_DOT: + case L_OP_POINTS: + return (compile_idxOp(expr, flags)); + case L_OP_CLASS_INDEX: + return (compile_clsDeref(expr, flags)); + case L_OP_INTERP_STRING: + case L_OP_INTERP_RE: + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + TclEmitInstInt1(INST_STR_CONCAT1, 2, L->frame->envPtr); + expr->type = L_string; + return (1); + case L_OP_LIST: + level = fnCallBegin(); + for (e = expr, expand = 0; e; e = e->b) { + if (e->a && isexpand(e->a)) { + TclEmitOpcode(INST_EXPAND_START, + L->frame->envPtr); + expand = 1; + break; + } + } + push_lit("::list"); + n = compile_expr(expr->a, L_PUSH_VAL); + if (n == 0) { // empty list {} + ASSERT(!expr->a && !expr->b); + type = L_poly; + } else if (iskv(expr->a)) { + ASSERT((n == 2) && ishash(expr->a)); + type = expr->a->type; + } else { + type = type_mkList(expr->a->type); + } + for (e = expr->b; e; e = e->b) { + ASSERT(e->op == L_OP_LIST); + n += compile_expr(e->a, L_PUSH_VAL); + if (ishashtype(type) && iskv(e->a)) { + } else if (islisttype(type) && !iskv(e->a)) { + /* + * The list type is literally a list of all the + * individual element types linked together. + */ + Type *t = type_mkList(e->a->type); + APPEND(Type, next, type, t); + } else unless (ispolytype(type)) { + L_errf(expr, "cannot mix hash and " + "non-hash elements"); + type = L_poly; + } + } + if (expand) { + emit_invoke_expanded(); + } else { + emit_invoke(n+1); + } + expr->type = type; + fnCallEnd(level); + return (1); + case L_OP_KV: + n = compile_expr(expr->a, L_PUSH_VAL); + n += compile_expr(expr->b, L_PUSH_VAL); + ASSERT(n == 2); + unless (isscalar(expr->a)) { + L_errf(expr->a, "hash keys must be scalar"); + } + expr->type = type_mkHash(expr->a->type, expr->b->type); + return (n); + case L_OP_EQTWID: + case L_OP_BANGTWID: + compile_twiddle(expr); + expr->type = L_int; + return (1); + case L_OP_COMMA: + compile_expr(expr->a, L_DISCARD); + compile_expr(expr->b, L_PUSH_VAL); + expr->type = expr->b->type; + return (1); + case L_OP_CAST: + return (compile_cast(expr, flags)); + case L_OP_CONCAT: + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + L_typeck_expect(L_STRING|L_WIDGET, expr->a, + "in lhs of . operator"); + L_typeck_expect(L_STRING|L_WIDGET, expr->b, + "in rhs of . operator"); + TclEmitInstInt1(INST_STR_CONCAT1, 2, L->frame->envPtr); + expr->type = L_string; + return (1); + default: + L_bomb("compile_binOp: malformed AST"); + return (1); + } +} + +private int +compile_cast(Expr *expr, Expr_f flags) +{ + int range; + Jmp *jmp; + Type *type = (Type *)expr->a; + + flags &= ~L_DISCARD; + if (flags & L_LVALUE) { + compile_expr(expr->b, flags); + } else if ((type->kind == L_INT) || (type->kind == L_FLOAT)) { + range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, + L->frame->envPtr); + TclEmitInstInt4(INST_BEGIN_CATCH4, range, L->frame->envPtr); + ExceptionRangeStarts(L->frame->envPtr, range); + if (type->kind == L_INT) { + push_lit("::tcl::mathfunc::int"); + compile_expr(expr->b, flags); + emit_invoke(2); + } else if (type->kind == L_FLOAT) { + push_lit("::tcl::mathfunc::double"); + compile_expr(expr->b, flags); + emit_invoke(2); + } + ExceptionRangeEnds(L->frame->envPtr, range); + jmp = emit_jmp_fwd(INST_JUMP4, 0); + /* error case */ + ExceptionRangeTarget(L->frame->envPtr, range, catchOffset); + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + /* out */ + fixup_jmps(&jmp); + TclEmitOpcode(INST_END_CATCH, L->frame->envPtr); + } else { + compile_expr(expr->b, flags); + } + L_typeck_deny(L_VOID|L_FUNCTION, expr->b); + expr->sym = expr->b->sym; + expr->flags = expr->b->flags; + expr->type = type; + return (1); +} + +private int +compile_trinOp(Expr *expr) +{ + int save, start_off; + int i = 0, n = 0; + Jmp *end_jmp, *false_jmp; + + switch (expr->op) { + case L_OP_EQTWID: + compile_twiddleSubst(expr); + expr->type = L_int; + n = 1; + break; + case L_OP_INTERP_STRING: + case L_OP_INTERP_RE: + compile_expr(expr->a, L_PUSH_VAL); + compile_expr(expr->b, L_PUSH_VAL); + compile_expr(expr->c, L_PUSH_VAL); + TclEmitInstInt1(INST_STR_CONCAT1, 3, L->frame->envPtr); + expr->type = L_string; + n = 1; + break; + case L_OP_ARRAY_SLICE: + compile_expr(expr->a, L_PUSH_VAL); + if (isstring(expr->a) || iswidget(expr->a)) { + push_lit("::string"); + push_lit("range"); + TclEmitInstInt1(INST_ROT, 2, L->frame->envPtr); + expr->type = L_string; + i = 5; + } else if (isarray(expr->a) || islist(expr->a)) { + push_lit("::lrange"); + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + expr->type = expr->a->type; + i = 4; + } else { + L_errf(expr->a, "illegal type for slice"); + expr->type = L_poly; + } + if (has_END(expr->b) || has_END(expr->c)) { + if (isstring(expr->a) || iswidget(expr->a)) { + TclEmitOpcode(INST_L_PUSH_STR_SIZE, + L->frame->envPtr); + } else { + TclEmitOpcode(INST_L_PUSH_LIST_SIZE, + L->frame->envPtr); + } + } + save = L->idx_op; + L->idx_op = L_OP_ARRAY_SLICE; + compile_expr(expr->b, L_PUSH_VAL); + unless (isint(expr->b)) { + L_errf(expr->b, "first slice index not an int"); + } + compile_expr(expr->c, L_PUSH_VAL); + unless (isint(expr->c)) { + L_errf(expr->c, "second slice index not an int"); + } + L->idx_op = save; + if (has_END(expr->b) || has_END(expr->c)) { + TclEmitOpcode(INST_L_POP_SIZE, L->frame->envPtr); + } + emit_invoke(i); + n = 1; + break; + case L_OP_TERNARY_COND: + compile_condition(expr->a); + false_jmp = emit_jmp_fwd(INST_JUMP_FALSE4, NULL); + start_off = currOffset(L->frame->envPtr); + n = compile_expr(expr->b, L_PUSH_VAL); + end_jmp = emit_jmp_fwd(INST_JUMP4, NULL); + track_cmd(start_off, expr->b); + fixup_jmps(&false_jmp); + start_off = currOffset(L->frame->envPtr); + compile_expr(expr->c, L_PUSH_VAL); + track_cmd(start_off, expr->c); + fixup_jmps(&end_jmp); + if (ispoly(expr->b) || ispoly(expr->c)) { + expr->type = L_poly; + } else if (L_typeck_same(expr->b->type, expr->c->type)) { + expr->type = expr->b->type; + } else if ((expr->b->type->kind & (L_INT|L_FLOAT)) && + (expr->c->type->kind & (L_INT|L_FLOAT))) { + expr->type = L_float; + } else { + L_errf(expr, "incompatible types in ? : expressions"); + expr->type = L_poly; + } + break; + default: + L_bomb("compile_trinOp: malformed AST"); + } + return (n); // stack effect +} + + +/* + * There are two kinds of defined(): + * defined(&var) - var is a call-by-reference formal + * defined(expr) - otherwise + */ +private void +compile_defined(Expr *expr) +{ + Sym *sym; + + if (isaddrof(expr)) { + unless (expr->a->kind == L_EXPR_ID) { + L_errf(expr, "arg to & not a call-by-reference parm"); + return; + } + sym = sym_lookup(expr->a, L_NOWARN); + unless (sym && (sym->decl->flags & DECL_REF)) { + L_errf(expr, "%s undeclared or not a " + "call-by-reference parm", expr->a->str); + return; + } + push_lit("::L_undef_ref_parm_"); + TclEmitInstInt4(INST_DIFFERENT_OBJ, sym->idx, L->frame->envPtr); + } else { + compile_expr(expr, L_PUSH_VAL); + L_typeck_deny(L_VOID, expr); + TclEmitOpcode(INST_L_DEFINED, L->frame->envPtr); + } +} + +/* + * Estimate how many submatches are in the given regexp. These are + * the sub-expressions within parens. If the regexp includes an + * interpolated string, we can't get this exact, so just assume + * the maximum (9) in that case. + */ +private int +re_submatchCnt(Expr *re) +{ + int n = 9; + Tcl_Obj *const_regexp; + Tcl_RegExp compiled; + + if (re->kind == L_EXPR_RE) { + const_regexp = Tcl_NewStringObj(re->str, -1); + Tcl_IncrRefCount(const_regexp); + compiled = Tcl_GetRegExpFromObj(L->interp, const_regexp, + TCL_REG_ADVANCED); + Tcl_DecrRefCount(const_regexp); + if (compiled) n = ((TclRegexp *)compiled)->re.re_nsub; + } + return (n); +} + +/* + * Determine whether a regexp is a constant (which can be matched with + * a string comparison), a glob (use string-match bytecode), a simpler + * regexp (no submatches, use the regexp bytecode), or a more complex + * regexp which requires the ::regexp command. If the regexp is + * interpolated, we can't tell for sure, so assume the worst. Also + * return flags indicating whether the re expr needs to be compiled. + * + * If ds is non-NULL return the equivalent glob in *ds; this becomes + * an operand to INST_STR_EQ or INST_STR_MATCH. + */ +private ReKind +re_kind(Expr *re, Tcl_DString *ds) +{ + Tcl_DString myds; + int exact, ret = 0; + + unless ((re->kind == L_EXPR_RE) || (re->op == L_OP_INTERP_RE)) { + return (RE_NOT_AN_RE); + } + unless (ds) ds = &myds; // to accommodate passing in ds==NULL + + if (re->op == L_OP_INTERP_RE) { + ret |= RE_NEEDS_EVAL; + } + if (re->flags & L_EXPR_RE_L) { + ret |= RE_NEEDS_EVAL | RE_GLOB; + } else if (re_submatchCnt(re) || (re->flags & L_EXPR_RE_G)) { + ret |= RE_NEEDS_EVAL | RE_COMPLEX; + } else if (isstring(re) && + (TclReToGlob(NULL, re->str, strlen(re->str), + ds, &exact, NULL) == TCL_OK) && + exact) { + if (ds == &myds) Tcl_DStringFree(&myds); + ret |= RE_CONST; + } else { + ret |= RE_NEEDS_EVAL | RE_SIMPLE; + } + return (ret); +} + +private void +compile_twiddle(Expr *expr) +{ + compile_expr(expr->a, L_PUSH_VAL); + compile_reMatch(expr->b); + if (expr->op == L_OP_BANGTWID) { + TclEmitOpcode(INST_LNOT, L->frame->envPtr); + L_typeck_expect(L_STRING|L_WIDGET, expr->a, "in !~"); + } else { + L_typeck_expect(L_STRING|L_WIDGET, expr->a, "in =~"); + } +} + +/* + * Compile a regexp match. It is assumed that the value to compare + * the regexp against will already be on the run-time stack. Code to + * push the regexp is generated here. When run, these are replaced + * with the match Boolean. + */ +private void +compile_reMatch(Expr *re) +{ + int i, cflags, mod_cnt, submatch_cnt; + int nocase = (re->flags & L_EXPR_RE_I); + Sym *s; + Expr *id; + ReKind kind; + Tcl_DString ds; + + kind = re_kind(re, &ds); + /* First push the regexp. */ + if (kind & RE_NEEDS_EVAL) { + compile_expr(re, L_PUSH_VAL); + } else { + push_lit(Tcl_DStringValue(&ds)); + Tcl_DStringFree(&ds); + } + /* Now emit the appropriate match instruction. */ + switch (kind & (RE_CONST|RE_GLOB|RE_SIMPLE|RE_COMPLEX)) { + case RE_CONST: + TclEmitOpcode(INST_STR_EQ, L->frame->envPtr); + break; + case RE_GLOB: + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + TclEmitInstInt1(INST_STR_MATCH, nocase, L->frame->envPtr); + break; + case RE_SIMPLE: + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + cflags = TCL_REG_ADVANCED | TCL_REG_NLSTOP | + (nocase ? TCL_REG_NOCASE : 0); + TclEmitInstInt1(INST_REGEXP, cflags, L->frame->envPtr); + break; + case RE_COMPLEX: + // val re + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + // re val + push_lit("::regexp"); + mod_cnt = push_regexpModifiers(re); + push_lit("--"); + // re val ::regexp <mods> -- + TclEmitInstInt1(INST_ROT, mod_cnt+3, L->frame->envPtr); + // val ::regexp <mods> -- re + TclEmitInstInt1(INST_ROT, mod_cnt+3, L->frame->envPtr); + // ::regexp <mods> -- re val + /* Submatch vars. This loop always iterates at least once. */ + submatch_cnt = re_submatchCnt(re); + for (i = 0; i <= submatch_cnt; i++) { + char buf[32]; + snprintf(buf, sizeof(buf), "$%d", i); + id = mkId(buf); + unless (sym_lookup(id, L_NOWARN)) { + s = sym_mk(buf, L_string, + SCOPE_LOCAL | DECL_LOCAL_VAR); + s->used_p = TRUE; // suppress unused var warning + } + push_lit(buf); + } + emit_invoke(5 + submatch_cnt + mod_cnt); + break; + default: ASSERT(0); + } +} + +private void +compile_twiddleSubst(Expr *expr) +{ + Expr *id, *lhs = expr->a; + int i, modCount, submatchCount; + Sym *s; + Tmp *tmp = NULL; + Tcl_Obj *varList; + + push_lit("::regsub"); + modCount = push_regexpModifiers(expr->b); + /* Submatch vars. This loop always iterates at least once. */ + push_lit("-submatches"); + submatchCount = re_submatchCnt(expr->b); + varList = Tcl_NewObj(); + Tcl_IncrRefCount(varList); + for (i = 0; i <= submatchCount; i++) { + char buf[32]; + snprintf(buf, sizeof(buf), "$%d", i); + id = mkId(buf); + unless (sym_lookup(id, L_NOWARN)) { + s = sym_mk(buf, L_string, + SCOPE_LOCAL | DECL_LOCAL_VAR); + s->used_p = TRUE; // suppress unused var warning + } + Tcl_AppendPrintfToObj(varList, "$%d ", i); + } + push_lit(Tcl_GetString(varList)); + Tcl_DecrRefCount(varList); + push_lit("-line"); + push_lit("--"); + compile_expr(expr->b, L_PUSH_VAL); + // ::regsub <mods> -submatches <varlist> -line -- <re> + compile_expr(expr->c, L_PUSH_VAL); + // ::regsub <mods> -submatches <varlist> -line -- <re> <subst> + compile_expr(lhs, L_PUSH_VALPTR | L_PUSH_VAL | L_LVALUE); + unless (lhs->sym) { + L_errf(expr, "invalid l-value in =~"); + return; + } + if (isdeepdive(lhs)) { + tmp = tmp_get(TMP_REUSE); + // ::regsub <mods> -submatches <varlist> + // -line -- <re> <subst> <lhs-val> <lhs-ptr> + TclEmitInstInt1(INST_ROT, -(8+modCount), L->frame->envPtr); + // <lhs-ptr> ::regsub <mods> -submatches <varlist> + // -line -- <re> <subst> <lhs-val> + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + // <lhs-ptr> ::regsub <mods> -submatches <varlist> + // -line -- <re> <lhs-val> <subst> + push_lit(tmp->name); + // <lhs-ptr> ::regsub <mods> -submatches <varlits> + // -line -- <re> <lhs-val> <subst> <tmp-name> + } else { + // ::regsub <mods> -submatches <varlist> + // -line -- <re> <subst> <lhs-val> + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + // ::regsub <mods> -submatches <varlist> + // -line -- <re> <lhs-val> <subst> + push_lit(lhs->sym->tclname); + // ::regsub <mods> -submatches <varlist> + // -line -- <re> <lhs-val> <subst> <lhs-name> + } + emit_invoke(modCount + 9); + if (isdeepdive(lhs)) { + // <lhs-ptr> <match> + emit_load_scalar(tmp->idx); + tmp_free(tmp); + // <lhs-ptr> <match> <new-val> + TclEmitInstInt1(INST_ROT, 2, L->frame->envPtr); + // <match> <new-val> <lhs-ptr> + TclEmitInstInt4(INST_L_DEEP_WRITE, + lhs->sym->idx, + L->frame->envPtr); + TclEmitInt4(L_PUSH_NEW, L->frame->envPtr); + // <match> <new-val> + emit_pop(); + } + L_typeck_expect(L_STRING|L_WIDGET, lhs, "in =~"); + // <match> +} + +private void +compile_shortCircuit(Expr *expr) +{ + Jmp *jmp; + unsigned char op; + + /* + * In case the operator "a op b" short-circuits, we need one + * value of "a" on the stack for the test and one for the value of + * the expression. If the operator doesn't short-circuit, we + * pop one of these off and move on to evaluating "b". + */ + ASSERT((expr->op == L_OP_ANDAND) || (expr->op == L_OP_OROR)); + op = (expr->op == L_OP_ANDAND) ? INST_JUMP_FALSE4 : INST_JUMP_TRUE4; + compile_condition(expr->a); + // <a-val> + TclEmitOpcode(INST_DUP, L->frame->envPtr); + // <a-val> <a-val> + jmp = emit_jmp_fwd(op, NULL); + // <a-val> if short-circuit and we jumped out + // <a-val> if did not short-circuit and we're still going + emit_pop(); + compile_condition(expr->b); + fixup_jmps(&jmp); + // <a-val> if short-circuit + // <b-val> if did not short-circuit +} + +/* + * Compile an expression that is used as a conditional test. + * This is compiled like a normal expression except that if it's + * of string type the expression is tested for defined. + */ +private void +compile_condition(Expr *cond) +{ + unless (cond) { + push_lit("1"); + return; + } + if (isaddrof(cond)) { + compile_defined(cond); + } else { + compile_expr(cond, L_PUSH_VAL); + if (isvoid(cond)) { + L_errf(cond, "void type illegal in predicate"); + } + unless (isint(cond) || isfloat(cond) || ispoly(cond)) { + TclEmitOpcode(INST_L_DEFINED, L->frame->envPtr); + } + } + cond->type = L_int; +} + +/* + * Compile if-unless as follows. + * + * No "else" leg: "Else" leg present: + * <eval cond> <eval cond> + * jmpFalse 1 jmpFalse 1 + * <if leg> <if leg> + * 1: jmp 2 + * 1: <else leg> + * 2: + */ +private void +compile_ifUnless(Cond *cond) +{ + Jmp *endjmp, *falsejmp; + + /* Test the condition and jmp if false. */ + compile_condition(cond->cond); + falsejmp = emit_jmp_fwd(INST_JUMP_FALSE4, NULL); + + /* Compile the "if" leg. */ + frame_push(cond, NULL, SEARCH); + compile_stmts(cond->if_body); + + if (cond->else_body) { + /* "Else" leg present. */ + frame_pop(); + frame_push(cond, NULL, SEARCH); + endjmp = emit_jmp_fwd(INST_JUMP4, NULL); + fixup_jmps(&falsejmp); + compile_stmts(cond->else_body); + fixup_jmps(&endjmp); + } else { + /* No "else" leg. */ + fixup_jmps(&falsejmp); + } + frame_pop(); +} + +private void +compile_loop(Loop *loop) +{ + switch (loop->kind) { + case L_LOOP_DO: + compile_do(loop); + break; + case L_LOOP_FOR: + case L_LOOP_WHILE: + compile_for_while(loop); + break; + default: + L_bomb("bad loop type"); + break; + } +} + +/* + * Do loop: + * + * 1: <body> + * <cond> + * jmpTrue 1 + */ +private void +compile_do(Loop *loop) +{ + int body_off; + Jmp *break_jmps, *continue_jmps; + + body_off = currOffset(L->frame->envPtr); + frame_push(loop, NULL, LOOP|SEARCH); + compile_stmts(loop->body); + break_jmps = L->frame->break_jumps; + continue_jmps = L->frame->continue_jumps; + frame_pop(); + fixup_jmps(&continue_jmps); + + compile_condition(loop->cond); + emit_jmp_back(TCL_TRUE_JUMP, body_off); + fixup_jmps(&break_jmps); +} + +/* + * While loop: For loop: + * + * <pre> + * 1: <cond> 1: <cond> + * jmpFalse 2 jmpFalse 2 + * <body> <body> + * <post> + * jmp 1 jmp 1 + * 2: 2: + */ +private void +compile_for_while(Loop *loop) +{ + int cond_off; + Jmp *break_jmps, *continue_jmps, *out_jmp; + + if (loop->kind == L_LOOP_FOR) compile_exprs(loop->pre, L_DISCARD); + + cond_off = currOffset(L->frame->envPtr); + compile_condition(loop->cond); + out_jmp = emit_jmp_fwd(INST_JUMP_FALSE4, NULL); + + frame_push(loop, NULL, LOOP|SEARCH); + compile_stmts(loop->body); + break_jmps = L->frame->break_jumps; + continue_jmps = L->frame->continue_jumps; + frame_pop(); + fixup_jmps(&continue_jmps); + + if (loop->kind == L_LOOP_FOR) compile_exprs(loop->post, L_DISCARD); + + emit_jmp_back(TCL_UNCONDITIONAL_JUMP, cond_off); + fixup_jmps(&out_jmp); + fixup_jmps(&break_jmps); +} + +/* + * Emit a jump instruction to a backwards target. jmp_type is one of + * TCL_UNCONDITIONAL, TCL_TRUE_JUMP, or TCL_FALSE_JUMP. The jump + * opcope is appropriately selected for the jump distance. + */ +private void +emit_jmp_back(TclJumpType jmp_type, int offset) +{ + int op = 0; + int dist = currOffset(L->frame->envPtr) - offset; + + if (dist > 127) { + switch (jmp_type) { + case TCL_UNCONDITIONAL_JUMP: + op = INST_JUMP4; + break; + case TCL_TRUE_JUMP: + op = INST_JUMP_TRUE4; + break; + case TCL_FALSE_JUMP: + op = INST_JUMP_FALSE4; + break; + default: + L_bomb("bad jmp type"); + break; + } + TclEmitInstInt4(op, -dist, L->frame->envPtr); + } else { + switch (jmp_type) { + case TCL_UNCONDITIONAL_JUMP: + op = INST_JUMP1; + break; + case TCL_TRUE_JUMP: + op = INST_JUMP_TRUE1; + break; + case TCL_FALSE_JUMP: + op = INST_JUMP_FALSE1; + break; + default: + L_bomb("bad jmp type"); + break; + } + TclEmitInstInt1(op, -dist, L->frame->envPtr); + } +} + +/* + * Emit a jump instruction with an unknown target offset and return a + * structure that can be passed in to fixup_jmps() to later fix-up the + * target to any desired bytecode offset. Caller must free the + * returned structure. + */ +private Jmp * +emit_jmp_fwd(int op, Jmp *next) +{ + Jmp *ret = (Jmp *)ckalloc(sizeof(Jmp)); + + ret->op = op; + ret->offset = currOffset(L->frame->envPtr); + ret->next = next; + switch (op) { + case INST_JUMP1: + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + ret->size = 1; + TclEmitInstInt1(op, 0, L->frame->envPtr); + break; + case INST_JUMP4: + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + ret->size = 4; + TclEmitInstInt4(op, 0, L->frame->envPtr); + break; + default: + L_bomb("unexpected jump instruction"); + break; + } + return (ret); +} + +/* + * Fix up jump targets to point to the current PC, free the + * passed-in fix-ups list and then set it to NULL. + */ +private void +fixup_jmps(Jmp **p) +{ + int target; + Jmp *t; + Jmp *j = *p; + unsigned char *jmp_pc; + + while (j) { + target = currOffset(L->frame->envPtr) - j->offset; + jmp_pc = L->frame->envPtr->codeStart + j->offset; + switch (j->size) { + case 1: + ASSERT(*jmp_pc == j->op); + TclUpdateInstInt1AtPc(j->op, target, jmp_pc); + break; + case 4: + ASSERT(*jmp_pc == j->op); + TclUpdateInstInt4AtPc(j->op, target, jmp_pc); + break; + default: + L_bomb("unexpected jump fixup"); + break; + } + t = j->next; + ckfree((char *)j); + j = t; + } + *p = NULL; +} + +private void +compile_foreach(ForEach *loop) +{ + /* + * Handle foreach(s in <expr>). + */ + if (loop->expr->op == L_OP_FILE) { + compile_foreachAngle(loop); + return; + } + + compile_expr(loop->expr, L_PUSH_VAL); + + switch (loop->expr->type->kind) { + case L_ARRAY: + case L_LIST: + compile_foreachArray(loop); + break; + case L_HASH: + compile_foreachHash(loop); + break; + case L_STRING: + compile_foreachString(loop); + break; + default: + L_errf(loop->expr, "foreach expression must be" + " array, hash, or string"); + break; + } +} + +/* + * Most of the following function came from tclCompCmds.c + * TclCompileForEachCmd(), modified in various ways for L. + */ +private void +compile_foreachArray(ForEach *loop) +{ + int i, continue_off, num_vars; + Expr *var; + ForeachInfo *info; + ForeachVarList *varlist; + Jmp *break_jumps, *continue_jumps, *false_jump; + int jumpBackDist, jumpBackOffset, infoIndex; + Tmp *loopctrTmp, *valTmp; + + /* The foreach(k=>v in expr) form is illegal in array iteration. */ + if (loop->value) { + L_errf(loop, "=> illegal in foreach over arrays"); + } + + /* + * Type-check the value variables. In "foreach (v1,v2,v3 in + * a)", v* are the value variables or variable list, and a is + * the value list, in tcl terminology. + */ + for (var = loop->key, num_vars = 0; var; var = var->next, ++num_vars) { + unless (sym_lookup(var, 0)) return; // undeclared var + unless (L_typeck_arrElt(var->type, loop->expr->type)) { + L_errf(var, "loop index type incompatible with" + " array element type"); + } + } + + /* Temps for value list value and loop counter. */ + valTmp = tmp_get(TMP_UNSET); + loopctrTmp = tmp_get(TMP_UNSET); + + /* + * ForeachInfo and ForeachVarList are structures required by + * the bytecode interpreter for foreach bytecodes. In our + * case, we have only one value and one variable list + * consisting of num_vars variables. + */ + info = (ForeachInfo *)ckalloc(sizeof(ForeachInfo) + + sizeof(ForeachVarList *)); + info->numLists = 1; + info->firstValueTemp = valTmp->idx; + info->loopCtTemp = loopctrTmp->idx; + varlist = (ForeachVarList *)ckalloc(sizeof(ForeachVarList) + + num_vars * sizeof(int)); + varlist->numVars = num_vars; + for (i = 0, var = loop->key; var; var = var->next, ++i) { + Sym *s = sym_lookup(var, 0); + varlist->varIndexes[i] = s->idx; + } + info->varLists[0] = varlist; + infoIndex = TclCreateAuxData(info, TclGetAuxDataType("ForeachInfo"), + L->frame->envPtr); + + /* The values to iterate through are already on the stack (the + * caller evaluated loop->expr). Assign to the value temp. */ + emit_store_scalar(valTmp->idx); + emit_pop(); + + /* Initialize the loop state. */ + TclEmitInstInt4(INST_FOREACH_START4, infoIndex, L->frame->envPtr); + + /* Top of the loop. Step, and jump out if done. */ + continue_off = currOffset(L->frame->envPtr); + TclEmitInstInt4(INST_FOREACH_STEP4, infoIndex, L->frame->envPtr); + false_jump = emit_jmp_fwd(INST_JUMP_FALSE4, NULL); + + /* Loop body. */ + frame_push(loop, NULL, LOOP|SEARCH); + compile_stmts(loop->body); + break_jumps = L->frame->break_jumps; + continue_jumps = L->frame->continue_jumps; + frame_pop(); + fixup_jmps(&continue_jumps); + + /* End of loop -- jump back to top. */ + jumpBackOffset = currOffset(L->frame->envPtr); + jumpBackDist = jumpBackOffset - continue_off; + if (jumpBackDist > 120) { + TclEmitInstInt4(INST_JUMP4, -jumpBackDist, L->frame->envPtr); + } else { + TclEmitInstInt1(INST_JUMP1, -jumpBackDist, L->frame->envPtr); + } + + fixup_jmps(&false_jump); + + /* Set the value variables to undef. */ + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + for (var = loop->key; var; var = var->next) { + Sym *s = sym_lookup(var, 0); + ASSERT(s); + emit_store_scalar(s->idx); + } + emit_pop(); + + fixup_jmps(&break_jumps); + tmp_free(valTmp); + tmp_free(loopctrTmp); +} + +private void +compile_foreachHash(ForEach *loop) +{ + Sym *key; + Sym *val = NULL; + int body_off, disp; + Jmp *break_jumps, *continue_jumps, *out_jmp; + Tmp *itTmp; + + /* Check types and ensure variables are declared etc. */ + unless ((key = sym_lookup(loop->key, 0))) return; + if (loop->value) { + unless ((val = sym_lookup(loop->value, 0))) return; + unless (L_typeck_compat(val->type, + loop->expr->type->base_type)) { + L_errf(loop->value, "loop index value type " + "incompatible with hash element type"); + } + } + unless (L_typeck_compat(key->type, loop->expr->type->u.hash.idx_type)) { + L_errf(loop->key, + "loop index key type incompatible with hash index type"); + } + if (loop->key->next) { + L_errf(loop, "multiple variables illegal in foreach over hash"); + } + + /* A temp to hold the iterator state.*/ + itTmp = tmp_get(TMP_UNSET); + + /* + * Both DICT_FIRST and DICT_NEXT leave value, key, and done-p + * on the stack. Check done-p and jump out of the loop if + * it's true. (We fixup the jump target once we know the size + * of the loop body.) + */ + TclEmitInstInt4(INST_DICT_FIRST, itTmp->idx, L->frame->envPtr); + out_jmp = emit_jmp_fwd(INST_JUMP_TRUE4, NULL); + + /* + * Update the key and value variables. We save the offset of + * this code so we can jump back to it after DICT_NEXT. + * Note: the caller already pushed loop->expr. + */ + body_off = currOffset(L->frame->envPtr); + emit_store_scalar(key->idx); + emit_pop(); + if (loop->value) emit_store_scalar(val->idx); + emit_pop(); + + /* + * Compile loop body. Note that we must grab the jump fix-ups + * out of the frame before popping it. + */ + frame_push(loop, NULL, LOOP|SEARCH); + compile_stmts(loop->body); + break_jumps = L->frame->break_jumps; + continue_jumps = L->frame->continue_jumps; + frame_pop(); + fixup_jmps(&continue_jumps); + + /* If there's another entry in the hash, go around again. */ + TclEmitInstInt4(INST_DICT_NEXT, itTmp->idx, L->frame->envPtr); + disp = body_off - currOffset(L->frame->envPtr); + TclEmitInstInt4(INST_JUMP_FALSE4, disp, L->frame->envPtr); + + /* End of the loop. Point the jump after the DICT_FIRST to here. */ + fixup_jmps(&out_jmp); + + /* All done. Cleanup the values that DICT_FIRST/DICT_NEXT left. */ + emit_pop(); + emit_pop(); + + /* Set key and/or value counters to undef. */ + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + emit_store_scalar(key->idx); + if (val) emit_store_scalar(val->idx); + emit_pop(); + + fixup_jmps(&break_jumps); + /* XXX We need to ensure that DICT_DONE happens in the face of + exceptions, so that the refcount on the dict will be + decremented, and the iterator freed. See the + implementation of "dict for" in tclCompCmds.c. --timjr + 2006.11.3 */ + TclEmitInstInt4(INST_DICT_DONE, itTmp->idx, L->frame->envPtr); + tmp_free(itTmp); +} + +/* + * Foreach over a string uses three temp variables (str_idx, len_idx, + * and it_idx) and compiles to this: + * + * str_idx = string value already on stack + * len_idx = [::string length $str_idx] + * it_idx = 0 + * jmp 2 + * 1: loopvar1 = str_idx[it_idx++] + * loopvar2 = str_idx[it_idx++] + * ... + * loopvarn = str_idx[it_idx++] + * <loop body> + * 2: test it_idx < len_idx + * jmp if true to 1 + */ +private void +compile_foreachString(ForEach *loop) +{ + int body_off, jmp_dist; + Jmp *break_jmps, *continue_jmps; + Jmp *cond_jmp = 0; + Expr *id; + Tmp *itTmp, *lenTmp, *strTmp; + + /* The foreach(k=>v in expr) form is illegal in string iteration. */ + if (loop->value) { + L_errf(loop, "=> illegal in foreach over strings"); + } + + /* Temps for the loop index, string value, and string length. */ + itTmp = tmp_get(TMP_REUSE); + lenTmp = tmp_get(TMP_REUSE); + strTmp = tmp_get(TMP_REUSE); + + emit_store_scalar(strTmp->idx); + + push_lit("::string"); + push_lit("length"); + TclEmitInstInt1(INST_ROT, 2, L->frame->envPtr); + emit_invoke(3); + emit_store_scalar(lenTmp->idx); + emit_pop(); + + push_lit("0"); + emit_store_scalar(itTmp->idx); + emit_pop(); + + cond_jmp = emit_jmp_fwd(INST_JUMP4, NULL); + body_off = currOffset(L->frame->envPtr); + + for (id = loop->key; id; id = id->next) { + unless (sym_lookup(id, 0)) return; // undeclared var + unless (L_typeck_compat(id->type, L_string)) { + L_errf(id, "loop index not of string type"); + } + emit_load_scalar(strTmp->idx); + emit_load_scalar(itTmp->idx); + TclEmitInstInt4(INST_L_INDEX, L_IDX_STRING | L_PUSH_VAL, + L->frame->envPtr); + emit_store_scalar(id->sym->idx); + emit_pop(); + TclEmitInstInt1(INST_INCR_SCALAR1_IMM, itTmp->idx, + L->frame->envPtr); + TclEmitInt1(1, L->frame->envPtr); + emit_pop(); + } + + frame_push(loop, NULL, LOOP|SEARCH); + compile_stmts(loop->body); + break_jmps = L->frame->break_jumps; + continue_jmps = L->frame->continue_jumps; + frame_pop(); + fixup_jmps(&continue_jmps); + + fixup_jmps(&cond_jmp); + emit_load_scalar(itTmp->idx); + emit_load_scalar(lenTmp->idx); + TclEmitOpcode(INST_LT, L->frame->envPtr); + jmp_dist = currOffset(L->frame->envPtr) - body_off; + TclEmitInstInt4(INST_JUMP_TRUE4, -jmp_dist, L->frame->envPtr); + + /* Set the loop counters to undef. */ + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); + for (id = loop->key; id; id = id->next) { + emit_store_scalar(id->sym->idx); + } + emit_pop(); + + fixup_jmps(&break_jmps); + tmp_free(itTmp); + tmp_free(lenTmp); + tmp_free(strTmp); +} + +private void +compile_foreachAngle(ForEach *loop) +{ + Expr *expr = loop->expr->a; + Expr *id; + Tmp *tmp; + Jmp *break_jmps, *continue_jmps, *out_jmp; + int top_off; + + /* Outlaw foreach(s in <>). */ + unless (expr) { + L_errf(loop, "this form is disallowed; did you mean " + "while (buf = <>)?"); + return; + } + + /* The foreach(k=>v in expr) form is illegal in string iteration. */ + if (loop->value) { + L_errf(loop, "=> illegal in foreach over strings"); + } + + push_lit("LgetNextLineInit_"); + compile_expr(expr, L_PUSH_VAL); + + /* Outlaw foreach(s in <a_FILE>). */ + if (typeisf(expr, "FILE")) { + L_errf(loop->expr, + "this form is disallowed; did you mean " + "while (buf = <F>)?"); + return; + } + unless (isstring(expr)) { + L_errf(expr, "in foreach, arg to <> must be a string"); + return; + } + + for (id = loop->key; id; id = id->next) { + unless (sym_lookup(id, 0)) return; // undeclared var + unless (L_typeck_compat(id->type, L_string)) { + L_errf(id, "loop index %s not of string type", id->str); + } + } + + /* + * tmp = LgetNextLineInit_(expr) + * 1: s1 = LgetNextLine_(tmp) + * s2 = LgetNextLine_(tmp) + * ... + * s<n> = LgetNextLine_(tmp) + * if (s1 is undef) jmp 2 + * <loop body> + * jmp 1 + * 2: + */ + + tmp = tmp_get(TMP_REUSE); + emit_invoke(2); + emit_store_scalar(tmp->idx); + emit_pop(); + top_off = currOffset(L->frame->envPtr); + for (id = loop->key; id; id = id->next) { + push_lit("LgetNextLine_"); + emit_load_scalar(tmp->idx); + emit_invoke(2); + emit_store_scalar(id->sym->idx); + emit_pop(); + } + emit_load_scalar(loop->key->sym->idx); + TclEmitOpcode(INST_L_DEFINED, L->frame->envPtr); + out_jmp = emit_jmp_fwd(INST_JUMP_FALSE4, NULL); + frame_push(loop, NULL, LOOP|SEARCH); + compile_stmts(loop->body); + break_jmps = L->frame->break_jumps; + continue_jmps = L->frame->continue_jumps; + frame_pop(); + fixup_jmps(&continue_jmps); + emit_jmp_back(TCL_UNCONDITIONAL_JUMP, top_off); + fixup_jmps(&break_jmps); + fixup_jmps(&out_jmp); +} + +private void +compile_switch(Switch *sw) +{ + Case *c; + + /* + * If all cases are constant, compile a jump table (fast), + * otherwise compile if-then-else code (slower). + */ + for (c = sw->cases; c; c = c->next) { + if (c->expr && !isconst(c->expr)) break; + } + if (c) { + compile_switch_slow(sw); + } else { + compile_switch_fast(sw); + } +} + +/* + * Generate if-then-else code like the following for a switch statement. + * + * local_tmp = <switch expression> + * # The following is generated for each case except the default case. + * # All jmps are forward jmps. + * next-test: + * load local_tmp + * <case expression> + * <appropriate compare opcode> + * jmp-false next-test + * next-body: + * <case body> + * jmp next-body + * # The following is generated for the default case. + * jmp next-test + * next-body: + * default: + * <case body> + * jmp next-body + * # Statement prologue. + * next-test: + * jmp default # backward jmp, only if default case present + * next-body: + * break-label: # where break stmts jmp to + * pop + */ +private void +compile_switch_slow(Switch *sw) +{ + Expr *e = sw->expr; + Case *c; + int def_off = -1; + int start_off; + Jmp *break_jmps; + Jmp *next_body_jmp = NULL, *next_test_jmp = NULL, *undef_jmp = NULL; + Tmp *tmp; + + compile_expr(e, L_PUSH_VAL); + tmp = tmp_get(TMP_REUSE); + emit_store_scalar(tmp->idx); + emit_pop(); + unless (istype(e, L_INT|L_STRING|L_WIDGET|L_POLY)) { + L_errf(e, "switch expression must be int or string"); + return; + } + + frame_push(sw, NULL, SWITCH|SEARCH); + /* + * If there's a case undef, check that first, because if the + * switch expr is undef, Tcl will still let us get its value + * and it would match a "" case and we don't want that. + */ + for (c = sw->cases; c; c = c->next) { + if (c->expr && isid(c->expr, "undef")) { + start_off = currOffset(L->frame->envPtr); + emit_load_scalar(tmp->idx); + TclEmitOpcode(INST_L_DEFINED, L->frame->envPtr); + undef_jmp = emit_jmp_fwd(INST_JUMP_FALSE4, NULL); + track_cmd(start_off, c->expr); + break; + } + } + for (c = sw->cases; c; c = c->next) { + start_off = currOffset(L->frame->envPtr); + if (c->expr && isid(c->expr, "undef")) { + next_test_jmp = emit_jmp_fwd(INST_JUMP4, next_test_jmp); + fixup_jmps(&undef_jmp); + } else if (c->expr) { + fixup_jmps(&next_test_jmp); + emit_load_scalar(tmp->idx); + if (isregexp(c->expr)) { + compile_reMatch(c->expr); + } else if (isint(e)) { + compile_expr(c->expr, L_PUSH_VAL); + TclEmitOpcode(INST_EQ, L->frame->envPtr); + } else { + compile_expr(c->expr, L_PUSH_VAL); + TclEmitOpcode(INST_STR_EQ, L->frame->envPtr); + } + unless (L_typeck_compat(e->type, c->expr->type)) { + L_errf(c, "case type incompatible" + " with switch expression"); + } + next_test_jmp = emit_jmp_fwd(INST_JUMP_FALSE4, + next_test_jmp); + track_cmd(start_off, c->expr); + } else { // default case (grammar ensures there's at most one) + next_test_jmp = emit_jmp_fwd(INST_JUMP4, next_test_jmp); + ASSERT(def_off == -1); + def_off = currOffset(L->frame->envPtr); + track_cmd(start_off, c); + } + fixup_jmps(&next_body_jmp); + compile_stmts(c->body); + next_body_jmp = emit_jmp_fwd(INST_JUMP4, NULL); + } + fixup_jmps(&next_test_jmp); + if (def_off != -1) { + emit_jmp_back(TCL_UNCONDITIONAL_JUMP, def_off); + } + fixup_jmps(&next_body_jmp); + break_jmps = L->frame->break_jumps; + frame_pop(); + fixup_jmps(&break_jmps); + tmp_free(tmp); +} + +/* + * Generate jump-table code like the following for a switch statement. + * + * <switch expression> + * INST_JUMP_TABLE + * jmp default + * # The following is generated for each case except the default case. + * # All jmps are forward jmps. + * next-body: + * <case body> + * jmp next-body + * # The following is the default case. + * default: + * next-body: + * <case body> (only if default case present) + * jmp next-body (only if default case present) + * # Statement prologue. + * next-body: + * break-label: # where break stmts jmp to + */ +private void +compile_switch_fast(Switch *sw) +{ + Expr *e = sw->expr; + Case *c; + int jt_idx, new, start_off; + Jmp *break_jmps; + Jmp *default_jmp; + Jmp *next_body_jmp = NULL; + Tcl_HashEntry *hPtr; + JumptableInfo *jt; + + jt = (JumptableInfo *)ckalloc(sizeof(JumptableInfo)); + Tcl_InitHashTable(&jt->hashTable, TCL_STRING_KEYS); + jt_idx = TclCreateAuxData(jt, &tclJumptableInfoType, L->frame->envPtr); + + compile_expr(e, L_PUSH_VAL); + unless (istype(e, L_INT|L_STRING|L_WIDGET|L_POLY)) { + L_errf(e, "switch expression must be int or string"); + return; + } + if (isint(e)) { + /* + * Since the jump table keys are strings, add 0 to + * guarantee a canonicalized string rep of an int. + */ + push_lit("0"); + TclEmitOpcode(INST_ADD, L->frame->envPtr); + } + + frame_push(sw, NULL, SWITCH|SEARCH); + + start_off = currOffset(L->frame->envPtr); + TclEmitInstInt4(INST_JUMP_TABLE, jt_idx, L->frame->envPtr); + default_jmp = emit_jmp_fwd(INST_JUMP4, NULL); + + for (c = sw->cases; c; c = c->next) { + if (c->expr) { + ASSERT(isconst(c->expr)); + hPtr = Tcl_CreateHashEntry(&jt->hashTable, + c->expr->str, + &new); + if (new) { + Tcl_SetHashValue(hPtr, + INT2PTR(currOffset(L->frame->envPtr) - + start_off)); + } else { + L_errf(c, "duplicate case value"); + } + unless (L_typeck_compat(e->type, c->expr->type)) { + L_errf(c, + "case type incompatible with switch expression"); + } + } else { // default case (grammar ensures there's at most one) + fixup_jmps(&default_jmp); + } + fixup_jmps(&next_body_jmp); + compile_stmts(c->body); + next_body_jmp = emit_jmp_fwd(INST_JUMP4, NULL); + } + fixup_jmps(&default_jmp); // no-op if default exists (already fixed up) + fixup_jmps(&next_body_jmp); + break_jmps = L->frame->break_jumps; + frame_pop(); + fixup_jmps(&break_jmps); +} + +private VarDecl * +struct_lookupMember(Type *t, Expr *idx, int *offset) +{ + VarDecl *m; + + ASSERT((idx->op == L_OP_DOT) || (idx->op == L_OP_POINTS)); + + unless (t->u.struc.members) { + L_errf(idx, "incomplete struct type %s", t->u.struc.tag); + return (NULL); + } + for (*offset = 0, m = t->u.struc.members; m; m = m->next, ++*offset) { + if (!strcmp(idx->str, m->id->str)) { + return (m); + } + } + return (NULL); +} + +/* + * Determine whether an array index expression contains a reference to + * the array's END index. + */ +private int +has_END(Expr *expr) +{ + Expr *p; + + unless (expr) return (0); + switch (expr->kind) { + case L_EXPR_FUNCALL: + for (p = expr->b; p; p = p->next) { + if (has_END(p)) return (1); + } + return (0); + case L_EXPR_CONST: + case L_EXPR_RE: + return (0); + case L_EXPR_ID: + return (isid(expr, "END")); + case L_EXPR_UNOP: + return (has_END(expr->a)); + case L_EXPR_BINOP: + switch (expr->op) { + case L_OP_ARRAY_INDEX: + /* END in a nested index refers to another array. */ + return (has_END(expr->a)); + case L_OP_CAST: + /* A cast is special: expr->a is a type not an expr. */ + return (has_END(expr->b)); + default: + return (has_END(expr->a) || has_END(expr->b)); + } + case L_EXPR_TRINOP: + if (expr->op == L_OP_ARRAY_SLICE) { + /* END in a nested index refers to another array. */ + return (has_END(expr->a)); + } else { + return (has_END(expr->a) || has_END(expr->b) || + has_END(expr->c)); + } + default: ASSERT(0); + } + /*NOTREACHED*/ + return (0); +} + +/* + * Generate code to push an array/hash/struct/string index onto the stack. + * Return flags suitable for the INST_L_INDEX instruction which indicate + * whether the operator is an array, hash, struct, or string index. + */ +private int +push_index(Expr *expr, int flags) +{ + int ret; + int reuse = flags & L_REUSE_IDX; + int save = flags & L_SAVE_IDX; + Type *type; + VarDecl *member; + Tmp *idxTmp; + int offset; + + /* Error-path return values. */ + ret = 0; + type = L_poly; + + ASSERT(type); + switch (expr->op) { + case L_OP_DOT: + case L_OP_POINTS: + unless (isstruct(expr->a)) { + L_errf(expr, "not a struct"); + goto out; + } + member = struct_lookupMember(expr->a->type, + expr, + &offset); + if (member) { + unless (reuse) push_litf("%i", offset); + type = member->type; + } else { + L_errf(expr, "struct field %s not found", expr->str); + } + ret = L_IDX_ARRAY; + break; + case L_OP_ARRAY_INDEX: + unless (reuse) { + compile_expr(expr->b, L_PUSH_VAL); + if (isid(expr->b, "undef")) { + L_errf(expr->b, "cannot use undef as an " + "array/string index"); + } + } + L_typeck_expect(L_INT, expr->b, "in array/string index"); + if (isarray(expr->a) || islist(expr->a)) { + type = expr->a->type->base_type; + ret = L_IDX_ARRAY; + } else if (isstring(expr->a) || iswidget(expr->a)) { + /* + * Disallow stringvar[0][0] = "x". It doesn't make much + * sense and INST_L_DEEP_WRITE can't handle it anyway. + */ + if ((expr->a->op == L_OP_ARRAY_INDEX) && + expr->a->sym && + isstring(expr->a->a) && + (expr->a->flags & L_LVALUE)) { + L_errf(expr, "cannot index a string index"); + } + type = L_string; + ret = L_IDX_STRING; + } else if (ispoly(expr->a)) { + type = L_poly; + ret = L_IDX_ARRAY; + } else { + L_errf(expr, "not an array or string"); + } + break; + case L_OP_HASH_INDEX: { + unless (reuse) { + compile_expr(expr->b, L_PUSH_VAL); + if (isid(expr->b, "undef")) { + L_errf(expr->b, "cannot use undef as a " + "hash index"); + } + } + if (ishash(expr->a)) { + L_typeck_expect(expr->a->type->u.hash.idx_type->kind, + expr->b, + "in hash index"); + type = expr->a->type->base_type; + } else if (ispoly(expr->a)) { + type = L_poly; + } else { + L_errf(expr, "not a hash"); + } + ret = L_IDX_HASH; + break; + } + default: + L_bomb("Invalid index op, %d", expr->op); + break; + } + out: + if (save) { + // save copy of index to a temp + idxTmp = tmp_get(TMP_REUSE); + expr->u.deepdive.idx = idxTmp; + emit_store_scalar(idxTmp->idx); + } else if (reuse) { + // get index value from temp + idxTmp = expr->u.deepdive.idx; + ASSERT(idxTmp); + emit_load_scalar(idxTmp->idx); + tmp_free(idxTmp); + expr->u.deepdive.idx = NULL; + } + expr->type = type; + return (ret); +} + +/* + * Compile a hash/array/struct/class or string index. These are the + * L_OP_HASH_INDEX, L_OP_ARRAY_INDEX, L_OP_DOT, and L_OP_POINTS nodes. + * + * The resulting stack depends on the flags which specify whether the + * indexed element's value, pointer, or both (and in what order) are + * wanted. We get one of + * + * <elem-obj> if flags & L_PUSH_VAL + * <deep-ptr> if flags & L_PUSH_PTR + * <elem-obj> <deep-ptr> if flags & L_PUSH_VAL_PTR + * <deep-ptr> <elem-obj> if flags & L_PUSH_PTR_VAL + * <tmp-name> if flags & L_PUSH_NAME + * + * For L_PUSH_NAME, we evaluate the indexed expression and store its + * value and all the indices in local temp variables, then use the + * value temp's name as the value of the expression. The expr nodes + * store information about the temps so they can be accessed later, + * such as for the copy-out part of copy in/out parameters. + */ +private int +compile_idxOp(Expr *expr, Expr_f flags) +{ + int ret; + Tmp *valTmp; + + if ((flags & L_PUSH_NAME) && !(flags & L_SAVE_IDX)) { + /* First time through for L_PUSH_NAME. */ + ret = compile_idxOp2(expr, flags | L_PUSH_VAL | L_SAVE_IDX); + /* + * Check whether this was really an object index (we + * don't know until now). + */ + if (isclass(expr->a)) return (ret); + valTmp = tmp_get(TMP_REUSE); + expr->u.deepdive.val = valTmp; + emit_store_scalar(valTmp->idx); + emit_pop(); + push_lit(valTmp->name); + } else { + ret = compile_idxOp2(expr, flags); + } + return (ret); +} + +private int +compile_idxOp2(Expr *expr, Expr_f flags) +{ + int save; + + /* + * Eval the thing being indexed. The flags magic here is + * because we always want its value if it's a variable, or a + * deep-pointer if it's the result of another deep-dive index, + * regardless of in what form we want expr. + */ + compile_expr(expr->a, L_PUSH_PTR | L_PUSH_VAL | + (flags & ~(L_PUSH_VALPTR | + L_PUSH_PTRVAL | + L_DISCARD | + L_PUSH_NAME))); + + /* + * Require "->" for all objects and call-by-reference structures. + * Require "." for all call-by-value and non-parameter structures. + */ + if (isclass(expr->a)) { + unless (expr->op == L_OP_POINTS) { + L_errf(expr, "must access object only with ->"); + } + } else if (expr->a->sym && + (expr->a->sym->decl->flags & DECL_REF) && + !(expr->a->flags & L_EXPR_DEEP)) { + if (expr->op == L_OP_DOT) { + L_errf(expr, ". illegal on call-by-reference " + "parms; use -> instead"); + } + } else { + if (expr->op == L_OP_POINTS) { + L_errf(expr, "-> illegal except on call-by-reference " + "parms; use . instead"); + } + } + + /* + * Handle obj->var. We check here because, in general, we + * don't know until now whether expr->a has type class. + */ + if (isclass(expr->a) && ((expr->op == L_OP_DOT) || + (expr->op == L_OP_POINTS))) { + return (compile_clsInstDeref(expr, flags)); + } + + if (has_END(expr->b)) { + if (flags & L_REUSE_IDX) { + } else if (isstring(expr->a) || iswidget(expr->a)) { + TclEmitOpcode(INST_L_PUSH_STR_SIZE, L->frame->envPtr); + } else { + TclEmitOpcode(INST_L_PUSH_LIST_SIZE, L->frame->envPtr); + } + } + + save = L->idx_op; + L->idx_op = expr->op; + flags |= push_index(expr, flags); + L->idx_op = save; + + if (has_END(expr->b)) { + TclEmitOpcode(INST_L_POP_SIZE, L->frame->envPtr); + } + + /* + * Perform an optimization and don't create a deep pointer if + * the caller won't be doing a deep dive into the expression + * being evaluated but instead just needs its value. This + * happens when the deep dive we're doing now results in + * something of type class and the caller requested a value. + * See the comments in compile_expr(). + * + * This wart is here because the caller can't know in general + * whether expr is a deep dive or a class deref. Their + * expressions look identical but are evaluated in drastically + * different ways. + */ + if (isclass(expr) && (flags & (L_PUSH_VAL | L_DISCARD))) { + flags &= ~(L_PUSH_PTR | L_PUSH_VALPTR | L_PUSH_PTRVAL | + L_LVALUE); + } else if (flags & (L_PUSH_PTR | L_PUSH_VALPTR | L_PUSH_PTRVAL)) { + flags &= ~L_PUSH_VAL; + } + + TclEmitInstInt4(INST_L_INDEX, flags, L->frame->envPtr); + + /* + * Adjust the stack depth that Tcl tracks (debug build) to + * reflect when two objs are left on the stack instead of one + * as indicated by the entry in the tclInstructionTable in + * tclCompile.c + */ + if (flags & (L_PUSH_PTRVAL | L_PUSH_VALPTR)) { + TclAdjustStackDepth(1, L->frame->envPtr); + } + + expr->sym = expr->a->sym; // propagate sym table ptr up the tree + expr->flags = flags | L_EXPR_DEEP; + return ((flags & L_DISCARD) ? 0 : 1); +} + +/* Compile classname->var. */ +private int +compile_clsDeref(Expr *expr, Expr_f flags) +{ + int in_class = 0; + char *clsnm, *varnm; + Sym *sym, *tmpsym; + Tmp *tmp; + Type *type = (Type *)expr->a; + ClsDecl *clsdecl = type->u.class.clsdecl; + Tcl_HashEntry *hPtr; + + expr->type = L_poly; + unless (isclasstype(type)) { + L_errf(expr, "can dereference only class types"); + return (0); + } + + ASSERT(type && clsdecl); + + clsnm = clsdecl->decl->id->str; + varnm = expr->str; + if (L->enclosing_func) { + in_class = L->enclosing_func->decl->flags & DECL_CLASS_FN; + } + + hPtr = Tcl_FindHashEntry(clsdecl->symtab, varnm); + unless (hPtr) { + L_errf(expr, "%s is not a member of class %s", varnm, clsnm); + return (0); + } + sym = (Sym *)Tcl_GetHashValue(hPtr); + unless (in_class || (sym->decl->flags & DECL_PUBLIC)) { + L_errf(expr, "%s is not a public variable of class %s", + varnm, clsnm); + } + unless (sym->decl->flags & DECL_CLASS_VAR) { + L_errf(expr, "%s is not a class variable of class %s", + varnm, clsnm); + } + + if (flags & L_PUSH_NAME) { + push_litf("::L::_class_%s::%s", clsnm, sym->name); + expr->sym = sym; + expr->type = sym->type; + return (1); // stack effect + } + + tmp = tmp_get(TMP_UNSET); + tmpsym = sym_mk(tmp->name, sym->type, SCOPE_LOCAL | DECL_LOCAL_VAR); + ASSERT(tmpsym); // cannot be multiply declared + tmpsym->used_p = TRUE; + + push_litf("::L::_class_%s", clsnm); + push_lit(sym->name); + TclEmitInstInt4(INST_NSUPVAR, tmp->idx, L->frame->envPtr); + emit_pop(); + + expr->sym = tmpsym; + expr->type = sym->type; + + if (flags & L_PUSH_VAL) { + emit_load_scalar(tmp->idx); + return (1); // stack effect + } else { + return (0); // stack effect + } +} + +/* + * Compile obj->var. Code to push the value of obj on the run-time + * stack already has been generated by compile_idxOp(). + */ +private int +compile_clsInstDeref(Expr *expr, Expr_f flags) +{ + int in_class = 0; + char *clsnm, *varnm; + Tmp *tmp; + Sym *sym, *tmpsym; + ClsDecl *clsdecl = expr->a->type->u.class.clsdecl; + Tcl_HashEntry *hPtr; + + ASSERT(isclass(expr->a) && clsdecl); + ASSERT(clsdecl->symtab); + + clsnm = clsdecl->decl->id->str; + varnm = expr->str; + if (L->enclosing_func) { + in_class = L->enclosing_func->decl->flags & DECL_CLASS_FN; + } + + hPtr = Tcl_FindHashEntry(clsdecl->symtab, varnm); + unless (hPtr) { + L_errf(expr, "%s is not a member of class %s", varnm, clsnm); + expr->type = L_poly; + return (0); // stack effect + } + sym = (Sym *)Tcl_GetHashValue(hPtr); + unless (in_class || (sym->decl->flags & DECL_PUBLIC)) { + L_errf(expr, "%s is not a public variable of class %s", + varnm, clsnm); + } + unless (sym->decl->flags & DECL_CLASS_INST_VAR) { + L_errf(expr, "%s is not an instance variable of class %s", + varnm, clsnm); + } + + if (flags & L_PUSH_NAME) { + // Caller already pushed obj value, so concat var name to it. + push_litf("::%s", sym->name); + TclEmitInstInt1(INST_STR_CONCAT1, 2, L->frame->envPtr); + expr->sym = sym; + expr->type = sym->type; + return (1); // stack effect + } + + tmp = tmp_get(TMP_UNSET); + tmpsym = sym_mk(tmp->name, sym->type, SCOPE_LOCAL | DECL_LOCAL_VAR); + ASSERT(tmpsym); // cannot be multiply declared + tmpsym->used_p = TRUE; + + push_lit(sym->name); + TclEmitInstInt4(INST_NSUPVAR, tmp->idx, L->frame->envPtr); + emit_pop(); + + expr->sym = tmpsym; + expr->type = sym->type; + + if (flags & L_PUSH_VAL) { + emit_load_scalar(tmp->idx); + return (1); // stack effect + } else { + return (0); // stack effect + } +} + +private void +compile_assign(Expr *expr) +{ + Expr *lhs = expr->a; + Expr *rhs = expr->b; + + if (lhs->op == L_OP_LIST) { + /* Handle {a,b,c} = ... */ + compile_assignComposite(expr); + } else { + /* Handle regular assignment. */ + compile_expr(rhs, L_PUSH_VAL); + compile_assignFromStack(lhs, rhs->type, expr, 0); + } +} + +private void +compile_assignFromStack(Expr *lhs, Type *rhs_type, Expr *expr, int flags) +{ + /* Whether it's an arithmetic assignment (lhs op= rhs). */ + int arith = (expr && (expr->op != L_OP_EQUALS)); + + compile_expr(lhs, (arith?L_PUSH_VALPTR:L_PUSH_PTR) | L_LVALUE | flags); + unless (lhs->sym) { + L_errf(lhs, "invalid l-value in assignment"); + return; + } + L_typeck_assign(lhs, rhs_type); + + if (isdeepdive(lhs)) { + // <rval> <lhs-ptr> if !arith + // <rval> <lhs-val> <lhs-ptr> if arith + if (arith) { + // <rval> <lhs-val> <lhs-ptr> + TclEmitInstInt4(INST_REVERSE, 3, L->frame->envPtr); + // <lhs-ptr> <lhs-val> <rval> + emit_instrForLOp(expr, expr->type); + // <lhs-ptr> <new-val> + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + } + // <rval> <lhs-ptr> or <new-val> <lhs-ptr> + TclEmitInstInt4(INST_L_DEEP_WRITE, + lhs->sym->idx, + L->frame->envPtr); + TclEmitInt4(L_PUSH_NEW, L->frame->envPtr); + } else { + // <rval> + if (arith) { + emit_load_scalar(lhs->sym->idx); + // <rval> <old-val> + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + // <old-val> <rval> + emit_instrForLOp(expr, expr->type); + // <new-val> + } + // <rval> or <new-val> + emit_store_scalar(lhs->sym->idx); + } + // <rval> +} + +private void +compile_assignComposite(Expr *expr) +{ + int i; + Expr *lhs = expr->a; + Expr *rhs = expr->b; + Type *list = NULL, *rhs_elt_type; + VarDecl *member = NULL; + + expr->type = L_poly; + unless (expr->op == L_OP_EQUALS) { + L_errf(expr, "arithmetic assignment illegal"); + lhs->type = L_poly; + rhs->type = L_poly; + return; + } + ASSERT(lhs->op == L_OP_LIST); + + compile_expr(rhs, L_PUSH_VAL); + + /* rhs_elt_type stores the current rhs type as we walk the elts. */ + switch (rhs->type->kind) { + case L_POLY: + rhs_elt_type = L_poly; + break; + case L_ARRAY: + rhs_elt_type = rhs->type->base_type; + break; + case L_STRUCT: + member = rhs->type->u.struc.members; + ASSERT(member); + rhs_elt_type = member->type; + break; + case L_LIST: + list = rhs->type; + rhs_elt_type = list->base_type; + break; + default: + L_errf(expr, + "right-hand side incompatible with composite assign"); + return; + } + /* Assign lhs <- rhs elements (left to right). */ + for (i = 0, lhs = expr->a; lhs; ++i, lhs = lhs->b) { + ASSERT(lhs->op == L_OP_LIST); + /* A lhs undef means skip the corresponding rhs element. */ + unless (isid(lhs->a, "undef")) { + TclEmitInstInt1(INST_L_LINDEX_STK, i, L->frame->envPtr); + compile_assignFromStack(lhs->a, rhs_elt_type, expr, 0); + emit_pop(); + } + /* Advance rhs_elt_type to type of next elt, if known. */ + if (member) { + member = member->next; + rhs_elt_type = member? member->type: NULL; + } else if (list) { + list = list->next; + rhs_elt_type = list? list->base_type: NULL; + } + } + /* Pop rhs. */ + emit_pop(); + /* The value of the assignment is undef. */ + TclEmitOpcode(INST_L_PUSH_UNDEF, L->frame->envPtr); +} + +private void +compile_incdec(Expr *expr) +{ + Expr *lhs = expr->a; + /* Whether expr is a postfix operator. */ + int post = ((expr->op == L_OP_PLUSPLUS_POST) || + (expr->op == L_OP_MINUSMINUS_POST)); + /* Whether expr is a ++ operator. */ + int inc = ((expr->op == L_OP_PLUSPLUS_PRE) || + (expr->op == L_OP_PLUSPLUS_POST)); + + compile_expr(lhs, L_PUSH_PTRVAL | (post?L_PUSH_VAL:0) | L_LVALUE); + unless (lhs->sym) { + L_errf(expr, "invalid l-value in inc/dec"); + return; + } + L_typeck_expect(L_INT|L_FLOAT, lhs, "in ++/--"); + + if (isdeepdive(lhs)) { + // <lhs-ptr> <lhs-val> + push_lit("1"); + // <hs-ptr> <lhs-val> 1 + TclEmitOpcode(inc?INST_ADD:INST_SUB, L->frame->envPtr); + // <lhs-ptr> <new-val> + TclEmitInstInt1(INST_ROT, 1, L->frame->envPtr); + // <new-val> <lhs-ptr> + TclEmitInstInt4(INST_L_DEEP_WRITE, + lhs->sym->idx, + L->frame->envPtr); + TclEmitInt4(post?L_PUSH_OLD:L_PUSH_NEW, L->frame->envPtr); + } else { + // <old-val> if post + TclEmitInstInt1(INST_INCR_SCALAR1_IMM, lhs->sym->idx, + L->frame->envPtr); + TclEmitInt1(inc? 1 : -1, L->frame->envPtr); + // <old-val> <new-val> if post + // <new-val> if !post + if (post) emit_pop(); + } + // <old-val> if post + // <new-val> if !post +} + +private int +push_regexpModifiers(Expr *regexp) +{ + int n = 0; + + push_lit("-linestop"); + n++; + if (regexp->flags & L_EXPR_RE_I) { + push_lit("-nocase"); + n++; + } + if (regexp->flags & L_EXPR_RE_G) { + push_lit("-all"); + n++; + } + return (n); +} + +private void +emit_instrForLOp(Expr *expr, Type *type) +{ + int arg = 0; + int op = 0; + + switch (expr->op) { + case L_OP_EQUALEQUAL: + case L_OP_NOTEQUAL: + case L_OP_GREATER: + case L_OP_GREATEREQ: + case L_OP_LESSTHAN: + case L_OP_LESSTHANEQ: + switch (type->kind) { + case L_INT: + case L_FLOAT: + case L_POLY: + switch (expr->op) { + case L_OP_EQUALEQUAL: + op = INST_EQ; + break; + case L_OP_NOTEQUAL: + op = INST_NEQ; + break; + case L_OP_GREATER: + op = INST_GT; + break; + case L_OP_GREATEREQ: + op = INST_GE; + break; + case L_OP_LESSTHAN: + op = INST_LT; + break; + case L_OP_LESSTHANEQ: + op = INST_LE; + break; + default: ASSERT(0); + } + break; + case L_STRING: + case L_WIDGET: + switch (expr->op) { + case L_OP_EQUALEQUAL: + op = INST_STR_EQ; + break; + case L_OP_NOTEQUAL: + op = INST_STR_NEQ; + break; + default: + TclEmitOpcode(INST_STR_CMP, L->frame->envPtr); + switch (expr->op) { + case L_OP_GREATER: + push_lit("1"); + op = INST_EQ; + break; + case L_OP_LESSTHAN: + push_lit("-1"); + op = INST_EQ; + break; + case L_OP_GREATEREQ: + push_lit("0"); + op = INST_GE; + break; + case L_OP_LESSTHANEQ: + push_lit("0"); + op = INST_LE; + break; + default: ASSERT(0); + } + break; + } + break; + default: + // We get here only for eq() of a composite type + // w/no numerics. + op = INST_STR_EQ; + break; + } + break; + case L_OP_STR_EQ: + op = INST_STR_EQ; + break; + case L_OP_STR_NE: + op = INST_STR_NEQ; + break; + case L_OP_STR_GT: + case L_OP_STR_GE: + case L_OP_STR_LT: + case L_OP_STR_LE: + TclEmitOpcode(INST_STR_CMP, L->frame->envPtr); + switch (expr->op) { + case L_OP_STR_GT: + push_lit("1"); + op = INST_EQ; + break; + case L_OP_STR_LT: + push_lit("-1"); + op = INST_EQ; + break; + case L_OP_STR_GE: + push_lit("0"); + op = INST_GE; + break; + case L_OP_STR_LE: + push_lit("0"); + op = INST_LE; + break; + default: ASSERT(0); + } + break; + case L_OP_PLUS: + case L_OP_EQPLUS: + op = INST_ADD; + break; + case L_OP_MINUS: + case L_OP_EQMINUS: + op = INST_SUB; + break; + case L_OP_STAR: + case L_OP_EQSTAR: + op = INST_MULT; + break; + case L_OP_SLASH: + case L_OP_EQSLASH: + op = INST_DIV; + break; + case L_OP_PERC: + case L_OP_EQPERC: + op = INST_MOD; + break; + case L_OP_BITAND: + case L_OP_EQBITAND: + op = INST_BITAND; + break; + case L_OP_BITOR: + case L_OP_EQBITOR: + op = INST_BITOR; + break; + case L_OP_BITXOR: + case L_OP_EQBITXOR: + op = INST_BITXOR; + break; + case L_OP_LSHIFT: + case L_OP_EQLSHIFT: + op = INST_LSHIFT; + break; + case L_OP_RSHIFT: + case L_OP_EQRSHIFT: + op = INST_RSHIFT; + break; + case L_OP_UMINUS: + op = INST_UMINUS; + break; + case L_OP_UPLUS: + op = INST_UPLUS; + break; + case L_OP_BANG: + op = INST_LNOT; + break; + case L_OP_BITNOT: + op = INST_BITNOT; + break; + default: + break; + } + if (op) { + TclEmitOpcode(op, L->frame->envPtr); + return; + } + switch (expr->op) { + case L_OP_EQDOT: + op = INST_STR_CONCAT1; + arg = 2; + break; + default: + L_bomb("Unable to map operator %d to an instruction", expr->op); + break; + } + if (op) { + TclEmitInstInt1(op, arg, L->frame->envPtr); + } +} + +private void +compile_continue(Stmt *stmt) +{ + Frame *loop_frame = frame_find(LOOP); + + unless (loop_frame) { + L_errf(stmt, "continue allowed only inside loops"); + return; + } + loop_frame->continue_jumps = emit_jmp_fwd(INST_JUMP4, + loop_frame->continue_jumps); +} + +private void +compile_break(Stmt *stmt) +{ + Frame *loop_frame = frame_find(LOOP|SWITCH); + + unless (loop_frame) { + L_errf(stmt, + "break allowed only inside switch and loop statements"); + return; + } + loop_frame->break_jumps = emit_jmp_fwd(INST_JUMP4, + loop_frame->break_jumps); +} + +private void +compile_label(Stmt *stmt) +{ + Label *label; + + if (!strcmp(stmt->u.label, "break")) { + L_errf(stmt, "break is not a legal label"); + } + label = label_lookup(stmt, LABEL_DEF); + fixup_jmps(&label->fixups); + label->fixups = NULL; + label->offset = currOffset(L->frame->envPtr); +} + +private void +compile_goto(Stmt *stmt) +{ + Label *label; + + label = label_lookup(stmt, LABEL_USE); + if (label->offset >= 0) { + emit_jmp_back(TCL_UNCONDITIONAL_JUMP, label->offset); + } else { + label->fixups = emit_jmp_fwd(INST_JUMP4, label->fixups); + } +} + +private Label * +label_lookup(Stmt *stmt, Label_f flags) +{ + int new; + char *name = stmt->u.label; + Label *label = NULL; + Frame *frame; + Tcl_HashEntry *hPtr = NULL; + + /* Labels are restricted to the enclosing proc's labeltab. */ + frame = frame_find(FUNC); + ASSERT(frame); + + hPtr = Tcl_FindHashEntry(frame->labeltab, name); + if (hPtr) { + label = (Label *)Tcl_GetHashValue(hPtr); + } else { + label = (Label *)ckalloc(sizeof(Label)); + memset(label, 0, sizeof(Label)); + label->name = name; + label->offset = -1; + hPtr = Tcl_CreateHashEntry(frame->labeltab, name, &new); + ASSERT(new); + Tcl_SetHashValue(hPtr, label); + } + if ((flags & LABEL_DEF) && (label->offset >= 0)) { + L_errf(stmt, "label %s already defined", name); + } + return (label); +} + +private void +emit_globalUpvar(Sym *sym) +{ + VarDecl *decl = sym->decl; + char *id = sym->name; + + /* + * Tim comment: We attempt to detect whether L global + * variables should be true globals, or should be shared with + * the calling proc, by checking if the current variable frame + * pointer in interp is the same as the global frame pointer. + * (Sharing variables with the calling proc is useful if you + * want to use L as an expr replacement). + */ + if (((Interp *)L->interp)->rootFramePtr != + ((Interp *)L->interp)->varFramePtr) { + ASSERT(!(decl->flags & (DECL_CLASS_VAR | DECL_CLASS_INST_VAR))); + frame_resumePrologue(); + push_lit("#0"); + push_lit(id); + TclEmitInstInt4(INST_UPVAR, sym->idx, L->frame->envPtr); + emit_pop(); + frame_resumeBody(); + return; + } + + /* + * The namespace of the var we're creating an upvar alias to is + * either ::, an L class namespace, or an L class instance namespace + * where the local "self" holds the namespace name. + */ + frame_resumePrologue(); + switch (decl->flags & + (DECL_GLOBAL_VAR | DECL_CLASS_VAR | DECL_CLASS_INST_VAR)) { + case DECL_GLOBAL_VAR: + push_lit("::"); + /* Private globals get mangled to avoid clashes. */ + if (decl->flags & DECL_PRIVATE) { + push_litf("_%s_%s", L->toplev, id); + } else { + push_lit(id); + } + break; + case DECL_CLASS_VAR: + push_litf("::L::_class_%s", decl->clsdecl->decl->id->str); + push_lit(id); + break; + case DECL_CLASS_INST_VAR: { + Sym *self = sym_lookup(mkId("self"), L_NOWARN); + ASSERT(self); + emit_load_scalar(self->idx); + push_lit(id); + break; + } + } + TclEmitInstInt4(INST_NSUPVAR, sym->idx, L->frame->envPtr); + emit_pop(); + frame_resumeBody(); +} + +/* + * Add a variable or function name to the symbol table. If it's a + * local variable, allocate a slot for it in the current proc. + * + * Print an error if the symbol is already defined. The rules are + * + * - Multiply defined globals are illegal, with the exception that + * main() can be re-defined. + * - A local cannot shadow any other local in the proc. + * - A local can shadow a global. + * - A local can shadow a global upvar shadow (which is a local + * with special status). + * + * Scopes are created as follows. The complexity stems from Tcl + * requiring local upvar shadows as the only way to access globals. + * So we have a scope in which the global symbol is stored and a + * nested scope for the proc in which the local upvar shadow is + * stored. + * + * There is one scope hierarchy per Tcl Interp in which L code + * appears, as illustrated next. OUTER,SCRIPT,TOPLEV,SKIP etc are frame + * flags (Frame_f); SKIP means that the scope is skipped when + * searching enclosing scopes. + * + * [ outer-most scope (OUTER): public globals go in this frame's symtab + * [ file scope (SCRIPT): private globals go in this frame's symtab + * [ * (%%n_toplevel proc) (TOPLEV|SKIP) + * global initializers get compiled in this scope, causing the + * local upvar shadows to go in this scope's symtab + * [ class outer-most (CLS_OUTER): class/instance vars & private + * member fns go in this frame's symtab + * [ * class top-level (CLS_TOPLEV|SKIP) + * class variable initializers get compiled in this scope + * (note that this is still in the %%n_toplevel proc) + * [ (constructor proc) + * instance var initializers get compiled here + * ] + * [ (destructor proc) + * ] + * [ (member fn proc): public fn names go in outer-most + * scope's, symtable, private fn names go in class + * outer-most scope, fn locals go in this frame's + * symtab + * [ block + * [ nested blocks... + * ] + * ] + * ] + * ] + * ] + * [ regular function (proc): public fn name goes in outer-most + * scope's symtab, private fn name goes in file scope's symtab, + * fn locals go in this frame's symtab + * [ block + * [ nested blocks... + * ] + * ] + * ] + * ] + * ] + * ] + */ +private Sym * +sym_store(VarDecl *decl) +{ + int new; + char *name = decl->id->str; + Sym *sym = NULL; + Sym *sym2; + Frame *frame = NULL; + Tcl_HashEntry *hPtr; + + /* Check for multiple declaration. */ + switch (decl->flags & + (SCOPE_LOCAL | SCOPE_GLOBAL | SCOPE_SCRIPT | SCOPE_CLASS)) { + case SCOPE_GLOBAL: + case SCOPE_SCRIPT: + /* Declaring a global -- search outer-most and file frames. */ + frame = frame_find(OUTER); + hPtr = Tcl_FindHashEntry(frame->symtab, name); + unless (hPtr) { + frame = frame_find(SCRIPT); + hPtr = Tcl_FindHashEntry(frame->symtab, name); + } + if (hPtr) { + sym2 = (Sym *)Tcl_GetHashValue(hPtr); + if (decl->flags & DECL_EXTERN) { + sym = (Sym *)Tcl_GetHashValue(hPtr); + if (L_typeck_same(decl->type, sym->type)) { + return (sym); + } + L_errf(decl, + "extern re-declaration type does not " + "match other declaration"); + return (NULL); + } else if (sym2->decl->flags & DECL_ERR) { + Tcl_DeleteHashEntry(hPtr); + } else { + L_errf(decl, + "multiple declaration of global %s", name); + return (NULL); + } + } + break; + case SCOPE_CLASS: + /* Declaring class var -- search up thru class outer scope. */ + for (frame = L->frame; frame; frame = frame->prevFrame) { + hPtr = Tcl_FindHashEntry(frame->symtab, name); + if (hPtr) { + sym2 = (Sym *)Tcl_GetHashValue(hPtr); + if (sym2->decl->flags & DECL_ERR) { + Tcl_DeleteHashEntry(hPtr); + } else { + L_errf(decl, "multiple declaration of %s", + name); + return (NULL); + } + } + if (frame->flags & CLS_OUTER) break; + } + break; + case SCOPE_LOCAL: + /* + * Declaring a local -- search current proc's local + * scopes, then the global scope so we can issue a warning + * if this is a local that shadows a class or global var. + */ + for (frame = L->frame; frame; frame = frame->prevFrame) { + unless (frame->envPtr == L->frame->envPtr) break; + hPtr = Tcl_FindHashEntry(frame->symtab, name); + if (hPtr) { + sym = (Sym *)Tcl_GetHashValue(hPtr); + ASSERT(sym->kind & L_SYM_LVAR); + unless (sym->kind & L_SYM_LSHADOW) { + L_errf(decl, "multiple declaration " + "of local %s", name); + return (NULL); + } + } + } + for (; frame; frame = frame->prevFrame) { + hPtr = Tcl_FindHashEntry(frame->symtab, name); + unless (hPtr && (frame->flags & SEARCH)) continue; + sym2 = (Sym *)Tcl_GetHashValue(hPtr); + if (sym2->decl->flags & DECL_GLOBAL_VAR) { + L_warnf(decl, "local variable %s shadows " + "a global declared at %s:%d", + name, sym2->decl->node.loc.file, + sym2->decl->node.loc.line); + } else if (sym2->decl->flags & DECL_CLASS_VAR) { + L_warnf(decl, "local variable %s shadows " + "a class variable declared at %s:%d", + name, sym2->decl->node.loc.file, + sym2->decl->node.loc.line); + } else if (sym2->decl->flags & DECL_CLASS_INST_VAR) { + L_warnf(decl, "local variable %s shadows a " + "class instance variable declared " + "at %s:%d", name, + sym2->decl->node.loc.file, + sym2->decl->node.loc.line); + } + } + break; + default: + ASSERT(0); + break; + } + + /* Select the frame to add the symbol to. */ + switch (decl->flags & + (SCOPE_LOCAL | SCOPE_GLOBAL | SCOPE_SCRIPT | SCOPE_CLASS)) { + case SCOPE_GLOBAL: + frame = frame_find(OUTER); + break; + case SCOPE_SCRIPT: + frame = frame_find(SCRIPT); + break; + case SCOPE_CLASS: + frame = frame_find(CLS_OUTER); + break; + case SCOPE_LOCAL: + frame = L->frame; + break; + default: + ASSERT(0); + break; + } + hPtr = Tcl_CreateHashEntry(frame->symtab, name, &new); + /* If it's not new, it must be shadowing a global. */ + ASSERT(new || (sym && (sym->kind & L_SYM_LSHADOW) && + (decl->flags & (DECL_LOCAL_VAR | DECL_CLASS_INST_VAR)))); + sym = (Sym *)ckalloc(sizeof(Sym)); + memset(sym, 0, sizeof(*sym)); + sym->name = ckstrdup(name); + sym->type = decl->type; + sym->decl = decl; + + /* + * Set the name of the tcl variable, mangling it to avoid + * clashes. + */ + if (isfntype(decl->type)) { + ASSERT(decl->flags & (DECL_FN | DECL_CLASS_FN)); + sym->kind = L_SYM_FN; + if (decl->tclprefix) { + sym->tclname = cksprintf("%s%s", decl->tclprefix, name); + } else { + sym->tclname = ckstrdup(name); + } + } else if (decl->flags & DECL_GLOBAL_VAR) { + sym->kind = L_SYM_GVAR; + sym->tclname = cksprintf("_%s", name); + } else if (decl->flags & (DECL_CLASS_VAR | DECL_CLASS_INST_VAR)) { + sym->kind = L_SYM_GVAR; + sym->tclname = cksprintf("_%s_%s", + decl->clsdecl->decl->id->str, + name); + } else { + ASSERT(decl->flags & DECL_LOCAL_VAR); + sym->kind = L_SYM_LVAR; + sym->tclname = ckstrdup(name); + } + + /* If a local, allocate a slot for it. */ + if (sym->kind & L_SYM_LVAR) { + sym->idx = TclFindCompiledLocal(name, strlen(name), + 1, L->frame->envPtr); + } else { + sym->idx = -1; + } + + decl->id->sym = sym; + decl->id->type = decl->type; + Tcl_SetHashValue(hPtr, sym); + + return (sym); +} + +/* + * Lookup id in the symbol table. + * + * flags & L_NOTUSED ==> don't mark the id as having been referenced + * (used for warning which variables are unused). + * + * flags & L_NOWARN ==> don't print error message if id not found. + * + * The first time a global is referenced within a scope, an upvar is + * created for it. + */ +private Sym * +sym_lookup(Expr *id, Expr_f flags) +{ + int new; + char *name; + Sym *shw; + Sym *sym = NULL; + Frame *frame; + Tcl_HashEntry *hPtr = NULL; + + unless (id->kind == L_EXPR_ID) return (NULL); + name = id->str; + + for (frame = L->frame; frame; frame = frame->prevFrame) { + if ((frame->envPtr == L->frame->envPtr) || + (frame->flags & SEARCH)) { + hPtr = Tcl_FindHashEntry(frame->symtab, name); + if (hPtr) break; + } + } + if (hPtr) sym = (Sym *)Tcl_GetHashValue(hPtr); + if (sym) { + /* + * If a global is being referenced for the first time + * in this scope, create a local upvar to shadow it + * in the symtab of the enclosing proc or top-level. + */ + if ((sym->kind & L_SYM_GVAR) && (sym->idx == -1)) { + Frame *proc_frame; + // assert global => in outer-most or file frame + ASSERT(!(sym->decl->flags & DECL_GLOBAL_VAR) || + (frame->flags & (OUTER|SCRIPT))); + // assert class var => in class outer-most frame + ASSERT(!(sym->decl->flags & DECL_CLASS_VAR) || + (frame->flags & CLS_OUTER)); + // assert class instance var => class outer-most frame + ASSERT(!(sym->decl->flags & DECL_CLASS_INST_VAR) || + (frame->flags & CLS_OUTER)); + proc_frame = frame_find(TOPLEV|CLS_TOPLEV|FUNC); + ASSERT(proc_frame); + hPtr = Tcl_CreateHashEntry(proc_frame->symtab, name, + &new); + ASSERT(new); + shw = (Sym *)ckalloc(sizeof(Sym)); + memset(shw, 0, sizeof(*shw)); + shw->kind = L_SYM_LVAR | L_SYM_LSHADOW; + shw->name = ckstrdup(name); + shw->tclname = ckstrdup(sym->tclname); + shw->type = sym->decl->type; + shw->decl = sym->decl; + shw->used_p = TRUE; + shw->idx = TclFindCompiledLocal(shw->tclname, + strlen(shw->tclname), + 1, + L->frame->envPtr); + emit_globalUpvar(shw); + Tcl_SetHashValue(hPtr, shw); + sym = shw; + } + unless (flags & L_NOTUSED) sym->used_p = TRUE; + id->sym = sym; + id->type = sym->type; + return (sym); + } else { + ASSERT(id->sym == NULL); + unless (flags & L_NOWARN) { + /* + * Add the undeclared variable to the symtab to avoid + * cascading errors. + */ + YYLTYPE loc = id->node.loc; + VarDecl *decl = ast_mkVarDecl(L_poly, id, loc, loc); + decl->flags = DECL_ERR | DECL_ARGUSED; + switch (L->frame->flags & (FUNC|CLS_TOPLEV|TOPLEV)) { + case TOPLEV | FUNC: + decl->flags |= SCOPE_GLOBAL | DECL_GLOBAL_VAR; + break; + case CLS_TOPLEV: + decl->flags |= SCOPE_CLASS | DECL_CLASS_VAR; + ASSERT(L->frame->clsdecl); + decl->clsdecl = L->frame->clsdecl; + break; + case FUNC: + case 0: // stmt block + decl->flags |= SCOPE_LOCAL | DECL_LOCAL_VAR; + break; + default: ASSERT(0); + } + L_errf(id, "undeclared variable: %s", name); + id->sym = sym_store(decl); + } + id->type = L_poly; + return (NULL); + } +} + +private Sym * +sym_mk(char *name, Type *t, Decl_f flags) +{ + YYLTYPE loc = { 0 }; + Expr *id = mkId(name); + VarDecl *decl = ast_mkVarDecl(t, id, loc, loc); + + decl->flags = flags; + return (sym_store(decl)); +} + +private Tmp * +tmp_get(TmpKind kind) +{ + Tmp *tmp; + + for (tmp = L->frame->tmps; tmp; tmp = tmp->next) { + if (tmp->free) break; + } + unless (tmp) { + tmp = (Tmp *)ckalloc(sizeof(*tmp)); + tmp->next = L->frame->tmps; + L->frame->tmps = tmp; + tmp->name = cksprintf("=temp%d", L->tmpnum++); + tmp->idx = TclFindCompiledLocal(tmp->name, strlen(tmp->name), + 1, L->frame->envPtr); + } + tmp->free = 0; + /* + * Sometimes we need a tmp var that is not set to anything. + * For example, to create an upvar or to use the INST_DICT_* + * bytecodes. + */ + if (kind == TMP_UNSET) { + TclEmitInstInt4(INST_UNSET_LOCAL, tmp->idx, L->frame->envPtr); + } + return (tmp); +} + +private void +tmp_free(Tmp *tmp) +{ + if (tmp) tmp->free = 1; +} + +private void +tmp_freeAll(Tmp *tmp) +{ + while (tmp) { + Tmp *next = tmp->next; + ckfree((char *)tmp); + tmp = next; + } +} + +void +L_bomb(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + fprintf(stderr, "L Internal Error: "); + vfprintf(stderr, format, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +/* + * L_synerr is Bison's yyerror and is called by the parser for syntax + * errors. Bail out by longjumping back to Tcl_LObjCmd, as a way + * to work-around a possible compiler bug in our Windows build where + * the Bison-generated parser's own internal longjmp causes a crash. + */ +void +L_synerr(const char *s) +{ + int i, off; + char *beg = Tcl_GetString(L->script); + char *end = beg + L->script_len; + char *line, *stop; + + unless (L->errs) { + L->errs = Tcl_NewObj(); + L->err = 1; + } + Tcl_AppendPrintfToObj(L->errs, "%s:%d: L Error: %s\n", + L->file, L->line, s); + + /* Search backwards to find the start of the offending line. */ + off = L_lloc.beg; + ASSERT(off >= 0); + ASSERT(beg); + for (line = beg+off; (line > beg) && (line[-1] != '\n'); --line) ; + off = beg+off - line; // is now offset from start of offending line + + /* Print the offending line with a ^ pointing to the current token. */ + stop = line + off; + for (i = 1; (*line != '\n') && (line < end); ++i) { + // adjust for tab printing >1 char + if ((*line == '\t') && (line <= stop)) { + off += 8 - i%8; + i += 7; + } + Tcl_AppendToObj(L->errs, line++, 1); + } + Tcl_AppendToObj(L->errs, "\n", 1); + ASSERT(off >= 0); + while (off--) Tcl_AppendToObj(L->errs, " ", 1); + Tcl_AppendToObj(L->errs, "^\n", 2); + + longjmp(L->jmp, 0); +} + +/* + * Like L_synerr() above but take the offset of the offending token + * instead of using the current token. + */ +void +L_synerr2(const char *s, int offset) +{ + L_lloc.beg = offset; + L_synerr(s); +} + +void +L_warnf(void *node, const char *format, ...) +{ + va_list ap; + int len = 64; + char *buf, *fmt; + + if (hash_get(L->options, "nowarn")) return; + + fmt = cksprintf("%s:%d: L Warning: %s\n", + ((Ast *)node)->loc.file, ((Ast *)node)->loc.line, + format); + va_start(ap, format); + while (!(buf = ckvsprintf(fmt, ap, len))) { + va_end(ap); + va_start(ap, format); + len *= 2; + } + va_end(ap); + unless (L->errs) { + L->errs = Tcl_NewObj(); + L->err = 1; + } + Tcl_AppendToObj(L->errs, buf, -1); + ckfree(fmt); + ckfree(buf); +} + +void +L_err(const char *format, ...) +{ + va_list ap; + int len = 64; + char *buf, *fmt; + + fmt = cksprintf("%s:%d: L Error: %s\n", L->file, L->line, format); + va_start(ap, format); + while (!(buf = ckvsprintf(fmt, ap, len))) { + va_end(ap); + va_start(ap, format); + len *= 2; + } + va_end(ap); + unless (L->errs) { + L->errs = Tcl_NewObj(); + L->err = 1; + } + Tcl_AppendToObj(L->errs, buf, -1); + ckfree(fmt); + ckfree(buf); +} + +void +L_errf(void *node, const char *format, ...) +{ + va_list ap; + int len = 64; + char *buf, *fmt; + + if (node) { + fmt = cksprintf("%s:%d: L Error: %s\n", + ((Ast *)node)->loc.file, + ((Ast *)node)->loc.line, + format); + } else { + fmt = cksprintf("L Error: %s\n", format); + } + va_start(ap, format); + while (!(buf = ckvsprintf(fmt, ap, len))) { + va_end(ap); + va_start(ap, format); + len *= 2; + } + va_end(ap); + unless (L->errs) { + L->errs = Tcl_NewObj(); + L->err = 1; + } + Tcl_AppendToObj(L->errs, buf, -1); + ckfree(fmt); +} + +private void +ast_free(Ast *ast_list) +{ + while (ast_list) { + Ast *node = ast_list; + ast_list = ast_list->next; + switch (node->type) { + case L_NODE_STMT: { + Stmt *s = (Stmt *)node; + if ((s->kind == L_STMT_LABEL) || + (s->kind == L_STMT_GOTO)) { + ckfree(s->u.label); + } + break; + } + case L_NODE_EXPR: + ckfree(((Expr *)node)->str); + break; + case L_NODE_VAR_DECL: + ckfree(((VarDecl *)node)->tclprefix); + break; + default: + break; + } + ckfree((char *)node); + } +} + +private void +type_free(Type *type_list) +{ + while (type_list) { + Type *type = type_list; + type_list = type_list->list; + if (type->kind == L_STRUCT) ckfree(type->u.struc.tag); + ckfree(type->name); + ckfree((char *)type); + } +} + +/* + * This is basically a whacked version of EnterCmdStartData and + * EnterCmdWordData from tclCompile.c. + */ +private void +track_cmd(int codeOffset, void *node) +{ + int cmdIndex = L->frame->envPtr->numCommands++; + Ast *ast = (Ast *)node; + int len = ast->loc.end - ast->loc.beg; + int srcOffset = ast->loc.beg; + ECL *ePtr; + CmdLocation *cmdLocPtr; + CompileEnv *envPtr = L->frame->envPtr; + ExtCmdLoc *eclPtr = envPtr->extCmdMapPtr; + + if ((cmdIndex < 0) || (cmdIndex >= envPtr->numCommands)) { + Tcl_Panic("track_cmd: bad command index %d", cmdIndex); + } + if (cmdIndex >= envPtr->cmdMapEnd) { + /* + * Expand the command location array by allocating + * more storage from the heap. The currently allocated + * CmdLocation entries are stored from cmdMapPtr[0] up + * to cmdMapPtr[envPtr->cmdMapEnd] (inclusive). + */ + size_t currElems = envPtr->cmdMapEnd; + size_t newElems = 2*currElems; + size_t currBytes = currElems * sizeof(CmdLocation); + size_t newBytes = newElems * sizeof(CmdLocation); + CmdLocation *newPtr = (CmdLocation *)ckalloc((int)newBytes); + + /* + * Copy from old command location array to new, free + * old command location array if needed, and mark new + * array as malloced. + */ + memcpy(newPtr, envPtr->cmdMapPtr, currBytes); + if (envPtr->mallocedCmdMap) ckfree((char *)envPtr->cmdMapPtr); + envPtr->cmdMapPtr = (CmdLocation *)newPtr; + envPtr->cmdMapEnd = newElems; + envPtr->mallocedCmdMap = 1; + } + + cmdLocPtr = &(envPtr->cmdMapPtr[cmdIndex]); + cmdLocPtr->codeOffset = codeOffset; + cmdLocPtr->srcOffset = srcOffset; + cmdLocPtr->numSrcBytes = len; + cmdLocPtr->numCodeBytes = currOffset(envPtr) - codeOffset; + + /* + * The command locations have to be sorted in ascending order + * by codeOffset. (Or Tcl panics in GetCmdLocEncodingSize(), + * if nothing else). However, when L compiles nested function + * calls, the outer one will get tracked second, even though + * it begins first. So we walk the new CmdLocation entry back + * from the end until it lands where it belongs. + */ + while ((cmdIndex > 0) && (envPtr->cmdMapPtr[cmdIndex-1].codeOffset > + envPtr->cmdMapPtr[cmdIndex].codeOffset)) { + CmdLocation cmdLoc = envPtr->cmdMapPtr[cmdIndex]; + envPtr->cmdMapPtr[cmdIndex] = envPtr->cmdMapPtr[cmdIndex-1]; + envPtr->cmdMapPtr[cmdIndex-1] = cmdLoc; + cmdIndex--; + } + + if (eclPtr->nuloc >= eclPtr->nloc) { + /* + * Expand the ECL array by allocating more storage + * from the heap. The currently allocated ECL entries + * are stored from eclPtr->loc[0] up to + * eclPtr->loc[eclPtr->nuloc-1] (inclusive). + */ + size_t currElems = eclPtr->nloc; + size_t newElems = (currElems ? 2*currElems : 1); + size_t newBytes = newElems * sizeof(ECL); + eclPtr->loc = (ECL *) ckrealloc((char *) eclPtr->loc, newBytes); + eclPtr->nloc = newElems; + } + + /* We enter only one word for the L command. */ + ePtr = &eclPtr->loc[eclPtr->nuloc]; + ePtr->srcOffset = srcOffset; + ePtr->line = (int *) ckalloc(sizeof(int)); + ePtr->nline = 1; + eclPtr->nuloc ++; +} + +/* + * API for tracking when we are compiling a function argument. This is + * used to check whether an (expand) operator is being used as a + * function argument (OK) or as something else (error). + * + * fnCallBegin: call just before compiling a fn call + * fnCallEnd: call just after compiling a fn call + * fnInArgList: returns 1 if we are just starting to compile a + * fn call arg; returns 0 if we're either outside of a + * fn call or nested within an expression inside of + * an arg: + * foo(x) -- true + * foo(x+y) -- false + */ +private int +fnCallBegin() +{ + int old = L->call_level; + L->call_level = L->expr_level; + return (old); +} +private void +fnCallEnd(int lev) +{ + L->call_level = lev; +} +private int +fnInArgList() +{ + return (L->expr_level == (L->call_level + 1)); +} + +private Expr * +mkId(char *name) +{ + YYLTYPE loc = { 0 }; + + return (ast_mkId(name, loc, loc)); +} + +char * +ckstrdup(const char *str) +{ + if (str) { + return (ckstrndup(str, strlen(str))); + } else { + return (NULL); + } +} + +char * +ckstrndup(const char *str, int len) +{ + char *newStr = ckalloc(len+1); + + strncpy(newStr, str, len); + newStr[len] = '\0'; + return (newStr); +} + +char * +cksprintf(const char *fmt, ...) +{ + va_list ap; + int len = 64; + char *buf; + + va_start(ap, fmt); + while (!(buf = ckvsprintf(fmt, ap, len))) { + va_end(ap); + va_start(ap, fmt); + len *= 2; + } + va_end(ap); + return (buf); +} + +/* + * Allocate a buffer of len bytes and attempt a vsnprintf and fail + * (return NULL) if len isn't enough. The caller should double len + * and re-try. We require the caller to re-try instead of re-trying + * here because on some platforms "ap" is changed by the vsnprintf + * call and there is no portable way to save and restore it. + */ +char * +ckvsprintf(const char *fmt, va_list ap, int len) +{ + char *buf = ckalloc(len); + int ret = vsnprintf(buf, len, fmt, ap); + /* + * The meaning of the return value depends on the platform. + * Some return the needed length (minus 1), some return -1, + * some truncate the buffer. For the latter, ret will be + * len-1 and we won't know whether it barely fit or wasn't + * enough, so just fail on that case. + */ + if ((ret >= (len-1)) || (ret < 0)) { + ckfree(buf); + return (NULL); + } + return (buf); +} + +/* + * Since we have C-like variable declarations in L, when hashes and + * arrays are declared, the base type is parsed separately from the + * array sizes or hash-element types. The next two functions put them + * back together. E.g., in + * + * string h{int}; + * + * the main type passed in to these functions is a hash type + * (w/index type of "int") but the hash type doesn't yet have its + * base type set, which in this example is "string". + * + * For simple declarations (like "string s") where there is no + * explicit array or hash, decl->type won't be set by the parser, so + * the base type goes there. For arrays/hashes, decl->type points to + * the first level of array or hash, and the base type must go onto + * the last nested hash or array type. + */ + +void +L_set_baseType(Type *type, Type *base_type) +{ + while (type->base_type) { + ASSERT((type->kind == L_ARRAY) || + (type->kind == L_HASH) || + (type->kind == L_NAMEOF)); + type = type->base_type; + } + type->base_type = base_type; +} + +void +L_set_declBaseType(VarDecl *decl, Type *base_type) +{ + if (decl->type) { + L_set_baseType(decl->type, base_type); + } else { + decl->type = base_type; + } + if (isnameoftype(base_type)) decl->flags |= DECL_REF; +} + +/* + * These are called before each Tcl interp is created (see + * tclInterp.c) and after it is deleted. Set up a top-level scope and + * call frame in order to persist typedefs, struct types, and globals + * across all the L programs compiled inside the interp. + */ +void +TclLInitCompiler(Tcl_Interp *interp) +{ + static Lglobal global; // L global state + +// putenv("MallocStackLogging=1"); + + /* Associate the L interp state with this interp. */ + L = (Linterp *)ckalloc(sizeof(Linterp)); + memset(L, 0, sizeof(Linterp)); + Tcl_SetAssocData(interp, "L", TclLCleanupCompiler, L); + + L->global = &global; + L->interp = interp; + frame_push(NULL, NULL, OUTER|SEARCH); + L_scope_enter(); + L->fn_calls = Tcl_NewObj(); + Tcl_SetVar2Ex(L->interp, "%%L_fnsCalled", NULL, L->fn_calls, + TCL_GLOBAL_ONLY); + L->fn_decls = Tcl_NewObj(); + Tcl_SetVar2Ex(L->interp, "L_fnsDeclared", NULL, L->fn_decls, + TCL_GLOBAL_ONLY); +} + +void +TclLCleanupCompiler(ClientData clientData, Tcl_Interp *interp) +{ + char buf[32]; + + L = (Linterp *)clientData; + L_scope_leave(); + frame_pop(); + ast_free(L->ast_list); + type_free(L->type_list); + if (L->include_table) { + Tcl_DeleteHashTable(L->include_table); + ckfree((char *)L->include_table); + } + ckfree(L->file); + ckfree(L->toplev); + if (L->script) Tcl_DecrRefCount(L->script); + ckfree((char *)L); + L = NULL; + + snprintf(buf, sizeof(buf), "/usr/bin/leaks %u", getpid()); +// system(buf); +} + +void +L_scope_enter() +{ + Scope *new_scope = (Scope *)ckalloc(sizeof(*new_scope)); + + new_scope->structs = (Tcl_HashTable *)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(new_scope->structs, TCL_STRING_KEYS); + + new_scope->typedefs = (Tcl_HashTable *)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(new_scope->typedefs, TCL_STRING_KEYS); + + new_scope->prev = L->curr_scope; + L->curr_scope = new_scope; +} + +void +L_scope_leave() +{ + Scope *prev = L->curr_scope->prev; + + Tcl_DeleteHashTable(L->curr_scope->structs); + ckfree((char *)L->curr_scope->structs); + + Tcl_DeleteHashTable(L->curr_scope->typedefs); + ckfree((char *)L->curr_scope->typedefs); + + ckfree((char *)L->curr_scope); + + L->curr_scope = prev; +} + +/* + * Called by parser to look up a reference to "struct tag". If + * "local" is true, check only the current scope. If the struct + * hasn't yet been declared, add an incomplete type to the current + * scope's struct table whose members will get filled up later when + * the struct is fully declared. + */ +Type * +L_struct_lookup(char *tag, int local) +{ + int new; + Type *type; + Tcl_HashEntry *hPtr = NULL; + Scope *scope; + + for (scope = L->curr_scope; !hPtr && scope; scope = scope->prev) { + hPtr = Tcl_FindHashEntry(scope->structs, tag); + if (local) break; + } + if (hPtr) { + type = (Type *)Tcl_GetHashValue(hPtr); + } else { + hPtr = Tcl_CreateHashEntry(L->curr_scope->structs, tag, &new); + type = type_mkStruct(tag, NULL); + Tcl_SetHashValue(hPtr, type); + } + return (type); +} + +/* + * Called by parser to declare a new struct type. If the struct + * already has been declared but without any members, fill them in + * now and return the existing type pointer. If tag is NULL, just + * sanity check the members' types (checking for void etc). + */ +Type * +L_struct_store(char *tag, VarDecl *m) +{ + Type *type = NULL; + + ASSERT(m); + + if (tag) { + type = L_struct_lookup(tag, TRUE); + if (type->u.struc.members) { + L_errf(m, "multiple declaration of struct %s", tag); + } else { + type->u.struc.members = m; + } + } + + /* Check member types for legality. */ + for (; m; m = m->next) { + L_typeck_declType(m); + } + + return (type); +} + +/* + * Called by parser to look up an ID in the typedef table to see if + * it's been previously declared as a type name. + */ +Type * +L_typedef_lookup(char *name) +{ + Tcl_HashEntry *hPtr = NULL; + Scope *scope; + + for (scope = L->curr_scope; !hPtr && scope; scope = scope->prev) { + hPtr = Tcl_FindHashEntry(scope->typedefs, name); + } + if (hPtr) { + return ((Type *)Tcl_GetHashValue(hPtr)); + } else { + return (NULL); + } +} + +/* + * Called by parser to define a new type name. + */ +void +L_typedef_store(VarDecl *decl) +{ + int new; + Tcl_HashEntry *hPtr; + Type *new_type; + char *name = decl->id->str; + + hPtr = Tcl_CreateHashEntry(L->curr_scope->typedefs, name, &new); + if (new) { + new_type = type_dup(decl->type); + if (new_type->name) ckfree(new_type->name); + new_type->name = ckstrdup(name); + Tcl_SetHashValue(hPtr, new_type); + } else { + Type *t = Tcl_GetHashValue(hPtr); + unless (L_typeck_same(decl->type, t)) { + L_errf(decl, "Cannot redefine type %s", name); + } + } +} + +void +hash_put(Tcl_Obj *hash, char *key, char *val) +{ + Tcl_Obj *keyObj, *valObj; + + ASSERT(hash && key); + keyObj = Tcl_NewStringObj(key, -1); + Tcl_IncrRefCount(keyObj); + if (val) { + valObj = Tcl_NewStringObj(val, -1); + } else { + valObj = *L_undefObjPtrPtr(); + } + Tcl_DictObjPut(L->interp, hash, keyObj, valObj); + Tcl_DecrRefCount(keyObj); +} + +void +hash_rm(Tcl_Obj *hash, char *key) +{ + Tcl_Obj *keyObj; + + ASSERT(hash && key); + keyObj = Tcl_NewStringObj(key, -1); + Tcl_IncrRefCount(keyObj); + Tcl_DictObjRemove(L->interp, hash, keyObj); + Tcl_DecrRefCount(keyObj); +} + +char * +hash_get(Tcl_Obj *hash, char *key) +{ + int ret; + Tcl_Obj *keyObj = Tcl_NewStringObj(key, -1); + Tcl_Obj *valObj; + + ASSERT(hash); + Tcl_IncrRefCount(keyObj); + ret = Tcl_DictObjGet(L->interp, hash, keyObj, &valObj); + unless (ret == TCL_OK) return (NULL); + Tcl_DecrRefCount(keyObj); + if (valObj) { + return (Tcl_GetString(valObj)); + } else { + return (NULL); + } +} + +/* For debugging. */ +void +hash_dump(Tcl_Obj *hash) +{ + int done, ret; + Tcl_Obj *key, *val; + Tcl_DictSearch ctxt; + + ret = Tcl_DictObjFirst(L->interp, hash, &ctxt, &key, &val, &done); + if ((ret != TCL_OK) || done) return; + do { + printf("%s -> %s\n", Tcl_GetString(key), + val->undef ? "<undef>" : Tcl_GetString(val)); + Tcl_DictObjNext(&ctxt, &key, &val, &done); + } while (!done); +} + +private char * +basenm(char *s) +{ + char *t; + + for (t = s; *t; t++); + do { + t--; + } while (*t != '/' && t > s); + if (*t == '/') t++; + return (t); +} + +/* + * Return the dirname of a path. The caller must ckfree() it. + */ +char * +L_dirname(char *path) +{ + Tcl_Obj *pathObj = Tcl_NewStringObj(path, -1); + Tcl_Obj *dirObj, *tmpObj; + char *ret = NULL; + + Tcl_IncrRefCount(pathObj); + tmpObj = Tcl_FSGetNormalizedPath(NULL, pathObj); + if (tmpObj == NULL) goto err; + dirObj = TclPathPart(L->interp, tmpObj, TCL_PATH_DIRNAME); + if (dirObj == NULL) goto err; + ret = ckstrdup(Tcl_GetString(dirObj)); + Tcl_DecrRefCount(dirObj); + err: Tcl_DecrRefCount(pathObj); + return (ret); +} + +/* + * This function executes the INST_L_SPLIT bytecode and is based on + * pieces from tclCmdMZ.c. + * + * For edge cases, some of Perl's "split" semantics are obeyed: + * + * - A limit <= 0 means no limit. + * + * - Trailing null fields in the result are always suppressed. + * + * - If there is no delim, split on white space and trim any leading + * null fields from the result. + * + * - If the delim is /regexp/t, trim any leading null fields. + * + * - If all result fields are null, they are considered to be trailing + * and are all suppressed. + */ +Tcl_Obj * +L_split(Tcl_Interp *interp, Tcl_Obj *strobj, Tcl_Obj *delimobj, + Tcl_Obj *limobj, Expr_f flags) +{ + int chlen, i, leading, len, lim, matches, nocase, off, ret; + int trim = (flags & L_EXPR_RE_T); + int start = 0, end = 0; + Tcl_RegExp regExpr = NULL; + Tcl_RegExpInfo info; + Tcl_Obj **elems, *resultPtr, *objPtr, *listPtr; + Tcl_UniChar ch; + char *str; + + if (limobj) { + Tcl_GetIntFromObj(interp, limobj, &lim); + if (lim <= 0) { + lim = INT_MAX; + } else { + /* The lim is the max # fields to return, + * which is one less than the max # matches to + * allow. */ + --lim; + } + } else { + lim = INT_MAX; + } + + /* + * Make sure to avoid problems where the objects are shared. This can + * cause RegExpObj <> UnicodeObj shimmering that causes data corruption. + * [Bug #461322] + */ + if (strobj == delimobj) { + objPtr = Tcl_DuplicateObj(strobj); + } else { + objPtr = strobj; + } + if (objPtr->typePtr == &tclByteArrayType) { + str = (char *)Tcl_GetByteArrayFromObj(objPtr, &len); + } else { + str = Tcl_GetStringFromObj(objPtr, &len); + } + + listPtr = Tcl_NewObj(); + matches = 0; + leading = 1; + off = 0; + + /* + * Split on white space if no delim was specified. + */ + unless (delimobj) { + int skip = 0; + for (start = 0; (off < len) && (matches < lim); off += chlen) { + chlen = TclUtfToUniChar(str+off, &ch); + if (skip) { + unless (Tcl_UniCharIsSpace(ch)) { + start = off; + skip = 0; + ++matches; + } + } else { + if (Tcl_UniCharIsSpace(ch)) { + /* Suppress leading null field + * in result. */ + if (off || start) { + resultPtr = Tcl_NewStringObj( + str+start, + off-start); + Tcl_ListObjAppendElement( + NULL, listPtr, + resultPtr); + } + skip = 1; + } + } + } + unless (skip) { + resultPtr = Tcl_NewStringObj(str+start, len-start); + Tcl_ListObjAppendElement(NULL, listPtr, resultPtr); + } + goto done; + } + + /* + * Split on a regular expression. + */ + nocase = (flags & L_EXPR_RE_I) ? TCL_REG_NOCASE : 0; + regExpr = Tcl_GetRegExpFromObj(interp, delimobj, + TCL_REG_ADVANCED | TCL_REG_PCRE | nocase); + unless (regExpr) { // bad regexp + listPtr = NULL; + goto done; + } + while ((off < len) && (matches < lim)) { + int flags = TCL_REG_BYTEOFFSET; + + if ((off > 0) && (str[off-1] != '\n')) flags |= TCL_REG_NOTBOL; + ret = Tcl_RegExpExecObj(interp, regExpr, objPtr, off, 1, flags); + if (ret < 0) goto done; + if (ret == 0) break; + Tcl_RegExpGetInfo(regExpr, &info); + start = info.matches[0].start; + end = info.matches[0].end; + matches++; + + /* + * Copy to the result list the portion of the source + * string before the match. If we matched the empty + * string, split after the current char. Don't add + * leading null fields if specified. + */ + if (leading && trim && (start == 0)) { + if (start == end) ++off; + off += end; + continue; + } + if (start == end) { + ASSERT(start == 0); + resultPtr = Tcl_NewStringObj(str+off, 1); + ++off; + } else { + resultPtr = Tcl_NewStringObj(str+off, start); + } + leading = 0; + Tcl_ListObjAppendElement(NULL, listPtr, resultPtr); + off += end; + } + /* + * Copy to the result list the portion of the source string after + * the last match, unless we matched the last char. + */ + if (off < len) { + resultPtr = Tcl_NewStringObj(str+off, len-off); + Tcl_ListObjAppendElement(NULL, listPtr, resultPtr); + } + + done: + if (objPtr && (strobj == delimobj)) { + Tcl_DecrRefCount(objPtr); + } + unless (listPtr) return (NULL); + + /* + * Strip any trailing empty fields in the result. This is + * to be consistent with Perl's split semantics. + */ + TclListObjGetElements(NULL, listPtr, &len, &elems); + for (i = len-1; i >= 0; --i) { + if (Tcl_GetCharLength(elems[i])) break; + Tcl_ListObjReplace(interp, listPtr, i, 1, 0, NULL); + } + return (listPtr); +} + +/* + * This command splits the given arguments according to bash-style + * quoting, returning a string[] array. + * + * xyz -- all escapes are processed except \<newline> ignored + * 'xyz' -- no single quotes allowed inside, no escapes processed + * "xyz" -- only \\ and \" are processed, \<newline> ignored + */ +int +Tcl_ShSplitObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + char *cmd; + int i, j, len; + Tcl_Obj *arg = NULL, *argv; + enum { LOOKING, ARG, SINGLE, DOUBLE } state; + + unless (objc >= 2) { + Tcl_WrongNumArgs(interp, 1, objv, "string ?string ...?"); + return (TCL_ERROR); + } + argv = Tcl_NewObj(); + for (i = 1; i < objc; ++i) { + cmd = TclGetStringFromObj(objv[i], &len); + state = LOOKING; + for (j = 0; j < len; ++j) { + char c = cmd[j]; + switch (state) { + case LOOKING: + if (isspace(c)) { + continue; + } else { + arg = Tcl_NewObj(); + state = ARG; + /*FALLTHRU*/ + } + case ARG: + if (isspace(c)) { + Tcl_ListObjAppendElement(interp, + argv, arg); + state = LOOKING; + } else if (c == '\\') { + char e = 0; + if ((j+1) < len) e = cmd[j+1]; + // escape anything but ignore \<newline> + if (!e) { + Tcl_AppendResult(interp, + "trailing \\", + NULL); + return (TCL_ERROR); + } else if (e == '\n') { + ++j; + } else { + Tcl_AppendToObj(arg, &e, 1); + ++j; + } + } else if (c == '\'') { + state = SINGLE; + } else if (c == '"') { + state = DOUBLE; + } else { + Tcl_AppendToObj(arg, &c, 1); + } + break; + case SINGLE: + if (c == '\'') { + state = ARG; + } else { + Tcl_AppendToObj(arg, &c, 1); + } + break; + case DOUBLE: + if (c == '\\') { + char e = 0; + if ((j+1) < len) e = cmd[j+1]; + // escape \ and " but ignore \<newline> + if ((e == '\\') || (e == '"')) { + Tcl_AppendToObj(arg, &e, 1); + ++j; + } else if (e == '\n') { + ++j; + } else { + Tcl_AppendToObj(arg, &c, 1); + } + } else if (c == '"') { + state = ARG; + } else { + Tcl_AppendToObj(arg, &c, 1); + } + break; + } + } + switch (state) { + case LOOKING: + break; + case ARG: + Tcl_ListObjAppendElement(interp, argv, arg); + break; + case SINGLE: + Tcl_AppendResult(interp, "unterminated \'", NULL); + return (TCL_ERROR); + case DOUBLE: + Tcl_AppendResult(interp, "unterminated \"", NULL); + return (TCL_ERROR); + } + } + Tcl_SetObjResult(interp, argv); + return (TCL_OK); +} + +int +Tcl_GetOptObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int ac, i, n, ret = TCL_OK; + char **av, *opts, *s; + longopt *lopts = NULL; + Tcl_Obj **objs; + + /* + * This is all about converting the L args to C args for the + * getopt() call and then mapping back for the return value. + */ + + unless (objc == 4) { + Tcl_WrongNumArgs(interp, 1, objv, "av opts lopts"); + return (TCL_ERROR); + } + + /* Set the C optind variable from its L counterpart. */ + s = (char *)Tcl_GetVar(interp, "optind", TCL_GLOBAL_ONLY); + if (s) optind = atoi(s); + + if (Tcl_ListObjGetElements(interp, objv[1], &ac, &objs) != TCL_OK) { + return (TCL_ERROR); + } + av = (char **)ckalloc(ac * sizeof(char *)); + for (i = 0; i < ac; ++i) { + av[i] = TclGetString(objs[i]); + } + opts = (objv[2]->undef ? "" : TclGetString(objv[2])); + /* + * For long opts, the C API wants an array of <char*,int>, and + * the L call sent in a string array, so map the long opt name to + * its L array index + 300 (values <= 256 are reserved for the + * short opts and GETOPT_ERR). + */ + if (Tcl_ListObjGetElements(interp, objv[3], &n, &objs) != TCL_OK) { + ret = TCL_ERROR; + goto done; + } + if (n) { + lopts = (longopt *)ckalloc((n+1) * sizeof(longopt)); + for (i = 0; i < n; ++i) { + lopts[i].name = TclGetString(objs[i]); + lopts[i].ret = 300 + i; + } + lopts[i].name = NULL; + } + i = getopt(ac, av, opts, lopts); + switch (i) { + case GETOPT_EOF: + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + break; + case GETOPT_ERR: + Tcl_SetObjResult(interp, Tcl_NewStringObj("", 0)); + break; + default: + if (i < 300) { + // short opt + char str[1]; + str[0] = i; + Tcl_SetObjResult(interp, Tcl_NewStringObj(str, 1)); + } else { + // long opt -- map back to the longopts array entry + // and strip any trailing :;| + s = TclGetStringFromObj(objs[i-300], &n); + if ((s[n-1] == ':') || (s[n-1] == ';') || + (s[n-1] == '|')) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj(s,n-1)); + } else { + Tcl_SetObjResult(interp, objs[i-300]); + } + } + break; + } + /* Set the optind, optopt, and optarg globals from the C variables. */ + s = cksprintf("%d", optind); + Tcl_SetVar(interp, "optind", s, TCL_GLOBAL_ONLY); + ckfree(s); + s = cksprintf("%c", optopt); + Tcl_SetVar(interp, "optopt", s, TCL_GLOBAL_ONLY); + ckfree(s); + if (optarg) { + Tcl_SetVar(interp, "optarg", optarg, TCL_GLOBAL_ONLY); + } else { + Tcl_SetVar2Ex(interp, "optarg", NULL, *L_undefObjPtrPtr(), + TCL_GLOBAL_ONLY); + } + done: + ckfree((char *)av); + ckfree((char *)lopts); + return (ret); +} + +int +Tcl_GetOptResetObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + unless (objc == 1) { + Tcl_WrongNumArgs(interp, 1, objv, NULL); + return (TCL_ERROR); + } + getoptReset(); + Tcl_SetVar(interp, "optind", "0", TCL_GLOBAL_ONLY); + Tcl_SetVar(interp, "optopt", "", TCL_GLOBAL_ONLY); + Tcl_SetVar2Ex(interp, "optarg", NULL, *L_undefObjPtrPtr(), + TCL_GLOBAL_ONLY); + return (TCL_OK); +} + +/* + * Parts of the next two functions are taken from Tcl_GetsObjCmd(). + * do_getline() is like Tcl_GetsObjCmd() except that it results in + * undef on error or EOF, and it returns the result object so you + * don't have to pull it out of the interp to see what happened. + */ + +private Tcl_Obj * +do_getline(Tcl_Interp *interp, Tcl_Channel chan) +{ + Tcl_Obj *ret; + + ret = Tcl_NewObj(); + if (Tcl_GetsObj(chan, ret) < 0) { + Tcl_DecrRefCount(ret); + if (!Tcl_Eof(chan) && !Tcl_InputBlocked(chan)) { + /* + * TIP #219. Capture error messages put by the + * driver into the bypass area and put them + * into the regular interpreter result. Fall + * back to the regular message if nothing was + * found in the bypass. + */ + if (!TclChanCaughtErrorBypass(interp, chan)) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, Tcl_PosixError(interp), + NULL); + } + Tcl_SetVar2Ex(interp, "::stdio_lasterr", NULL, + Tcl_GetObjResult(interp), + TCL_GLOBAL_ONLY); + return (NULL); + } + ret = *L_undefObjPtrPtr(); + } + Tcl_SetObjResult(interp, ret); + return (ret); +} + +int +Tcl_FGetlineObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int mode; + Tcl_Channel chan; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "channelId"); + return (TCL_ERROR); + } + if (TclGetChannelFromObj(interp, objv[1], &chan, + &mode, 0) != TCL_OK) { + goto err; + } + unless (mode & TCL_READABLE) { + Tcl_AppendResult(interp, "channel \"", TclGetString(objv[1]), + "\" wasn't opened for reading", NULL); + goto err; + } + unless (do_getline(interp, chan)) { + goto err; + } + return (TCL_OK); + err: + Tcl_SetVar2Ex(interp, "::stdio_lasterr", NULL, + Tcl_GetObjResult(interp), + TCL_GLOBAL_ONLY); + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + return (TCL_OK); +} + +int +Tcl_LAngleReadObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tcl_Obj *ret = NULL; + int argc, res; + Tcl_Obj **argv; + static int cur = 0; + static Tcl_Channel chan = NULL; + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, NULL); + return (TCL_ERROR); + } + unless (L->global->script_argc) { + Tcl_Obj *objv[2]; + + objv[0] = Tcl_NewStringObj("angle_read_", -1); + objv[1] = Tcl_NewStringObj("stdin", -1); + res = Tcl_FGetlineObjCmd(dummy, interp, 2, objv); + Tcl_DecrRefCount(objv[0]); + Tcl_DecrRefCount(objv[1]); + return (res); + } + Tcl_ListObjGetElements(L->interp, L->global->script_argv, &argc, &argv); + while (1) { + if (chan) { + ret = do_getline(interp, chan); + if (ret && !ret->undef) break; + Tcl_UnregisterChannel(interp, chan); + chan = NULL; + } + if (cur >= argc) { + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + break; + } + chan = Tcl_FSOpenFileChannel(interp, argv[cur++], "r", 0); + if (chan) { + Tcl_RegisterChannel(interp, chan); + } else { + fprintf(stderr, "%s\n", Tcl_GetStringResult(interp)); + Tcl_ResetResult(interp); + } + } + return (TCL_OK); +} + +extern int Tcl_WriteObjN(Tcl_Channel chan, Tcl_Obj *objPtr, int numBytes); + +int +Tcl_LWriteCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int mode, nbytes; + char *errmsg = ""; + Tcl_Channel chan; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 1, objv, "channel buffer numBytes"); + return (TCL_ERROR); + } + if (TclGetChannelFromObj(interp, objv[1], &chan, &mode, 0) != TCL_OK) { + return (TCL_ERROR); + } + if (!(mode & TCL_WRITABLE)) { + errmsg = "channel wasn't opened for writing"; + goto err; + } + if (Tcl_GetIntFromObj(interp, objv[3], &nbytes) != TCL_OK) { + return (TCL_ERROR); + } + nbytes = Tcl_WriteObjN(chan, objv[2], nbytes); + if (nbytes < 0) { + if (!TclChanCaughtErrorBypass(interp, chan)) { + errmsg = (char *)Tcl_PosixError(interp); + } + goto err; + } + out: + Tcl_SetObjResult(interp, Tcl_NewIntObj(nbytes)); + return (TCL_OK); + err: + Tcl_SetVar2(interp, "::stdio_lasterr", NULL, errmsg, TCL_GLOBAL_ONLY); + nbytes = -1; + goto out; +} + +int +Tcl_LReadCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int mode, nbytes = -1; + char *errmsg = ""; + Tcl_Channel chan; + Tcl_Obj *buf; + + if ((objc != 4) && (objc != 3)) { + Tcl_WrongNumArgs(interp, 1, objv, "channel varName ?numBytes"); + return (TCL_ERROR); + } + if (TclGetChannelFromObj(interp, objv[1], &chan, &mode, 0) != TCL_OK) { + return (TCL_ERROR); + } + if (!(mode & TCL_READABLE)) { + errmsg = "channel wasn't opened for reading"; + goto err; + } + if (Tcl_Eof(chan)) { + errmsg = "end of file"; + goto err; + } + if (objc == 4) { + if (Tcl_GetIntFromObj(interp, objv[3], &nbytes) != TCL_OK) { + return (TCL_ERROR); + } + } + buf = Tcl_NewObj(); + Tcl_IncrRefCount(buf); + nbytes = Tcl_ReadChars(chan, buf, nbytes, 0); + if (nbytes < 0) { + if (!TclChanCaughtErrorBypass(interp, chan)) { + errmsg = (char *)Tcl_PosixError(interp); + } + Tcl_DecrRefCount(buf); + goto err; + } + Tcl_ObjSetVar2(interp, objv[2], NULL, buf, TCL_LEAVE_ERR_MSG); + Tcl_DecrRefCount(buf); + Tcl_SetObjResult(interp, Tcl_NewIntObj(nbytes)); + return (TCL_OK); + err: + Tcl_SetVar(interp, "::stdio_lasterr", errmsg, TCL_GLOBAL_ONLY); + Tcl_SetObjResult(interp, Tcl_NewIntObj(-1)); + return (TCL_OK); +} + +int +Tcl_LRefCnt( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "object"); + return (TCL_ERROR); + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(objv[1]->refCount)); + return (TCL_OK); +} + +/* + * This defines a defined() proc even though it also is a compiler + * built-in. When L code uses defined(), it gets the built-in. + * Having the proc allows access to this functionality from Tcl code. + */ +int +Tcl_LDefined( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "object"); + return (TCL_ERROR); + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(objv[1]->undef ? 0 : 1)); + return (TCL_OK); +} + +/* + * This evaluates an Lhtml document. All input is passed through + * to Tcl's stdout channel with two kinds of interpolation: + * + * - Anything between <? and ?> is taken to be L statements + * and is replaced by whatever that L code outputs. + * + * - Anything between <?= and ?> is taken to be an L expression and is + * replaced by whatever it evaluates to (this is just like regular L + * string interpolation). + * + * This works by putting the scanner into an Lhtml mode where + * <?, <?=, and ?> are recognized. The parser contains rules for + * wrapping the html in puts() calls. + */ +int +Tcl_LHtmlObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int ret; + + L_lex_begLhtml(); + ret = Tcl_LObjCmd(NULL, interp, objc, objv); + L_lex_endLhtml(); + return (ret); +} + +/* + * A Tcl_Obj type to store a pointer into a string buffer that we can + * walk down over time. The twpPtrValue internalrep is used, with the + * first ptr pointing to a ckalloc'd Bufptr struct (defined below) and + * the second ptr pointing to a copy of the buffer. + */ +static Tcl_ObjType L_bufPtrType = { + "l-bufPtrType", + NULL, /* freeIntRepProc */ + NULL, /* dupIntRepProc */ + NULL, /* updateStringProc */ + NULL /* setFromAnyProc */ +}; +typedef struct { + char *p; + char *end; +} Bufptr; + +int +Tcl_LGetNextLineInit( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int len; + char *beg, *s; + Tcl_Obj *tmp; + Bufptr *bufptr; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "object"); + return (TCL_ERROR); + } + if (objv[1]->undef) { + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + return (TCL_OK); + } + + /* + * Make a copy of the string whose lines we will walk. Do + * this instead of copying the Tcl_Obj to avoid problems with + * possible shimmering (i.e., the Tcl_Obj's string-rep buffer is + * not guaranteed to remain). + */ + s = Tcl_GetStringFromObj(objv[1], &len); + beg = ckalloc(len + 1); + memcpy(beg, s, len); + beg[len] = '\0'; + + /* + * Stash the copied string and a Bufptr into it inside of a + * tmp Tcl_Obj that will live for the duration of the walk. + * Tcl_LGetNextLine() will process it. + */ + tmp = Tcl_NewObj(); + tmp->typePtr = &L_bufPtrType; + bufptr = (Bufptr *)ckalloc(sizeof(Bufptr)); + bufptr->p = beg; + bufptr->end = beg + len; + tmp->internalRep.twoPtrValue.ptr1 = bufptr; + tmp->internalRep.twoPtrValue.ptr2 = beg; + + Tcl_SetObjResult(interp, tmp); + return (TCL_OK); +} + +int +Tcl_LGetNextLine( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tcl_Obj *ret, *tmp; + char *beg, *p; + Bufptr *bufptr; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "tmp"); + return (TCL_ERROR); + } + tmp = objv[1]; + if (tmp->undef) goto nomore; + unless (tmp->typePtr == &L_bufPtrType) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("invalid tmp object", -1)); + return (TCL_ERROR); + } + bufptr = (Bufptr *)tmp->internalRep.twoPtrValue.ptr1; + unless (bufptr) goto nomore; + + beg = bufptr->p; + if (beg >= bufptr->end) goto nomore; + + for (p = beg; p < bufptr->end; ++p) { + if (p[0] == '\n') { + bufptr->p = p + 1; + break; + } + if (((p+1) < bufptr->end) && (p[0] == '\r') && (p[1] == '\n')) { + bufptr->p = p + 2; + break; + } + } + ret = Tcl_NewStringObj(beg, p - beg); + if (p == bufptr->end) { + ckfree(tmp->internalRep.twoPtrValue.ptr2); + ckfree((char *)bufptr); + tmp->internalRep.twoPtrValue.ptr1 = NULL; + tmp->internalRep.twoPtrValue.ptr2 = NULL; + } + Tcl_SetObjResult(interp, ret); + return (TCL_OK); + nomore: + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + return (TCL_OK); +} + +#ifdef _WIN32 + +int +Tcl_LGetDirX( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int len, ret; + Tcl_Obj *argv[2], *dirObjs, *eltObjs[3], *fileObjs, *listObj; + char *buf, *dir, *type, *utfname; + Tcl_DString ds; + HANDLE hFind; + WIN32_FIND_DATA f; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "directory"); + return (TCL_ERROR); + } + + // Append \* to the given directory path. + dir = cksprintf("%s\\*", Tcl_GetString(objv[1])); + Tcl_WinUtfToTChar(dir, -1, &ds); + + hFind = FindFirstFile((TCHAR *)Tcl_DStringValue(&ds), &f); + if (hFind == INVALID_HANDLE_VALUE) { + TclWinConvertError(GetLastError()); + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char *)&buf, + 0, NULL); + // Chomp the cr,lf that windows added to buf. + len = strlen(buf); + if (len > 2) buf[len-2] = 0; + Tcl_SetVar(interp, "::stdio_lasterr", + buf, + TCL_GLOBAL_ONLY); + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + LocalFree(buf); + return (TCL_OK); + } + ckfree(dir); + Tcl_DStringFree(&ds); + + fileObjs = Tcl_NewListObj(0, NULL); + dirObjs = Tcl_NewListObj(0, NULL); + do { + utfname = Tcl_WinTCharToUtf(f.cFileName, -1, &ds); + eltObjs[0] = Tcl_NewStringObj(utfname, -1); + if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + type = "directory"; + } else { + type = "file"; + } + eltObjs[1] = Tcl_NewStringObj(type, -1); + if ((f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || + (*utfname == '.')) { + eltObjs[2] = Tcl_NewIntObj(1); + } else { + eltObjs[2] = Tcl_NewIntObj(0); + } + listObj = Tcl_NewListObj(3, eltObjs); + if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + Tcl_ListObjAppendElement(interp, dirObjs, listObj); + } else { + Tcl_ListObjAppendElement(interp, fileObjs, listObj); + } + Tcl_DStringFree(&ds); + } while (FindNextFile(hFind, &f)); + FindClose(hFind); + + // Sort the lists. + argv[1] = dirObjs; + Tcl_IncrRefCount(dirObjs); + Tcl_ResetResult(interp); + ret = Tcl_LsortObjCmd(NULL, interp, 2, argv); + Tcl_DecrRefCount(dirObjs); + if (ret == TCL_OK) { + dirObjs = Tcl_DuplicateObj(Tcl_GetObjResult(interp)); + } + + argv[1] = fileObjs; + Tcl_IncrRefCount(fileObjs); + Tcl_ResetResult(interp); + ret = Tcl_LsortObjCmd(NULL, interp, 2, argv); + Tcl_DecrRefCount(fileObjs); + if (ret == TCL_OK) { + fileObjs = Tcl_GetObjResult(interp); + } + + // Return a list with the file names after all the dir names. + Tcl_ListObjAppendList(interp, dirObjs, fileObjs); + Tcl_SetObjResult(interp, dirObjs); + return (TCL_OK); +} + +#else // #ifdef WIN32 + +int +Tcl_LGetDirX( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int ret; + Tcl_Obj *argv[2], *dirObjs, *eltObjs[3], *fileObjs, *listObj; + DIR *d; + struct dirent *dent; + char *dir, *type; +#ifndef HAVE_STRUCT_DIRENT_D_TYPE + char *path; + struct stat st; +#endif + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "directory"); + return (TCL_ERROR); + } + + dir = Tcl_GetString(objv[1]); + d = opendir(dir); + unless (d) { + Tcl_SetVar(interp, "::stdio_lasterr", + strerror(errno), + TCL_GLOBAL_ONLY); + Tcl_SetObjResult(interp, *L_undefObjPtrPtr()); + return (TCL_OK); + } + + fileObjs = Tcl_NewListObj(0, NULL); + dirObjs = Tcl_NewListObj(0, NULL); + while (dent = readdir(d)) { + eltObjs[0] = Tcl_NewStringObj(dent->d_name, -1); +#ifdef HAVE_STRUCT_DIRENT_D_TYPE + switch (dent->d_type) { + case DT_REG: type = "file"; break; + case DT_DIR: type = "directory"; break; + default: type = "other"; break; + } +#else + path = cksprintf("%s/%s", dir, dent->d_name); + if (stat(path, &st)) { + type = "unknown"; + } else if (S_ISREG(st.st_mode)) { + type = "file"; + } else if (S_ISDIR(st.st_mode)) { + type = "directory"; + } else { + type = "other"; + } + ckfree(path); +#endif + eltObjs[1] = Tcl_NewStringObj(type, -1); + if (*dent->d_name == '.') { + eltObjs[2] = Tcl_NewIntObj(1); + } else { + eltObjs[2] = Tcl_NewIntObj(0); + } + listObj = Tcl_NewListObj(3, eltObjs); + if (*type == 'd') { + Tcl_ListObjAppendElement(interp, dirObjs, listObj); + } else { + Tcl_ListObjAppendElement(interp, fileObjs, listObj); + } + } + closedir(d); + + // Sort the lists. + argv[1] = dirObjs; + Tcl_IncrRefCount(dirObjs); + Tcl_ResetResult(interp); + ret = Tcl_LsortObjCmd(NULL, interp, 2, argv); + Tcl_DecrRefCount(dirObjs); + if (ret == TCL_OK) { + dirObjs = Tcl_DuplicateObj(Tcl_GetObjResult(interp)); + } + + argv[1] = fileObjs; + Tcl_IncrRefCount(fileObjs); + Tcl_ResetResult(interp); + ret = Tcl_LsortObjCmd(NULL, interp, 2, argv); + Tcl_DecrRefCount(fileObjs); + if (ret == TCL_OK) { + fileObjs = Tcl_GetObjResult(interp); + } + + // Return a list with the file names after all the dir names. + Tcl_ListObjAppendList(interp, dirObjs, fileObjs); + Tcl_SetObjResult(interp, dirObjs); + return (TCL_OK); +} + +#endif // #ifdef WIN32 |