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