1: /***************************************************************************
2: * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne. JOVE *
3: * is provided to you without charge, and with no warranty. You may give *
4: * away copies of JOVE, including sources, provided that this notice is *
5: * included in all the files. *
6: ***************************************************************************/
7:
8: /* Contains commands for C mode. Paren matching routines are in here. */
9:
10: #include "jove.h"
11: #include "re.h"
12: #include "ctype.h"
13:
14: #ifdef MAC
15: # undef private
16: # define private
17: #endif
18:
19: #ifdef LINT_ARGS
20: private int
21: backslashed(char *, int);
22: private void
23: do_expr(int, int),
24: FindMatch(int),
25: parse_cmt_fmt(char *),
26: strip_c(char *, char *);
27: #else
28: private int
29: backslashed();
30: private void
31: do_expr(),
32: FindMatch(),
33: parse_cmt_fmt(),
34: strip_c();
35: #endif /* LINT_ARGS */
36:
37: #ifdef MAC
38: # undef private
39: # define private static
40: #endif
41:
42:
43: private int
44: backslashed(lp, cpos)
45: register char *lp;
46: register int cpos;
47: {
48: register int cnt = 0;
49:
50: while (cpos > 0 && lp[--cpos] == '\\')
51: cnt += 1;
52: return (cnt % 2);
53: }
54:
55: private char *p_types = "(){}[]";
56: private int mp_kind;
57: #define MP_OKAY 0
58: #define MP_MISMATCH 1
59: #define MP_UNBALANCED 2
60:
61: void
62: mp_error()
63: {
64: switch (mp_kind) {
65: case MP_MISMATCH:
66: message("[Mismatched parentheses]");
67: break;
68:
69: case MP_UNBALANCED:
70: message("[Unbalanced parenthesis]");
71: break;
72:
73: case MP_OKAY:
74: default:
75: return;
76: }
77: rbell();
78: }
79:
80: /* Search from the current position for the paren that matches p_type.
81: Search in the direction dir. If can_mismatch is YES then it is okay
82: to have mismatched parens. If stop_early is YES then when an open
83: paren is found at the beginning of a line, it is assumed that there
84: is no point in backing up further. This is so when you hit tab or
85: LineFeed outside, in-between procedure/function definitions, it won't
86: sit there searching all the way to the beginning of the file for a
87: match that doesn't exist. {forward,backward}-s-expression are the
88: only ones that insist on getting the "true" story. */
89:
90: Bufpos *
91: m_paren(p_type, dir, can_mismatch, can_stop)
92: char p_type;
93: register int dir;
94: {
95: static Bufpos ret;
96: Bufpos savedot,
97: *sp;
98: char re_buf[100],
99: *re_alts[NALTS];
100: int count = 0;
101: register char *lp,
102: c;
103: char p_match,
104: re_str[128],
105: *cp,
106: quote_c = 0;
107: register int c_char;
108: int in_comment = -1,
109: stopped = NO;
110:
111: sprintf(re_str, "[(){}[\\]%s]", (MajorMode(CMODE)) ? "/\"'" : "\"");
112: REcompile(re_str, 1, re_buf, re_alts);
113: if (cp = index(p_types, p_type))
114: p_match = cp[dir];
115: else
116: complain("[Cannot match %c's]", p_type);
117: DOTsave(&savedot);
118:
119: /* To make things a little faster I avoid copying lines into
120: linebuf by setting curline and curchar by hand. Warning:
121: this is slightly to very risky. When I did this there were
122: lots of problems with procedures that expect the contents of
123: curline to be in linebuf. */
124: while (count >= 0) {
125: sp = docompiled(dir, re_buf, re_alts);
126: if (sp == 0)
127: break;
128: lp = lbptr(sp->p_line);
129:
130: if (sp->p_line != curline)
131: /* let's assume that strings do NOT go over line
132: bounderies (for now don't check for wrapping
133: strings) */
134: quote_c = 0;
135: curline = sp->p_line;
136: curchar = sp->p_char; /* here's where I cheat */
137: c_char = curchar;
138: if (dir == FORWARD)
139: c_char -= 1;
140: if (backslashed(lp, c_char))
141: continue;
142: c = lp[c_char];
143: /* check if this is a comment (if we're not inside quotes) */
144: if (quote_c == 0 && c == '/') {
145: int new_ic;
146:
147: if ((c_char != 0) && lp[c_char - 1] == '*') {
148: new_ic = (dir == FORWARD) ? NO : YES;
149: if (new_ic == NO && in_comment == -1) {
150: count = 0;
151: quote_c = 0;
152: }
153: } else if (lp[c_char + 1] == '*') {
154: new_ic = (dir == FORWARD) ? YES : NO;
155: if (new_ic == NO && in_comment == -1) {
156: count = 0;
157: quote_c = 0;
158: }
159: }
160: in_comment = new_ic;
161: }
162: if (in_comment == YES)
163: continue;
164: if (c == '"' || c == '\'') {
165: if (quote_c == c)
166: quote_c = 0;
167: else if (quote_c == 0)
168: quote_c = c;
169: }
170: if (quote_c != 0)
171: continue;
172: if (isopenp(c)) {
173: count += dir;
174: if (c_char == 0 && can_stop == YES && count >= 0) {
175: stopped = YES;
176: break;
177: }
178: } else if (isclosep(c))
179: count -= dir;
180: }
181:
182: ret.p_line = curline;
183: ret.p_char = curchar;
184:
185: curline = savedot.p_line;
186: curchar = savedot.p_char; /* here's where I undo it */
187:
188: if (count >= 0)
189: mp_kind = MP_UNBALANCED;
190: else if (c != p_match)
191: mp_kind = MP_MISMATCH;
192: else
193: mp_kind = MP_OKAY;
194:
195: /* If we stopped (which means we were allowed to stop) and there
196: was an error, we clear the error so no error message is printed.
197: An error should be printed ONLY when we are sure about the fact,
198: namely we didn't stop prematurely HOPING that it was the right
199: answer. */
200: if (stopped && mp_kind != MP_OKAY) {
201: mp_kind = MP_OKAY;
202: return 0;
203: }
204: if (mp_kind == MP_OKAY || (mp_kind == MP_MISMATCH && can_mismatch == YES))
205: return &ret;
206: return 0;
207: }
208:
209: private void
210: do_expr(dir, skip_words)
211: register int dir;
212: {
213: register char c,
214: syntax = (dir == FORWARD) ? _Op : _Cl;
215:
216: if (dir == BACKWARD)
217: b_char(1);
218: c = linebuf[curchar];
219: for (;;) {
220: if (!skip_words && ismword(c)) {
221: WITH_TABLE(curbuf->b_major)
222: if (dir == FORWARD)
223: f_word(1);
224: else
225: b_word(1);
226: END_TABLE();
227: break;
228: } else if (has_syntax(c, syntax)) {
229: FindMatch(dir);
230: break;
231: }
232: f_char(dir);
233: if (eobp() || bobp())
234: return;
235: c = linebuf[curchar];
236: }
237: }
238:
239: void
240: FSexpr()
241: {
242: register int num = arg_value();
243:
244: if (num < 0) {
245: set_arg_value(-num);
246: BSexpr();
247: }
248: while (--num >= 0)
249: do_expr(FORWARD, NO);
250: }
251:
252: void
253: FList()
254: {
255: register int num = arg_value();
256:
257: if (num < 0) {
258: set_arg_value(-num);
259: BList();
260: }
261: while (--num >= 0)
262: do_expr(FORWARD, YES);
263: }
264:
265: void
266: BSexpr()
267: {
268: register int num = arg_value();
269:
270: if (num < 0) {
271: negate_arg_value();
272: FSexpr();
273: }
274: while (--num >= 0)
275: do_expr(BACKWARD, NO);
276: }
277:
278: void
279: BList()
280: {
281: register int num = arg_value();
282:
283: if (num < 0) {
284: negate_arg_value();
285: FList();
286: }
287: while (--num >= 0)
288: do_expr(BACKWARD, YES);
289: }
290:
291: void
292: BUpList()
293: {
294: Bufpos *mp;
295: char c = (MajorMode(CMODE) ? '}' : ')');
296:
297: mp = m_paren(c, BACKWARD, NO, YES);
298: if (mp == 0)
299: mp_error();
300: else
301: SetDot(mp);
302: }
303:
304: void
305: FDownList()
306: {
307: Bufpos *sp;
308: char *sstr = (MajorMode(CMODE) ? "[{([\\])}]" : "[()]"),
309: *lp;
310:
311: sp = dosearch(sstr, FORWARD, YES);
312: if (sp != 0)
313: lp = lcontents(sp->p_line);
314: if (sp == 0 || has_syntax(lp[sp->p_char - 1], _Cl))
315: complain("[No contained expression]");
316: SetDot(sp);
317: }
318:
319: /* Move to the matching brace or paren depending on the current position
320: in the buffer. */
321:
322: private void
323: FindMatch(dir)
324: {
325: register Bufpos *bp;
326: register char c = linebuf[curchar];
327:
328: if ((index(p_types, c) == 0) ||
329: (backslashed(linebuf, curchar)))
330: complain((char *) 0);
331: if (dir == FORWARD)
332: f_char(1);
333: bp = m_paren(c, dir, YES, NO);
334: if (dir == FORWARD)
335: b_char(1);
336: if (bp != 0)
337: SetDot(bp);
338: mp_error(); /* if there is an error the user wants to
339: know about it */
340: }
341:
342: Bufpos *
343: c_indent(incrmt)
344: {
345: Bufpos *bp;
346: int indent = 0;
347:
348: if (bp = m_paren('}', BACKWARD, NO, YES)) {
349: Bufpos save;
350:
351: DOTsave(&save);
352: SetDot(bp);
353: ToIndent();
354: indent = calc_pos(linebuf, curchar);
355: SetDot(&save);
356: }
357: if (incrmt) {
358: if (indent == 0)
359: incrmt = tabstop;
360: else
361: incrmt = (tabstop - (indent%tabstop));
362: }
363: n_indent(indent + incrmt);
364: return bp;
365: }
366:
367: #ifdef CMT_FMT
368:
369: char CmtFmt[80] = "/*%n%! * %c%!%n */";
370:
371:
372: Comment()
373: {
374: FillComment(CmtFmt);
375: }
376:
377: /* Strip leading and trailing white space. Skip over any imbedded '\r's. */
378:
379: private void
380: strip_c(from, to)
381: char *from,
382: *to;
383: {
384: register char *fr_p = from,
385: *to_p = to,
386: c;
387:
388: while (c = *fr_p) {
389: if (c == ' ' || c == '\t' || c == '\r')
390: fr_p += 1;
391: else
392: break;
393: }
394: while (c = *fr_p) {
395: if (c != '\r')
396: *to_p++ = c;
397: fr_p += 1;
398: }
399: while (--to_p >= to)
400: if (*to_p != ' ' && *to_p != '\t')
401: break;
402: *++to_p = '\0';
403: }
404:
405: private char open_c[20], /* the open comment format string */
406: open_pat[20], /* the search pattern for open comment */
407: [20], /* the prefix for each comment line */
408: l_trailer[20], /* the suffix ... */
409: close_c[20],
410: close_pat[20];
411:
412: private char *comment_body[] = {
413: open_c,
414: l_header,
415: l_trailer,
416: close_c
417: };
418:
419: private int nlflags;
420:
421: /* Fill in the data structures above from the format string. Don't return
422: if there's trouble. */
423:
424: private void
425: parse_cmt_fmt(str)
426: char *str;
427: {
428: register char *fmtp = str;
429: register char **c_body = comment_body,
430: *body_p = *c_body;
431: int c,
432: newlines = 1;
433:
434: /* pick apart the comment string */
435: while (c = *fmtp++) {
436: if (c != '%') {
437: *body_p++ = c;
438: continue;
439: }
440: switch(c = *fmtp++) {
441: case 'n':
442: if (newlines == 2 || newlines == 3)
443: complain("%n not allowed in line header or trailer: %s",
444: fmtp - 2);
445: nlflags += newlines;
446: *body_p++ = '\r';
447: break;
448: case 't':
449: *body_p++ = '\t';
450: break;
451: case '%':
452: *body_p++ = '%';
453: break;
454: case '!':
455: case 'c':
456: newlines += 1;
457: *body_p++ = '\0';
458: body_p = *++c_body;
459: break;
460: default:
461: complain("[Unknown comment escape: %%%c]", c);
462: /* VARARGS */
463: break;
464: }
465: }
466: *body_p = '\0';
467: /* make search patterns */
468: strip_c(open_c, open_pat);
469: strip_c(close_c, close_pat);
470: }
471:
472: #define NL_IN_OPEN_C ((nlflags % 4) == 1)
473: #define NL_IN_CLOSE_C (nlflags >= 4)
474:
475:
476: FillComment(format)
477: char *format;
478: {
479: int saveRMargin,
480: indent_pos,
481: close_at_dot = NO,
482: slen,
483: header_len,
484: trailer_len;
485: register char *cp;
486: static char inside_err[] = "[Must be between %s and %s to re-format]";
487: Bufpos open_c_pt,
488: close_c_pt,
489: tmp_bp,
490: *match_o,
491: *match_c;
492: Mark *entry_mark,
493: *open_c_mark,
494: *savedot;
495:
496: parse_cmt_fmt(format);
497: /* figure out if we're "inside" a comment */
498: if ((match_o = dosearch(open_pat, BACKWARD, 0)) == 0)
499: /* VARARGS */
500: complain("No opening %s to match to.", open_pat);
501: open_c_pt = *match_o;
502: if ((match_c = dosearch(close_pat, BACKWARD, NO)) != 0 &&
503: inorder(open_c_pt.p_line, open_c_pt.p_char,
504: match_c->p_line, match_c->p_char))
505: complain(inside_err, open_pat, close_pat);
506: if ((match_o = dosearch(open_pat, FORWARD, NO)) != 0) {
507: tmp_bp = *match_o;
508: match_o = &tmp_bp;
509: }
510: if ((match_c = dosearch(close_pat, FORWARD, 0)) != (Bufpos *) 0)
511: close_c_pt = *match_c;
512:
513: /* Here's where we figure out whether to format from dot or from
514: the close comment. Note that we've already searched backwards to
515: find the open comment symbol for the comment we are formatting.
516: The open symbol mentioned below refers to the possible existence
517: of the next comment. There are 5 cases:
518: 1) no open or close symbol ==> dot
519: 2) open, but no close symbol ==> dot
520: 3) close, but no open ==> close
521: 4) open, close are inorder ==> dot
522: 5) open, close are not inorder ==> close */
523:
524:
525: if (match_o == (Bufpos *) 0) {
526: if (match_c == (Bufpos *) 0)
527: close_at_dot = YES;
528: } else if (match_c == (Bufpos *) 0)
529: close_at_dot = YES;
530: else if (inorder(match_o->p_line, match_o->p_char,
531: match_c->p_line, match_c->p_char))
532: close_at_dot = YES;
533: if (close_at_dot) {
534: close_c_pt.p_line = curline;
535: close_c_pt.p_char = curchar;
536: } else {
537: SetDot(match_c);
538: }
539: SetDot(&open_c_pt);
540: open_c_mark = MakeMark(curline, curchar, M_FLOATER);
541: indent_pos = calc_pos(linebuf, curchar);
542: /* search for a close comment; delete it if it exits */
543: SetDot(&close_c_pt);
544: if (close_at_dot == 0) {
545: slen = strlen(close_pat);
546: while (slen--)
547: del_char(BACKWARD, 1);
548: }
549: entry_mark = MakeMark(curline, curchar, M_FLOATER);
550: ToMark(open_c_mark);
551: /* always separate the comment body from anything preceeding it */
552: LineInsert(1);
553: DelWtSpace();
554: Bol();
555: for (cp = open_c; *cp; cp++) {
556: if (*cp == '\r') {
557: if (!eolp())
558: LineInsert(1);
559: else
560: line_move(FORWARD, 1, NO);
561: } else if (*cp == ' ' || *cp == '\t') {
562: if (linebuf[curchar] != *cp)
563: insert_c(*cp, 1);
564: } else
565: /* Since we matched the open comment string on this
566: line, we don't need to worry about crossing line
567: boundaries. */
568: curchar += 1;
569: }
570: savedot = MakeMark(curline, curchar, M_FLOATER);
571:
572: /* We need to strip the line header pattern of leading white space
573: since we need to match the line after all of its leading
574: whitespace is gone. */
575: for (cp = l_header; *cp && (isspace(*cp)); cp++)
576: ;
577: header_len = strlen(cp);
578: trailer_len = strlen(l_trailer);
579:
580: /* Strip each comment line of the open and close comment strings
581: before reformatting it. */
582:
583: do {
584: Bol();
585: DelWtSpace();
586: if (header_len && !strncmp(linebuf, cp, header_len))
587: del_char(FORWARD, header_len);
588: if (trailer_len) {
589: Eol();
590: if ((curchar > trailer_len) &&
591: (!strncmp(&linebuf[curchar - trailer_len],
592: l_trailer, trailer_len)))
593: del_char(BACKWARD, trailer_len);
594: }
595: if (curline->l_next != 0)
596: line_move(FORWARD, 1, NO);
597: else
598: break;
599: } while (curline != entry_mark->m_line->l_next);
600:
601: do_set_mark(savedot->m_line, savedot->m_char);
602: ToMark(entry_mark);
603: saveRMargin = RMargin;
604: RMargin = saveRMargin - strlen(l_header) -
605: strlen(l_trailer) - indent_pos + 2;
606: do_rfill(NO);
607: RMargin = saveRMargin;
608: /* get back to the start of the comment */
609: PopMark();
610: do {
611: if (curline == open_c_mark->m_line->l_next) {
612: ;
613: } else {
614: n_indent(indent_pos);
615: ins_str(l_header, NO);
616: }
617: Eol();
618: if (!NL_IN_CLOSE_C && (curline == entry_mark->m_line))
619: ;
620: else
621: ins_str(l_trailer, NO);
622: if (curline->l_next != 0)
623: line_move(FORWARD, 1, NO);
624: else
625: break;
626: } while (curline != entry_mark->m_line->l_next);
627: /* handle the close comment symbol */
628: if (curline == entry_mark->m_line->l_next) {
629: line_move(BACKWARD, 1, NO);
630: Eol();
631: }
632: DelWtSpace();
633: /* if the addition of the close symbol would cause the line to be
634: too long, put the close symbol on the next line. */
635: if (!(NL_IN_CLOSE_C) &&
636: strlen(close_c) + calc_pos(linebuf, curchar) > RMargin) {
637: LineInsert(1);
638: n_indent(indent_pos);
639: }
640: for (cp = close_c; *cp; cp++) {
641: if (*cp == '\r') {
642: LineInsert(1);
643: n_indent(indent_pos);
644: } else
645: insert_c(*cp, 1);
646: }
647: ToMark(open_c_mark);
648: Eol();
649: del_char(FORWARD, 1);
650: }
651:
652: #endif /* CMT_FMT */