mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1923 lines
58 KiB
1923 lines
58 KiB
/*++
|
|
|
|
Copyright (c) 1988-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
cparse.c
|
|
|
|
Abstract:
|
|
|
|
Command parsing
|
|
|
|
--*/
|
|
|
|
#include "cmd.h"
|
|
|
|
/*
|
|
NT Command Interpreter
|
|
|
|
This file contains all of the routines which make up Command's
|
|
parser. The main routines below have names like ParseXXX where
|
|
XXX refers to the name of the production that routine is supposed
|
|
to parse.
|
|
|
|
None of the routines in this file, except Parser, will return if
|
|
they detect a syntax error. Instead, they will all call PSError() or
|
|
PError(). These routines do a longjmp() back to Parser.
|
|
|
|
Command Language Grammar:
|
|
|
|
statement -> s0
|
|
s0 -> s1 "&" s0 | s1
|
|
s1 -> s2 "||" s1 | s2
|
|
s2 -> s3 "&&" s2 | s3
|
|
s3 -> s4 "|" s3 | s4
|
|
s4 -> redir s5 | s5 redir | s5
|
|
s5 -> "(" statement ")" |
|
|
"@" statement |
|
|
FOR var IN "(" arglist ")" DO s1 |
|
|
IF condition statement ELSE statement |
|
|
IF condition statement |
|
|
cmd arglist
|
|
|
|
var -> "%"c | "%%"c
|
|
c -> any-character
|
|
arglist -> (arg)*
|
|
arg -> any-string
|
|
condition -> NOT cond | cond
|
|
cond -> ERRORLEVEL n | arg == arg | EXIST fname
|
|
n -> any-number
|
|
fname -> any-file-name
|
|
cmd -> internal-command | external-command
|
|
|
|
redir -> in out | out in
|
|
in -> "<" arg | epsilon
|
|
out -> ( ">" | ">>" ) arg | epsilon
|
|
|
|
|
|
Operator precedence from lowest to highest:
|
|
& Command Separator
|
|
|| Or Operator
|
|
&& And Operator
|
|
| Pipe Operator
|
|
< > >> I/O Redirectors
|
|
() Command Grouper
|
|
|
|
Examples:
|
|
x & y | z => x & (y | z)
|
|
x | y & z => (x | y) & z
|
|
x || y | z => x || (y | z)
|
|
a & b || c && d | e || f => a & ((b || (c && (d | e))) || f)
|
|
|
|
*/
|
|
|
|
|
|
extern jmp_buf CmdJBuf1 ; /* Used by setjmp() and longjmp() */
|
|
|
|
extern TCHAR TmpBuf[] ; /* The 1st 1/2 of this is used as the token buffer */
|
|
/* M007 */
|
|
|
|
TCHAR *TokBuf = TmpBuf ; /* Current token buffer */ /* M007 */
|
|
unsigned TokTyp ; /* " " " " type */
|
|
unsigned TokLen ; /* " " " " length */
|
|
|
|
extern unsigned DebGroup ;
|
|
extern unsigned DebLevel ;
|
|
|
|
extern TCHAR Delimiters[] ; /* M022 */
|
|
|
|
/* Command strings */
|
|
extern TCHAR ForStr[], IfStr[], DetStr[], InStr[], DoStr[], ElseStr[], ErrStr[] ;
|
|
extern TCHAR ForHelpStr[], IfHelpStr[], RemHelpStr[];
|
|
extern TCHAR ForLoopStr[];
|
|
extern TCHAR ForDirTooStr[];
|
|
extern TCHAR ForParseStr[];
|
|
extern TCHAR ForRecurseStr[];
|
|
extern TCHAR ExsStr[], NotStr[] ;
|
|
extern TCHAR CmdExtVerStr[], DefinedStr[] ;
|
|
extern TCHAR RemStr[] ; /* M002 */
|
|
extern TCHAR ExtprocStr[] ; /* M023 */
|
|
extern TCHAR Fmt19[] ; /* M018 */
|
|
extern TCHAR Fmt00[] ; /* @@4 */
|
|
|
|
int NulNode = FALSE ; /* M018 */
|
|
unsigned global_dfvalue; /* @@4 */
|
|
|
|
|
|
BOOLEAN fDumpTokens;
|
|
BOOLEAN fDumpParse;
|
|
void DuOp(TCHAR *op, struct node *n, int pad);
|
|
void DuRd(struct node *n);
|
|
|
|
/*** DumpParseTree - display a parse tree
|
|
*
|
|
* Purpose:
|
|
* Display a parse tree in a reasonable format.
|
|
*
|
|
* DumpParseTree(struct node *n, int pad)
|
|
*
|
|
* Args:
|
|
* n - the root of the tree to be displayed
|
|
* pad - the number of spaces to indent the listing of the current node
|
|
*
|
|
* Notes:
|
|
* This program is only compiled when DBG is defined.
|
|
*
|
|
*/
|
|
|
|
void
|
|
DumpParseTree( struct node *n, int pad)
|
|
{
|
|
struct cmdnode *c ; /* All node ptrs are used to display that node type */
|
|
struct fornode *f ;
|
|
struct ifnode *i ;
|
|
int j ; /* Used to pad output */
|
|
|
|
if (!n)
|
|
return ;
|
|
|
|
for (j = 0 ; j < pad ; j++)
|
|
cmd_printf(Fmt19, SPACE) ; /* M018 */
|
|
pad += 2 ;
|
|
|
|
switch (n->type) {
|
|
case LFTYP:
|
|
DuOp(TEXT("CRLF"), n, pad-2) ;
|
|
break ;
|
|
|
|
case CSTYP:
|
|
DuOp(CSSTR, n, pad) ;
|
|
break ;
|
|
|
|
case ORTYP:
|
|
DuOp(ORSTR, n, pad) ;
|
|
break ;
|
|
|
|
case ANDTYP:
|
|
DuOp(ANDSTR, n, pad) ;
|
|
break ;
|
|
|
|
case PIPTYP:
|
|
DuOp(PIPSTR, n, pad) ;
|
|
break ;
|
|
|
|
case PARTYP:
|
|
DuOp(LEFTPSTR, n, pad) ;
|
|
break ;
|
|
|
|
/* M015 - Added new type
|
|
*/
|
|
case SILTYP:
|
|
DuOp(SILSTR, n, pad) ;
|
|
break ;
|
|
|
|
case FORTYP:
|
|
f = (struct fornode *) n ;
|
|
cmd_printf(TEXT("%s (%s) %s\n"), f->cmdline, f->arglist, f->cmdline+DOPOS) ; /* M010 */
|
|
DumpParseTree(f->body, pad) ;
|
|
break ;
|
|
|
|
case IFTYP:
|
|
i = (struct ifnode *) n ;
|
|
cmd_printf(TEXT("%s\n"), i->cmdline) ; /* M010 */
|
|
DumpParseTree((struct node *)i->cond, pad) ;
|
|
DumpParseTree(i->ifbody, pad) ;
|
|
if (i->elsebody) {
|
|
for (j = 0 ; j < pad-2 ; j++)
|
|
cmd_printf(Fmt19, SPACE) ; /* M018 */
|
|
cmd_printf(TEXT("%s\n"), i->elseline) ; /* M010 */
|
|
DumpParseTree(i->elsebody, pad) ;
|
|
} ;
|
|
break ;
|
|
|
|
case NOTTYP:
|
|
c = (struct cmdnode *) n ;
|
|
cmd_printf(TEXT("%s\n"), c->cmdline) ; /* M010 */
|
|
DumpParseTree((struct node *)c->argptr, pad) ;
|
|
break ;
|
|
|
|
case REMTYP: /* M002 - New REM type */
|
|
case CMDTYP:
|
|
case ERRTYP:
|
|
case EXSTYP:
|
|
case STRTYP:
|
|
case CMDVERTYP:
|
|
case DEFTYP:
|
|
c = (struct cmdnode *) n ;
|
|
cmd_printf( TEXT("Cmd: %s Type: %x "), c->cmdline, c->type) ; /* M010 */
|
|
if (c->argptr)
|
|
cmd_printf( TEXT("Args: `%s' "), c->argptr) ; /* M010 */
|
|
DuRd((struct node *)c) ;
|
|
break ;
|
|
|
|
default:
|
|
cmd_printf(TEXT("*** Unknown type: %x\n"), n->type) ; /* M010 */
|
|
} ;
|
|
}
|
|
|
|
void
|
|
DuOp(TCHAR *op, struct node *n, int pad)
|
|
{
|
|
cmd_printf( TEXT("%s "), op) ; /* M010 */
|
|
|
|
DuRd(n) ;
|
|
|
|
DumpParseTree(n->lhs, pad) ;
|
|
DumpParseTree(n->rhs, pad) ;
|
|
}
|
|
|
|
/* M013 - The DuRd function has been extensively modified to conform
|
|
* to new data structures for redirection.
|
|
* M014 - Altered to handle input redirection for other than handle 0.
|
|
*/
|
|
|
|
void
|
|
DuRd(struct node *n)
|
|
{
|
|
struct relem *tmp ;
|
|
|
|
if (tmp = n->rio)
|
|
CmdPutString( TEXT("Redir: ")) ;
|
|
|
|
while (tmp) {
|
|
|
|
cmd_printf(TEXT(" %x %c"), tmp->rdhndl, tmp->rdop) ;
|
|
|
|
if (tmp->flag)
|
|
CmdPutString( TEXT( ">" )) ;
|
|
|
|
CmdPutString( tmp->fname );
|
|
|
|
tmp = tmp->nxt ;
|
|
} ;
|
|
|
|
CmdPutString( CrLf );
|
|
}
|
|
|
|
|
|
|
|
/*** Parser - controls statement parsing
|
|
*
|
|
* Purpose:
|
|
* Initialize the lexer, free memory previously used by the parser, and
|
|
* call Parse to parse the next statement.
|
|
*
|
|
* struct node *Parser(unsigned dfvalue, INT_PTR dpvalue, int fsarg)
|
|
*
|
|
* Args:
|
|
* dfvalue - the value to be assigned to DataFlag by InitLex
|
|
* dpvalue - the value to be assigned to DataPtr by InitLex
|
|
* fsarg - passed to FreeStack, tells how many elements should be left on
|
|
* the data stack
|
|
*
|
|
* Returns:
|
|
* The root of the parse tree of the statement just parsed or PARSERROR
|
|
* if an error was detected.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseStatement(int type) ;
|
|
#define MAX_STATEMENT_DEPTH 256
|
|
int StatementDepth;
|
|
BYTE StatementType[ MAX_STATEMENT_DEPTH ];
|
|
int PendingParens;
|
|
|
|
struct node *Parser(dfvalue, dpvalue, fsarg)
|
|
unsigned dfvalue ;
|
|
INT_PTR dpvalue ;
|
|
int fsarg ;
|
|
{
|
|
struct node *n ; /* Root of the parse tree */
|
|
INT_PTR retcode ; /* setjmp()'s return code */
|
|
unsigned GeToken() ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PARSER: Entered.")) ;
|
|
|
|
global_dfvalue = dfvalue; /* @@4 */
|
|
|
|
FreeStack(fsarg) ; /* Free unneeded memory */
|
|
InitLex(dfvalue, dpvalue) ; /* Initialize the lexer */
|
|
|
|
if ((retcode = setjmp(CmdJBuf1)) != 0)
|
|
return((struct node *) retcode) ; /* Return a parser error code */
|
|
|
|
StatementDepth = 0;
|
|
PendingParens = 0;
|
|
n = ParseStatement(0) ;
|
|
|
|
/* Error if any data left in the buffer. N is NULL then a blank line was
|
|
* found. In this case, it's alright that the buffer isn't empty.
|
|
*/
|
|
|
|
/* M011 - Ref to IsData() was ref to Peek()
|
|
* M018 - Reorganized to save null/non-null state of last node.
|
|
*/
|
|
if (n) {
|
|
if(IsData() && GeToken(GT_NORMAL) != NLN && TokTyp != EOS)
|
|
PSError() ;
|
|
NulNode = FALSE ;
|
|
} else {
|
|
NulNode = TRUE ;
|
|
} ;
|
|
|
|
if (fDumpParse)
|
|
DumpParseTree(n, 0) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PARSER: Exited.")) ;
|
|
return(n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseStatement - parse the statement production
|
|
*
|
|
* Purpose:
|
|
* Parse the statement production.
|
|
*
|
|
* struct node *ParseStatement()
|
|
*
|
|
* Returns:
|
|
* A pointer to the statement parsed or PARSERROR.
|
|
*
|
|
* Note:
|
|
* Removed the tests for and calls to parse FOR, IF, DET and REM
|
|
* from ParseStatement to ParseS5 (M012).
|
|
*
|
|
*/
|
|
|
|
extern int AtIsToken; /* @@4 */
|
|
extern int ColonIsToken;
|
|
|
|
struct node *ParseStatement(int type)
|
|
{
|
|
struct node *n;
|
|
|
|
#ifdef USE_STACKAVAIL
|
|
if (stackavail() > MINSTACKNEED) { /* @WM1 Is there enough stack left? */
|
|
#endif
|
|
StatementType[StatementDepth++] = (UCHAR)type;
|
|
if (type == PARTYP)
|
|
PendingParens += 1;
|
|
|
|
AtIsToken = 1; /* @@4 */
|
|
GeToken(GT_LPOP) ; /* M000 - Was GT_NORMAL */
|
|
AtIsToken = 0; /* @@4 */
|
|
DEBUG((PAGRP, PALVL, "PST: Entered, token = `%ws'", TokBuf)) ;
|
|
|
|
if (TokTyp == EOS)
|
|
longjmp(CmdJBuf1, EOF) ;
|
|
|
|
n = ParseS0() ;
|
|
StatementDepth -= 1;
|
|
if (type == PARTYP)
|
|
PendingParens -= 1;
|
|
return n;
|
|
#ifdef USE_STACKAVAIL
|
|
} else {
|
|
PutStdErr( MSG_TRAPC, ONEARG, Fmt00 ); /* @@4 */
|
|
Abort(); /* @@4 No, return error to user */
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*** ParseFor - parse for loops
|
|
*
|
|
* Purpose:
|
|
* Parse a FOR statement.
|
|
*
|
|
* Returns:
|
|
* A pointer to a parsed FOR statement or PARSERROR.
|
|
*
|
|
* struct node *ParseFor()
|
|
*
|
|
* Notes:
|
|
* Microsoft's DOS manual says for loop vars can't be digits but the IBM
|
|
* documentation makes no such restriction. Because my for/batch var
|
|
* substitution function will have no problem with it, I have taken out
|
|
* the check for that. - EKE
|
|
* - M022 * Changed FOR parser to treat all cases of FOR variable the
|
|
* same now that variable substitution is done in the lexer. Note
|
|
* that all commands will now look the same whether in batch files or
|
|
* on the command line.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseFor()
|
|
{
|
|
struct fornode *n ; /* Holds ptr to the for node to be built and filled */
|
|
struct cmdnode *LoadNodeTC() ;
|
|
BOOL Help=FALSE;
|
|
|
|
DEBUG((PAGRP, PALVL, "PFOR: Entered.")) ;
|
|
|
|
// if "for/?", convert to "for /?"
|
|
|
|
if (_tcsicmp(ForHelpStr, TokBuf) == 0) {
|
|
TokBuf[_tcslen(ForStr)] = NULLC;
|
|
Help=TRUE;
|
|
}
|
|
|
|
TokLen = FORLINLEN ; /* NEEDED for LoadNodeTC call following */
|
|
n = (struct fornode *) LoadNodeTC(FORTYP) ;
|
|
|
|
/* Get and verify the for loop variable */
|
|
if (Help) {
|
|
TokBuf[0] = SWITCHAR;
|
|
TokBuf[1] = QMARK;
|
|
TokBuf[2] = NULLC;
|
|
} else {
|
|
GeToken(GT_NORMAL) ;
|
|
}
|
|
|
|
if (TokBufCheckHelp( TokBuf, FORTYP ) ) {
|
|
n->type = HELPTYP ;
|
|
n->cmdline = NULL;
|
|
return((struct node *) n) ;
|
|
//Abort();
|
|
}
|
|
|
|
n->flag = 0;
|
|
//
|
|
// If extensions are enabled, check for additional forms of the FOR
|
|
// statement, all identified by a switch character after the FOR
|
|
// keyword.
|
|
//
|
|
if (fEnableExtensions) {
|
|
while (TRUE) {
|
|
//
|
|
// FOR /L %i in (start,step,end) do
|
|
//
|
|
if (_tcsicmp(ForLoopStr, TokBuf) == 0) {
|
|
n->flag |= FOR_LOOP;
|
|
GeToken(GT_NORMAL) ;
|
|
continue;
|
|
} else
|
|
|
|
//
|
|
// FOR /D %i in (set) do
|
|
//
|
|
if (_tcsicmp(ForDirTooStr, TokBuf) == 0) {
|
|
n->flag |= FOR_MATCH_DIRONLY;
|
|
GeToken(GT_NORMAL) ;
|
|
continue;
|
|
} else
|
|
|
|
//
|
|
// FOR /F ["parse options"] %i in (set) do
|
|
//
|
|
if (_tcsicmp(ForParseStr, TokBuf) == 0) {
|
|
n->flag |= FOR_MATCH_PARSE;
|
|
GeToken(GT_NORMAL) ;
|
|
|
|
//
|
|
// If next token does not begin with % then must be
|
|
// parse options
|
|
//
|
|
|
|
if (*TokBuf != PERCENT && *TokBuf != SWITCHAR) {
|
|
if (n->parseOpts != NULL) {
|
|
PSError( );
|
|
}
|
|
n->parseOpts = gmkstr((TokLen+3)*sizeof(TCHAR)) ;
|
|
mystrcpy(n->parseOpts, TokBuf) ;
|
|
GeToken(GT_NORMAL) ;
|
|
}
|
|
continue;
|
|
} else
|
|
|
|
//
|
|
// FOR /R [directoryPath] %i in (set) do
|
|
//
|
|
if (_tcsicmp(ForRecurseStr, TokBuf) == 0) {
|
|
n->flag |= FOR_MATCH_RECURSE;
|
|
GeToken(GT_NORMAL) ;
|
|
//
|
|
// If next token does not begin with % then must be
|
|
// directory path to start recursive walk from
|
|
//
|
|
if (n->recurseDir != NULL) {
|
|
PSError( );
|
|
}
|
|
|
|
if (*TokBuf != PERCENT && *TokBuf != SWITCHAR) {
|
|
n->recurseDir = gmkstr((TokLen+1)*sizeof(TCHAR)) ;
|
|
mystrcpy(n->recurseDir, TokBuf) ;
|
|
GeToken(GT_NORMAL) ;
|
|
}
|
|
continue;
|
|
} else
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check for validity of switches:
|
|
// FOR_LOOP with no others
|
|
// FOR_MATCH_DIRONLY possibly with FOR_MATCH_RECURSE
|
|
// FOR_MATCH_PARSE with no others
|
|
// FOR_MATCH_RECURSE possibly with FOR_MATCH_DIRONLY
|
|
//
|
|
|
|
if (n->flag == FALSE
|
|
|| n->flag == FOR_LOOP
|
|
|| n->flag == FOR_MATCH_DIRONLY
|
|
|| n->flag == (FOR_MATCH_DIRONLY | FOR_MATCH_RECURSE)
|
|
|| n->flag == FOR_MATCH_PARSE
|
|
|| n->flag == FOR_MATCH_RECURSE
|
|
) {
|
|
} else {
|
|
PSError( );
|
|
}
|
|
|
|
}
|
|
|
|
if (*TokBuf != PERCENT ||
|
|
(_istspace(TokBuf[1]) || mystrchr(Delimiters, (TCHAR)(n->forvar = (unsigned)TokBuf[1]))) ||
|
|
TokLen != 3) {
|
|
PSError() ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PFOR: var = %c", n->forvar)) ;
|
|
|
|
SpaceCat(n->cmdline, n->cmdline, TokBuf) ; /* End of var verify */
|
|
|
|
GetCheckStr(InStr) ; /* M005 */
|
|
SpaceCat(n->cmdline, n->cmdline, TokBuf) ;
|
|
|
|
n->arglist = BuildArgList() ;
|
|
|
|
GetCheckStr(DoStr) ; /* M005 */
|
|
mystrcpy(n->cmdline+DOPOS, TokBuf) ;
|
|
|
|
if (!(n->body = ParseStatement(FORTYP)))
|
|
PSError() ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PFOR: Exiting.")) ;
|
|
return((struct node *) n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseIf - parse if statements
|
|
*
|
|
* Purpose:
|
|
* Parse a IF statement.
|
|
*
|
|
* struct node *ParseIf()
|
|
*
|
|
* Returns:
|
|
* A pointer to a parsed IF statement or PARSERROR.
|
|
*
|
|
* *** W A R N I N G ! ***
|
|
* THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
|
|
* THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
|
|
* CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
|
|
*
|
|
*/
|
|
|
|
struct node *ParseIf()
|
|
{
|
|
struct ifnode *n ; /* hold ptr to if node to be built and filled */
|
|
BOOL Help=FALSE;
|
|
int fIgnoreCase;
|
|
|
|
DEBUG((PAGRP, PALVL, "PIF: Entered.")) ;
|
|
|
|
// if "if/?", convert to "if /?"
|
|
|
|
if (_tcsicmp(IfHelpStr, TokBuf) == 0) {
|
|
TokBuf[_tcslen(IfStr)] = NULLC;
|
|
Help=TRUE;
|
|
}
|
|
|
|
n = (struct ifnode *) LoadNodeTC(IFTYP) ;
|
|
|
|
if (Help) {
|
|
TokBuf[0] = SWITCHAR;
|
|
TokBuf[1] = QMARK;
|
|
TokBuf[2] = NULLC;
|
|
} else {
|
|
GeToken(GT_NORMAL) ;
|
|
}
|
|
|
|
//
|
|
// Check for help flag
|
|
//
|
|
if (TokBufCheckHelp(TokBuf, IFTYP)) {
|
|
n->type = HELPTYP ;
|
|
n->cmdline = NULL;
|
|
return((struct node *) n) ;
|
|
// Abort();
|
|
|
|
} else {
|
|
fIgnoreCase = FALSE;
|
|
//
|
|
// If extensions are enabled, check for the /I switch which
|
|
// specifies case insensitive comparison.
|
|
//
|
|
if (fEnableExtensions && !_tcsicmp(TokBuf, TEXT("/I"))) {
|
|
fIgnoreCase = TRUE;
|
|
} else
|
|
//
|
|
// if no help flag then put it all back and
|
|
// have ParseCond refetch token
|
|
//
|
|
Lex(LX_UNGET,0) ;
|
|
}
|
|
n->cond = ParseCond(PC_NOTS) ;
|
|
if (n->cond && fIgnoreCase)
|
|
if (n->cond->type != NOTTYP)
|
|
n->cond->flag = CMDNODE_FLAG_IF_IGNCASE;
|
|
else
|
|
((struct cmdnode *)(n->cond->argptr))->flag = CMDNODE_FLAG_IF_IGNCASE;
|
|
|
|
if (!(n->ifbody = ParseStatement(IFTYP)))
|
|
PSError() ;
|
|
|
|
if (IsData()) { /* M011 - Was Peek() */
|
|
GeToken(GT_NORMAL) ;
|
|
if (_tcsicmp(ElseStr, TokBuf) == 0) {
|
|
DEBUG((PAGRP, PALVL, "PIF: Found else.")) ;
|
|
n->elseline = gmkstr(TokLen*sizeof(TCHAR)) ; /*WARNING*/
|
|
mystrcpy(n->elseline, TokBuf) ;
|
|
if (!(n->elsebody = ParseStatement(IFTYP)))
|
|
PSError() ;
|
|
|
|
} else
|
|
Lex(LX_UNGET,0) ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PIF: Entered.")) ;
|
|
return((struct node *) n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseRem - parse REM statements (M002 - New function)
|
|
*
|
|
* Purpose:
|
|
* Parse a REM statement.
|
|
*
|
|
* struct node *ParseRem()
|
|
*
|
|
* Returns:
|
|
* A pointer to a parsed REM statement.
|
|
* Returns FAILURE if not able to allocate memory
|
|
*
|
|
*
|
|
* *** W A R N I N G ! ***
|
|
* THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
|
|
* THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
|
|
* CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
|
|
*/
|
|
|
|
struct node *ParseRem()
|
|
{
|
|
struct cmdnode *n ; /* Ptr to REM node to build/fill */
|
|
BOOL Help=FALSE;
|
|
|
|
DEBUG((PAGRP, PALVL, "PREM: Entered.")) ;
|
|
|
|
// if rem/?, convert to rem /?
|
|
|
|
if (_tcsicmp(RemHelpStr, TokBuf) == 0) {
|
|
TokBuf[_tcslen(RemStr)] = NULLC;
|
|
Help=TRUE;
|
|
}
|
|
|
|
n = LoadNodeTC(REMTYP) ;
|
|
|
|
if (Help) {
|
|
TokBuf[0] = SWITCHAR;
|
|
TokBuf[1] = QMARK;
|
|
TokBuf[2] = NULLC;
|
|
} else {
|
|
GeToken(GT_NORMAL) ;
|
|
}
|
|
|
|
//
|
|
// Check for help flag
|
|
//
|
|
|
|
if (TokBufCheckHelp(TokBuf, REMTYP)) {
|
|
n->type = HELPTYP ;
|
|
n->cmdline = NULL;
|
|
return((struct node *) n) ;
|
|
|
|
//Abort();
|
|
|
|
} else {
|
|
//
|
|
// if no help flag then put it all back and
|
|
// have ParseCond refetch token
|
|
//
|
|
Lex(LX_UNGET,0) ;
|
|
}
|
|
|
|
if (IsData()) { /* Read in args, if any (M011 - Was Peek()) */
|
|
if (GeToken(GT_REM) == TEXTOKEN) {
|
|
n->argptr = gmkstr(TokLen*sizeof(TCHAR)) ; /*WARNING*/
|
|
mystrcpy(n->argptr, TokBuf) ;
|
|
DEBUG((PAGRP, PALVL, "PREM: args = `%ws'", n->argptr)) ;
|
|
|
|
} else
|
|
Lex(LX_UNGET,0) ; /* M011 - Was UnGeToken() */
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PREM: Exited.")) ;
|
|
return((struct node *) n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseS0 - parse production s0
|
|
*
|
|
* Purpose:
|
|
* Parse the s0 production.
|
|
*
|
|
* struct node *ParseS0()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
* Notes:
|
|
* If a parenthesised statement group is NOT being parsed, NLN
|
|
* is consider to be an empty side of a command separator so NULL is
|
|
* returned.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseS0()
|
|
{
|
|
DEBUG((PAGRP, PALVL, "PS0: Entered.")) ;
|
|
|
|
if (!ColonIsToken && TokTyp == TEXTOKEN && TokBuf[0] == COLON) {
|
|
do {
|
|
GeToken(GT_NORMAL) ;
|
|
} while (TokBuf[0] != NULLC && TokBuf[0] != NLN);
|
|
if (StatementType[StatementDepth-1] != PARTYP) {
|
|
return(NULL) ;
|
|
}
|
|
GeToken(GT_NORMAL) ;
|
|
}
|
|
|
|
if (StatementType[StatementDepth-1] != PARTYP) {
|
|
if (TokTyp == TEXTOKEN && !_tcsicmp(TokBuf, RPSTR)) {
|
|
//
|
|
// If a goto was done inside a parenthesized list of statements
|
|
// we will eventually hit the terminating right paren. Skip over
|
|
// it so we dont declare an error
|
|
//
|
|
do {
|
|
GeToken(GT_NORMAL) ;
|
|
} while (TokBuf[0] != NULLC && TokBuf[0] != NLN);
|
|
}
|
|
|
|
if (TokTyp == NLN) {
|
|
DEBUG((PAGRP, PALVL, "PS0: Returning null statement.")) ;
|
|
return(NULL) ;
|
|
}
|
|
}
|
|
|
|
return(BinaryOperator(CSSTR, CSTYP, (PPARSE_ROUTINE)ParseS0, (PPARSE_ROUTINE)ParseS1)) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseS1 - parse production s1
|
|
*
|
|
* Purpose:
|
|
* Parse the s1 production.
|
|
*
|
|
* struct node *ParseS1()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseS1()
|
|
{
|
|
DEBUG((PAGRP, PALVL, "PS1: Entered.")) ;
|
|
return(BinaryOperator(ORSTR, ORTYP, (PPARSE_ROUTINE)ParseS1, (PPARSE_ROUTINE)ParseS2)) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseS2 - parse production s2
|
|
*
|
|
* Purpose:
|
|
* Parse the s2 production.
|
|
*
|
|
* struct node *ParseS2()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseS2()
|
|
{
|
|
DEBUG((PAGRP, PALVL, "PS2: Entered.")) ;
|
|
return(BinaryOperator(ANDSTR, ANDTYP, (PPARSE_ROUTINE)ParseS2, (PPARSE_ROUTINE)ParseS3)) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseS3 - parse production s3
|
|
*
|
|
* Purpose:
|
|
* Parse the s3 production.
|
|
*
|
|
* struct node *ParseS3()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseS3()
|
|
{
|
|
DEBUG((PAGRP, PALVL, "PS3: Entered.")) ;
|
|
return(BinaryOperator(PIPSTR, PIPTYP, (PPARSE_ROUTINE)ParseS3, (PPARSE_ROUTINE)ParseS4)) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseS4 - parse production s4
|
|
*
|
|
* Purpose:
|
|
* Parse the s4 production.
|
|
*
|
|
* struct node *ParseS4()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
* Notes:
|
|
* M013 - Almost the whole of this function was rewritten to
|
|
* conform to new structures and methods of redirection parsing.
|
|
* The primary data item is a linked list of structures which
|
|
* identify the individual redirection commands.
|
|
*/
|
|
|
|
struct node *ParseS4()
|
|
{
|
|
struct node *n ; /* Node ptr to add redir info to */
|
|
struct relem *io = NULL ;
|
|
struct relem *tmpio ;
|
|
int flg = 0;
|
|
int i ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PS4: Entered.")) ;
|
|
|
|
/* Parse leading redirection receiving in return a list of redirection
|
|
* structure elements pointed to by io. Get a new token for ParseS5
|
|
* if necessary (ParseRedir successful).
|
|
*/
|
|
if (ParseRedir(&io)) { /* M013 - Now use list of structs */
|
|
GeToken(GT_LPOP) ; /* M011 - '(' Now operator */
|
|
|
|
DEBUG((PAGRP,PALVL,"PS4: List ptr io = %04x",io)) ;
|
|
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PS4: Calling PS5.")) ;
|
|
|
|
n = ParseS5() ;
|
|
|
|
DEBUG((PAGRP,PALVL,"PS4: Post PS5 io= %04x, n->rio= %04x",io,n->rio)) ;
|
|
|
|
/* If more redirection was found beyond ParseS5 (in ParseCmd), n-rio in
|
|
* the node will point to another list. If two lists exist, integrate
|
|
* them, giving priority to the later one by forcing them to be in
|
|
* chronological order (io becomes n->rio and n->rio is appended).
|
|
*
|
|
* NOTE: FOR and IF nodes are explicitly barred from having leading
|
|
* redirection (use will result in a Syntax Error). This restriction may
|
|
* later be removed by inserting code to walk the parse tree and insert
|
|
* the leading redirection in the first non-IF/FOR/DET node.
|
|
*/
|
|
if (io) { /* If leading redirection... */
|
|
|
|
DEBUG((PAGRP,PALVL,"PS4: Have leading redirection.")) ;
|
|
|
|
if (n->type == FORTYP ||
|
|
n->type == IFTYP) {
|
|
|
|
DEBUG((PAGRP,PALVL,"PS4: n=IF/FOR !!ERROR!!")) ;
|
|
|
|
mystrcpy(TokBuf,((struct cmdnode *)n)->cmdline) ;
|
|
PSError() ;
|
|
} ;
|
|
|
|
tmpio = n->rio ; /* ...save possible Cmd redir... */
|
|
n->rio = io ; /* ...install leading redir... */
|
|
if (tmpio) { /* ...and if Cmd redirection... */
|
|
|
|
DEBUG((PAGRP,PALVL,"PS4: Have Cmd redirection.")) ;
|
|
|
|
while (io->nxt) /* ...find list end... */
|
|
io = io->nxt ;
|
|
io->nxt = tmpio ; /* ...and install it */
|
|
} ;
|
|
} ;
|
|
|
|
/* The nodes n->rio field points to the head of a single list (or NULL
|
|
* if no redirection). If further input remains in Lexer buffer, a
|
|
* new list is created from that and appended to any existing one.
|
|
*/
|
|
DEBUG((PAGRP,PALVL,"PS4: After fixup, n->rio = %04x",n->rio)) ;
|
|
|
|
if (IsData()) { /* If data remains in buffer */
|
|
|
|
DEBUG((PAGRP, PALVL, "PS4: Doing 2nd ParseRedir call.")) ;
|
|
|
|
GeToken(GT_NORMAL) ; /* Get token for ParseRedir... */
|
|
io = NULL ; /* ...zero the pointer & call */
|
|
if (ParseRedir(&io)) { /* If redir, then... */
|
|
if (tmpio = n->rio) { /* ...fix list */
|
|
while (tmpio->nxt)
|
|
tmpio = tmpio->nxt ;
|
|
tmpio->nxt = io ;
|
|
} else
|
|
n->rio = io ;
|
|
} else /* Else, if no redir... */
|
|
Lex(LX_UNGET,0) ; /* ...unget the token. */
|
|
} ;
|
|
|
|
#if DBG
|
|
if (io = n->rio) {
|
|
i = 0 ;
|
|
while (io) {
|
|
|
|
DEBUG((PAGRP,PALVL,"PS4: RD#%d - io is at %04x",i,io)) ;
|
|
DEBUG((PAGRP,PALVL,"PS4: RD#%d - io->rdhndl = %04x",i,io->rdhndl)) ;
|
|
DEBUG((PAGRP,PALVL,"PS4: RD#%d - io->fname = %ws",i,io->fname)) ;
|
|
DEBUG((PAGRP,PALVL,"PS4: RD#%d - io->flag = %d",i,io->flag)) ;
|
|
DEBUG((PAGRP,PALVL,"PS4: RD#%d - io->rdop = %c",i,io->rdop)) ;
|
|
DEBUG((PAGRP,PALVL,"PS4: RD#%d - io->nxt = %04x",i,io->nxt)) ;
|
|
io = io->nxt ;
|
|
++i ;
|
|
} ;
|
|
} ;
|
|
#endif
|
|
|
|
/* The pointer n->rio heads a single list of redirection structures with
|
|
* possible duplicates for a single handle. The code below eliminates
|
|
* any duplicates giving priority to the later of the two.
|
|
*/
|
|
if (tmpio = n->rio) {
|
|
while (tmpio) {
|
|
i = 1 << tmpio->rdhndl ;
|
|
if (flg & i) {
|
|
i = tmpio->rdhndl ;
|
|
tmpio = n->rio ;
|
|
while (tmpio) {
|
|
if (i == tmpio->rdhndl) {
|
|
if (tmpio == n->rio)
|
|
n->rio = tmpio->nxt ;
|
|
else
|
|
io->nxt = tmpio->nxt ;
|
|
flg = 0 ;
|
|
tmpio = n->rio ;
|
|
break ;
|
|
} ;
|
|
io = tmpio ;
|
|
tmpio = io->nxt ;
|
|
} ;
|
|
continue ;
|
|
} else
|
|
flg |= i ;
|
|
io = tmpio ;
|
|
tmpio = io->nxt ;
|
|
} ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PS4: Redir handles flag = %02x",flg)) ;
|
|
DEBUG((PAGRP, PALVL, "PS4: Redir list = %04x",n->rio)) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PS4: Exited")) ;
|
|
return(n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseS5 - parse production s5
|
|
*
|
|
* Purpose:
|
|
* Parse the s5 production.
|
|
*
|
|
* struct node *ParseS5()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseS5()
|
|
{
|
|
struct node *n ; /* Ptr to paren group node to build and fill */
|
|
|
|
DEBUG((PAGRP, PALVL, "PS5: Entered, TokTyp = %04x", TokTyp)) ;
|
|
|
|
/* M012 - Moved functionality for parsing FOR, IF and REM to
|
|
* ParseS5 from ParseStatement to give these four commands a
|
|
* lower precedence than the operators.
|
|
*/
|
|
if (TokTyp == TEXTOKEN) {
|
|
if ((_tcsicmp(ForStr, TokBuf) == 0) ||
|
|
(_tcsicmp(ForHelpStr, TokBuf) == 0))
|
|
return(ParseFor()) ;
|
|
|
|
else if ((_tcsicmp(IfStr, TokBuf) == 0) ||
|
|
(_tcsicmp(IfHelpStr, TokBuf) == 0))
|
|
return(ParseIf()) ;
|
|
|
|
/* M002 - Treat REM as unique command
|
|
*/
|
|
else if ((_tcsicmp(RemStr, TokBuf) == 0) ||
|
|
(_tcsicmp(RemHelpStr, TokBuf) == 0))
|
|
return(ParseRem()) ; /* ...parse seperate */
|
|
/* M002 ends */
|
|
|
|
else {
|
|
n = ParseCmd() ;
|
|
// if (_tcsicmp(ExtprocStr, ((struct cmdnode *)n)->cmdline) == 0) {
|
|
// n->type = EXTTYP ;
|
|
//
|
|
// DEBUG((PAGRP, PALVL, "PS5: Found EXTPROC type = %d", n->type)) ;
|
|
//} ;
|
|
return(n) ;
|
|
} ;
|
|
|
|
/* M015 - Added code to handle new SILTYP unary operator like left paren
|
|
*/
|
|
} else if (*TokBuf == LPOP || *TokBuf == SILOP) { /* M015 */
|
|
|
|
n = mknode() ;
|
|
|
|
if (n == NULL) {
|
|
Abort();
|
|
}
|
|
|
|
if (*TokBuf == LPOP) { /* M015 */
|
|
n->type = PARTYP ;
|
|
/* M004 - Strip leading newlines from the current paren group
|
|
*/
|
|
do {
|
|
GeToken(GT_NORMAL|GT_RPOP) ;
|
|
} while (*TokBuf == NLN) ;
|
|
Lex(LX_UNGET,0) ; /* M011 - Was UnGeToken */
|
|
/* M004 ends */
|
|
n->lhs = ParseStatement(PARTYP) ;
|
|
} else { /* M015 */
|
|
n->type = SILTYP ; /* M015 */
|
|
DEBUG((PAGRP,PALVL,"PS5: Silent mode starts")) ;
|
|
n->lhs = ParseStatement(0) ;
|
|
} ;
|
|
|
|
|
|
DEBUG((PAGRP, PALVL, "PS5: ParseStatement has returned.")) ;
|
|
|
|
if (n->type == SILTYP) /* M015 */
|
|
return(n) ; /* M015 */
|
|
/* M015 ends */
|
|
|
|
if (GeToken(GT_RPOP) == RPOP) { /* M000 - Was GT_NORMAL */
|
|
return(n) ;
|
|
} ;
|
|
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PS5: Error, no right paren. Token = `%ws'", TokBuf)) ;
|
|
PSError() ;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseCond - parse the condition production
|
|
*
|
|
* Purpose:
|
|
* Parse a CONDITION production.
|
|
*
|
|
* struct cmdnode *ParseCond(unsigned pcflag)
|
|
*
|
|
* Args:
|
|
* pcflag - nonzero if "NOT"s are not allowed because one has been found
|
|
* already
|
|
*
|
|
* Returns:
|
|
* A pointer to a parsed COND production or PARSERROR.
|
|
*
|
|
* Notes:
|
|
* The token after "errorlevel" is checked to make sure it is a number.
|
|
* If it isn't, a syntax error is generated.
|
|
*
|
|
* If a valid "NOT" is found, ParseCond() is called recursively to parse
|
|
* the rest of the condition. A pointer to the node is put in the
|
|
* argptr field of the node.
|
|
*
|
|
* M020 - "ERRORLEVEL=x" not being parsed correctly due to entire
|
|
* string being lexed as single token. Now Lex first as GT_NORMAL
|
|
* and if not ERRORLEVEL, re-Lex as GT_EQOK.
|
|
*
|
|
*/
|
|
|
|
struct cmdnode *ParseCond(pcflag)
|
|
unsigned pcflag ;
|
|
{
|
|
struct cmdnode *n ; /* Ptr to cond node to build and fill */
|
|
struct cmdnode *LoadNodeTC() ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PCOND: Entered.")) ;
|
|
|
|
|
|
if (GeToken(GT_NORMAL) != TEXTOKEN) /* M020 */
|
|
PSError() ;
|
|
n = LoadNodeTC(0) ;
|
|
|
|
if (_tcsicmp(ErrStr, TokBuf) == 0) { /* ERRORLEVEL */
|
|
n->type = ERRTYP ;
|
|
} else
|
|
if (_tcsicmp(ExsStr, TokBuf) == 0) /* EXIST */
|
|
n->type = EXSTYP ;
|
|
else
|
|
if (fEnableExtensions && _tcsicmp(CmdExtVerStr, TokBuf) == 0) /* CMDEXTVERSION */
|
|
n->type = CMDVERTYP ;
|
|
else
|
|
if (fEnableExtensions && _tcsicmp(DefinedStr, TokBuf) == 0) /* DEFINED */
|
|
n->type = DEFTYP ;
|
|
else
|
|
if (_tcsicmp(NotStr, TokBuf) == 0) { /* Not */
|
|
if (pcflag)
|
|
PSError() ;
|
|
n->type = NOTTYP ;
|
|
n->argptr = (TCHAR *) ParseCond(PC_NONOTS) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PCOND: Exited, type = 0x%02x", n->type)) ;
|
|
return(n);
|
|
|
|
} else {
|
|
Lex(LX_UNGET,0);
|
|
|
|
n->type = STRTYP ; /* String comparison */
|
|
ParseArgEqArg(n) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PCOND: Exited, type = 0x%02x", n->type)) ;
|
|
return(n);
|
|
} ;
|
|
|
|
/* Errorlevel, Exist, CmdExtVersion or Defined */
|
|
n->argptr = TokStr(GeTexTok(GT_NORMAL), NULL, TS_NOFLAGS) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PCOND: Exited, type = 0x%02x", n->type)) ;
|
|
|
|
return(n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseArgEqArg - parse IF statement string comparisons
|
|
*
|
|
* Purpose:
|
|
* Parse an if statement string comparison conditional.
|
|
* The comparison can be in the following formats:
|
|
* s1==s2 s1== s2
|
|
* s1 ==s2 s1 == s2
|
|
*
|
|
* ParseArgEqArg(struct cmdnode *n)
|
|
*
|
|
* Args:
|
|
* n - ptr to the conditional structure
|
|
*
|
|
* *** W A R N I N G ! ***
|
|
* THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
|
|
* THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
|
|
* CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
|
|
*
|
|
*/
|
|
|
|
void ParseArgEqArg(n)
|
|
struct cmdnode *n ;
|
|
{
|
|
|
|
//
|
|
// Get LHS of test
|
|
//
|
|
|
|
n->cmdline = GeTexTok( GT_NORMAL );
|
|
|
|
//
|
|
// Get Operator
|
|
//
|
|
|
|
if (GeToken( GT_EQOK ) != TEXTOKEN) {
|
|
PSError( );
|
|
}
|
|
|
|
//
|
|
// If it is a double equal, the arg ptr (RHS) is the next token
|
|
//
|
|
|
|
if (!_tcscmp( TokBuf, EQSTR)) {
|
|
n->argptr = GeTexTok( GT_NORMAL );
|
|
DEBUG((PAGRP, PALVL, "PARG: s1 == s2"));
|
|
}
|
|
|
|
//
|
|
// if it begins with a double equal, skip it and make the remainder
|
|
// of the string the RHS of the test
|
|
//
|
|
|
|
else if (TokLen >= 4 && TokBuf[0] == EQ && TokBuf[1] == EQ) {
|
|
n->argptr = gmkstr( (TokLen - 2) * sizeof( TCHAR ));
|
|
mystrcpy( n->argptr, TokBuf + 2 );
|
|
DEBUG((PAGRP, PALVL, "PARG: s1 ==s2"));
|
|
}
|
|
|
|
//
|
|
// We have something other than the ==. If extensions are enabled
|
|
// then we test for the extended comparisons
|
|
//
|
|
|
|
else if (fEnableExtensions) {
|
|
if (!_tcsicmp( TokBuf, TEXT( "EQU" )))
|
|
n->cmdarg = CMDNODE_ARG_IF_EQU;
|
|
else
|
|
if (!_tcsicmp( TokBuf, TEXT( "NEQ" )))
|
|
n->cmdarg = CMDNODE_ARG_IF_NEQ;
|
|
else
|
|
if (!_tcsicmp( TokBuf, TEXT( "LSS" )))
|
|
n->cmdarg = CMDNODE_ARG_IF_LSS;
|
|
else
|
|
if (!_tcsicmp( TokBuf, TEXT( "LEQ" )))
|
|
n->cmdarg = CMDNODE_ARG_IF_LEQ;
|
|
else
|
|
if (!_tcsicmp( TokBuf, TEXT( "GTR" )))
|
|
n->cmdarg = CMDNODE_ARG_IF_GTR;
|
|
else
|
|
if (!_tcsicmp( TokBuf, TEXT( "GEQ" )))
|
|
n->cmdarg = CMDNODE_ARG_IF_GEQ;
|
|
else
|
|
PSError( );
|
|
n->type = CMPTYP;
|
|
n->argptr = GeTexTok( GT_NORMAL );
|
|
|
|
} else {
|
|
PSError( );
|
|
}
|
|
DEBUG((PAGRP, PALVL, "PARG: s1 = `%ws' s2 = `%ws'", n->cmdline, n->argptr));
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseCmd - parse production cmd
|
|
*
|
|
* Purpose:
|
|
* Parse a command
|
|
*
|
|
* struct node *ParseCmd()
|
|
*
|
|
* Returns:
|
|
* A pointer to the production just parsed or PARSERROR.
|
|
*
|
|
* Notes:
|
|
* The code below has been completely rewritten to allow the parsing
|
|
* of redirection strings to occur anywhere within the command line.
|
|
* ParseS4() will catch redirection prior to the command name.
|
|
* Redirection between the name and argument or within the argument
|
|
* will be caught by the code below which checks for those operators
|
|
* during the building of the arguments.
|
|
* ParseS4() will then catch any occurring after the command argument.
|
|
*
|
|
* *** W A R N I N G ! ***
|
|
* THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
|
|
* THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
|
|
* CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
|
|
*
|
|
* M013 - The redirection portion of this routine has been rewritten
|
|
* to conform to the new methods of parsing redirection.
|
|
*
|
|
*/
|
|
|
|
struct node *ParseCmd()
|
|
{
|
|
struct cmdnode *n ; /* Ptr to node to build/fill */
|
|
TCHAR *tptr ; /* M003 - Pointer temp */
|
|
struct relem *io = NULL ; /* M013 - Redir list pointer */
|
|
struct relem **tmpio = &io ; /* M013 - ptr to ptr to redir list */
|
|
|
|
DEBUG((PAGRP, PALVL, "PCMD: Entered.")) ;
|
|
|
|
n = LoadNodeTC(CMDTYP) ;
|
|
|
|
/* M003 - The following section has been completely rewritten
|
|
*/
|
|
while (IsData()) { /* M011 - Was Peek() */
|
|
if (GeToken(GT_ARGSTR) == TEXTOKEN) {
|
|
/*WARNING*/ tptr = gmkstr((mystrlen(n->argptr)+TokLen)*sizeof(TCHAR)) ;
|
|
mystrcpy(tptr, n->argptr) ;
|
|
mystrcat(tptr, TokBuf) ;
|
|
n->argptr = tptr ;
|
|
DEBUG((PAGRP, PALVL, "PCMD: args = `%ws'", n->argptr)) ;
|
|
|
|
/* M013 - If not text, the current token must be tested as possible
|
|
* redirection. Note that tmpio is a pointer to a pointer to a
|
|
* structure. It first points to the head-of-list pointer, but
|
|
* after each successful call to ParseRedir, it is advanced to
|
|
* point to the 'nxt' pointer field in the last list element.
|
|
*/
|
|
} else if (ParseRedir(tmpio)) {
|
|
|
|
DEBUG((PAGRP,PALVL,"PCMD: Found redir")) ;
|
|
|
|
do {
|
|
tmpio = &(*tmpio)->nxt ;
|
|
} while (*tmpio) ;
|
|
/* M013 ends */
|
|
|
|
/* If this is neither a text token (part of the argument) or a redirection
|
|
* sequence, then this must be an operator and must be 'ungot' for the next
|
|
* parsing sequence.
|
|
*/
|
|
} else {
|
|
DEBUG((PAGRP,PALVL,"PCMD: Found `%ws'", TokBuf)) ;
|
|
Lex(LX_UNGET,0) ; /* M011 - Was UnGeToken() */
|
|
break ;
|
|
}
|
|
} ;
|
|
|
|
/* M013 - Once the command is fully parsed and all mixed redirection
|
|
* identified, the list is placed in the node pointer to pass
|
|
* it back.
|
|
*/
|
|
DEBUG((PAGRP,PALVL,"PCMD: Redirlist = %04x", io)) ;
|
|
|
|
n->rio = io ;
|
|
|
|
/* M003/M013 ends */
|
|
|
|
DEBUG((PAGRP, PALVL, "PCMD: Exited.")) ;
|
|
return((struct node *) n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** ParseRedir - controls I/O redirection parsing
|
|
*
|
|
* Purpose:
|
|
* Parse the redir production.
|
|
*
|
|
* int ParseRedir(struct relem **io)
|
|
*
|
|
* Args:
|
|
* io - This is a pointer to the head-of-list pointer to the list
|
|
* of redirection elements being built. On entry this MUST be NULL.
|
|
*
|
|
* Returns:
|
|
* TRUE if redirection was found.
|
|
* FALSE if redirection wasn't found.
|
|
*
|
|
* Notes:
|
|
* M013 - This routine has been completely rewritten to conform to
|
|
* new structures and methods of redirection parsing. With these
|
|
* changes, redirection may now begin with ">", "<", "n>" or "n<"
|
|
* where n is a handle number. This function now loops through
|
|
* lex'd input, parsing as many redirection instructions as exist
|
|
* in sequence, building a new structure for each one and linking
|
|
* it into a list of such structures. Like XENIX we require the
|
|
* sequences 'n>' and 'n>&' to exist without separating whitespace
|
|
* although that sequence and any filename or trailing digit may be
|
|
* separated by the normal set of delimiters. The XENIX << operator
|
|
* is lex'd correctly but currently restricted from use.
|
|
* M014 - Input redirection may now exist for handles other than 0.
|
|
*/
|
|
|
|
int ParseRedir(io)
|
|
struct relem **io ; /* M013 - Ptr to ptr to redir elem */
|
|
{
|
|
TCHAR rdop ; /* M013 - Type of operation */
|
|
int didflg = 0, /* M013 - Loop count */
|
|
getcnt = 0 ; /* M013 - GeToken count */
|
|
TCHAR *i ; /* M013 - General ptr temp */
|
|
|
|
DEBUG((PAGRP, PALVL, "PREDIR: Entered, token = `%ws'", TokBuf)) ;
|
|
|
|
/* M013 - If the 'while' conditional succeeds, rdop will equal the type of
|
|
* redirection being parsed. A structure is malloc'd for *io and filled
|
|
* with the redirection information. If more redirection is found, io
|
|
* becomes the address of the 'nxt' field in *io, and the cycle is repeated
|
|
* until the 1st non-redirection token is found.
|
|
* Error checking for "<<" and for handle substitution operators '&' without
|
|
* matching digits is done here, returning syntax errors when found.
|
|
*/
|
|
while ((rdop = *TokBuf) == INOP || rdop == OUTOP ||
|
|
(_istdigit(*TokBuf) &&
|
|
((rdop = *(TokBuf+1)) == INOP || rdop == OUTOP))) {
|
|
|
|
if (!(*io = (struct relem *)mkstr(sizeof(struct relem)))) {
|
|
|
|
PutStdErr(ERROR_NOT_ENOUGH_MEMORY, NOARGS); /* M021 */
|
|
PError() ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP,PALVL,"PREDIR: List element made.")) ;
|
|
|
|
++didflg ; /* Set == found at least one */
|
|
|
|
i = TokBuf ;
|
|
|
|
(*io)->rdop = rdop ; /* M014 - New field for type */
|
|
|
|
if (_istdigit(*i)) {
|
|
(*io)->rdhndl = *i - TEXT('0');
|
|
++i ;
|
|
|
|
DEBUG((PAGRP,PALVL,"PREDIR: Specific-handle Redir.")) ;
|
|
|
|
} else {
|
|
if (rdop == OUTOP)
|
|
(*io)->rdhndl = STDOUT;
|
|
else
|
|
(*io)->rdhndl = STDIN ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP,PALVL,"PREDIR:Redir handle %d...",(*io)->rdhndl)) ;
|
|
DEBUG((PAGRP,PALVL,(rdop == INOP)? "PREDIR:...for input" :
|
|
"PREDIR:...for output")) ;
|
|
|
|
if (*i == *(i+1)) {
|
|
if (rdop == INOP) /* M013 - Disallow '<<' */
|
|
PSError() ;
|
|
|
|
(*io)->flag = 1 ;
|
|
++i ;
|
|
} ;
|
|
|
|
++i ;
|
|
|
|
if (*i == CSOP) {
|
|
if (mystrlen(i) == 2 && _istdigit(*(i+1)) &&
|
|
((*io)->fname = mkstr(3*sizeof(TCHAR))))
|
|
mystrcpy((*io)->fname, i) ;
|
|
else
|
|
PSError() ;
|
|
} else
|
|
(*io)->fname = GeTexTok(GT_NORMAL) ;
|
|
|
|
DEBUG((PAGRP,PALVL,"PREDIR: RD fname = `%ws'.",(*io)->fname)) ;
|
|
|
|
if (IsData()) {
|
|
GeToken(GT_NORMAL) ;
|
|
io = &((*io)->nxt) ;
|
|
++getcnt ;
|
|
} else
|
|
break ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "PREDIR: Exited current token = `%ws'", TokBuf)) ;
|
|
|
|
/* M013 - Note that this function is called with a valid token in the
|
|
* token buffer. If the routine returns FALSE, that token must
|
|
* still be valid. If it returns TRUE, there must not be a valid
|
|
* token in the buffer. This is complicated by the fact that at
|
|
* return time, there will either be a valid non-redirection token
|
|
* in the buffer, or there will be no valid token because none were
|
|
* available. To determine whether to do an unget, we keep counts
|
|
* of tokens read (minus the one passed at call time) and tokens
|
|
* used. If they are equal, we unget the last one; if not, then
|
|
* no data was available.
|
|
*/
|
|
|
|
if (didflg) {
|
|
if (getcnt == didflg)
|
|
Lex(LX_UNGET,0) ;
|
|
return(TRUE) ;
|
|
} else
|
|
return(FALSE) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** BinaryOperator - parse binary operators
|
|
*
|
|
* Purpose:
|
|
* Parse a production which contains a binary operator. Parse the left
|
|
* side of the operator. If the next token is the operator we are looking
|
|
* for, build a node for it, call the operator's production parsing
|
|
* routine to parse the right side of the operator, put all the pieces
|
|
* together.
|
|
*
|
|
* struct node *BinaryOperator(TCHAR *opstr, int optype,
|
|
* struct node *opprodfunc(), struct node *leftprodfunc())
|
|
*
|
|
* Args:
|
|
* opstr - string representation of the operator to look for
|
|
* optype - type of operator
|
|
* opprodfunc - the function which parses this operator
|
|
* leftprodfunc - the function which parses the left side of the operator
|
|
*
|
|
* Returns:
|
|
* If the node is found, a pointer to the node for the operator.
|
|
* Otherwise, a pointer to the "left side" of the oprator.
|
|
*
|
|
*/
|
|
|
|
struct node *BinaryOperator(opstr, optype, opprodfunc, leftprodfunc)
|
|
TCHAR *opstr ;
|
|
int optype ;
|
|
PPARSE_ROUTINE opprodfunc ;
|
|
PPARSE_ROUTINE leftprodfunc ;
|
|
{
|
|
struct node *n ; /* Ptr to binop node to build and fill */
|
|
struct node *leftside ; /* Ptr to the leftside of the binop */
|
|
|
|
DEBUG((PAGRP, PALVL, "BINOP: op = %ws", opstr)) ;
|
|
|
|
leftside = (*leftprodfunc)() ;
|
|
|
|
/* M011 - Ref to IsData() was ref to Peek()
|
|
*/
|
|
if (IsData()) /* If data left, read token - else, return */
|
|
GeToken(GT_NORMAL) ;
|
|
else {
|
|
DEBUG((PAGRP, PALVL, "BINOP: No more data, return lh side.")) ;
|
|
return(leftside) ;
|
|
} ;
|
|
|
|
/* This conditional tests for two cases; a true occurance of the binary op
|
|
* being sought, or the occurance of a newline acting as a command seperator
|
|
* within a parenthetical statement.
|
|
*/
|
|
if (_tcscmp(opstr, TokBuf) == 0 ||
|
|
(StatementType[StatementDepth-1] == PARTYP &&
|
|
_tcscmp(opstr, CSSTR) == 0 &&
|
|
*TokBuf == NLN
|
|
)
|
|
) {
|
|
|
|
/* M004 - This functionality was moved here from ParseS0. It handles the
|
|
* stripping of newlines that occur following the first production
|
|
* in a parenthetical operation. And will eliminate the newline's
|
|
* acting as a command seperator when the right side production is
|
|
* only the terminating right paren.
|
|
* M010 - Added fix for problem of token after newline being eaten if
|
|
* inside a parenthetical statment.
|
|
*/
|
|
if (*TokBuf == NLN) { /* Only TRUE if inside () */
|
|
do {
|
|
GeToken(GT_NORMAL) ;
|
|
} while (*TokBuf == NLN) ;
|
|
Lex(LX_UNGET,0) ; /* M011 - Was UnGeToken() */
|
|
if (*TokBuf == RPOP) {
|
|
DEBUG((PAGRP, PALVL, "BINOP: Ungetting right paren.")) ;
|
|
return(leftside) ; /* Return left only */
|
|
}
|
|
optype = LFTYP;
|
|
} ;
|
|
/* M004/M010 ends */
|
|
|
|
DEBUG((PAGRP, PALVL, "BINOP: Found %ws", opstr)) ;
|
|
n = mknode() ;
|
|
|
|
if (n == NULL) {
|
|
Abort();
|
|
}
|
|
|
|
n->type = optype ;
|
|
n->lhs = leftside ;
|
|
AtIsToken = 1; /* @@4 */
|
|
GeToken(GT_LPOP) ; /* M000 - Was GT_NORMAL */
|
|
AtIsToken = 0; /* @@4 */
|
|
n->rhs = (*opprodfunc)() ;
|
|
DEBUG((PAGRP, PALVL, "BINOP: Exiting op = %ws", opstr)) ;
|
|
return(n) ;
|
|
|
|
} else {
|
|
Lex(LX_UNGET,0) ; /* M011 - Was UnGeToken() */
|
|
DEBUG((PAGRP, PALVL, "BINOP: Did NOT find %ws", opstr)) ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "BINOP: Exiting op = %ws", opstr)) ;
|
|
return(leftside) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** BuildArgList - parse FOR statement argument list
|
|
*
|
|
* Purpose:
|
|
* Build a FOR statement's argument list. Collapse it into the following
|
|
* form "a0 a1 a2 a3...".
|
|
*
|
|
* TCHAR *BuildArgList()
|
|
*
|
|
* Returns:
|
|
* A pointer to the argument list.
|
|
*/
|
|
|
|
TCHAR *BuildArgList()
|
|
{
|
|
TCHAR *args = NULL ; /* Ptr to squeezed FOR arg list */
|
|
int arglen = 0 ; /* Length of current arg */
|
|
int done = 0 ; /* Flag, nonzero if done */
|
|
|
|
DEBUG((PAGRP, PALVL, "BARGL: Entered.")) ;
|
|
if(GeToken(GT_LPOP) != LPOP)
|
|
PSError() ;
|
|
|
|
for ( ; !done ; ) {
|
|
switch (GeToken(GT_RPOP)) { /* M000 - Was GT_NORMAL */
|
|
case TEXTOKEN: /* Another arg was found, add it to the list */
|
|
arglen += TokLen ;
|
|
if (args) {
|
|
args = resize(args, arglen*sizeof(TCHAR)) ;
|
|
SpaceCat(args, args, TokBuf) ;
|
|
} else {
|
|
args = gmkstr(arglen*sizeof(TCHAR)) ; /*WARNING*/
|
|
mystrcpy(args, TokBuf) ;
|
|
} ;
|
|
|
|
DEBUG((PAGRP, PALVL, "BARGL: Current args = %ws", args)) ;
|
|
break ;
|
|
|
|
case NLN: /* Skip newlines */
|
|
continue ;
|
|
|
|
default: /* If anything else, we're done */
|
|
done = TRUE ;
|
|
break ;
|
|
} ;
|
|
} ;
|
|
|
|
/* When the loop exits, the current token should be a right paren */
|
|
if (TokTyp == RPOP) {
|
|
DEBUG((PAGRP, PALVL, "BARGL: Exiting, args = %ws", args)) ;
|
|
return(args) ;
|
|
} ;
|
|
|
|
PSError() ;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*** GetCheckStr - get and check a token
|
|
*
|
|
* Purpose:
|
|
* Get a token and compare it against the string passed. If they don't
|
|
* match, call PSError().
|
|
*
|
|
* GetCheckStr(TCHAR *str)
|
|
*
|
|
* Args:
|
|
* str - the string to compare with the token
|
|
*
|
|
*/
|
|
|
|
void GetCheckStr(str)
|
|
TCHAR *str ;
|
|
{
|
|
GeToken(GT_NORMAL) ;
|
|
|
|
if (_tcsicmp(str, TokBuf) != 0)
|
|
PSError() ;
|
|
|
|
DEBUG((PAGRP, PALVL, "GETCS: Exiting.")) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** GeTexTok - get a text token
|
|
*
|
|
* Purpose:
|
|
* Get the next text token, allocate a string for it and copy it into the
|
|
* string.
|
|
*
|
|
* TCHAR *GeTexTok(unsigned gtflag)
|
|
*
|
|
* Args:
|
|
* gtflag - flag to be passed to GeToken()
|
|
*
|
|
* Returns:
|
|
* A pointer to the string.
|
|
*
|
|
* *** W A R N I N G ! ***
|
|
* THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
|
|
* THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
|
|
* CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
|
|
*
|
|
*/
|
|
|
|
TCHAR *GeTexTok(gtflag)
|
|
unsigned gtflag ;
|
|
{
|
|
TCHAR *s ; /* Ptr to the text token */
|
|
|
|
if (GeToken(gtflag) != TEXTOKEN)
|
|
PSError() ;
|
|
|
|
s = gmkstr(TokLen*sizeof(TCHAR)) ; /*WARNING*/
|
|
mystrcpy(s, TokBuf) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "GETT: Exiting.")) ;
|
|
return(s) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** GeToken - get a token
|
|
*
|
|
* Purpose:
|
|
* If there is a previous token, make it the current token. Otherwise,
|
|
* call the lexer to get another token and make it the current token.
|
|
*
|
|
* unsigned GeToken(unsigned flag)
|
|
*
|
|
* Args:
|
|
* flag - passed to lexer to tell it to get an argstring or regular token
|
|
*
|
|
* Returns:
|
|
* The type of the current token.
|
|
*
|
|
*/
|
|
|
|
unsigned GeToken(flag)
|
|
unsigned flag ;
|
|
{
|
|
unsigned Lex() ;
|
|
|
|
if(PendingParens != 0)
|
|
flag = flag | GT_RPOP ;
|
|
if ((TokTyp = Lex((TCHAR *)TokBuf, (unsigned)flag)) == (unsigned )LEXERROR)
|
|
PError() ;
|
|
|
|
TokLen = mystrlen(TokBuf)+1 ;
|
|
|
|
DEBUG((PAGRP, PALVL, " GET: type = 0x%04x token = `%ws' toklen = %d", TokTyp, TokBuf, TokLen)) ;
|
|
if (fDumpTokens)
|
|
cmd_printf( TEXT("GeToken: (%x) '%s'\n"), TokTyp, TokBuf);
|
|
return(TokTyp) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** LoadNodeTC - make and load a command node
|
|
*
|
|
* Purpose:
|
|
* Make a command node and load its type field with the argument and
|
|
* its cmdline field with the current token.
|
|
*
|
|
* struct cmdnode *LoadNodeTC(int type)
|
|
*
|
|
* Args:
|
|
* type - the type of command
|
|
*
|
|
* Returns:
|
|
* A pointer to the node that was made.
|
|
*
|
|
* *** W A R N I N G ! ***
|
|
* THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
|
|
* THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
|
|
* CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
|
|
*
|
|
*/
|
|
|
|
struct cmdnode *LoadNodeTC(type)
|
|
int type ;
|
|
{
|
|
struct cmdnode *n ; /* Ptr to the cmdnode to build and fill */
|
|
|
|
n = (struct cmdnode *) mknode() ;
|
|
|
|
if (n == NULL) {
|
|
Abort();
|
|
}
|
|
|
|
n->type = type ;
|
|
n->flag = 0 ;
|
|
n->cmdline = gmkstr(TokLen*sizeof(TCHAR)) ; /*WARNING*/
|
|
mystrcpy(n->cmdline, TokBuf) ;
|
|
|
|
DEBUG((PAGRP, PALVL, "LOAD: type = %04x", type)) ;
|
|
return(n) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** PError - handle parser error
|
|
*
|
|
* Purpose:
|
|
* Parser via longjmp().
|
|
*
|
|
* PError()
|
|
*
|
|
* Returns:
|
|
* PARSERROR
|
|
*
|
|
*/
|
|
|
|
void PError()
|
|
{
|
|
global_dfvalue = MSG_SYNERR_GENL; /* @@J1 PSError, not jump in parser*/
|
|
longjmp(CmdJBuf1, PARSERROR) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** PSError - print error message and handle parser error
|
|
*
|
|
* Purpose:
|
|
* Print the parser syntax error message and return to Parser via
|
|
* longjmp().
|
|
*
|
|
* PSError()
|
|
*
|
|
* Returns:
|
|
* PARSERROR
|
|
*
|
|
* Notes:
|
|
* M021 - Unfragmented Syntax error messages and revised function.
|
|
*
|
|
*/
|
|
|
|
void PSError( )
|
|
{
|
|
/*@@4*/ unsigned do_jmp;
|
|
|
|
/*@@4*/ do_jmp = global_dfvalue != MSG_SYNERR_GENL;
|
|
|
|
/*@@4*/ if ( global_dfvalue == READFILE )
|
|
{
|
|
/*@@4*/ global_dfvalue = MSG_SYNERR_GENL;
|
|
}
|
|
else
|
|
{
|
|
if (*TokBuf == NLN)
|
|
{
|
|
PutStdErr(MSG_BAD_SYNTAX, NOARGS) ;
|
|
}
|
|
else
|
|
{
|
|
/*@@4*/ if (*TokBuf != NULLC) /* @@J1 if no data wrong then */
|
|
/*@@4*/ { /* @@J1 do not give message */
|
|
PutStdErr(MSG_SYNERR_GENL, ONEARG, TokBuf );
|
|
/*@@4*/ } /* @@J1 */
|
|
}
|
|
}
|
|
/*@@4*/ if ( do_jmp ) {
|
|
longjmp(CmdJBuf1, PARSERROR) ;
|
|
/*@@4*/ }
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** SpaceCat - concatenate 2 strings and delimit the strings with a space
|
|
*
|
|
* Purpose:
|
|
* Copy src1 to dst. Then concatenate a space and src2 to the end of
|
|
* dst.
|
|
*
|
|
* SpaceCat(TCHAR *dst, TCHAR *src1, TCHAR *src2)
|
|
*
|
|
* Args:
|
|
* See above
|
|
*
|
|
*/
|
|
|
|
void SpaceCat(dst, src1, src2)
|
|
TCHAR *dst,
|
|
*src1,
|
|
*src2 ;
|
|
{
|
|
mystrcpy(dst, src1) ;
|
|
mystrcat(dst, TEXT(" ")) ;
|
|
mystrcat(dst, src2) ;
|
|
}
|