--- freem/src/xecline.c 2025/04/10 01:24:39 1.13 +++ freem/src/xecline.c 2025/05/20 16:20:42 1.30 @@ -1,5 +1,5 @@ /* - * $Id: xecline.c,v 1.13 2025/04/10 01:24:39 snw Exp $ + * $Id: xecline.c,v 1.30 2025/05/20 16:20:42 snw Exp $ * freem interpreter proper * * @@ -24,6 +24,57 @@ * along with FreeM. If not, see . * * $Log: xecline.c,v $ + * Revision 1.30 2025/05/20 16:20:42 snw + * Update ROUTINE SSVN after ZEDIT and ZSAVE + * + * Revision 1.29 2025/05/20 14:56:56 snw + * Fix direct-mode interface to online help + * + * Revision 1.28 2025/05/20 14:36:06 snw + * Documentation updates; raise ZCMMND instead of NOSTAND for restricted_mode restrictions + * + * Revision 1.27 2025/05/19 21:29:29 snw + * Add basic tab completion to direct mode + * + * Revision 1.26 2025/05/19 02:03:31 snw + * Reverse-engineer and document argumented ZPRINT (thanks to D. Wicksell) + * + * Revision 1.25 2025/05/18 18:15:38 snw + * Add ZEDIT command for editing routines + * + * Revision 1.24 2025/05/14 12:22:04 snw + * Further work on shared memory + * + * Revision 1.23 2025/05/06 16:10:06 snw + * Add extra blank before readline call on NetBSD + * + * Revision 1.22 2025/05/05 14:53:17 snw + * Modify rpm spec to include documentation TODO + * + * Revision 1.21 2025/05/01 17:02:30 snw + * Further debugging improvements + * + * Revision 1.20 2025/04/30 17:19:16 snw + * Improve backtraces in debugger + * + * Revision 1.19 2025/04/30 14:41:03 snw + * Further debugger work + * + * Revision 1.18 2025/04/29 18:46:17 snw + * Begin work on interactive debugger + * + * Revision 1.17 2025/04/28 19:38:55 snw + * Add trace mode + * + * Revision 1.16 2025/04/28 14:52:54 snw + * Temporarily revert global handler refactor and fix reference regression in xecline + * + * Revision 1.15 2025/04/15 16:49:36 snw + * Make use of logprintf throughout codebase + * + * Revision 1.14 2025/04/13 04:22:43 snw + * Fix snprintf calls + * * Revision 1.13 2025/04/10 01:24:39 snw * Remove C++ style comments * @@ -144,6 +195,162 @@ void rbuf_dump(void); short rbuf_slot_from_name(char *); short is_standard(void); +#ifdef HAVE_LIBREADLINE +char *m_commands[] = { + "?", + "@", + "!<", + "!>", + "!!", + "ablock", + "astart", + "astop", + "aunblock", + "break", + "close", + "do", + "else", + "events", + "for", + "goto", + "halt", + "hang", + "history", + "if", + "job", + "jobtab", + "kill", + "ksubscripts", + "kvalue", + "lock", + "locktab", + "merge", + "new", + "open", + "quit", + "rbuf", + "rcl", + "read", + "set", + "shmpages", + "shmstat", + "tcommit", + "then", + "trantab", + "trollback", + "tstart", + "use", + "view", + "wh", + "write", + "xecute", + "zassert", + "zbreak", + "zconst", + "zedit", + "zgoto", + "zhalt", + "zinsert", + "zjob", + "zload", + "zmap", + "znew", + "zprint", + "zquit", + "zremove", + "zsave", + "zthrow", + "ztrap", + "zunmap", + "zwatch", + "zwith", + "zwrite", + "ABLOCK", + "ASTART", + "ASTOP", + "AUNBLOCK", + "BREAK", + "CLOSE", + "DO", + "ELSE", + "FOR", + "GOTO", + "HALT", + "HANG", + "IF", + "JOB", + "KILL", + "KSUBSCRIPTS", + "KVALUE", + "LOCK", + "MERGE", + "NEW", + "OPEN", + "QUIT", + "READ", + "SET", + "TCOMMIT", + "THEN", + "TROLLBACK", + "TSTART", + "USE", + "VIEW", + "WRITE", + "XECUTE", + "ZASSERT", + "ZBREAK", + "ZCONST", + "ZEDIT", + "ZGOTO", + "ZHALT", + "ZINSERT", + "ZJOB", + "ZLOAD", + "ZMAP", + "ZNEW", + "ZPRINT", + "ZQUIT", + "ZREMOVE", + "ZSAVE", + "ZTHROW", + "ZTRAP", + "ZUNMAP", + "ZWATCH", + "ZWITH", + "ZWRITE", + NULL +}; + +char **command_completion(const char *, int, int); +char *command_generator(const char *, int); + +char **command_completion(const char *text, int start, int end) +{ + if (start > 0) return NULL; + rl_attempted_completion_over = 1; + return rl_completion_matches(text, command_generator); +} + +char *command_generator(const char *text, int state) +{ + static int list_index, len; + char *name; + + if (!state) { + list_index = 0; + len = strlen(text); + } + + while ((name = m_commands[list_index++])) { + if (strncmp(name, text, len) == 0) { + return strdup(name); + } + } + + return NULL; +} +#endif + /* * xecline(): * typ (where to go on function entry): 1 = restart @@ -156,13 +363,14 @@ int xecline(int typ) short new_and_set = FALSE; short new_object = FALSE; short destructor_run = FALSE; - short debug_mode = FALSE; short libcall = FALSE; char *namold; long rouoldc; unsigned long jobtime; char label[256], routine[256]; + char tracestr[512]; + char *vn; char *an; char *tmp; @@ -177,6 +385,7 @@ int xecline(int typ) char *reeval_codptr; char reeval_code[512]; + char entryref[256]; int i; int j; @@ -226,7 +435,10 @@ int xecline(int typ) next_line: /* entry point for next command line */ - + if (debug_mode) { + debug_mode = debugger (DEBENTRY_LINE, entryref); + } + job_set_status (pid, JSTAT_INTERPRETER); if (then_ctr > 0) { @@ -274,6 +486,11 @@ next_line: /* entry point for n codptr = code; next_cmnd: /* continue line entry point */ + getraddress (entryref, nstx); + if (debug_mode) { + debug_mode = debugger (DEBENTRY_CMD, entryref); + } + if (sigint_in_for) goto for_quit; if (forsw && (forpost[forx][0] != '\0')) { @@ -494,7 +711,7 @@ next0: if (ch == '!') { /* UNIXCALL */ if (restricted_mode) { - merr_raise (NOSTAND); + merr_raise (CMMND); goto err; } @@ -531,14 +748,14 @@ next0: else if (tmp2[0] == '<') { /* call write output to %-array */ FILE *pipdes; - char key[STRLEN + 1 /*was 256 */ ]; - char data[STRLEN + 1 /*was 256 */ ]; + char key[STRLEN]; + char data[STRLEN]; char data_kill[256]; data_kill[255] = EOL; - for (i = 0; i < STRLEN + 1; i++) vn[i] = EOL; + for (i = 0; i < STRLEN - 1; i++) vn[i] = EOL; - snprintf (key, 255, "^$JOB\202%d\202PIPE_GLVN\201", pid); + snprintf (key, STRLEN - 1, "^$JOB\202%d\202PIPE_GLVN\201", pid); ssvn (get_sym, key, vn); if (vn[0] == '^') { @@ -556,7 +773,7 @@ next0: symtab (kill_sym, vn, data); } - snprintf (key, 255, "^$JOB\202%d\202PIPE_GLVN\201", pid); + snprintf (key, STRLEN - 1, "^$JOB\202%d\202PIPE_GLVN\201", pid); ssvn (get_sym, key, vn); data[0] = '0'; @@ -585,7 +802,7 @@ next0: int glvn_len = 0; while (fgets (data, STRLEN, pipdes)) { - snprintf (key, 255, "^$JOB\202%d\202PIPE_GLVN\201", pid); + snprintf (key, STRLEN - 1, "^$JOB\202%d\202PIPE_GLVN\201", pid); ssvn (get_sym, key, vn); glvn_len = stlen (vn); @@ -646,7 +863,7 @@ next0: for (i = 0; i < STRLEN + 1; i++) vn[i] = EOL; - snprintf (key, 255, "^$JOB\202%d\202PIPE_GLVN\201", pid); + snprintf (key, STRLEN - 1, "^$JOB\202%d\202PIPE_GLVN\201", pid); ssvn (get_sym, key, vn); if (vn[0] == '^') { @@ -675,7 +892,7 @@ next0: int glvn_len = 0; for (i = 1; i <= k; i++) { - snprintf (key, 255, "^$JOB\202%d\202PIPE_GLVN\201", pid); + snprintf (key, STRLEN - 1, "^$JOB\202%d\202PIPE_GLVN\201", pid); ssvn (get_sym, key, vn); glvn_len = stlen (vn); @@ -845,6 +1062,8 @@ again: } } + if (trace_mode) tracestr[0] = '\0'; + switch (mcmnd) { case MAP: @@ -967,10 +1186,15 @@ set0: ch = *codptr; - if (ch == '.') { - setref = TRUE; - codptr++; - expr (NAME); + if (ch == '.') { + if (!isdigit (*(codptr + 1))) { + setref = TRUE; + codptr++; + expr (NAME); + } + else { + expr (STRING); + } } else { expr (STRING); @@ -2353,6 +2577,10 @@ s_end: case QUIT: + if (trace_mode) { + fprintf (stderr, ">> TRACE: $STACK = %d QUIT CMD = %c\r\n", nstx, nestc[nstx]); + } + if (tp_level > 0) { merr_raise (M42); goto err; @@ -2382,6 +2610,10 @@ s_end: if (nestc[nstx] == '$') { /* extrinsic function/variable */ + if (trace_mode) { + fprintf (stderr, ">> TRACE: QUIT FROM EXTRINSIC\r\n"); + } + #ifdef DEBUG_NEWSTACK printf ("EXTRINSIC\r\n"); #endif @@ -2411,6 +2643,10 @@ s_end: else { expr (STRING); + + if (trace_mode) { + fprintf (stderr, ">> TRACE: QUIT FROM SUBROUTINE\r\n"); + } if (ierr != OK - CTRLB && merr () != OK && merr () != INRPT) { @@ -2577,7 +2813,6 @@ quit0: printf ("CHECK 03 (Stack PUSH)\r\n"); #endif - if (++nstx > NESTLEVLS) { nstx--; merr_raise (STKOV); @@ -2603,7 +2838,7 @@ quit0: nestn[nstx] = 0; /* no overring of routine */ nestr[nstx] = roucur - rouptr; /* save roucur: only for $V(26) needed */ ztrap[nstx][0] = EOL; - + forsw = TRUE; ftyp = 0; /* no args is FOREVER */ @@ -3047,6 +3282,21 @@ do_xecute: do_goto: + if (trace_mode) { + char rn[256]; + stcpy (rn, rou_name); + stcnv_m2c (rn); + + switch (mcmnd) { + case DO: + snprintf (tracestr, sizeof (tracestr) - 1, "rtn = %s $stack = %d do ", rn, nstx); + break; + case GOTO: + snprintf (tracestr, sizeof (tracestr) - 1, "rtn = %s $stack = %d goto ", rn, nstx); + break; + } + } + offset = 0; label[0] = routine[0] = EOL; dofram0 = 0; @@ -3081,6 +3331,14 @@ do_goto: stcpy (label, varnam); + if (trace_mode) { + char ttt[256]; + stcpy (ttt, label); + stcnv_m2c (ttt); + + strcat (tracestr, ttt); + } + ch = *++codptr; } @@ -3096,7 +3354,12 @@ do_goto: /* unless argument is numeric, expr returns wrong codptr */ if ((ch = *codptr) != SP && (ch != EOL) && (ch != ',') && (ch != '^')) ch = *++codptr; - + + if (trace_mode) { + char ttt[256]; + snprintf (ttt, 255, "+%d", offset); + strcat (tracestr, ttt); + } } if (ch == '^') { /* parse routine */ @@ -3106,12 +3369,26 @@ do_goto: if (merr () > OK) goto err; stcpy (routine, varnam); + + if (trace_mode) { + char ttt[256]; + char ttx[256]; + + stcpy (ttt, routine); + stcnv_m2c (ttt); + snprintf (ttx, 255, "^%s", ttt); + strcat (tracestr, ttx); + } dosave[0] = EOL; ch = *++codptr; loadsw = TRUE; } + if (trace_mode) { + fprintf (stderr, ">> TRACE: %s\r\n", tracestr); + } + if (ch == '(' && mcmnd == DO) { /* parse parameter */ if (offset) { @@ -3989,6 +4266,7 @@ off3: if (merr () > OK) goto err; + /* TODO: check this snprintf for proper sizing */ snprintf (&tmp3[1], 255, "%s\201", &constructor[1]); goto private; @@ -4436,7 +4714,7 @@ use_socket: if (k > MAXSEQ) goto open_socket; if (restricted_mode) { - merr_raise (NOSTAND); + merr_raise (CMMND); goto err; } @@ -4868,7 +5146,8 @@ open_socket: if (merr () > OK) break; } else { - halt:i = 0; +halt: + i = 0; } cleanup (); @@ -5067,24 +5346,37 @@ open_socket: expr (STRING); if (merr () > OK) break; - switch (intexpr (argptr)) { - - case 2: - DSM2err = TRUE; - break; /* enable DSM V 2 error processing */ - - case -2: - DSM2err = FALSE; - break; /* enable normal error processing */ - - case 0: - breakon = FALSE; - break; /* disable CTRL/C */ - - default: - breakon = TRUE; - break; /* enable CTRL/C */ + { + char brkstr[256]; + + stcpy (brkstr, argptr); + stcnv_m2c (brkstr); + + if (strcmp (brkstr, "DEBUG") == 0) { + debug_mode = TRUE; + } + else { + switch (intexpr (argptr)) { + + case 2: + DSM2err = TRUE; + break; /* enable DSM V 2 error processing */ + + case -2: + DSM2err = FALSE; + break; /* enable normal error processing */ + + case 0: + breakon = FALSE; + break; /* disable CTRL/C */ + + default: + breakon = TRUE; + break; /* enable CTRL/C */ + } + } } + break; case VIEW: @@ -5174,6 +5466,11 @@ open_socket: break; /* Z-COMMANDS */ + case ZEDIT: + merr_raise (cmd_zedit (&ra)); + MRESCHECK(ra); + break; + case ZGO: /* ZGO with arguments: same as GOTO but with BREAK on */ @@ -5313,7 +5610,7 @@ zgo: merr_raise (NOPGM); break; - } /*error */ + } /* error */ stcpy (varnam, rou_name); } @@ -5331,6 +5628,7 @@ zgo: } zsave (varnam); + ssvn_routine_update (); break; @@ -5449,11 +5747,11 @@ zgo: if (*codptr == EOL || *codptr == SP) { merr_raise (ARGLIST); break; - } /*error */ + } /* error */ dosave[0] = EOL; - /* parse stringlit */ + /* parse strlit */ expr (STRING); if (merr () > OK) break; @@ -5562,7 +5860,7 @@ zgo: } if ((*(beg + 1)) == EOL) break; - + write_m (beg + 1); if (merr () > OK) break; } @@ -6649,8 +6947,7 @@ syn_evt_loop_bottom: if (ch != ',' && merr () == OK) { merr_raise (SPACER); } - else if ((ierr <= OK) || (debug_mode == TRUE)) { - if (debug_mode) goto direct_mode; + else if (ierr <= OK) { if (*++codptr != SP && *codptr != EOL) goto again; merr_raise (ARGLIST); @@ -6692,6 +6989,7 @@ err: } } + if (merr () > OK ) { char er_buf[ERRLEN]; @@ -6701,8 +6999,9 @@ err: stcpy (er_buf, errmes[merr ()]); stcnv_m2c (er_buf); + #if !defined(MSDOS) - m_log (LOG_ERR, er_buf); + logprintf (FM_LOG_DEBUG, "xecline: interpreter error %d [%s]", ierr, er_buf); #endif } @@ -7073,6 +7372,7 @@ restart: free (t_nsn); + } @@ -7134,9 +7434,6 @@ restore: goto next_cmnd; } - else { - if (debug_mode) goto direct_mode; - } if (libcall == TRUE) { /* library mode: don't go to direct mode, just return */ return merr (); @@ -7165,18 +7462,24 @@ direct_mode: int hist_idx; HIST_ENTRY *hist_ent; + rl_attempted_completion_function = command_completion; + if (quiet_mode == FALSE) { if (tp_level == 0) { - snprintf (fmrl_prompt, 255, "\r\n%s.%s> ", shm_env, nsname); + snprintf (fmrl_prompt, sizeof (fmrl_prompt) - 1, "\r\n%s.%s> ", shm_env, nsname); } else { - snprintf (fmrl_prompt, 255, "\r\nTL%d:%s.%s> ", tp_level, shm_env, nsname); + snprintf (fmrl_prompt, sizeof (fmrl_prompt) - 1 , "\r\nTL%d:%s.%s> ", tp_level, shm_env, nsname); } } set_io (UNIX); job_set_status (pid, JSTAT_DIRECTMODE); - + +#if defined(__NetBSD__) + printf ("\r\n"); +#endif + /* readline() does its own malloc() */ fmrl_buf = readline (fmrl_prompt); @@ -7187,6 +7490,7 @@ direct_mode: goto halt; } + if (strlen (fmrl_buf) > 0) { add_history (fmrl_buf); @@ -7195,10 +7499,10 @@ direct_mode: if (fmrl_buf[0] == '?') { char kb[20]; - char db[255]; + char db[STRLEN]; - snprintf (kb, 19, "%%SYS.HLP\201"); - snprintf (db, 19, "\201"); + snprintf (kb, sizeof (kb) - 1, "%%SYSHLP\201"); + snprintf (db, STRLEN - 1, "\201"); symtab (kill_sym, kb, db); @@ -7208,18 +7512,11 @@ direct_mode: stcpy (code, "DO ^%ZHELP\201"); if (strlen (fmrl_buf) > 1) { - snprintf (db, 254, "%s\201", &fmrl_buf[1]); + snprintf (db, STRLEN - 1, "%s\201", &fmrl_buf[1]); symtab (set_sym, kb, db); } } - else if (strcmp (fmrl_buf, "step") == 0) { - debug_mode = TRUE; - goto zgo; - } - else if ((strcmp (fmrl_buf, "cont") == 0) || (strcmp (fmrl_buf, "continue") == 0)) { - debug_mode = FALSE; - } else if (strcmp (fmrl_buf, "rbuf") == 0) { rbuf_dump (); } @@ -7277,7 +7574,7 @@ direct_mode: } else if (isdigit(fmrl_buf[0]) || (fmrl_buf[0] == '(') || (fmrl_buf[0] == '-') || (fmrl_buf[0] == '\'') || (fmrl_buf[0] == '+') || (fmrl_buf[0] == '$') || (fmrl_buf[0] == '^')) { - snprintf (code, 255, "W %s", fmrl_buf); + snprintf (code, STRLEN - 1, "W %s", fmrl_buf); stcnv_c2m (code); set_io (MUMPS); @@ -7359,10 +7656,10 @@ direct_mode: char fmrl_prompt[256]; if (tp_level == 0) { - snprintf (fmrl_prompt, 256, "\r\n%s> \201", nsname); + snprintf (fmrl_prompt, sizeof (fmrl_prompt) - 1, "\r\n%s> \201", nsname); } else { - snprintf (fmrl_prompt, 256, "\r\nTL%d:%s> \201", tp_level, nsname); + snprintf (fmrl_prompt, sizeof (fmrl_prompt) - 1, "\r\nTL%d:%s> \201", tp_level, nsname); } write_m (fmrl_prompt);