3 #define C_OP_LOWEST C_MULTI
4 #define C_OP_HIGHEST C_PIPE
5 static const TCHAR OpString
[][3] = { _T("&"), _T("||"), _T("&&"), _T("|") };
7 static const TCHAR RedirString
[][3] = { _T("<"), _T(">"), _T(">>") };
9 static BOOL
IsSeparator(TCHAR Char
)
11 /* These three characters act like spaces to the parser */
12 return _istspace(Char
) || (Char
&& _tcschr(_T(",;="), Char
));
15 enum { TOK_END
, TOK_NORMAL
, TOK_OPERATOR
, TOK_REDIRECTION
,
16 TOK_BEGIN_BLOCK
, TOK_END_BLOCK
};
18 static BOOL bParseError
;
19 static BOOL bLineContinuations
;
20 static TCHAR ParseLine
[CMDLINE_LENGTH
];
21 static TCHAR
*ParsePos
;
24 static TCHAR CurrentToken
[CMDLINE_LENGTH
];
25 static int CurrentTokenType
;
26 static int InsideBlock
;
28 static TCHAR
ParseChar()
36 /* Although CRs can be injected into a line via an environment
37 * variable substitution, the parser ignores them - they won't
38 * even separate tokens. */
41 while (Char
== _T('\r'));
46 if (bLineContinuations
)
48 if (!ReadLine(ParseLine
, TRUE
))
50 /* ^C pressed, or line was too long */
53 else if (*(ParsePos
= ParseLine
))
59 return CurChar
= Char
;
62 static void ParseError()
64 if (CurrentTokenType
== TOK_END
)
65 ConOutResPuts(STRING_SYNTAX_COMMAND_INCORRECT
);
67 ConOutPrintf(_T("%s was unexpected at this time.\n"), CurrentToken
);
71 /* Yes, cmd has a Lexical Analyzer. Whenever the parser gives an "xxx was
72 * unexpected at this time." message, it shows what the last token read was */
73 static int ParseToken(TCHAR ExtraEnd
, BOOL PreserveSpace
)
75 TCHAR
*Out
= CurrentToken
;
78 BOOL bInQuote
= FALSE
;
82 while (Char
!= _T('\n') && IsSeparator(Char
))
86 while (Char
&& Char
!= _T('\n'))
88 bInQuote
^= (Char
== _T('"'));
91 /* Check for all the myriad ways in which this token
92 * may be brought to an untimely end. */
93 if ((Char
>= _T('0') && Char
<= _T('9') &&
94 (ParsePos
== &ParseLine
[1] || IsSeparator(ParsePos
[-2]))
95 && (*ParsePos
== _T('<') || *ParsePos
== _T('>')))
96 || _tcschr(_T(")&|<>") + (InsideBlock
? 0 : 1), Char
)
97 || (!PreserveSpace
&& IsSeparator(Char
))
98 || (Char
== ExtraEnd
))
106 /* Eat up a \n, allowing line continuation */
107 if (Char
== _T('\n'))
109 /* Next character is a forced literal */
112 if (Out
== &CurrentToken
[CMDLINE_LENGTH
- 1])
118 /* Check if we got at least one character before reaching a special one.
119 * If so, return them and leave the special for the next call. */
120 if (Out
!= CurrentToken
)
124 else if (Char
== _T('('))
126 Type
= TOK_BEGIN_BLOCK
;
130 else if (Char
== _T(')'))
132 Type
= TOK_END_BLOCK
;
136 else if (Char
== _T('&') || Char
== _T('|'))
141 /* check for && or || */
148 else if ((Char
>= _T('0') && Char
<= _T('9'))
149 || (Char
== _T('<') || Char
== _T('>')))
151 Type
= TOK_REDIRECTION
;
152 if (Char
>= _T('0') && Char
<= _T('9'))
161 /* Strangely, the tokenizer allows << as well as >>... (it
162 * will cause an error when trying to parse it though) */
169 while (IsSeparator(Char
= ParseChar()))
171 if (Char
>= _T('0') && Char
<= _T('9'))
183 return CurrentTokenType
= Type
;
186 static BOOL
ParseRedirection(REDIRECTION
**List
)
188 TCHAR
*Tok
= CurrentToken
;
193 if (*Tok
>= _T('0') && *Tok
<= _T('9'))
194 Number
= *Tok
++ - _T('0');
196 Number
= *Tok
== _T('<') ? 0 : 1;
198 if (*Tok
++ == _T('<'))
200 RedirType
= REDIR_READ
;
206 RedirType
= REDIR_WRITE
;
209 RedirType
= REDIR_APPEND
;
216 /* The file name was not part of this token, so it'll be the next one */
217 if (ParseToken(0, FALSE
) != TOK_NORMAL
)
222 /* If a redirection for this handle number already exists, delete it */
223 while ((Redir
= *List
))
225 if (Redir
->Number
== Number
)
234 Redir
= cmd_alloc(FIELD_OFFSET(REDIRECTION
, Filename
[_tcslen(Tok
) + 1]));
236 Redir
->OldHandle
= INVALID_HANDLE_VALUE
;
237 Redir
->Number
= Number
;
238 Redir
->Type
= RedirType
;
239 _tcscpy(Redir
->Filename
, Tok
);
244 FreeRedirection(*List
);
249 static PARSED_COMMAND
*ParseCommandOp(int OpType
);
251 /* Parse a parenthesized block */
252 static PARSED_COMMAND
*ParseBlock(REDIRECTION
*RedirList
)
254 PARSED_COMMAND
*Cmd
, *Sub
, **NextPtr
;
255 Cmd
= cmd_alloc(sizeof(PARSED_COMMAND
));
258 Cmd
->Subcommands
= NULL
;
259 Cmd
->Redirections
= RedirList
;
261 /* Read the block contents */
262 NextPtr
= &Cmd
->Subcommands
;
266 Sub
= ParseCommandOp(C_OP_LOWEST
);
270 NextPtr
= &Sub
->Next
;
272 else if (bParseError
)
279 if (CurrentTokenType
== TOK_END_BLOCK
)
281 /* Skip past the \n */
286 /* Process any trailing redirections */
287 while (ParseToken(0, FALSE
) == TOK_REDIRECTION
)
289 if (!ParseRedirection(&Cmd
->Redirections
))
298 static PARSED_COMMAND
*ParseCommandPart(void)
300 TCHAR ParsedLine
[CMDLINE_LENGTH
];
304 REDIRECTION
*RedirList
= NULL
;
307 while (IsSeparator(CurChar
))
309 if (CurChar
== _T('\n'))
317 if (CurChar
== _T(':'))
319 /* "Ignore" the rest of the line.
320 * (Line continuations will still be parsed, though.) */
321 while (ParseToken(0, TRUE
) != TOK_END
)
326 if (CurChar
== _T('@'))
329 Cmd
= cmd_alloc(sizeof(PARSED_COMMAND
));
332 /* @ acts like a unary operator with low precedence,
333 * so call the top-level parser */
334 Cmd
->Subcommands
= ParseCommandOp(C_OP_LOWEST
);
335 Cmd
->Redirections
= NULL
;
339 /* Get the head of the command */
342 Type
= ParseToken(_T('('), FALSE
);
343 if (Type
== TOK_NORMAL
)
345 Pos
= _stpcpy(ParsedLine
, CurrentToken
);
348 else if (Type
== TOK_REDIRECTION
)
350 if (!ParseRedirection(&RedirList
))
353 else if (Type
== TOK_BEGIN_BLOCK
)
355 return ParseBlock(RedirList
);
357 else if (Type
== TOK_END_BLOCK
&& !RedirList
)
364 FreeRedirection(RedirList
);
368 TailOffset
= Pos
- ParsedLine
;
370 /* FIXME: FOR, IF, and REM need special processing by the parser. */
372 /* Now get the tail */
375 Type
= ParseToken(0, TRUE
);
376 if (Type
== TOK_NORMAL
)
378 if (Pos
+ _tcslen(CurrentToken
) >= &ParsedLine
[CMDLINE_LENGTH
])
381 FreeRedirection(RedirList
);
384 Pos
= _stpcpy(Pos
, CurrentToken
);
386 else if (Type
== TOK_REDIRECTION
)
388 if (!ParseRedirection(&RedirList
))
397 Cmd
= cmd_alloc(FIELD_OFFSET(PARSED_COMMAND
, CommandLine
[Pos
+ 1 - ParsedLine
]));
398 Cmd
->Type
= C_COMMAND
;
400 Cmd
->Subcommands
= NULL
;
401 Cmd
->Redirections
= RedirList
;
402 _tcscpy(Cmd
->CommandLine
, ParsedLine
);
403 Cmd
->Tail
= Cmd
->CommandLine
+ TailOffset
;
407 static PARSED_COMMAND
*ParseCommandOp(int OpType
)
409 PARSED_COMMAND
*Cmd
, *Left
, *Right
;
411 if (OpType
== C_OP_HIGHEST
)
412 Cmd
= ParseCommandPart();
414 Cmd
= ParseCommandOp(OpType
+ 1);
416 if (Cmd
&& !_tcscmp(CurrentToken
, OpString
[OpType
- C_OP_LOWEST
]))
419 Right
= ParseCommandOp(OpType
);
424 /* & is allowed to have an empty RHS */
425 if (OpType
== C_MULTI
)
433 Cmd
= cmd_alloc(sizeof(PARSED_COMMAND
));
436 Cmd
->Redirections
= NULL
;
437 Cmd
->Subcommands
= Left
;
446 ParseCommand(LPTSTR Line
)
452 _tcscpy(ParseLine
, Line
);
453 bLineContinuations
= FALSE
;
457 if (!ReadLine(ParseLine
, FALSE
))
459 bLineContinuations
= TRUE
;
462 ParsePos
= ParseLine
;
465 Cmd
= ParseCommandOp(C_OP_LOWEST
);
468 if (CurrentTokenType
!= TOK_END
)
479 /* Reconstruct a parse tree into text form;
480 * used for echoing batch file commands */
482 EchoCommand(PARSED_COMMAND
*Cmd
)
490 ConOutPrintf(_T("%s"), Cmd
->CommandLine
);
496 for (Sub
= Cmd
->Subcommands
; Sub
; Sub
= Sub
->Next
)
499 ConOutChar(_T('\n'));
507 Sub
= Cmd
->Subcommands
;
509 ConOutPrintf(_T(" %s "), OpString
[Cmd
->Type
- C_OP_LOWEST
]);
510 EchoCommand(Sub
->Next
);
514 for (Redir
= Cmd
->Redirections
; Redir
; Redir
= Redir
->Next
)
516 ConOutPrintf(_T(" %c%s%s"), _T('0') + Redir
->Number
,
517 RedirString
[Redir
->Type
], Redir
->Filename
);
522 FreeCommand(PARSED_COMMAND
*Cmd
)
524 if (Cmd
->Subcommands
)
525 FreeCommand(Cmd
->Subcommands
);
527 FreeCommand(Cmd
->Next
);
528 FreeRedirection(Cmd
->Redirections
);