mirror of
https://github.com/rfivet/uemacs.git
synced 2024-12-24 10:06:30 -05:00
705 lines
17 KiB
C
705 lines
17 KiB
C
/* input.c -- implements input.h */
|
|
#include "input.h"
|
|
|
|
/* Various input routines
|
|
*
|
|
* written by Daniel Lawrence 5/9/86
|
|
* modified by Petri Kutvonen
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "bind.h"
|
|
#include "bindable.h"
|
|
#include "defines.h"
|
|
#include "display.h" /* rubout(), echos(), echoc(), update() */
|
|
#include "exec.h"
|
|
#include "isa.h"
|
|
#include "mlout.h"
|
|
#include "names.h"
|
|
#include "terminal.h"
|
|
#include "utf8.h"
|
|
#include "wrapper.h"
|
|
|
|
#if PKCODE && UNIX
|
|
#define COMPLC 1
|
|
#else
|
|
#define COMPLC 0
|
|
#endif
|
|
|
|
#define NKBDM 256 /* # of strokes, keyboard macro */
|
|
int kbdm[ NKBDM] ; /* Macro */
|
|
int *kbdptr ; /* current position in keyboard buf */
|
|
int *kbdend = &kbdm[0] ; /* ptr to end of the keyboard */
|
|
|
|
kbdstate kbdmode = STOP ; /* current keyboard macro mode */
|
|
int lastkey = 0 ; /* last keystoke */
|
|
int kbdrep = 0 ; /* number of repetitions */
|
|
|
|
int metac = CTL_ | '[' ; /* current meta character */
|
|
int ctlxc = CTL_ | 'X' ; /* current control X prefix char */
|
|
int reptc = CTL_ | 'U' ; /* current universal repeat char */
|
|
int abortc = CTL_ | 'G' ; /* current abort command char */
|
|
|
|
const int nlc = CTL_ | 'J' ; /* end of input char */
|
|
|
|
|
|
void ue_system( const char *cmd) {
|
|
int ret ;
|
|
|
|
ret = system( cmd) ;
|
|
if( ret == -1) {
|
|
/* some actual handling needed here */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ask a yes or no question in the message line. Return either TRUE, FALSE, or
|
|
* ABORT. The ABORT status is returned if the user bumps out of the question
|
|
* with a ^G. Used any time a confirmation is required.
|
|
*/
|
|
int mlyesno( const char *prompt)
|
|
{
|
|
int c ; /* input character */
|
|
|
|
for (;;) {
|
|
/* prompt the user */
|
|
mloutfmt( "%s (y/n)? ", prompt) ;
|
|
|
|
/* get the response */
|
|
c = get1key() ;
|
|
|
|
if( c == abortc) /* Bail out! */
|
|
return ABORT;
|
|
|
|
if (c == 'y' || c == 'Y')
|
|
return TRUE;
|
|
|
|
if (c == 'n' || c == 'N')
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* newnextarg:
|
|
* get the next argument
|
|
*
|
|
* char **outbufref ; buffer to put token into
|
|
* const char *prompt ; prompt to use if we must be interactive
|
|
* int size ; size of the buffer
|
|
* int terminator ; terminating char to be used on interactive fetch
|
|
*/
|
|
static int newnextarg( char **outbufref, const char *prompt, int size,
|
|
int terminator) {
|
|
int status ;
|
|
char *buf ;
|
|
|
|
/* if we are interactive, go get it! */
|
|
if( clexec == FALSE) {
|
|
if( size <= 1) {
|
|
size = term.t_ncol - strlen( prompt) + 1 ;
|
|
if( size < 24)
|
|
size = 24 ;
|
|
}
|
|
|
|
buf = malloc( size) ;
|
|
if( buf == NULL)
|
|
status = FALSE ;
|
|
else {
|
|
status = getstring( prompt, buf, size, terminator) ;
|
|
if( TRUE != status) {
|
|
free( buf) ;
|
|
buf = NULL ;
|
|
}
|
|
}
|
|
} else {
|
|
buf = getnewtokval() ;
|
|
status = (buf == NULL) ? FALSE : TRUE ;
|
|
}
|
|
|
|
*outbufref = buf ;
|
|
return status ;
|
|
}
|
|
|
|
/*
|
|
* Write a prompt into the message line, then read back a response. Keep
|
|
* track of the physical position of the cursor. If we are in a keyboard
|
|
* macro throw the prompt away, and return the remembered response. This
|
|
* lets macros run at full speed. The reply is always terminated by a carriage
|
|
* return. Handle erase, kill, and abort keys.
|
|
*/
|
|
|
|
int newmlarg( char **outbufref, const char *prompt, int size) {
|
|
return newnextarg( outbufref, prompt, size, nlc) ;
|
|
}
|
|
|
|
int newmlargt( char **outbufref, const char *prompt, int size) {
|
|
return newnextarg( outbufref, prompt, size, metac) ;
|
|
}
|
|
|
|
/*
|
|
* ectoc:
|
|
* expanded character to character
|
|
* collapse the CTL_ and SPEC flags back into an ascii code
|
|
*/
|
|
int ectoc( int c) {
|
|
if( c & CTL_)
|
|
c ^= CTL_ | 0x40 ;
|
|
|
|
if( c & SPEC)
|
|
c &= 255 ;
|
|
|
|
return c ;
|
|
}
|
|
|
|
/*
|
|
* get a command name from the command line. Command completion means
|
|
* that pressing a <SPACE> will attempt to complete an unfinished command
|
|
* name if it is unique.
|
|
*/
|
|
nbind_p getname( void) {
|
|
int cpos; /* current column on screen output */
|
|
nbind_p ffp; /* first ptr to entry in name binding table */
|
|
nbind_p cffp; /* current ptr to entry in name binding table */
|
|
nbind_p lffp; /* last ptr to entry in name binding table */
|
|
char buf[NSTRING]; /* buffer to hold tentative command name */
|
|
|
|
/* starting at the beginning of the string buffer */
|
|
cpos = 0;
|
|
|
|
/* if we are executing a command line get the next arg and match it */
|
|
if (clexec) {
|
|
if( TRUE != gettokval( buf, sizeof buf))
|
|
return NULL;
|
|
return fncmatch( buf) ;
|
|
}
|
|
|
|
/* build a name string from the keyboard */
|
|
while (TRUE) {
|
|
int c ;
|
|
|
|
c = tgetc();
|
|
|
|
/* if we are at the end, just match it */
|
|
if (c == 0x0d) {
|
|
buf[cpos] = 0;
|
|
|
|
/* and match it off */
|
|
return fncmatch( buf) ;
|
|
|
|
} else if( c == ectoc(abortc)) { /* Bell, abort */
|
|
ctrlg( FALSE, 1) ;
|
|
TTflush() ;
|
|
return NULL ;
|
|
|
|
} else if (c == 0x7F || c == 0x08) { /* rubout/erase */
|
|
if (cpos != 0) {
|
|
rubout() ;
|
|
--cpos;
|
|
TTflush();
|
|
}
|
|
|
|
} else if (c == 0x15) { /* C-U, kill */
|
|
while (cpos != 0) {
|
|
rubout() ;
|
|
--cpos;
|
|
}
|
|
|
|
TTflush();
|
|
|
|
} else if (c == ' ' || c == 0x1b || c == 0x09) {
|
|
/* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
|
|
/* attempt a completion */
|
|
buf[ cpos] = 0 ; /* terminate it for us */
|
|
int buflen = strlen( buf) ;
|
|
/* scan for matches */
|
|
for( ffp = names ; ffp->n_func != NULL ; ffp++) {
|
|
if( strncmp( buf, bind_name( ffp), buflen) == 0) {
|
|
/* a possible match! More than one? */
|
|
if( (ffp + 1)->n_func == NULL ||
|
|
(strncmp( buf, bind_name( ffp + 1), buflen) != 0)) {
|
|
/* no...we match, print it */
|
|
echos( &bind_name( ffp)[ cpos]) ;
|
|
TTflush() ;
|
|
return ffp ;
|
|
} else {
|
|
/* << << << << << << << << << << << << << << << << << */
|
|
/* try for a partial match against the list */
|
|
|
|
/* first scan down until we no longer match the
|
|
* current input */
|
|
for( lffp = ffp + 1 ; (lffp + 1)->n_func != NULL ;
|
|
lffp++)
|
|
if( strncmp( buf, bind_name( lffp + 1),
|
|
buflen) != 0)
|
|
break ;
|
|
|
|
/* and now, attempt to partial complete the string,
|
|
* one char at a time */
|
|
while (TRUE) {
|
|
/* add the next char in */
|
|
buf[ cpos] = bind_name( ffp)[ cpos] ;
|
|
|
|
/* scan through the candidates */
|
|
for( cffp = ffp + 1 ; cffp <= lffp ; cffp++)
|
|
if( bind_name( cffp)[ cpos] != buf[ cpos])
|
|
goto onward ;
|
|
|
|
/* add the character */
|
|
echoc( buf[ cpos++]) ;
|
|
}
|
|
/* << << << << << << << << << << << << << << << << << */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* no match.....beep and onward */
|
|
TTbeep();
|
|
onward:
|
|
TTflush();
|
|
/* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
|
|
} else {
|
|
if( cpos < NSTRING - 1 && (islower( c) || c == '-')) {
|
|
buf[cpos++] = c;
|
|
echoc( c) ;
|
|
TTflush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* tgetc: Get a key from the terminal driver, resolve any keyboard
|
|
macro action */
|
|
|
|
int tgetc(void)
|
|
{
|
|
int c; /* fetched character */
|
|
|
|
/* if we are playing a keyboard macro back, */
|
|
if (kbdmode == PLAY) {
|
|
|
|
/* if there is some left... */
|
|
if (kbdptr < kbdend)
|
|
return (int) *kbdptr++;
|
|
|
|
/* at the end of last repetition? */
|
|
if (--kbdrep < 1) {
|
|
kbdmode = STOP;
|
|
#if VISMAC == 0
|
|
/* force a screen update after all is done */
|
|
update(FALSE);
|
|
#endif
|
|
} else {
|
|
|
|
/* reset the macro to the begining for the next rep */
|
|
kbdptr = &kbdm[0];
|
|
return (int) *kbdptr++;
|
|
}
|
|
}
|
|
|
|
/* fetch a character from the terminal driver */
|
|
c = TTgetc();
|
|
|
|
/* record it for $lastkey */
|
|
lastkey = c;
|
|
|
|
/* save it if we need to */
|
|
if (kbdmode == RECORD) {
|
|
*kbdptr++ = c;
|
|
kbdend = kbdptr;
|
|
|
|
/* don't overrun the buffer */
|
|
if (kbdptr == &kbdm[NKBDM - 1]) {
|
|
kbdmode = STOP;
|
|
TTbeep();
|
|
}
|
|
}
|
|
|
|
/* and finally give the char back */
|
|
return c;
|
|
}
|
|
|
|
/* GET1KEY: Get one keystroke. The only prefixes legal here are the SPEC
|
|
and CTL_ prefixes. */
|
|
static int get1unicode( int *up) {
|
|
/* Accept UTF-8 sequence */
|
|
int bytes ;
|
|
|
|
int c = tgetc() ;
|
|
if( c > 0xC1 && c <= 0xF4) {
|
|
char utf[ 4] ;
|
|
char cc ;
|
|
|
|
utf[ 0] = c ;
|
|
utf[ 1] = cc = tgetc() ;
|
|
if( (c & 0x20) && ((cc & 0xC0) == 0x80)) { /* at least 3 bytes and a valid encoded char */
|
|
utf[ 2] = cc = tgetc() ;
|
|
if( (c & 0x10) && ((cc & 0xC0) == 0x80)) /* at least 4 bytes and a valid encoded char */
|
|
utf[ 3] = tgetc() ;
|
|
}
|
|
|
|
bytes = utf8_to_unicode( utf, 0, sizeof utf, (unicode_t *) up) ;
|
|
} else {
|
|
if( (c >= 0x00 && c <= 0x1F) || c == 0x7F) /* C0 control -> C- */
|
|
c ^= CTL_ | 0x40 ;
|
|
|
|
*up = c ;
|
|
bytes = 1 ;
|
|
}
|
|
|
|
return bytes ;
|
|
}
|
|
|
|
/* Terminal sequences need up to 7 read ahead characters */
|
|
#define STACKSIZE 7
|
|
static int keystack[ STACKSIZE] ;
|
|
static int *stackptr = &keystack[ STACKSIZE] ;
|
|
#define KPUSH( c) *(--stackptr) = (c)
|
|
|
|
int get1key( void) {
|
|
int c ;
|
|
|
|
/* fetch from queue if any were pushed back */
|
|
if( stackptr != &keystack[ STACKSIZE])
|
|
return *(stackptr++) ;
|
|
|
|
/* fetch from keyboard */
|
|
get1unicode( &c) ;
|
|
return c ;
|
|
}
|
|
|
|
/* GETCMD: Get a command from the keyboard. Process all applicable prefix
|
|
keys. Handle alted and controlled FNx, not shifted.
|
|
*/
|
|
int getcmd( void) {
|
|
int prefix = 0 ; /* prefixes M- or ^X */
|
|
int keyread[ STACKSIZE] ; /* room to process sequences like ^[[24;2~ */
|
|
int *kptr = keyread ;
|
|
int c ;
|
|
|
|
for( ;;) {
|
|
c = *(kptr++) = get1key() ;
|
|
if( c == 0x9B)
|
|
goto foundCSI ;
|
|
else if( c == (CTL_ | '[')) {
|
|
/* fetch terminal sequence */
|
|
c = *(kptr++) = get1key() ;
|
|
if( c == 'O') { /* F1 .. F4 */
|
|
c = *(kptr++) = get1key() ;
|
|
if( c >= 'P' && c <= 'S')
|
|
return c | SPEC | prefix ;
|
|
} else if( c == '[') {
|
|
int v1, v ; /* ^[[v1;v~ or ^[[v~ */
|
|
|
|
foundCSI:
|
|
v1 = v = 0 ;
|
|
while( kptr < &keyread[ STACKSIZE]) {
|
|
c = *(kptr++) = get1key() ;
|
|
if( (c == '~')
|
|
|| (c >= 'A' && c <= 'Z')
|
|
|| (c >= 'a' && c <= 'z')) {
|
|
/* Found end of sequence */
|
|
int mask = prefix ;
|
|
if( v1) { /* Handle ALT/CTL, not SHFT */
|
|
if( (v - 1) & 2)
|
|
mask = META ;
|
|
|
|
if( (v - 1) & 4)
|
|
mask |= CTL_ ;
|
|
|
|
v = v1 ;
|
|
}
|
|
|
|
if( c == '~') {
|
|
if( v)
|
|
c = v + ((v <= 9) ? '0' : 'a' - 10) ;
|
|
else
|
|
break ;
|
|
}
|
|
|
|
return c | SPEC | mask ;
|
|
} else if( c == ';') { /* Start of SHFT/ALT/CTL state */
|
|
v1 = v ;
|
|
v = 0 ;
|
|
} else if( c >= '0' && c <= '9')
|
|
v = v * 10 + c - '0' ;
|
|
else
|
|
break ;
|
|
}
|
|
}
|
|
|
|
/* not a match, unget the keys read so far */
|
|
while( kptr > keyread)
|
|
KPUSH( *(--kptr)) ;
|
|
|
|
c = get1key() ;
|
|
} else
|
|
kptr-- ;
|
|
|
|
if( c == metac) {
|
|
prefix = META ;
|
|
} else if( c == ctlxc) {
|
|
if( prefix)
|
|
break ; /* ^X^X or M-^X */
|
|
else
|
|
prefix = CTLX ;
|
|
} else
|
|
break ;
|
|
}
|
|
|
|
if( prefix && islower( c))
|
|
c = flipcase( c) ;
|
|
|
|
return c | prefix ;
|
|
}
|
|
|
|
/* A more generalized prompt/reply function allowing the caller
|
|
to specify the proper terminator. If the terminator is not
|
|
a return ('\n') it will echo as "<NL>"
|
|
*/
|
|
|
|
static void echov( int c) {
|
|
/* verbose echo of a character */
|
|
if( c == '\n') /* put out <NL> for <ret> */
|
|
echos( "<NL>") ;
|
|
else {
|
|
if( c < ' ' || c == 0x7F) {
|
|
echoc( '^') ;
|
|
c ^= 0x40 ;
|
|
}
|
|
|
|
echoc( c) ;
|
|
}
|
|
}
|
|
|
|
static void rubc( int c) {
|
|
rubout() ;
|
|
if( (c >= 0 && c < ' ') || c == 0x7F) {
|
|
/* ^x range */
|
|
rubout() ;
|
|
if( c == '\n') { /* <NL> */
|
|
rubout() ;
|
|
rubout() ;
|
|
}
|
|
} else if( utf8_width( c) == 2)
|
|
rubout() ;
|
|
}
|
|
|
|
int getstring( const char *prompt, char *buf, int nbuf, int eolchar)
|
|
{
|
|
int cpos; /* current character position in string */
|
|
int c;
|
|
boolean quote_f ; /* are we quoting the next char? */
|
|
int retval ; /* TRUE, FALSE, ABORT */
|
|
#if COMPLC
|
|
boolean file_f ;
|
|
int ocpos, nskip = 0, didtry = 0;
|
|
#if MSDOS
|
|
struct ffblk ffblk;
|
|
char *fcp;
|
|
#endif
|
|
#if UNIX
|
|
static char tmp[] = "/tmp/meXXXXXX";
|
|
FILE *tmpf = NULL;
|
|
#endif
|
|
/* Look for "find-file: ", "View file: ", "Insert file: ", "Write file: ",
|
|
** "Read file: ", "Execute file: " */
|
|
file_f = NULL != strstr( prompt, "file: ") ;
|
|
#endif
|
|
|
|
cpos = 0;
|
|
quote_f = FALSE;
|
|
|
|
/* prompt the user for the input string */
|
|
mloutstr( prompt);
|
|
|
|
for (;;) {
|
|
#if COMPLC
|
|
if (!didtry)
|
|
nskip = -1;
|
|
didtry = 0;
|
|
#endif
|
|
/* get a character from the user */
|
|
int bytes = get1unicode( &c) ;
|
|
|
|
/* Quoting? Store as it is */
|
|
if( quote_f == TRUE) {
|
|
quote_f = FALSE ;
|
|
if( cpos < nbuf - bytes) {
|
|
c = ectoc( c) ;
|
|
cpos += unicode_to_utf8( c, &buf[ cpos]) ;
|
|
echov( c) ;
|
|
TTflush() ;
|
|
}
|
|
|
|
continue ;
|
|
}
|
|
|
|
/* If it is a <ret>, change it to a <NL> */
|
|
if( c == (CTL_ | 'M'))
|
|
c = CTL_ | 0x40 | '\n' ;
|
|
|
|
if( c == eolchar) {
|
|
/* if they hit the line terminator, wrap it up */
|
|
buf[ cpos] = 0 ;
|
|
|
|
/* clear the message line */
|
|
mloutstr( "") ;
|
|
|
|
/* if we default the buffer, return FALSE */
|
|
retval = cpos != 0 ;
|
|
break ;
|
|
} else if( c == abortc) {
|
|
/* Abort the input? */
|
|
retval = ctrlg( FALSE, 1) ;
|
|
break ;
|
|
}
|
|
|
|
/* change from command form back to character form */
|
|
c = ectoc( c) ;
|
|
|
|
if( c == 0x7F || c == 0x08) {
|
|
/* rubout/erase */
|
|
if (cpos != 0) {
|
|
int c ;
|
|
|
|
cpos -= 1 ;
|
|
cpos -= utf8_revdelta( (unsigned char *) &buf[ cpos], cpos) ;
|
|
utf8_to_unicode( &buf[ cpos], 0, 4, (unicode_t *) &c) ;
|
|
rubc( c) ;
|
|
TTflush();
|
|
}
|
|
} else if( c == 0x15) {
|
|
/* C-U, kill */
|
|
mloutstr( prompt) ;
|
|
cpos = 0 ;
|
|
#if COMPLC
|
|
} else if( (c == 0x09 || c == ' ') && file_f) {
|
|
/* TAB, complete file name */
|
|
char ffbuf[255];
|
|
#if MSDOS
|
|
char sffbuf[128];
|
|
int lsav = -1;
|
|
#endif
|
|
int n, iswild = 0;
|
|
|
|
didtry = 1;
|
|
ocpos = cpos;
|
|
mloutstr( prompt) ;
|
|
while( cpos != 0) {
|
|
c = buf[ --cpos] ;
|
|
if( c == '*' || c == '?') {
|
|
iswild = 1 ;
|
|
cpos = 0 ;
|
|
break ;
|
|
}
|
|
}
|
|
|
|
if (nskip < 0) {
|
|
buf[ocpos] = 0;
|
|
#if UNIX
|
|
if (tmpf != NULL) {
|
|
fclose(tmpf);
|
|
tmpf = NULL ;
|
|
unlink( tmp) ;
|
|
}
|
|
|
|
strcpy( tmp, "/tmp/meXXXXXX") ;
|
|
xmkstemp( tmp) ;
|
|
if( strlen( buf) < sizeof ffbuf - 26 - 1)
|
|
sprintf( ffbuf, "echo %s%s >%s 2>&1", buf,
|
|
!iswild ? "*" : "", tmp) ;
|
|
else
|
|
sprintf( ffbuf, "echo ERROR >%s 2>&1", tmp) ;
|
|
|
|
ue_system( ffbuf) ;
|
|
tmpf = fopen(tmp, "r");
|
|
#endif
|
|
#if MSDOS
|
|
strcpy(sffbuf, buf);
|
|
if (!iswild)
|
|
strcat(sffbuf, "*.*");
|
|
#endif
|
|
nskip = 0;
|
|
}
|
|
#if UNIX
|
|
c = ' ';
|
|
for (n = nskip; n > 0; n--)
|
|
while ((c = getc(tmpf)) != EOF
|
|
&& c != ' ');
|
|
#endif
|
|
#if MSDOS
|
|
if (nskip == 0) {
|
|
strcpy(ffbuf, sffbuf);
|
|
c = findfirst(ffbuf, &ffblk,
|
|
FA_DIREC) ? '*' : ' ';
|
|
} else if (nskip > 0)
|
|
c = findnext(&ffblk) ? 0 : ' ';
|
|
#endif
|
|
nskip++;
|
|
|
|
if (c != ' ') {
|
|
TTbeep();
|
|
nskip = 0;
|
|
}
|
|
#if UNIX
|
|
while ((c = getc(tmpf)) != EOF && c != '\n'
|
|
&& c != ' ' && c != '*')
|
|
#endif
|
|
#if MSDOS
|
|
if (c == '*')
|
|
fcp = sffbuf;
|
|
else {
|
|
strncpy(buf, sffbuf, lsav + 1);
|
|
cpos = lsav + 1;
|
|
fcp = ffblk.ff_name;
|
|
}
|
|
while (c != 0 && (c = *fcp++) != 0 && c != '*')
|
|
#endif
|
|
{
|
|
if (cpos < nbuf - 1)
|
|
buf[cpos++] = c;
|
|
}
|
|
#if UNIX
|
|
if (c == '*')
|
|
TTbeep();
|
|
#endif
|
|
|
|
for( n = 0 ; n < cpos ; ) {
|
|
n += utf8_to_unicode( buf, n, nbuf, (unicode_t *) &c) ;
|
|
echov( c) ;
|
|
}
|
|
|
|
TTflush() ;
|
|
#if UNIX
|
|
rewind(tmpf);
|
|
#endif
|
|
#endif
|
|
|
|
} else if( c == 0x11 || c == 0x16)
|
|
/* ^Q or ^V */
|
|
quote_f = TRUE ;
|
|
else {
|
|
/* store as it is */
|
|
if( cpos + bytes < nbuf) {
|
|
cpos += unicode_to_utf8( c, &buf[ cpos]) ;
|
|
echov( c) ;
|
|
TTflush() ;
|
|
}
|
|
}
|
|
}
|
|
|
|
TTflush() ;
|
|
if( tmpf != NULL) {
|
|
fclose( tmpf) ;
|
|
unlink( tmp) ;
|
|
}
|
|
|
|
return retval ;
|
|
}
|
|
|
|
/* end of input.c */
|