/* fileIn reads in a module definition */ void fileIn(FILE *fd, boolean printit) { while (fgets(textBuffer, TextBufferSize, fd) != NULL) { lexinit(textBuffer); if (token == inputend) ; /* do nothing, get next line */ else if ((token == binary) && streq(tokenString, "\"")) ; /* do nothing, its a comment */ /* Syntax for Class and Methods definitions Number extend [ radiusToArea [ ^self squared * Float pi ] radiusToCircumference [ ^self * 2 * Float pi ] ] */ else if ((token == nameconst) && streq(tokenString,"Class")) readClassDeclaration(); else if ((token == nameconst) && streq(tokenString,"Methods")) readMethods(fd, printit); else sysError("unrecognized line", textBuffer); } }
void loop(int toplevel) { List list; #ifdef DEBUG int oasp = toplevel ? 0 : alloc_stackp; #endif pushheap(); for (;;) { freeheap(); errflag = 0; if (interact && isset(SHINSTDIN)) preprompt(); hbegin(); /* init history mech */ intr(); /* interrupts on */ lexinit(); /* initialize lexical state */ if (!(list = parse_event())) { /* if we couldn't parse a list */ hend(); if (tok == ENDINPUT && !errflag) break; continue; } if (hend()) { int toksav = tok; if (stopmsg) /* unset 'you have stopped jobs' flag */ stopmsg--; execlist(list, 0, 0); tok = toksav; if (toplevel) noexitct = 0; } DPUTS(alloc_stackp != oasp, "BUG: alloc_stackp changed in loop()"); if (ferror(stderr)) { zerr("write error", NULL, 0); clearerr(stderr); } if (subsh) /* how'd we get this far in a subshell? */ exit(lastval); if (((!interact || sourcelevel) && errflag) || retflag) break; if (trapreturn) { lastval = trapreturn; trapreturn = 0; } if (isset(SINGLECOMMAND) && toplevel) { if (sigtrapped[SIGEXIT]) dotrap(SIGEXIT); exit(lastval); } } popheap(); }
/* YY_STARTBLOCK -- Terminate the last command block and start a new one. * Save old command block in history (if interactive) and in logfile (if * interactive, logging is enabled, and logflag argument is true). Even * if logging is enabled, a command will not be logged which aborts or is * interrupted. */ void yy_startblock ( int logflag ) { register char *ip; if (cldebug) eprintf ("startblock (%d)\n", logflag); /* Log cmdblk only if it was filled by an interactive task. We must * make the test when the new block is initialized since the write is * delayed. */ if (cmdblk_save) { /* Do not record commands which consist only of whitespace. */ for (ip=cmdblk; isspace (*ip); ip++) ; if (*ip != EOS) { /* Use the raw_cmdblk, saved in get_command(). */ put_history (raw_cmdblk); if (logflag && log_commands()) put_logfile (raw_cmdblk); } } if (cldebug) eprintf ("startblock: ifseen=%d\n", ifseen); if (!ifseen) { ip_cmdblk = op_cmdblk = cmdblk; *ip_cmdblk = EOS; } cmdblk_line = 0; cmdblk_save = (currentask->t_flags & T_INTERACTIVE); /* Mode switching of the lexical analyzer is enabled by this call * if the CL parameter lexmodes is set. Called between blocks * entered interactively and also during error recovery. */ lexinit(); }
enum loop_return loop(int toplevel, int justonce) { Eprog prog; int err, non_empty = 0; queue_signals(); pushheap(); if (!toplevel) zcontext_save(); for (;;) { freeheap(); if (stophist == 3) /* re-entry via preprompt() */ hend(NULL); hbegin(1); /* init history mech */ if (isset(SHINSTDIN)) { setblock_stdin(); if (interact && toplevel) { int hstop = stophist; stophist = 3; /* * Reset all errors including the interrupt error status * immediately, so preprompt runs regardless of what * just happened. We'll reset again below as a * precaution to ensure we get back to the command line * no matter what. */ errflag = 0; preprompt(); if (stophist != 3) hbegin(1); else stophist = hstop; /* * Reset all errors, including user interupts. * This is what allows ^C in an interactive shell * to return us to the command line. */ errflag = 0; } } use_exit_printed = 0; intr(); /* interrupts on */ lexinit(); /* initialize lexical state */ if (!(prog = parse_event(ENDINPUT))) { /* if we couldn't parse a list */ hend(NULL); if ((tok == ENDINPUT && !errflag) || (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) || justonce) break; if (exit_pending) { /* * Something down there (a ZLE function?) decided * to exit when there was stuff to clear up. * Handle that now. */ stopmsg = 1; zexit(exit_pending >> 1, 0); } if (tok == LEXERR && !lastval) lastval = 1; continue; } if (hend(prog)) { enum lextok toksav = tok; non_empty = 1; if (toplevel && (getshfunc("preexec") || paramtab->getnode(paramtab, "preexec" HOOK_SUFFIX))) { LinkList args; char *cmdstr; /* * As we're about to freeheap() or popheap() * anyway, there's no gain in using permanent * storage here. */ args = newlinklist(); addlinknode(args, "preexec"); /* If curline got dumped from the history, we don't know * what the user typed. */ if (hist_ring && curline.histnum == curhist) addlinknode(args, hist_ring->node.nam); else addlinknode(args, ""); addlinknode(args, dupstring(getjobtext(prog, NULL))); addlinknode(args, cmdstr = getpermtext(prog, NULL, 0)); callhookfunc("preexec", args, 1, NULL); /* The only permanent storage is from getpermtext() */ zsfree(cmdstr); /* * Note this does *not* remove a user interrupt error * condition, even though we're at the top level loop: * that would be inconsistent with the case where * we didn't execute a preexec function. This is * an implementation detail that an interrupting user * does't care about. */ errflag &= ~ERRFLAG_ERROR; } if (stopmsg) /* unset 'you have stopped jobs' flag */ stopmsg--; execode(prog, 0, 0, toplevel ? "toplevel" : "file"); tok = toksav; if (toplevel) noexitct = 0; } if (ferror(stderr)) { zerr("write error"); clearerr(stderr); } if (subsh) /* how'd we get this far in a subshell? */ exit(lastval); if (((!interact || sourcelevel) && errflag) || retflag) break; if (isset(SINGLECOMMAND) && toplevel) { dont_queue_signals(); if (sigtrapped[SIGEXIT]) dotrap(SIGEXIT); exit(lastval); } if (justonce) break; }
void loop(int toplevel, int justonce) { List list; #ifdef DEBUG int oasp = toplevel ? 0 : alloc_stackp; #endif pushheap(); for (;;) { freeheap(); errflag = 0; if (isset(SHINSTDIN)) { setblock_stdin(); if (interact) preprompt(); } hbegin(); /* init history mech */ intr(); /* interrupts on */ lexinit(); /* initialize lexical state */ if (!(list = parse_event())) { /* if we couldn't parse a list */ hend(); if ((tok == ENDINPUT && !errflag) || justonce) break; continue; } if (hend()) { int toksav = tok; List prelist; if (toplevel && (prelist = getshfunc("preexec")) != &dummy_list) { Histent he = gethistent(curhist); LinkList args; PERMALLOC { args = newlinklist(); addlinknode(args, "preexec"); if (he && he->text) addlinknode(args, he->text); } LASTALLOC; doshfunc(prelist, args, 0, 1); freelinklist(args, (FreeFunc) NULL); errflag = 0; } if (stopmsg) /* unset 'you have stopped jobs' flag */ stopmsg--; execlist(list, 0, 0); tok = toksav; if (toplevel) noexitct = 0; } DPUTS(alloc_stackp != oasp, "BUG: alloc_stackp changed in loop()"); if (ferror(stderr)) { zerr("write error", NULL, 0); clearerr(stderr); } if (subsh) /* how'd we get this far in a subshell? */ exit(lastval); if (((!interact || sourcelevel) && errflag) || retflag) break; if (trapreturn) { lastval = trapreturn; trapreturn = 0; } if (isset(SINGLECOMMAND) && toplevel) { if (sigtrapped[SIGEXIT]) dotrap(SIGEXIT); exit(lastval); } if (justonce) break; }
int main(int argc, char *argv[]) { int i; char *s; while(argc>1 && argv[1][0]=='-'){ s=argv[1]+1; while(*s) switch(*s++){ case 'b': /* suppress become flattening */ bflag=1; break; case 'c': /* print constants */ cflag=1; break; case 'C': /* suppress constant compilation */ Cflag=1; break; case 'e': /* dump core on errors */ eflag=1; break; case 'i': /* print compiled instructions */ iflag=1; break; case 'm': /* trace message passing */ mflag=1; break; case 'P': /* set number of procs */ if(*s==0){ --argc, argv++; s=argv[1]; if(s==0) goto Usage; } Nproc=atol(s); if(Nproc<=0) goto Usage; goto Out; case 'p': /* trace process creation */ pflag++; break; case 't': /* dump parse trees */ tflag=1; break; case 'x': /* trace execution */ xflag=1; break; default: Usage: fprint(2, "usage: squint [-ixpb -PNPROC] <files>\n"); return 1; } Out: --argc; argv++; } interactive=argc==1; procinit(); fmtinstall('b', bconv); fmtinstall('e', econv); fmtinstall('m', mconv); fmtinstall('n', nconv); fmtinstall('t', tconv); fmtinstall('i', iconv); fmtinstall('A', Aconv); fmtinstall('C', Cconv); fmtinstall('U', Uconv); fmtinstall('z', zconv); fmtinstall('Z', zconv); lexinit(); typeinit(); initializing=1; if(errmark()){ fprint(2, "squint: error during initialization; exiting\n"); exits("initialization error"); } if(argc==1) newfile("<stdin>", 1); for(i=argc-1; i>0; --i) newfile(argv[i], 0); initializing=0; errmark(); /* Fflush(1); */ do; while(yyparse()); return 0; }
/* GET_COMMAND -- Get command line from the input stream. If not interactive, * all we do is read the line into the cmdblk buffer. If called when parsing * command input to an interactive task, we must output a prompt before * reading in the command line. The prompt changes depending on whether or * not the command is the first in a command block (whether or not we have * closure). After reading the command, we check if it is a history directive * and process it if so. Otherwise we must still process it to expand any * history macros. Ignore all blank or comment lines. These are * any line in which the first non-blank character is a newline or a * '#'. This will make some things a bit more efficient, but is * actually to allow the if/else parsing to work properly. * * N.B.: We must directly or indirectly set ip_cmdblk so that yy_getc takes * the next character from the right place. This is either done directly * or by a call to yy_startblock. */ int get_command ( FILE *fp ) { register char *ip, *op; char raw_cmd[SZ_LINE+1]; /* buffer for raw command line */ char new_cmd[SZ_CMDBLK+1]; /* temporary for processed cmd */ int execute=1, temp, status; if (!(currentask->t_flags & T_INTERACTIVE) || parse_state == PARSE_PARAMS) { /* Ensure that searches through string terminate. */ cmdblk[SZ_LINE] = '\0'; ip_cmdblk = cmdblk; while (YES) { currentask->t_scriptln++; /* noninteractive mode */ status = (fgets (cmdblk, SZ_LINE, fp) == NULL ? EOF : OK); if (status == EOF) { cmdblk[0] = '\0'; break; } /* Check if this is a blank line. */ for (ip = cmdblk; *ip == ' ' || *ip == '\t'; ip++) ; if (*ip == '\n' || *ip == '\0') continue; /* Check for the #{ ... #} lexmode toggle sequences. These * are matched only at the beginning of a line. #{ sets * command mode on the command input stream and #} clears it. */ if (*ip == '#') { if (ip == cmdblk) { if (*(ip+1) == '{') { lex_setcpumode (fp); lexinit(); } else if (*(ip+1) == '}') { lex_clrcpumode (fp); lexinit(); } } continue; } break; } if (cldebug || echocmds()) eprintf ("%s", status == EOF ? "bye\n" : cmdblk); return (status); } raw_cmd[SZ_LINE] = '\0'; while (YES) { /* Prompt the user for a new command if the input buffer is empty. * The CL prompt clears raw mode in case it is left in effect by a * program abort. */ input_: if (c_fstati (fileno(fp), F_UNREAD) == 0) { if (c_fstati ((XINT)STDIN, F_RAW) == YES) c_fseti ((XINT)STDIN, F_RAW, NO); if (cmdblk_line == 0) pprompt (curpack->pk_name); else pprompt (NOCLOSURE); } /* Read the next command line. */ if (fgets (raw_cmd, SZ_LINE, fp) == NULL) return (EOF); /* Check for the #{ ... #} lexmode toggle sequences. These * are matched only at the beginning of a line. #{ sets * command mode on the command input stream and #} clears it. */ if (*(ip=raw_cmd) == '#') { if (*(ip+1) == '{') { lex_setcpumode (fp); lexinit(); } else if (*(ip+1) == '}') { lex_clrcpumode (fp); lexinit(); } } /* Skip leading whitespace. */ for (ip=raw_cmd; *ip == ' ' || *ip == '\t'; ip++) ; /* For interactive comments, make sure we store them in the * history and the logfile. This is so that users can add * comments into the logfile interactively. */ if (*ip == '#') { put_history (raw_cmd); if (log_commands()) put_logfile (raw_cmd); } else if (*ip != '\n' && *ip != '\0') { cmdblk_line++; break; } } /* If history directive, transform the directive into an executable * command block using the history data. Echo the new command as * if the user had typed it, for verification. */ if (*raw_cmd == HISTCHAR) { /* Use screen style history editing only if the CL parameter * "ehinit" contains the boolean variable "verify" (or if the * cmd is "ehistory", below). */ if (eh_verify) execute = edit_history_directive (raw_cmd+1, new_cmd); else { execute = process_history_directive (raw_cmd, new_cmd); fputs (new_cmd, currentask->t_stdout); } } else if (expand_history_macros (raw_cmd, new_cmd)) { fputs (new_cmd, currentask->t_stdout); } else { static char ehist[] = "ehistory"; int n; for (n=0, ip=raw_cmd, op=ehist; (*ip == *op); ip++, op++) n++; if (n > 0 && isspace (*ip)) { while (isspace (*ip)) ip++; execute = edit_history_directive (ip, new_cmd); } } /* If user deletes entire line go back and get another command. */ for (ip=new_cmd; isspace (*ip); ip++) ; if (*ip == EOS) { cmdblk_line = 0; execute = 1; goto input_; } /* Now move the processed command into the cmdblk buffer. If there * is not enough storage remaining in the cmdblk buffer, we have to * break the actual (large) command block up, calling yy_startblock to * start a new block, but without changing the line number within the * block. We must not let the history mechanism limit the size of a * command block. */ op_cmdblk = ip_cmdblk - 1; /* back up to EOS */ if (strlen (new_cmd) > (cmdblk + SZ_CMDBLK - op_cmdblk)) { temp = cmdblk_line; yy_startblock (LOG); cmdblk_line = temp; } ip_cmdblk = op = op_cmdblk; for (ip=new_cmd; (*op++ = *ip++) != EOS; ) ; /* Save the "raw command" here for use in yy_startblock. This is * to handle the problem of procedure script parsing overwriting * the raw command in cmdblk. */ strcpy (raw_cmdblk, cmdblk); if (!execute) yy_startblock (NOLOG); fflush (currentask->t_stdout); return (OK); }
// Call this to get the tokens. // The number of returned tokens is returned in *plen. Token* _gettoks(uchar* data, int datalen, int chset, int mtype, int* plen) { TokenSource* ts; Token* a; int alen; int ai; int starti; int c; int tag; if(!lexinited) lexinit(); ts = newtokensource(data, datalen, chset, mtype); if(dbglex) fprint(2, "_gettoks starts, ts.i=%d, ts.edata=%d\n", ts->i, ts->edata); alen = 0; ai = 0; a = 0; if(ts->mtype == TextHtml) { for(;;) { if(alen - ai < ToksChunk/32) { alen += ToksChunk; a = erealloc(a, alen*sizeof *a); } starti = ts->i; c = getchar(ts); if(c < 0) break; if(c == '<') { tag = gettag(ts, starti, a, &ai); if(tag == Tscript || tag == Tstyle) { // special rules for getting Data after.... starti = ts->i; c = getchar(ts); tag = getscriptdata(ts, c, starti, a, &ai, tag); } } else tag = getdata(ts, c, starti, a, &ai); if(tag == -1) break; else if(dbglex > 1 && tag != Comment) fprint(2, "lex: got token %T\n", &a[ai-1]); } } else { // plain text (non-html) tokens for(;;) { if(alen - ai < ToksChunk/32) { alen += ToksChunk; a = erealloc(a, alen*sizeof *a); } tag = getplaindata(ts, a, &ai); if(tag == -1) break; if(dbglex > 1) fprint(2, "lex: got token %T\n", &a[ai]); } } free(ts); if(dbglex) fprint(2, "lex: returning %d tokens\n", ai); *plen = ai; if(ai == 0){ free(a); a = 0; } return a; }
/* CALLNEWTASK -- Called from CALL instruction to push and setup a new task * structure. If find a known ltask with given name create a new task on * control stack, set up newtask and defaults for the pseudofiles. * Pseudofiles may be effected by other instructions before it gets to exec. * Make sure we have a pfile list; either try to read it if task is * supposed to have a real one or manufacture the beginnings of one if it * isn't and set PF_FAKE. New task runs with a copy of the pfile if it * wasn't fake. Guard against making more than one copy. Also, don't dup * the cl's params to maintain the meaning of "firstask". Things like mode, * logfile and abbreviations should be global and permanent. * Special case for package names essentially runs a cl but with a new curpack, * the only real semantic intent of "running" a package. * This lets a package name given as a command appear to change the current * package and yet remain interactive. Since it really is a new task, state * saving and restoring on error will work right and we also achieve an * ability to have multiple package defn's in a script ltask. * Any parameter references will refer to the cl's also. */ void callnewtask ( char *name ) { /* x1 and x2 are just place holders to call breakout(). */ char *x1, *pk, *t, *x2; struct ltask *ltp; int flags, ltflags; if (cldebug) eprintf ("callnewtask: name=%s, currentask=%x\n", name, currentask); /* Save current dictionary and stack pointers. they get restored when * the new task dies normally and the current task is to continue. * save pc when get to the EXEC instruction so it continues from there. */ currentask->t_topos = topos; /* save these two just in case */ currentask->t_basos = basos; /* something is left on the stk */ currentask->t_topcs = topcs; /* save before adding newtask */ currentask->t_topd = topd; /* save before adding pfile */ currentask->t_curpack = curpack;/* save in case changing to a new one*/ c_envmark (¤task->t_envp);/* save env stack pointer */ currentask->t_pno = 0; /* set only if task defines pkg */ newtask = pushtask(); flags = 0; /* Search for the command to run. A leading '$' signifies that * execution is to be time but is not part of the name. Set ltp * and newtask->t_pfp depending on whether we are running a task or * a package. */ if (*name == '$') { flags |= T_TIMEIT; name++; } breakout (name, &x1, &pk, &t, &x2); ltp = cmdsrch (pk, t); if (ltp->lt_flags & LT_CL) { /* Change curpack if LT_PACCL. (cmdsrch() set lt_pkp). Just * changing packages; use cl's ltask and pfile. Push a new cl() * on the control stack, with the T_PKGCL and T_CL flags set. */ if (ltp->lt_flags & LT_PACCL) { flags |= T_PKGCL; curpack = ltp->lt_pkp; } else if (ltp->lt_flags & LT_CLEOF) flags |= T_CLEOF; ltp = firstask->t_ltp; newtask->t_pfp = firstask->t_pfp; /* Initialize the lexical analyzer (necessary to recognize BOL). */ lexinit(); } else { if (ltp->lt_flags & LT_PFILE) { register struct pfile *pfp; /* This task has a real pfile. read in if not already in * core. Copy if not already one and not just cl. */ newtask->t_pfp = NULL; if ((pfp = pfilefind (ltp)) == NULL) pfp = pfileload (ltp); if (!(pfp->pf_flags & PF_COPY) && ltp != firstask->t_ltp) pfp = pfilecopy (pfp); newtask->t_pfp = pfp; /* Also load any pset files associated with the main pfile. * These are linked into a list with the main pfile at the * head of the list, pointed to by the task descriptor. */ if (pfp->pf_flags & PF_PSETREF) { register struct param *pp; struct operand o; char *pset; for (pp = pfp->pf_pp; pp != NULL; pp = pp->p_np) { if (!(pp->p_type & PT_PSET)) continue; o = pp->p_valo; if (opundef(&o) || *(pset = o.o_val.v_s) == EOS) pset = pp->p_name; pfp = pfp->pf_npset = pfilecopy (pfilesrch (pset)); pfp->pf_psetp = pp; } } } else { /* This task does not have a real pfile so start a fake one. */ newtask->t_pfp = newpfile (ltp); newtask->t_pfp->pf_flags = PF_FAKE; } } newtask->t_pfp->pf_n = 0; /* init number of command line args */ newtask->t_ltp = ltp; newtask->t_pid = -1; /* gets set if do a real exec */ newtask->t_stdin = currentask->t_stdin; /* inherit files */ newtask->t_stdout = currentask->t_stdout; newtask->t_stderr = currentask->t_stderr; newtask->t_stdgraph = currentask->t_stdgraph; newtask->t_stdimage = currentask->t_stdimage; newtask->t_stdplot = currentask->t_stdplot; /* Init i/o redirection for a foreign task. */ newtask->ft_in = newtask->ft_out = newtask->ft_err = NULL; /* Set up flags describing the kind of task we are about to run. the * absence of any of these flags will imply a genuine executable task. * the flags in t_flags are more of a convenience than anything since * later tests could use the same tests used here. */ ltflags = ltp->lt_flags; if (ltflags & LT_PSET) { flags = (T_SCRIPT|T_PSET); } else if (ltflags & LT_SCRIPT) { newtask->t_scriptln = 0; flags = T_SCRIPT; } else if (ltflags & LT_FOREIGN) { flags = T_BUILTIN | T_FOREIGN; /* a type of builtin */ } else if (ltflags & LT_BUILTIN) { flags = T_BUILTIN; } else if (ltflags & LT_CL) { /* Or, not assign: preserve T_PKGCL and T_CLEOF flags if set. */ flags |= T_CL; } if (ltflags & LT_STDINB) flags |= T_STDINB; if (ltflags & LT_STDOUTB) flags |= T_STDOUTB; newtask->t_flags = flags; }
enum loop_return loop(int toplevel, int justonce) { Eprog prog; int err, non_empty = 0; pushheap(); if (!toplevel) lexsave(); for (;;) { freeheap(); if (stophist == 3) /* re-entry via preprompt() */ hend(NULL); hbegin(1); /* init history mech */ if (isset(SHINSTDIN)) { setblock_stdin(); if (interact && toplevel) { int hstop = stophist; stophist = 3; preprompt(); if (stophist != 3) hbegin(1); else stophist = hstop; errflag = 0; } } use_exit_printed = 0; intr(); /* interrupts on */ lexinit(); /* initialize lexical state */ if (!(prog = parse_event())) { /* if we couldn't parse a list */ hend(NULL); if ((tok == ENDINPUT && !errflag) || (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) || justonce) break; if (exit_pending) { /* * Something down there (a ZLE function?) decided * to exit when there was stuff to clear up. * Handle that now. */ stopmsg = 1; zexit(exit_pending >> 1, 0); } if (tok == LEXERR && !lastval) lastval = 1; continue; } if (hend(prog)) { int toksav = tok; non_empty = 1; if (toplevel && (getshfunc("preexec") || paramtab->getnode(paramtab, "preexec" HOOK_SUFFIX))) { LinkList args; char *cmdstr; /* * As we're about to freeheap() or popheap() * anyway, there's no gain in using permanent * storage here. */ args = newlinklist(); addlinknode(args, "preexec"); /* If curline got dumped from the history, we don't know * what the user typed. */ if (hist_ring && curline.histnum == curhist) addlinknode(args, hist_ring->node.nam); else addlinknode(args, ""); addlinknode(args, dupstring(getjobtext(prog, NULL))); addlinknode(args, cmdstr = getpermtext(prog, NULL, 0)); callhookfunc("preexec", args, 1, NULL); /* The only permanent storage is from getpermtext() */ zsfree(cmdstr); errflag = 0; } if (stopmsg) /* unset 'you have stopped jobs' flag */ stopmsg--; execode(prog, 0, 0, toplevel ? "toplevel" : "file"); tok = toksav; if (toplevel) noexitct = 0; } if (ferror(stderr)) { zerr("write error"); clearerr(stderr); } if (subsh) /* how'd we get this far in a subshell? */ exit(lastval); if (((!interact || sourcelevel) && errflag) || retflag) break; if (isset(SINGLECOMMAND) && toplevel) { if (sigtrapped[SIGEXIT]) dotrap(SIGEXIT); exit(lastval); } if (justonce) break; }