mirror of
https://github.com/rfivet/uemacs.git
synced 2024-11-16 01:26:06 -05:00
739 lines
18 KiB
C
739 lines
18 KiB
C
/* input.c -- implements input.h */
|
|
|
|
#include "input.h"
|
|
|
|
/* input.c
|
|
*
|
|
* 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 "estruct.h"
|
|
#include "bindable.h"
|
|
#include "display.h"
|
|
#include "exec.h"
|
|
#include "isa.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 = CONTROL | '[' ; /* current meta character */
|
|
int ctlxc = CONTROL | 'X' ; /* current control X prefix char */
|
|
int reptc = CONTROL | 'U' ; /* current universal repeat char */
|
|
int abortc = CONTROL | 'G' ; /* current abort command char */
|
|
|
|
const int nlc = CONTROL | '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 */
|
|
mlwrite( "%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 CONTROL and SPEC flags back into an ascii code
|
|
*/
|
|
int ectoc( int c) {
|
|
if( c & CONTROL)
|
|
c ^= CONTROL | 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.
|
|
*/
|
|
fn_t getname(void)
|
|
{
|
|
int cpos; /* current column on screen output */
|
|
struct name_bind *ffp; /* first ptr to entry in name binding table */
|
|
struct name_bind *cffp; /* current ptr to entry in name binding table */
|
|
struct name_bind *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[0]);
|
|
}
|
|
|
|
/* 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[0]);
|
|
|
|
} else if (c == ectoc(abortc)) { /* Bell, abort */
|
|
ctrlg(FALSE, 0);
|
|
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 */
|
|
ffp = &names[0]; /* scan for matches */
|
|
while (ffp->n_func != NULL) {
|
|
if (strncmp(buf, ffp->n_name, strlen(buf))
|
|
== 0) {
|
|
/* a possible match! More than one? */
|
|
if ((ffp + 1)->n_func == NULL ||
|
|
(strncmp
|
|
(buf, (ffp + 1)->n_name,
|
|
strlen(buf)) != 0)) {
|
|
/* no...we match, print it */
|
|
echos( ffp->n_name + cpos) ;
|
|
TTflush();
|
|
return ffp->n_func;
|
|
} else {
|
|
/* << << << << << << << << << << << << << << << << << */
|
|
/* try for a partial match against the list */
|
|
|
|
/* first scan down until we no longer match the current input */
|
|
lffp = (ffp + 1);
|
|
while ((lffp +
|
|
1)->n_func !=
|
|
NULL) {
|
|
if (strncmp
|
|
(buf,
|
|
(lffp +
|
|
1)->n_name,
|
|
strlen(buf))
|
|
!= 0)
|
|
break;
|
|
++lffp;
|
|
}
|
|
|
|
/* and now, attempt to partial complete the string, char at a time */
|
|
while (TRUE) {
|
|
/* add the next char in */
|
|
buf[cpos] =
|
|
ffp->
|
|
n_name[cpos];
|
|
|
|
/* scan through the candidates */
|
|
cffp = ffp + 1;
|
|
while (cffp <=
|
|
lffp) {
|
|
if (cffp->
|
|
n_name
|
|
[cpos]
|
|
!=
|
|
buf
|
|
[cpos])
|
|
goto onward;
|
|
++cffp;
|
|
}
|
|
|
|
/* add the character */
|
|
echoc( buf[ cpos++]) ;
|
|
}
|
|
/* << << << << << << << << << << << << << << << << << */
|
|
}
|
|
}
|
|
++ffp;
|
|
}
|
|
|
|
/* 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 prefixs legal here are the SPEC
|
|
and CONTROL prefixes. */
|
|
int get1key( void) {
|
|
int c ;
|
|
|
|
/* get a keystroke */
|
|
c = tgetc();
|
|
|
|
if( (c >= 0x00 && c <= 0x1F) || c == 0x7F) /* C0 control -> C- */
|
|
c ^= CONTROL | 0x40 ;
|
|
|
|
return c ;
|
|
}
|
|
|
|
/* GETCMD: Get a command from the keyboard. Process all applicable
|
|
prefix keys */
|
|
|
|
static int get1unicode( int *k) {
|
|
/* Accept UTF-8 sequence */
|
|
int c = *k ;
|
|
if( c > 0xC1 && c <= 0xF4) {
|
|
char utf[ 4] ;
|
|
char cc ;
|
|
|
|
utf[ 0] = c ;
|
|
utf[ 1] = cc = get1key() ;
|
|
if( (c & 0x20) && ((cc & 0xC0) == 0x80)) { /* at least 3 bytes and a valid encoded char */
|
|
utf[ 2] = cc = get1key() ;
|
|
if( (c & 0x10) && ((cc & 0xC0) == 0x80)) /* at least 4 bytes and a valid encoded char */
|
|
utf[ 3] = get1key() ;
|
|
}
|
|
|
|
return utf8_to_unicode( utf, 0, sizeof utf, (unicode_t *) k) ;
|
|
} else
|
|
return 1 ;
|
|
}
|
|
|
|
int getcmd(void)
|
|
{
|
|
int c; /* fetched keystroke */
|
|
#if VT220
|
|
int d; /* second character P.K. */
|
|
int cmask = 0;
|
|
#endif
|
|
/* get initial character */
|
|
c = get1key();
|
|
|
|
#if VT220
|
|
proc_metac:
|
|
#endif
|
|
if (c == 128+27) /* CSI */
|
|
goto handle_CSI;
|
|
/* process META prefix */
|
|
if (c == (CONTROL | '[')) {
|
|
c = get1key();
|
|
#if VT220
|
|
if (c == '[' || c == 'O') { /* CSI P.K. */
|
|
handle_CSI:
|
|
c = get1key();
|
|
if (c >= 'A' && c <= 'D')
|
|
return SPEC | c | cmask;
|
|
if (c >= 'E' && c <= 'z' && c != 'i' && c != 'c')
|
|
return SPEC | c | cmask;
|
|
d = get1key();
|
|
if (d == '~') /* ESC [ n ~ P.K. */
|
|
return SPEC | c | cmask;
|
|
switch (c) { /* ESC [ n n ~ P.K. */
|
|
case '1':
|
|
c = d + 32;
|
|
break;
|
|
case '2':
|
|
c = d + 48;
|
|
break;
|
|
case '3':
|
|
c = d + 64;
|
|
break;
|
|
default:
|
|
c = '?';
|
|
break;
|
|
}
|
|
if (d != '~') /* eat tilde P.K. */
|
|
get1key();
|
|
if (c == 'i') { /* DO key P.K. */
|
|
c = ctlxc;
|
|
goto proc_ctlxc;
|
|
} else if (c == 'c') /* ESC key P.K. */
|
|
c = get1key();
|
|
else
|
|
return SPEC | c | cmask;
|
|
}
|
|
#endif
|
|
#if VT220
|
|
if (c == (CONTROL | '[')) {
|
|
cmask = META;
|
|
goto proc_metac;
|
|
}
|
|
#endif
|
|
if( islower( c)) /* Force to upper */
|
|
c = flipcase( c) ;
|
|
else if( c >= 0x00 && c <= 0x1F) /* control key */
|
|
c = CONTROL | (c + '@');
|
|
return META | c;
|
|
}
|
|
#if PKCODE
|
|
else if (c == metac) {
|
|
c = get1key();
|
|
#if VT220
|
|
if (c == (CONTROL | '[')) {
|
|
cmask = META;
|
|
goto proc_metac;
|
|
}
|
|
#endif
|
|
if( islower( c)) /* Force to upper */
|
|
c = flipcase( c) ;
|
|
else if( c >= 0x00 && c <= 0x1F) /* control key */
|
|
c = CONTROL | (c + '@');
|
|
return META | c;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if VT220
|
|
proc_ctlxc:
|
|
#endif
|
|
/* process CTLX prefix */
|
|
if (c == ctlxc) {
|
|
c = get1key();
|
|
#if VT220
|
|
if (c == (CONTROL | '[')) {
|
|
cmask = CTLX;
|
|
goto proc_metac;
|
|
}
|
|
#endif
|
|
if (c >= 'a' && c <= 'z') /* Force to upper */
|
|
c -= 0x20;
|
|
if (c >= 0x00 && c <= 0x1F) /* control key */
|
|
c = CONTROL | (c + '@');
|
|
return CTLX | c;
|
|
}
|
|
|
|
#ifdef CYGWIN
|
|
get1unicode( &c) ;
|
|
#endif
|
|
|
|
/* otherwise, just return it */
|
|
return c;
|
|
}
|
|
|
|
/* 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( char c) {
|
|
rubout() ;
|
|
if( (c >= 0 && c < ' ') || c == 0x7F) {
|
|
/* ^x range */
|
|
rubout() ;
|
|
if( c == '\n') {
|
|
rubout() ;
|
|
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 */
|
|
mlwrite( "%s", prompt);
|
|
|
|
for (;;) {
|
|
#if COMPLC
|
|
if (!didtry)
|
|
nskip = -1;
|
|
didtry = 0;
|
|
#endif
|
|
/* get a character from the user */
|
|
c = get1key();
|
|
|
|
/* Quoting? Store as it is */
|
|
if( quote_f == TRUE) {
|
|
quote_f = FALSE ;
|
|
if( cpos < nbuf - 1) {
|
|
c = ectoc( c) ;
|
|
buf[ cpos++] = c ;
|
|
echov( c) ;
|
|
TTflush() ;
|
|
}
|
|
|
|
continue ;
|
|
}
|
|
|
|
/* If it is a <ret>, change it to a <NL> */
|
|
if( c == (CONTROL | 'M'))
|
|
c = CONTROL | 0x40 | '\n' ;
|
|
|
|
if( c == eolchar) {
|
|
/* if they hit the line terminator, wrap it up */
|
|
buf[ cpos] = 0 ;
|
|
|
|
/* clear the message line */
|
|
mlwrite("");
|
|
|
|
/* if we default the buffer, return FALSE */
|
|
retval = cpos != 0 ;
|
|
break ;
|
|
} else if( c == abortc) {
|
|
/* Abort the input? */
|
|
ctrlg( FALSE, 0) ;
|
|
retval = ABORT ;
|
|
break ;
|
|
}
|
|
|
|
/* change from command form back to character form */
|
|
c = ectoc( c) ;
|
|
|
|
if( c == 0x7F || c == 0x08) {
|
|
/* rubout/erase */
|
|
if (cpos != 0) {
|
|
rubc( buf[ --cpos]) ;
|
|
cpos -= utf8_revdelta( (unsigned char *) &buf[ cpos], cpos) ;
|
|
TTflush();
|
|
}
|
|
} else if( c == 0x15) {
|
|
/* C-U, kill */
|
|
mlwrite( "%s", 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;
|
|
mlwrite( "%s", 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 */
|
|
int n ;
|
|
|
|
n = get1unicode( &c) ; /* fetch multiple bytes */
|
|
if( cpos + n < nbuf) {
|
|
cpos += unicode_to_utf8( c, &buf[ cpos]) ;
|
|
echov( c) ;
|
|
TTflush() ;
|
|
}
|
|
}
|
|
}
|
|
|
|
TTflush() ;
|
|
if( tmpf != NULL) {
|
|
fclose( tmpf) ;
|
|
unlink( tmp) ;
|
|
}
|
|
|
|
return retval ;
|
|
}
|