Skip to content

Commit

Permalink
Terminal: Handle tabs, LSh: support environment variables and argumet…
Browse files Browse the repository at this point in the history
…ns in quotes
  • Loading branch information
fido2020 committed Feb 21, 2022
1 parent b114820 commit 6b88346
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 36 deletions.
176 changes: 140 additions & 36 deletions Applications/LSh/main.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
#include <Lemon/System/Spawn.h>
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <list>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <string>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <stack>
#include <vector>

termios execAttributes; // Set before executing
Expand All @@ -18,7 +20,7 @@ termios readAttributes; // Set on ReadLine
std::string ln;
int lnPos = 0;

typedef void (*builtin_call_t)(int, char**);
typedef int (*builtin_call_t)(int, char**);

typedef struct {
const char* name;
Expand All @@ -33,36 +35,46 @@ std::list<builtin_t> builtins;

std::vector<std::string> history;

void LShBuiltin_Cd(int argc, char** argv) {
int LShBuiltin_Cd(int argc, char** argv) {
if (argc > 3) {
printf("cd: Too many arguments");
return 1;
} else if (argc == 2) {
if (chdir(argv[1])) {
printf("cd: Error changing working directory to %s\n", argv[1]);
}

return 1;
} else
chdir("/");

getcwd(currentDir, PATH_MAX);
return 0;
}

void LShBuiltin_Pwd(int argc, char** argv) {
int LShBuiltin_Pwd(int argc, char** argv) {
getcwd(currentDir, PATH_MAX);
printf("%s\n", currentDir);

return 0;
}

void LShBuiltin_Export(int argc, char** argv) {
int LShBuiltin_Export(int argc, char** argv) {
for (int i = 1; i < argc; i++) {
putenv(argv[i]);
}

return 0;
}

void LShBuiltin_Clear(int argc, char** argv) {
int LShBuiltin_Clear(int argc, char** argv) {
printf("\033c");
fflush(stdout);

return 0;
}

void LShBuiltin_Exit(int argc, char** argv) { exit(0); }
[[noreturn]] int LShBuiltin_Exit(int argc, char** argv) { exit(0); }

builtin_t builtinCd = {.name = "cd", .func = LShBuiltin_Cd};
builtin_t builtinPwd = {.name = "pwd", .func = LShBuiltin_Pwd};
Expand All @@ -72,8 +84,10 @@ builtin_t builtinExit = {.name = "exit", .func = LShBuiltin_Exit};

pid_t job = -1;

void InterruptSignalHandler(int sig){
if(job > 0){
int commandResult = 0; // Return code of last command

void InterruptSignalHandler(int sig) {
if (job > 0) {
// If we have a current job, send signal to child.
kill(job, sig);
}
Expand All @@ -85,7 +99,7 @@ void ReadLine() {
bool esc = false; // Escape sequence
bool csi = false; // Control sequence indicator

lnPos = 0; // Line cursor position
lnPos = 0; // Line cursor position
int historyIndex = -1; // History index
ln.clear();

Expand Down Expand Up @@ -183,67 +197,158 @@ void ReadLine() {
}

void ParseLine() {
if (!ln.length()) {
if (ln.empty()) {
return;
}

enum {
ParseNormal,
ParseSingleQuotes,
ParseDoubleQuotes,
ParseEnv,
ParseEscape,
};

std::stack<int> state;
state.push(ParseNormal);

history.push_back(ln);
ln.push_back('\n'); // Insert a '\n' at the end

int argc = 0;
std::vector<char*> argv;

char* lnC = strdup(ln.c_str());
std::string lineBuf;
std::string envBuf;

if (!lnC) {
return;
auto pushArg = [&]() -> void {
if(lineBuf.empty()) {
return;
}

assert(lineBuf.c_str());

argv.push_back(strdup(lineBuf.c_str()));
lineBuf.clear();
};

auto pushEnv = [&](std::string val) -> void {
lineBuf += std::move(val);
envBuf.clear();
};

auto isLineSeperator = [](char c) -> bool { return c == ' ' || c == '\n'; };

for (auto it = ln.begin(); it != ln.end(); it++) {
char c = *it;
switch (state.top()) {
case ParseNormal:
if (c == '\'') {
state.push(ParseSingleQuotes);
break;
} else if (c == '\"') {
state.push(ParseDoubleQuotes);
break;
} else if (isLineSeperator(c)) {
pushArg();
break;
}

[[fallthrough]];
case ParseDoubleQuotes:
if (c == '\"') {
// The fallthrough will not matter as
// this case is already tested for ParseNormal
state.pop();
break;
}

if (c == '\\') {
state.push(ParseEscape);
} else if (c == '$') {
state.push(ParseEnv);
} else {
lineBuf += c;
}
break;
case ParseSingleQuotes:
if (c == '\'') { // End single quotes
state.pop();
} else {
lineBuf += c;
}
break;
case ParseEnv:
if (envBuf.empty()) {
if (c == '$') { // Shell Process ID
pushEnv(std::to_string(getpid()));
state.pop();
} else if (c == '?') { // Return value of last command
pushEnv(std::to_string(commandResult));
state.pop();
}
}

if (isalnum(c)) {
envBuf += c;
} else if (isLineSeperator(c) || c == '\\' || c == '\'' || c == '\"') {
pushEnv(getenv(envBuf.c_str()));
state.pop();
it--; // The previous state deals with the separator
}
break;
case ParseEscape:
lineBuf += c;
state.pop();
break;
}
}

char* tok = strtok(lnC, " \t\n");
argv.push_back(tok);
argc++;
assert(lineBuf.empty());

while ((tok = strtok(NULL, " \t\n"))) {
argv.push_back(tok);
argc++;
if(!argv.size()) {
return;
}

for (builtin_t builtin : builtins) {
if (strcmp(builtin.name, argv[0]) == 0) {
builtin.func(argc, argv.data());
commandResult = builtin.func(argv.size(), argv.data());
return;
}
}

errno = 0;

if (strchr(argv[0], '/')) {
job = lemon_spawn(argv[0], argc, argv.data(), 1);
job = lemon_spawn(argv[0], argv.size(), argv.data(), 1);
if (job > 0) {
int status = 0;
int ret = 0;
while((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR))
while ((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR))
;

commandResult = WEXITSTATUS(status);

job = -1;
} else if (errno == ENOENT) {
printf("\nNo such file or directory: %s\n", argv[0]);
} else {
perror("Error spawning process");
}

if (lnC)
free(lnC);

return;
} else
for (std::string path : path) {
job = lemon_spawn((path + "/" + argv[0]).c_str(), argc, argv.data(), 1);
assert(!path.empty());

job = lemon_spawn((path + "/" + argv[0]).c_str(), argv.size(), argv.data(), 1);
if (job > 0) {
int status = 0;
int ret = 0;
while((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR))
while ((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR))
;

commandResult = WEXITSTATUS(status);

job = -1;
return;
} else if (errno == ENOENT) {
Expand All @@ -256,9 +361,6 @@ void ParseLine() {

printf("\nUnknown Command: %s\n", argv[0]);

if (lnC)
free(lnC);

return;
}

Expand All @@ -269,6 +371,8 @@ int main() {
chdir(h);
}

setenv("SHELL", "/system/bin/lsh", 1);

getcwd(currentDir, PATH_MAX);
tcgetattr(STDOUT_FILENO, &execAttributes);
readAttributes = execAttributes;
Expand All @@ -281,12 +385,12 @@ int main() {
sigemptyset(&action.sa_mask);

// Send both SIGINT and SIGWINCH to child
if(sigaction(SIGINT, &action, nullptr)){
if (sigaction(SIGINT, &action, nullptr)) {
perror("sigaction");
return 99;
}

if(sigaction(SIGWINCH, &action, nullptr)){
if (sigaction(SIGWINCH, &action, nullptr)) {
perror("sigaction");
return 99;
}
Expand All @@ -298,9 +402,9 @@ int main() {
builtins.push_back(builtinExit);

std::string pathEnv = getenv("PATH");
std::string temp;
std::string temp = "";
for (char c : pathEnv) {
if (c == ':' && temp.length()) {
if (c == ':' && !temp.empty()) {
path.push_back(temp);
temp.clear();
} else {
Expand Down
1 change: 1 addition & 0 deletions Applications/Terminal/escape.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum {
Control_Backspace = '\b', // Backspaces one column, however not past the beginning of the line
Control_LineFeed = '\n', // Line feed, by default will also carriage return
Control_CarriageReturn = '\r', // Carriage return
Control_Tab = '\t',
};

#define ESC_SAVE_CURSOR '7'
Expand Down
3 changes: 3 additions & 0 deletions Applications/Terminal/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ void ParseChar(char ch) {
if (parseState == State_Normal) {
if (isprint(ch) || ch == ' ') {
PrintChar(ch);
} else if(ch == Control_Tab) {
cursorPosition.x = std::min(cursorPosition.x + 8, terminalSize.x);
} else if (ch == Control_Escape) {
escapeBuffer.clear();
parseState = State_Escape;
Expand Down Expand Up @@ -448,6 +450,7 @@ int main(int argc, char** argv) {
int ptySlaveFd = -1;

setenv("TERM", "xterm-256color", 1);
setenv("COLORTERM", "truecolor", 1);

struct sigaction action = {
.sa_handler = SIGCHLDHandler,
Expand Down

0 comments on commit 6b88346

Please sign in to comment.