diff options
Diffstat (limited to 'unproto/unproto.c')
-rw-r--r-- | unproto/unproto.c | 466 |
1 files changed, 365 insertions, 101 deletions
diff --git a/unproto/unproto.c b/unproto/unproto.c index 9ca26ce..f7c03f7 100644 --- a/unproto/unproto.c +++ b/unproto/unproto.c @@ -2,68 +2,140 @@ /* NAME /* unproto 1 /* SUMMARY -/* ANSI C to old C converter +/* compile ANSI C with traditional UNIX C compiler /* PACKAGE /* unproto /* SYNOPSIS -/* /lib/cpp ... | unproto -/* /* /somewhere/cpp ... +/* +/* cc cflags -E file.c | unproto >file.i; cc cflags -c file.i /* DESCRIPTION -/* This document describes a filter that sits between the -/* C preprocessor (usually \fI/lib/cpp\fP) and the next C compiler -/* pass. It rewrites ANSI-C style function headers, function type -/* declarations, function pointer types, and function pointer casts -/* to old style. Other ANSI-isms are passed on without modification -/* (token pasting, pragmas, etcetera). +/* This document describes a filter that sits in between the UNIX +/* C preprocessor and the next UNIX C compiler stage, on the fly rewriting +/* ANSI-style syntax to old-style syntax. Typically, the program is +/* invoked by the native UNIX C compiler as an alternate preprocessor. +/* The unprototyper in turn invokes the native C preprocessor and +/* massages its output. Similar tricks can be used with the lint(1) +/* command. +/* +/* Language constructs that are always rewritten: +/* .TP +/* function headings, prototypes, pointer types +/* ANSI-C style function headings, function prototypes, function +/* pointer types and type casts are rewritten to old style. +/* <stdarg.h> support is provided for functions with variable-length +/* argument lists. +/* .TP +/* character and string constants +/* The \\a and \\x escape sequences are rewritten to their (three-digit) +/* octal equivalents. +/* +/* Multiple string tokens are concatenated; an arbitrary number of +/* whitespace or comment tokens may appear between successive +/* string tokens. /* -/* For maximal flexibility, the "cpp | unproto" pipeline can be -/* packaged as an executable shell script named "/somewhere/cpp". -/* This script should then be specified to the C compiler as a -/* non-default preprocessor. It will not work if your C compiler -/* specifies output file names to the preprocessor. +/* Within string constants, octal escape sequences are rewritten to the +/* three-digit \\ddd form, so that string concatenation produces correct +/* results. +/* .TP +/* date and time +/* The __DATE__ and __TIME__ tokens are replaced by string constants +/* of the form "Mmm dd yyyy" and "hh:mm:ss", respectively. The result +/* is subjected to string concatenation, just like any other string +/* constant. +/* .PP +/* Language constructs that are rewritten only if the program has been +/* configured to do so: +/* .TP +/* void types +/* The unprototyper can be configured to rewrite "void *" to "char *", +/* and even to rewrite plain "void" to "int". +/* These features are configurable because many traditional UNIX C +/* compilers do not need them. +/* +/* Note: (void) argument lists are always replaced by empty ones. +/* .PP +/* ANSI C constructs that are not rewritten because the traditional +/* UNIX C preprocessor provides suitable workarounds: +/* .TP +/* const and volatile +/* Use the "-Dconst=" and/or "-Dvolatile=" preprocessor directives to +/* get rid of unimplemented keywords. +/* .TP +/* token pasting and stringizing +/* The traditional UNIX C preprocessor provides excellent alternatives. +/* For example: +/* +/* .nf +/* .ne 2 +/* #define string(bar) "bar" /* instead of: # x */ +/* #define paste(x,y) x/**\/y /* instead of: x##y */ +/* .fi /* -/* The overhead of shell script interpretation can be avoided by -/* having the unprototyper itself open the pipe to the preprocessor. -/* In that case, the source should be compiled with the PIPE_THROUGH_CPP -/* macro defined (usually as "/lib/cpp"), and the resulting binary -/* should be installed as "/somewhere/cpp". +/* There is a good reason why the # and ## operators are not implemented +/* in the unprototyper. +/* After program text has gone through a non-ANSI C preprocessor, all +/* information about the grouping of the operands of # and ## is lost. +/* Thus, if the unprototyper were to perform these operations, it would +/* produce correct results only in the most trivial cases. Operands +/* with embedded blanks, operands that expand to null tokens, and nested +/* use of # and/or ## would cause all kinds of obscure problems. +/* .PP +/* Unsupported ANSI features: +/* .TP +/* trigraphs and #pragmas +/* Trigraphs are useful only for systems with broken character sets. +/* If the local compiler chokes on #pragma, insert a blank before the +/* "#" character, and enclose the offending directive between #ifdef +/* and #endif. /* SEE ALSO /* .ad /* .fi /* cc(1), how to specify a non-default C preprocessor. -/* -/* Some versions of the lint command are implemented as a shell +/* Some versions of the lint(1) command are implemented as a shell /* script. It should require only minor modification for integration -/* with the unprotoizer. Other versions of the lint command accept the same -/* command syntax as the C compiler for the specification of a non-default -/* preprocessor. Some research may be needed. +/* with the unprototyper. Other versions of the lint(1) command accept +/* the same command syntax as the C compiler for the specification of a +/* non-default preprocessor. Some research may be needed. +/* FILES +/* /wherever/stdarg.h, provided with the unproto filter. /* DIAGNOSTICS -/* The progam will complain if it unexpectedly -/* reaches the end of input. +/* Problems are reported on the standard error stream. +/* A non-zero exit status means that there was a problem. /* BUGS -/* Should be run on preprocessed source only, i.e. after macro expansion. +/* The unprototyper should be run on preprocessed source only: +/* unexpanded macros may confuse the program. +/* +/* Declarations of (object) are misunderstood and will result in +/* syntax errors: the objects between parentheses disappear. /* -/* Declarations of (whatever) are misunderstood and will result in -/* syntax errors. +/* Sometimes does not preserve whitespace after parentheses and commas. +/* This is a purely aesthetical matter, and the compiler should not care. +/* Whitespace within string constants is, of course, left intact. /* -/* Does not generate explicit type casts for function argument -/* expressions. +/* Does not generate explicit type casts for function-argument +/* expressions. The lack of explicit conversions between integral +/* and/or pointer argument types should not be a problem in environments +/* where sizeof(int) == sizeof(long) == sizeof(pointer). A more serious +/* problem is the lack of automatic type conversions between integral and +/* floating-point argument types. Let lint(1) be your friend. /* AUTHOR(S) /* Wietse Venema (wietse@wzv.win.tue.nl) /* Eindhoven University of Technology /* Department of Mathematics and Computer Science /* Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands /* LAST MODIFICATION -/* 91/09/22 21:21:35 +/* 93/06/18 22:29:37 /* VERSION/RELEASE -/* 1.2 +/* 1.6 /*--*/ -static char unproto_sccsid[] = "@(#) unproto.c 1.3 91/11/30 21:10:30"; +static char unproto_sccsid[] = "@(#) unproto.c 1.6 93/06/18 22:29:37"; /* C library */ +#include <sys/types.h> +#include <sys/stat.h> #include <stdio.h> #include <errno.h> @@ -91,6 +163,7 @@ static void show_arg_name(); static void show_type(); static void pair_flush(); static void check_cast(); +static void show_empty_list(); #define check_cast_flush(t) (check_cast(t), tok_free(t)) @@ -110,8 +183,8 @@ static int pipe_stdin_through_cpp(); #define STREQ(x,y) (*(x) == *(y) && !strcmp((x),(y))) -#define LAST_ARG_AND_EQUAL(s,c) ((s)->next == 0 && (s)->head \ - && ((s)->head == (s)->tail) \ +#define LAST_ARG_AND_EQUAL(s,c) ((s)->next && (s)->next->next == 0 \ + && (s)->head && ((s)->head == (s)->tail) \ && (STREQ((s)->head->vstr->str, (c)))) #define LIST_BEGINS_WITH_STAR(s) (s->head->head && s->head->head->tokno == '*') @@ -120,6 +193,14 @@ static int pipe_stdin_through_cpp(); && s->next->tokno == TOK_LIST \ && LIST_BEGINS_WITH_STAR(s)) +/* What to look for to detect a (void) argument list. */ + +#ifdef MAP_VOID +#define VOID_ARG "int" /* bare "void" is mapped to "int" */ +#else +#define VOID_ARG "void" /* bare "void" is left alone */ +#endif + /* main - driver */ int main(argc, argv) @@ -133,7 +214,8 @@ char **argv; int cpp_pid; cpp_pid = pipe_stdin_through_cpp(argv); -#else +#endif +#ifdef REOPEN if ( argc > 1 ) { if( freopen(argv[1], "r", stdin) == 0 ) { fprintf(stderr, "Cannot open '%s'\n", argv[1]); @@ -150,7 +232,7 @@ char **argv; sym_init(); /* prime the symbol table */ - while (t = tok_class(DO_WSPACE)) { + while (t = tok_class()) { if (t = dcl_flush(t)) { /* try declaration */ if (t->tokno == '{') { /* examine rejected token */ block_flush(t); /* body */ @@ -163,9 +245,9 @@ char **argv; #ifdef PIPE_THROUGH_CPP /* pipe through /lib/cpp */ while ((wait_pid = wait(&cpp_status)) != -1 && wait_pid != cpp_pid) /* void */ ; - return (wait_pid != cpp_pid || cpp_status != 0); + return (errcount != 0 || wait_pid != cpp_pid || cpp_status != 0); #else - return (0); + return (errcount != 0); #endif } @@ -179,6 +261,22 @@ char **argv; int pipefds[2]; int pid; char **cpptr = argv; + int i; + struct stat st; + + /* + * The code that sets up the pipe requires that file descriptors 0,1,2 + * are already open. All kinds of mysterious things will happen if that + * is not the case. The following loops makes sure that descriptors 0,1,2 + * are set up properly. + */ + + for (i = 0; i < 3; i++) { + if (fstat(i, &st) == -1 && open("/dev/null", 2) != i) { + perror("open /dev/null"); + exit(1); + } + } /* * With most UNIX implementations, the second non-option argument to @@ -226,20 +324,22 @@ char **argv; case -1: /* error */ perror("fork"); exit(1); + /* NOTREACHED */ case 0: /* child */ - close(pipefds[0]); /* close reading end */ - close(1); /* connect stdout to pipe */ + (void) close(pipefds[0]); /* close reading end */ + (void) close(1); /* connect stdout to pipe */ if (dup(pipefds[1]) != 1) - error(1, "dup() problem"); - close(pipefds[1]); /* close redundant fd */ - execv(PIPE_THROUGH_CPP, argv); + fatal("dup() problem"); + (void) close(pipefds[1]); /* close redundant fd */ + (void) execv(PIPE_THROUGH_CPP, argv); perror(PIPE_THROUGH_CPP); exit(1); + /* NOTREACHED */ default: /* parent */ - close(pipefds[1]); /* close writing end */ - close(0); /* connect stdin to pipe */ + (void) close(pipefds[1]); /* close writing end */ + (void) close(0); /* connect stdin to pipe */ if (dup(pipefds[0]) != 0) - error(1, "dup() problem"); + fatal("dup() problem"); close(pipefds[0]); /* close redundant fd */ return (pid); } @@ -247,47 +347,160 @@ char **argv; #endif -/* header_flush - rewrite new-style function header to old style */ +/* show_arg_names - display function argument names */ -static void header_flush(t) +static void show_arg_names(t) register struct token *t; { register struct token *s; /* Do argument names, but suppress void and rewrite trailing ... */ - if (LAST_ARG_AND_EQUAL(t->head, "void")) { - put_str("()\n"); /* no arguments */ + if (LAST_ARG_AND_EQUAL(t->head, VOID_ARG)) { + show_empty_list(t); /* no arguments */ } else { for (s = t->head; s; s = s->next) { /* foreach argument... */ if (LAST_ARG_AND_EQUAL(s, "...")) { #ifdef _VA_ALIST_ /* see ./stdarg.h */ - put_ch(s->tokno); /* ',' */ + tok_show_ch(s); /* ',' */ put_str(_VA_ALIST_); /* varargs magic */ #endif } else { - put_ch(s->tokno); /* opening '(' or ',' */ + tok_show_ch(s); /* '(' or ',' or ')' */ show_arg_name(s); /* extract argument name */ } } - put_str(")\n"); /* closing ')' */ } +} + +/* show_arg_types - display function argument types */ + +static void show_arg_types(t) +register struct token *t; +{ + register struct token *s; /* Do argument types, but suppress void and trailing ... */ - if (!LAST_ARG_AND_EQUAL(t->head, "void")) { + if (!LAST_ARG_AND_EQUAL(t->head, VOID_ARG)) { for (s = t->head; s; s = s->next) { /* foreach argument... */ - if (!LAST_ARG_AND_EQUAL(s, "...")) { + if (LAST_ARG_AND_EQUAL(s, "...")) { +#ifdef _VA_DCL_ /* see ./stdarg.h */ + put_str(_VA_DCL_); /* varargs magic */ + put_nl(); /* make output look nicer */ +#endif + } else { if (s->head != s->tail) { /* really new-style argument? */ - show_line_control(); /* fix line number */ show_type(s); /* rewrite type info */ - put_str(";\n"); + put_ch(';'); + put_nl(); /* make output look nicer */ } } } } - tok_free(t); - show_line_control(); /* because '{' follows */ +} + +/* header_flush - rewrite new-style function heading to old style */ + +static void header_flush(t) +register struct token *t; +{ + show_arg_names(t); /* show argument names */ + put_nl(); /* make output look nicer */ + show_arg_types(t); /* show argument types */ + tok_free(t); /* discard token */ +} + +/* fpf_header_names - define func returning ptr to func, no argument types */ + +static void fpf_header_names(list) +struct token *list; +{ + register struct token *s; + register struct token *p; + + /* + * Recurse until we find the argument list. Account for the rare case + * that list is a comma-separated list (which should be a syntax error). + * Display old-style fuction argument names. + */ + + for (s = list->head; s; s = s->next) { + tok_show_ch(s); /* '(' or ',' or ')' */ + for (p = s->head; p; p = p->next) { + if (p->tokno == TOK_LIST) { + if (IS_FUNC_PTR_TYPE(p)) { /* recurse */ + fpf_header_names(p); + show_empty_list(p = p->next); + } else { /* display argument names */ + show_arg_names(p); + } + } else { /* pass through other stuff */ + tok_show(p); + } + } + } +} + +/* fpf_header_types - define func returning ptr to func, argument types only */ + +static void fpf_header_types(list) +struct token *list; +{ + register struct token *s; + register struct token *p; + + /* + * Recurse until we find the argument list. Account for the rare case + * that list is a comma-separated list (which should be a syntax error). + * Display old-style function argument types. + */ + + for (s = list->head; s; s = s->next) { + for (p = s->head; p; p = p->next) { + if (p->tokno == TOK_LIST) { + if (IS_FUNC_PTR_TYPE(p)) { /* recurse */ + fpf_header_types(p); + p = p->next; + } else { /* display argument types */ + show_arg_types(p); + } + } + } + } +} + +/* fpf_header - define function returning pointer to function */ + +static void fpf_header(l1, l2) +struct token *l1; +struct token *l2; +{ + fpf_header_names(l1); /* strip argument types */ + show_empty_list(l2); /* strip prototype */ + put_nl(); /* nicer output */ + fpf_header_types(l1); /* show argument types */ +} + +/* skip_enclosed - skip over enclosed tokens */ + +static struct token *skip_enclosed(p, stop) +register struct token *p; +register int stop; +{ + register int start = p->tokno; + + /* Always return a pointer to the last processed token, never NULL. */ + + while (p->next) { + p = p->next; + if (p->tokno == start) { + p = skip_enclosed(p, stop); /* recurse */ + } else if (p->tokno == stop) { + break; /* done */ + } + } + return (p); } /* show_arg_name - extract argument name from argument type info */ @@ -304,6 +517,10 @@ register struct token *s; for (p = s->head; p; p = p->next) { if (p->tokno == TOK_WORD) { t = p; /* remember last word */ + } else if (p->tokno == '{') { + p = skip_enclosed(p, '}'); /* skip structured stuff */ + } else if (p->tokno == '[') { + break; /* dimension may be a macro */ } else if (IS_FUNC_PTR_TYPE(p)) { t = p; /* or function pointer */ p = p->next; @@ -328,35 +545,60 @@ register struct token *s; { register struct token *p; + /* + * Rewrite (*stuff)(args) to (*stuff)(). Rewrite word(args) to word(), + * but only if the word was preceded by a word, '*' or '}'. Leave + * anything else alone. + */ + for (p = s->head; p; p = p->next) { if (IS_FUNC_PTR_TYPE(p)) { - p = show_func_ptr_type(p); /* function pointer type */ + p = show_func_ptr_type(p, p->next); /* function pointer type */ } else { + register struct token *q; + register struct token *r; + tok_show(p); /* other */ + if ((p->tokno == TOK_WORD || p->tokno == '*' || p->tokno == '}') + && (q = p->next) && q->tokno == TOK_WORD + && (r = q->next) && r->tokno == TOK_LIST) { + tok_show(q); /* show name */ + show_empty_list(p = r); /* strip args */ + } } } } /* show_func_ptr_type - display function_pointer type using old-style syntax */ -static struct token *show_func_ptr_type(t) -struct token *t; +static struct token *show_func_ptr_type(t1, t2) +struct token *t1; +struct token *t2; { register struct token *s; /* - * Rewrite (list1) (list2) to (list1) (). Only (list1) is given to us; - * the caller must have verified the presence of (list2). Account for the - * rare case that (list1) is a comma-separated list. That should be an - * error, but we do not want to waste any information. + * Rewrite (list1) (list2) to (list1) (). Account for the rare case that + * (list1) is a comma-separated list. That should be an error, but we do + * not want to waste any information. */ - for (s = t->head; s; s = s->next) { - put_ch(s->tokno); /* opening paren or ',' */ + for (s = t1->head; s; s = s->next) { + tok_show_ch(s); /* '(' or ',' or ')' */ show_type(s); /* recurse */ } - put_str(")()"); /* closing paren */ - return (t->next); + show_empty_list(t2); + return (t2); +} + +/* show_empty_list - display opening and closing parentheses (if available) */ + +static void show_empty_list(t) +register struct token *t; +{ + tok_show_ch(t->head); /* opening paren */ + if (t->tail->tokno == ')') + tok_show_ch(t->tail); /* closing paren */ } /* show_struct_type - display structured type, rewrite function-pointer types */ @@ -369,7 +611,7 @@ register struct token *p; while (p->next) { /* XXX cannot return 0 */ p = p->next; if (IS_FUNC_PTR_TYPE(p)) { - p = show_func_ptr_type(p); /* function-pointer member */ + p = show_func_ptr_type(p, p->next); /* function-pointer member */ } else if (p->tokno == '{') { p = show_struct_type(p); /* recurse */ } else { @@ -409,6 +651,8 @@ register struct token *t; return (is_func_ptr_cast(p)); case TOK_WORD: /* name in list */ return (0); + case '[': + return (1); /* dimension may be a macro */ } } return (1); /* no name found */ @@ -430,13 +674,13 @@ struct token *t; */ for (s = t->head; s; s = s->next) { - put_ch(s->tokno); /* opening paren or ',' */ + tok_show_ch(s); /* '(' or ',' or ')' */ for (p = s->head; p; p = p->next) { switch (p->tokno) { case TOK_LIST: if (is_func_ptr_cast(p)) { /* not: IS_FUNC_PTR_TYPE(p) */ - p = show_func_ptr_type(p); /* or we might take away */ - } else { /* function-call arguments */ + p = show_func_ptr_type(p, p->next); + } else { check_cast(p); /* recurse */ } break; @@ -449,7 +693,6 @@ struct token *t; } } } - put_ch(')'); /* closing paren */ } /* block_dcls - on the fly rewrite decls/initializers at start of block */ @@ -469,7 +712,7 @@ static void block_dcls() * "union" tokens to the type_dcl() function. */ - while (t = tok_class(DO_WSPACE)) { + while (t = tok_class()) { switch (t->tokno) { case TOK_WSPACE: /* preserve white space */ case '\n': /* preserve line count */ @@ -477,7 +720,8 @@ static void block_dcls() break; case TOK_WORD: /* type declarations? */ tok_flush(t); /* advance to next token */ - t = tok_class(DO_WSPACE); /* null return is ok */ + t = tok_class(); /* null return is ok */ + /* FALLTRHOUGH */ case TOK_COMPOSITE: /* struct or union */ if ((t = dcl_flush(t)) == 0) break; @@ -511,7 +755,7 @@ register struct token *t; /* Remainder of block: only rewrite function pointer casts. */ - while (t = tok_class(DO_WSPACE)) { + while (t = tok_class()) { if (t->tokno == TOK_LIST) { check_cast_flush(t); } else if (t->tokno == '{') { @@ -536,7 +780,7 @@ register int stop; { tok_flush(t); - while (t = tok_class(DO_WSPACE)) { + while (t = tok_class()) { if (t->tokno == start) { /* recurse */ pair_flush(t, start, stop); } else if (t->tokno == TOK_LIST) { /* expression or cast */ @@ -557,7 +801,7 @@ static void initializer() { register struct token *t; - while (t = tok_class(DO_WSPACE)) { + while (t = tok_class()) { switch (t->tokno) { case ',': /* list separator */ case ';': /* list terminator */ @@ -566,7 +810,7 @@ static void initializer() case TOK_LIST: /* expression or cast */ check_cast_flush(t); break; - case '[': /* array substript, may nest */ + case '[': /* array subscript, may nest */ pair_flush(t, '[', ']'); break; case '{': /* structured data, may nest */ @@ -579,28 +823,47 @@ static void initializer() } } -/* func_ptr_dcl_flush - rewrite function pointer declaration */ +/* func_ptr_dcl_flush - rewrite function pointer stuff */ static struct token *func_ptr_dcl_flush(list) register struct token *list; { register struct token *t; + register struct token *t2; /* - * Ignore blanks because they would be output earlier than the list that - * preceded them... Recover gracefully from syntax errors. + * Ignore blanks and newlines because we are too lazy to maintain more + * than one token worth of lookahead. The output routines will regenerate + * discarded newline tokens. */ - while (t = tok_class(NO_WSPACE)) { + while (t = tok_class()) { switch (t->tokno) { - case '\n': /* preserve line count */ - tok_flush(t); + case TOK_WSPACE: + case '\n': + tok_free(t); break; case TOK_LIST: - /* Function pointer type: (list1) (list2) -> (list1) () */ - (void) show_func_ptr_type(list); /* may be recursive */ + /* Function pointer or function returning pointer to function. */ + while ((t2 = tok_class()) /* skip blanks etc. */ + &&(t2->tokno == TOK_WSPACE || t2->tokno == '\n')) + tok_free(t2); + switch (t2 ? t2->tokno : 0) { + case '{': /* function heading (new) */ + fpf_header(list, t); + break; + case TOK_WORD: /* function heading (old) */ + tok_show(list); + tok_show(t); + break; + default: /* func pointer type */ + (void) show_func_ptr_type(list, t); + break; + } tok_free(list); tok_free(t); + if (t2) + tok_unget(t2); return (0); default: /* not a declaration */ tok_unget(t); @@ -621,15 +884,16 @@ register struct token *list; register struct token *t; /* - * Ignore blanks because they would be output earlier than the list that - * preceded them... + * Ignore blanks and newlines because we are too lazy to maintain more + * than one token worth of lookahead. The output routines will regenerate + * ignored newline tokens. */ - while (t = tok_class(NO_WSPACE)) { + while (t = tok_class()) { switch (t->tokno) { + case TOK_WSPACE: case '\n': - /* Preserve line count */ - tok_flush(t); + tok_free(t); break; case '{': /* Function heading: word (list) { -> old style heading */ @@ -637,21 +901,21 @@ register struct token *list; tok_unget(t); return (0); case TOK_WORD: - /* Old-style function heading: word (list) word...{ */ + /* Old-style function heading: word (list) word... */ tok_flush(list); tok_unget(t); return (0); case TOK_LIST: - /* Function typedef? word (list1) (list) -> word (list1) () */ + /* Function pointer: word (list1) (list2) -> word (list1) () */ tok_flush(list); - put_str("()"); + show_empty_list(t); tok_free(t); return (0); case ',': case ';': /* Function type declaration: word (list) -> word () */ + show_empty_list(list); tok_free(list); - put_str("()"); tok_unget(t); return (0); default: @@ -686,7 +950,7 @@ register struct token *t; * level. The caller should give is the "struct" or "union" token. */ - for (got_word = 0; t; t = tok_class(DO_WSPACE)) { + for (got_word = 0; t; t = tok_class()) { switch (t->tokno) { case TOK_WSPACE: /* advance past blanks */ case '\n': /* advance past newline */ |