opSys-shell/shell.c

251 lines
6.7 KiB
C

#include <stdbool.h>
#include <string.h>
#include "scanner.h"
/**
* The function _acceptToken checks whether the current token matches a target identifier.
* if this is the case, it will mark this token as the given Type, then move to the next token.
* @param lp List pointer to the start of the tokenlist.
* @param ident target identifier
* @param type enum describing the nature to assign if the token is accepted.
* @return a bool denoting whether the current token matches the target identifier.
*/
bool _acceptToken(List *lp, char *ident, enum Type type) {
if (*lp != NULL && strcmp(((*lp)->t), ident) == 0) {
(*lp)->type = type;
*lp = (*lp)->next;
return true;
}
return false;
}
/**
* The function parseExecutable parses an executable.
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the executable was parsed successfully.
*/
bool _parseExecutable(List *lp) {
char s = (*lp)->t[0];
// Right now we only check if the first character is illegal (to prevent an operator token from mistakenly being assigned a COMMAND type.)
switch (s) {
case '|':
case '&':
case ';':
case '\n':
case '>':
case '<':
return false;
}
(*lp)->type = EXECUTABLE;
*lp = (*lp)->next;
return true;
}
/**
* Checks whether the input string \param s is an operator.
* @param s input string.
* @return a bool denoting whether the current string is an operator.
*/
//bool isOperator(char *s) {
bool _isOperator(List *lp) {
// NULL-terminated array makes it easy to expand this array later
// without changing the code at other places.
char *operators[] = {
"&",
"&&",
"||",
";",
"<",
">",
"|",
NULL
};
for (int i = 0; operators[i] != NULL; i++) {
if (strcmp((*lp)->t, operators[i]) == 0) {
return true;
}
}
return false;
}
/**
* The function parseOptions parses options.
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the options were parsed successfully.
*/
bool _parseOptions(List *lp) {
while (*lp != NULL && !_isOperator(lp)) {
(*lp)->type = OPTION;
(*lp) = (*lp)->next;
}
return true;
}
/**
* The function parseRedirections parses a command according to the grammar:
*
* <command> ::= <executable> <options>
*
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the command was parsed successfully.
*/
bool _parseCommand(List *lp) {
return _parseExecutable(lp) && _parseOptions(lp);
}
/**
* The function parsePipeline parses a pipeline according to the grammar:
*
* <pipeline> ::= <command> "|" <pipeline>
* | <command>
*
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the pipeline was parsed successfully.
*/
bool _parsePipeline(List *lp) {
if (!_parseCommand(lp)) {
return false;
}
if (_acceptToken(lp, "|", PIPELINE)) {
return _parsePipeline(lp);
}
return true;
}
/**
* The function parseFileName parses a filename.
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the filename was parsed successfully.
*/
bool _parseFilename(List *lp) {
if (*lp == NULL) return false;
//we run a POSIX compliant system, meaning all characters save '/' and NULL are allowed in filenames.
//NULL is already taken care of by the List library, and / just means the file is located in a directory.
//as a result the only limit we place on filenames are the reserved (operator) characters.
switch ((*lp)->t[0]) {
case '|':
case '&':
case ';':
case '\n':
case '>':
case '<':
return false;
}
(*lp)->type = FILENAME;
*lp = (*lp)->next;
return true;
}
/**
* The function parseRedirections parses redirections according to the grammar:
*
* <redirections> ::= <pipeline> <redirections>
* | <builtin> <options>
*
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the redirections were parsed successfully.
*/
bool _parseRedirections(List *lp) {
if (isEmpty(*lp)) {
return true;
}
if (_acceptToken(lp, "<", REDIRECT)) {
if (!_parseFilename(lp)) return false;
if (_acceptToken(lp, ">", REDIRECT)) return _parseFilename(lp);
else return true;
} else if (_acceptToken(lp, ">", REDIRECT)) {
if (!_parseFilename(lp)) return false;
if (_acceptToken(lp, "<", REDIRECT)) return _parseFilename(lp);
else return true;
}
return true;
}
/**
* The function parseBuiltIn parses a builtin.
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the builtin was parsed successfully.
*/
bool _parseBuiltIn(List *lp) {
// NULL-terminated array makes it easy to expand this array later
// without changing the code at other places.
char *builtIns[] = {
"exit",
"status",
"cd",
#if EXT_PROMPT
"debug",
#endif
NULL
};
for (int i = 0; builtIns[i] != NULL; i++) {
if (_acceptToken(lp, builtIns[i], BUILTIN)) {
return true;
}
}
return false;
}
/**
* The function parseChain parses a chain according to the grammar:
*
* <chain> ::= <pipeline> <redirections>
* | <builtin> <options>
*
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the chain was parsed successfully.
*/
bool _parseChain(List *lp) {
if (_parseBuiltIn(lp)) {
return _parseOptions(lp);
}
if (_parsePipeline(lp)) {
return _parseRedirections(lp);
}
return false;
}
/**
* The function parseInputLine parses an inputline according to the grammar:
*
* <inputline> ::= <chain> & <inputline>
* | <chain> && <inputline>
* | <chain> || <inputline>
* | <chain> ; <inputline>
* | <chain>
* | <empty>
*
* @param lp List pointer to the start of the tokenlist.
* @return a bool denoting whether the inputline was parsed successfully.
*/
bool parseInputLine(List *lp) {
if (isEmpty(*lp)) {
return true;
}
if (!_parseChain(lp)) {
return false;
}
if (_acceptToken(lp, "&", BACKGROUND) || _acceptToken(lp, "&&", COMPOSITION)) {
return parseInputLine(lp);
} else if (_acceptToken(lp, "||", COMPOSITION)) {
return parseInputLine(lp);
} else if (_acceptToken(lp, ";", COMPOSITION)) {
return parseInputLine(lp);
}
return true;
}