DZMW5PRBTJV4XFWSDSYNR4AWK5LIRBHW5TMN5E4N633AOT5KMLCAC # Paled TODO## August 25, 2020[ ] Finally fix up and down arrow problems[ ] Cd builtin command[ ] Tokenizer - paths can start with "./", "../", "~/", "~", or "/" on macOS, Linux, and Windowsor ".\", "..\", "~\", "~", "C:", "\", or "C:\" (or "C:/") for Windows only[ ] getenv & setenv[ ] dhist builtin## August 22, 2020[x] write_strings[x] write_at and print_at[~] ncure example program in Odin[x] More parsing - Arguments## Overall[ ] Ncure - Linux[x] General manipulation[~] Batch commands[x] Enable/disable echo[~] Colors[ ] 256 Colors[ ] 32-bit Colors[~] Input[x] ASCII[ ] UTF8[ ] Directory hashing[x] tools/wrappers folders[ ] Read paths.pconfig files[ ] Reconstruct hashtable on directory changes[ ] Command Input[ ] Line Editor[~] Backspace[~] Delete[ ] Left and Right Keys[ ] Mouse Input?[ ] Command history[~] Lexer[x] Char[x] String[x] Ident[x] Keyword[ ] Path?[x] Others: (, ), {, }, ., ;[ ] Parser[x] Builtins[ ] Argument handling[ ] Math expressions: +, -, /, *, (), %, ^[ ] Paths[ ] Subcommand mode[ ] Pipes/IPC[ ] Execution[x] Builtins[ ] Paled Lib[ ] Subcommand[ ] Pipes/IPC[ ] Argument handling[ ] Paths[ ] Builtins[ ] cd[ ] hash[x] exit[~] help[x] clear[ ] debug[ ] lib - load and call into libraries from the command line.[ ] dhist[ ] Tools[ ] List[ ] Copy[ ] Make[ ] Move[ ] Remove[ ] Rename[ ] Trash[ ] Wrappers[ ] Odin[ ] Man[ ] Vi[ ] Ssh[ ] Finger[ ] Pijul[ ] Go[ ] Gcc[ ] Builtin File Management[ ] Copy file(s)[ ] Paste file(s) into current dir or specific dir[ ] Cut file(s)[ ] Clear copied file(s)[ ] Directory history (back and forward)[ ] Directory stack (push and pop directories)[ ] Search files/dirs, ability to go to containing directory quickly[ ] Change file/dir properties[ ] 'list' command sortby and filtering options?[ ] Project directories (named dirs) - start with '~'[ ] Launching default program for a specified file[ ] Text files[ ] Source code files - editor or compiler[ ] Image files[ ] Video files[ ] directories?[ ] .desktop, executable, appimage, dmg, msi, etc.[ ] Launch default program for a function[ ] A way to specify default programs for these functions[ ] editor[ ] shell[ ] shell script[ ] pager[ ] webbrowser, gopher browser, gemini browser[ ] file browser[ ] terminal[ ] terminal multiplexer* max history to 400-450 by default ?* Possible memory leak - hit just an enter many many times, and eventually the memory jumped - could it be ncure?
package mainimport "core:runtime"import "tokenizer"import "ncure"BuiltinProc :: #type proc(self: ^Builtin, globalData: ^GlobalData);CallProc :: #type proc(self: ^Call, globalData: ^GlobalData);Parser :: struct {builtins: ^map[string]BuiltinProc,builtinCalls: ^map[string]CallProc,statements: [dynamic]Statement,currentTokenIndex: int,}Statement :: union {Builtin,Call,ParserError,}ParserError :: union {ParserError_Internal,ParserError_UnknownIdent,ParserError_UnknownCall,ParserError_NoParams,ParserError_UnexpectedToken,ParserError_Unimplemented,}/*ParserErrorBase :: struct {tokens: []tokenizer.Token,}*/ParserError_Internal :: struct {tokens: []tokenizer.Token,internal_location: runtime.Source_Code_Location,}ParserError_UnknownIdent :: struct {tokens: []tokenizer.Token,}ParserError_UnknownCall :: struct {tokens: []tokenizer.Token,}ParserError_NoParams :: struct {tokens: []tokenizer.Token,}ParserError_UnexpectedToken :: struct {tokens: []tokenizer.Token,}ParserError_Unimplemented :: struct {}Builtin :: struct {p: BuiltinProc,rest: []tokenizer.Token,}make_builtin :: proc(builtin: ^Builtin, p: BuiltinProc, rest: []tokenizer.Token) {builtin.p = p;builtin.rest = rest;}Call :: struct {p: CallProc,command: []tokenizer.Token,subcommand: []tokenizer.Token,hasSubcommand: bool,help: bool, // True is no parentheses - should show help of commandparams: [dynamic]Parameter, // TODO}make_call :: proc(call: ^Call, command: []tokenizer.Token, subcommand: []tokenizer.Token = nil) {call.p = defaultCallProc;call.command = command;call.subcommand = nil;if len(subcommand) > 0 {call.hasSubcommand = true;call.subcommand = subcommand;}call.help = true;call.params = make([dynamic]Parameter);}make_builtinCall :: proc(call: ^Call, p: CallProc) {make_call(call, nil, nil);call.p = p;}destroy_call :: proc(call: ^Call) {delete(call.params);}Parameter :: struct {value: Value,name: ^tokenizer.Token,}makeParser :: proc(parser: ^Parser, builtins: ^map[string]BuiltinProc, builtinCalls: ^map[string]CallProc) {parser.builtins = builtins;parser.builtinCalls = builtinCalls;parser.statements = make([dynamic]Statement);}destroyParser :: proc(parser: ^Parser) {for i in 0..<len(parser.statements) {statement := &parser.statements[i];#partial switch v in statement {case Call: {destroy_call(transmute(^Call) statement);}}}delete(parser.statements);}Value :: struct {using _: ^tokenizer.Token,boolean: bool,}currentToken :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> (^tokenizer.Token, int) {if parser.currentTokenIndex >= len(tok.tokens) do return nil, parser.currentTokenIndex;return &tok.tokens[parser.currentTokenIndex], parser.currentTokenIndex;}nextToken :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> (^tokenizer.Token, int) {parser.currentTokenIndex += 1;return currentToken(parser, tok);}hasNextToken :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> bool {token, i := peekNextToken(parser, tok);return i < len(tok.tokens) && token.type != tokenizer.TokenType.End;}hasNextTokenIf :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, type: tokenizer.TokenType) -> bool {token, i := peekNextToken(parser, tok);return i < len(tok.tokens) && token.type != tokenizer.TokenType.End && token.type == type;}nextTokenIf :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, type: tokenizer.TokenType) -> (^tokenizer.Token, int, bool) {index := parser.currentTokenIndex + 1;token := &tok.tokens[index];if token.type == type {parser.currentTokenIndex = index;return token, index, true;}return nil, -1, false;}peekNextToken :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> (^tokenizer.Token, int) {return &tok.tokens[parser.currentTokenIndex + 1], parser.currentTokenIndex + 1;}incrementTokenIndex :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) {parser.currentTokenIndex += 1;}parseInput :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> ParserError {using tokenizer;parser.currentTokenIndex = 0;for {current_token, current_i := currentToken(parser, tok);#partial switch (current_token.type) {case TokenType.Keyword: {statement := parseKeywordStatement(parser, tok);if err, isError := statement.(ParserError); isError {return err;}ncure.println(statement);append(&parser.statements, statement);}case TokenType.Identifier: {statement := parseIdentifierStatement(parser, tok);if err, isError := statement.(ParserError); isError {return err;}append(&parser.statements, statement);}case: {// TODO: Cleanuperror: ParserError = ParserError_UnexpectedToken{tok.tokens[current_i:current_i + 1]};return error;}}current_token, current_i = currentToken(parser, tok);if current_i >= len(tok.tokens) || current_token.type == TokenType.End {break;}}return nil;}// TODOparseKeywordStatement :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> (Statement) {using tokenizer;keyword, startTokenIndex := currentToken(parser, tok);nextToken, nextTokenIndex := nextToken(parser, tok);#partial switch nextToken.type {case TokenType.LeftParen: {incrementTokenIndex(parser, tok);p, ok := parser.builtinCalls[keyword.str];if ok {call: Call;make_builtinCall(&call, p);return call;} else {// Check if regular builtinp, ok := parser.builtins[keyword.str];if ok {semicolonToken_index := findStatementEnd(parser, tok);builtin: Builtin;make_builtin(&builtin, p, tok.tokens[parser.currentTokenIndex:semicolonToken_index]);parser.currentTokenIndex = semicolonToken_index + 1; // TODOreturn builtin;} else {// ERROR// error: ParserError = ParserError_Internal{tok.tokens[startTokenIndex:parser.currentTokenIndex], #location};// return error;}}}case: {/*p, ok := parser.builtins[keyword.str];if ok {semicolonToken_index := findStatementEnd(parser, tok);builtin: Builtin;make_builtin(&builtin, p, tok.tokens[parser.currentTokenIndex:semicolonToken_index]);parser.currentTokenIndex = semicolonToken_index + 1;return builtin;} else {}*/// TODO: Cleanuperror: ParserError = ParserError_Unimplemented{};return error;}}unreachable();}// Check ident in builtins// Check left paren// Check Subcommand// Check ident in builtin Calls// Otherwise, check in command hashmaps// Check in current directory last (maybe...)parseIdentifierStatement :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> (Statement) {using tokenizer;ident, identIndex := currentToken(parser, tok);nextToken, nextTokenIndex := nextToken(parser, tok);// Check if in builtinsp, is_builtin := parser.builtins[ident.str];if is_builtin {semicolonToken_index := findStatementEnd(parser, tok);builtin: Builtin;make_builtin(&builtin, p, tok.tokens[parser.currentTokenIndex:semicolonToken_index]);parser.currentTokenIndex = semicolonToken_index + 1;return builtin;}#partial switch nextToken.type {case TokenType.Dot: {return parseSubcommand(parser, tok, identIndex);}case TokenType.LeftParen: {return parseCall(parser, tok, identIndex);}case TokenType.End, TokenType.Semicolon: {return parseHelpCall(parser, tok, identIndex);}case: {// TODO: Cleanuperror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}}unreachable();}parseSubcommand :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, identIndex: int) -> Statement {using tokenizer;subcommand, subcommandIndex, hasSubcommand := nextTokenIf(parser, tok, TokenType.Identifier);if !hasSubcommand {// TODO: Cleanuperror: ParserError = ParserError_UnexpectedToken{tok.tokens[subcommandIndex:subcommandIndex + 1]};return error;}nextToken, nextTokenIndex := nextToken(parser, tok);#partial switch nextToken.type {case TokenType.LeftParen: {return parseCall(parser, tok, identIndex, true, subcommandIndex);}case TokenType.End, TokenType.Semicolon: {incrementTokenIndex(parser, tok);return parseHelpCall(parser, tok, identIndex, true, subcommandIndex);}case: {// TODO: Cleanuperror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}}unreachable();}parseHelpCall :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, identIndex: int, hasSubcommand := false, subcommandIndex: int = 0) -> Statement {call: Call;if hasSubcommand do make_call(&call, tok.tokens[identIndex:identIndex + 1], tok.tokens[subcommandIndex:subcommandIndex + 1]);else do make_call(&call, tok.tokens[identIndex:identIndex + 1]);call.help = true;return call;}parseCall :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, identIndex: int, hasSubcommand := false, subcommandIndex: int = 0) -> Statement {using tokenizer;call: Call;if hasSubcommand do make_call(&call, tok.tokens[identIndex:identIndex + 1], tok.tokens[subcommandIndex:subcommandIndex + 1]);else do make_call(&call, tok.tokens[identIndex:identIndex + 1]);call.help = false;error := parseParameters(parser, tok, &call.params);if error != nil {// TODO: Cleanup stuffreturn error;}return call;}// FirstParameter := (ident = value) | value// OtherParameters := ',' ((ident = value) | value)// value := Number | String | Char | BooleanparseParameters :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, params: ^[dynamic]Parameter) -> ParserError {using tokenizer;param: Parameter;hasName := false;hasValue := false;outer: for {nextToken, nextTokenIndex := nextToken(parser, tok);#partial switch nextToken.type {case TokenType.Identifier: {// Check if there's an equals, if not, then expression, otherwise a named parameterequalsToken, equalsTokenIndex, hasEquals := nextTokenIf(parser, tok, TokenType.Equal);if hasEquals && !hasName {param.name = nextToken;hasName = true;} else {// Identifier Value// TODO: Not currently allowed atmreturn ParserError_UnexpectedToken {tok.tokens[nextTokenIndex + 1:nextTokenIndex + 2]};}}case TokenType.Number: {if hasValue {// TODO: Cleanup// TODO: Expressions?error: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}param.value = Value { nextToken, false };hasValue = true;}case TokenType.String: {if hasValue {// TODO: Cleanup// TODO: Expressions?error: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}param.value = Value { nextToken, false };hasValue = true;}case TokenType.Char: {if hasValue {// TODO: Cleanup// TODO: Expressions?error: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}param.value = Value { nextToken, false };hasValue = true;}case TokenType.Keyword: {if hasValue {// TODO: Cleanup// TODO: Expressions?error: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}if nextToken.str == "true" || nextToken.str == "false" {param.value = Value { nextToken, true };hasValue = true;}}case TokenType.Comma: {// End of current parameter. Append it to listif !hasValue {// TODO: Cleanup// TODO: No parameter value errorerror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}append(params, param);param.name = nil;param.value = Value { nil, false };hasName = false;hasValue = false;}case TokenType.RightParen: {if hasName && !hasValue {// TODO: Cleanup// TODO: No parameter value errorerror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;} else if hasValue {append(params, param);// ncure.println("Test");param.name = nil;param.value = Value { nil, false };hasName = false;hasValue = false;}nextTokenIf(parser, tok, TokenType.Semicolon);incrementTokenIndex(parser, tok);break outer;}case TokenType.Semicolon: {if hasName && !hasValue {// TODO: Cleanup// TODO: No parameter value errorerror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;} else {// TODO: Cleanup// TODO: Error, no right parentheseserror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}}case TokenType.End: {if hasName && !hasValue {// TODO: Cleanup// TODO: No parameter value errorerror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;} else {// TODO: Cleanup// TODO: Error, no right parentheseserror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}}case: {// TODO: Cleanuperror: ParserError = ParserError_UnexpectedToken{tok.tokens[nextTokenIndex:nextTokenIndex + 1]};return error;}}}return nil;}findStatementEnd :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer) -> int {using tokenizer;offset := parser.currentTokenIndex;for token, i in tok.tokens[parser.currentTokenIndex:] {if token.type == TokenType.Semicolon || token.type == TokenType.End {return offset + i;}}unreachable();}findToken :: proc(parser: ^Parser, tok: ^tokenizer.Tokenizer, type: tokenizer.TokenType) -> (int, bool) {using tokenizer;offset := parser.currentTokenIndex;for token, i in tok.tokens[parser.currentTokenIndex:] {if token.type == type {return offset + i, true;}}return len(tok.tokens) - 1, false;}
package mainimport "core:fmt"import "core:os"import "core:path"import "core:strings"import "core:unicode/utf8"// import "core:hash"import "core:container"import "core:runtime"import "linux"import "ncure"import "tokenizer"VERSION :: "0.5";COPYRIGHT_YEAR :: "2020";GlobalData :: struct {running: bool,executablePath: string,executableDir: string,username: string,homeDirectory: string,toolsFolder: string,wrappersFolder: string,path_hash: map[string]string,shell_vars: map[string]string, // TODOdirectoryHistory: [dynamic]string,// commandHistory: [dynamic]string, // TODO: Use a queuecommandHistory: container.Queue(string),history_index: int,current: string,}init_globalData :: proc(globalData: ^GlobalData) {globalData.path_hash = make(map[string]string);globalData.shell_vars = make(map[string]string); // TODOglobalData.directoryHistory = make([dynamic]string);// globalData.commandHistory = make([dynamic]string);container.queue_init(&globalData.commandHistory, 0, 5);globalData.history_index = 0;}add_history :: proc(globalData: ^GlobalData, s: string) {if container.queue_len(globalData.commandHistory) >= 200 {for i in 0..<(container.queue_len(globalData.commandHistory) - 200) {container.queue_pop_front(&globalData.commandHistory);}}container.queue_push(&globalData.commandHistory, s);}terminate :: proc "c" (signum: int) {context = runtime.default_context();// TODO: Determine if should wait for something to finish or not// TODO: Wait for child processes to finish? Or deattach them// TODO: Save important state herencure.batch_end();ncure.newLine();ncure.enableEcho();os.exit(0);}foo :: cast(^u8) cast(uintptr) 35251;setupSignals :: proc() {// str := (cast(^u8) uintptr(35251));// blah := cast(cstring) str;// Ignore Ctrl+C Interactive Attention Signal// and Ctrl+Z Terminal Stop Signallinux.signal(linux.SIGINT, linux.SIG_IGN);linux.signal(linux.SIGTSTP, linux.SIG_IGN);linux.signal(linux.SIGQUIT, linux.sighandler_t(terminate));linux.signal(linux.SIGTERM, linux.sighandler_t(terminate));linux.signal(linux.SIGABRT, linux.sighandler_t(terminate));linux.signal(linux.SIGALRM, linux.sighandler_t(terminate));linux.signal(linux.SIGVTALRM, linux.sighandler_t(terminate));linux.signal(linux.SIGXCPU, linux.sighandler_t(terminate));linux.signal(linux.SIGXFSZ, linux.sighandler_t(terminate));// TODO: Handle SIGCONT signal? This signal is sent when// the process is restarted from being suspended/paused by SIGSTOP or SIGTSTP// TODO: Handle SIGFPE so that an erroneous arithmetic operation doesn't terminate the shell?}main :: proc() {setupSignals();globalData: GlobalData;init_globalData(&globalData);setupUserInfo(&globalData);setupDefaultDirectories(&globalData);setupEnvironmentVariables(&globalData);globalData.current = os.get_current_directory();linux.setenv("PWD", globalData.current, true);// TODO: Set OLDPWD for previous working directoryncure.disableEcho(false);defer ncure.enableEcho();ncure.batch_start();{defer ncure.batch_end();ncure.clearScreen();ncure.setCursor_topleft();// ncure.println(typeid_of(type_of(foo)));printPrompt(&globalData);}// NOTE: Only used for tokenizerkeywords: tokenizer.Set = transmute(tokenizer.Set) map[string]bool {"true" = true,"false" = true,"sh" = true,"bash" = true,"nil" = true,};builtins := map[string]BuiltinProc {"cd" = BuiltinCd,"clear" = BuiltinClear,"help" = BuiltinHelp,"version" = BuiltinVersion,"exit" = BuiltinExit,"debug" = BuiltinDebug,"getenv" = BuiltinGetenv,"hash" = BuiltinUnimplemented,"tools" = BuiltinUnimplemented,"wrappers" = BuiltinUnimplemented,"motd" = BuiltinUnimplemented,"dhist" = BuiltinUnimplemented,"sh" = BuiltinUnimplemented,"bash" = BuiltinUnimplemented,"mksh" = BuiltinUnimplemented,};builtinCalls := map[string]CallProc {};running := true;first := true;input := strings.make_builder();defer strings.destroy_builder(&input);for running {strings.reset_builder(&input);if !first do printPrompt(&globalData);else do first = false;cliInput(&input, &globalData);inputString := strings.to_string(input);fmt.println("");if len(inputString) == 0 do continue;tok := tokenizer.makeTokenizer(inputString, &keywords);tokenizer.tokenize(&tok);defer tokenizer.destroyTokenizer(&tok);// tokenizer.printTokens(&tok);parser: Parser;makeParser(&parser, &builtins, &builtinCalls);defer destroyParser(&parser);error := parseInput(&parser, &tok);if error != nil {#partial switch v in error {case ParserError_UnexpectedToken: {token := error.(ParserError_UnexpectedToken).tokens[0];ncure.printf(ncure.ForegroundColor.Red, "Parsing Error: Unexpected %s '%s'", token.type, token.str);ncure.newLine();}}continue;}ncure.newLine();// Runs statementsfor statement in parser.statements {#partial switch v in statement { // TODOcase Builtin: {builtin := statement.(Builtin);builtin->p(&globalData);}case Call: {call := statement.(Call);call->p(&globalData);}}}ncure.newLine();/*fmt.printf("\n");fmt.println(inputString);*/// Add command to history// append(&globalData.commandHistory, strings.clone(strings.to_string(input)));container.queue_push(&globalData.commandHistory, strings.clone(strings.to_string(input)));}ncure.write_rune('\n');}printPrompt :: proc(globalData: ^GlobalData) {ncure.setColor(ncure.ForegroundColor.Green);ncure.write_string(globalData.username);ncure.write_string(": ");ncure.write_string(globalData.current);ncure.write_string("|> ");ncure.resetColors();}cliInput :: proc(input: ^strings.Builder, globalData: ^GlobalData) {strings.reset_builder(input);data: byte;for {data = ncure.getch();// ncure.batch_start();// defer ncure.batch_end();if ncure.Input(data) == ncure.Input.CTRL_C {globalData.running = false;strings.reset_builder(input);break;} else if ncure.Input(data) == ncure.Input.BACKSPACE {if len(input.buf) <= 0 do continue;strings.pop_rune(input);ncure.backspace();continue;} else if ncure.Input(data) == ncure.Input.ENTER {globalData.history_index = 0;break;} else if ncure.Input(data) == ncure.Input.CTRL_BACKSPACE {if len(input.buf) <= 0 do continue;// Search for whitespace before cursorlast_whitespace_index := strings.last_index(string(input.buf[:]), " ");rune_count := strings.rune_count(string(input.buf[:]));if last_whitespace_index == -1{strings.reset_builder(input);ncure.backspace(rune_count);continue;}num_to_delete := rune_count - last_whitespace_index;ncure.backspace(num_to_delete);for i in 0..<num_to_delete {strings.pop_rune(input);}continue;} else if ncure.Input(data) == ncure.Input.CTRL_L {ncure.clearScreen();ncure.setCursor_topleft();printPrompt(globalData);ncure.write_string(string(input.buf[:]));continue;} else if ncure.isSpecial(data) {data = ncure.getch();handleHistory :: proc(input: ^strings.Builder, using globalData: ^GlobalData) {old_rune_count := strings.rune_count(string(input.buf[:]));if history_index > 0 && history_index <= container.queue_len(commandHistory) {strings.reset_builder(input);hist_str := container.queue_get(commandHistory, container.queue_len(commandHistory) - history_index);strings.write_string(input, hist_str);ncure.backspace(old_rune_count - 1);ncure.write_string(hist_str);} else if history_index <= 0 { // TODO: Buggystrings.reset_builder(input);ncure.backspace(old_rune_count);}}if ncure.Input(data) == ncure.Input.UP {if globalData.history_index < container.queue_len(globalData.commandHistory) do globalData.history_index += 1;handleHistory(input, globalData);} else if ncure.Input(data) == ncure.Input.DOWN {ncure.write_string("test down");if globalData.history_index != 0 do globalData.history_index -= 1;handleHistory(input, globalData);}} else if data >= 32 && data <= 126 {ncure.write_byte(data);strings.write_byte(input, data);}}}setupUserInfo :: proc(globalData: ^GlobalData) {username, username_exists := os.getenv("USER");homeDir, homeDir_exists := os.getenv("HOME");if !homeDir_exists {uid := linux.getuid();passwd := linux.getpwuid(uid);globalData.homeDirectory = string(passwd.pw_dir);globalData.username = string(passwd.pw_name);} else {globalData.homeDirectory = string(homeDir);globalData.username = string(username);}}setupDefaultDirectories :: proc(globalData: ^GlobalData) {globalData.executablePath = linux.get_executable_path();globalData.executableDir = path.dir(globalData.executablePath);builder := strings.make_builder(0, len(globalData.executableDir));defer strings.destroy_builder(&builder);strings.write_string(&builder, globalData.executableDir);append_to_path(&builder, "tools");globalData.toolsFolder = strings.clone(strings.to_string(builder));strings.reset_builder(&builder);strings.write_string(&builder, globalData.executableDir);append_to_path(&builder, "wrappers");globalData.wrappersFolder = strings.clone(strings.to_string(builder));hashDirectoryFiles(globalData, globalData.toolsFolder);hashDirectoryFiles(globalData, globalData.wrappersFolder);}handlePathsConfigFile :: proc(globalData: ^GlobalData, path: string) {contents, ok := os.read_entire_file(path);}setupEnvironmentVariables :: proc(globalData: ^GlobalData) {linux.setenv("USER", globalData.username, true);linux.setenv("USERNAME", globalData.username, true);linux.setenv("HOME", globalData.homeDirectory, true);linux.setenv("SHELL", "paled", true);// TODO: LOGNAME?}hashDirectoryFiles :: proc(globalData: ^GlobalData, directory: string) {dp: ^linux.DIR;dp_error: os.Errno;dirp: ^linux.dirent;dirp_error: os.Errno;dp, dp_error = linux.opendir(directory);if (dp == nil) {fmt.printf("Error opening directory '%s': %s\n", directory, dp_error);ncure.enableEcho();os.exit(1); // TODO}defer linux.closedir(dp);path_builder := strings.make_builder();defer strings.destroy_builder(&path_builder);for {defer strings.reset_builder(&path_builder);dirp, dirp_error = linux.readdir(dp);if dirp == nil && dirp_error == os.ERROR_NONE do break;else if dirp == nil do continue; // TODO: Print error?d_name_length := len(cstring(&dirp.d_name[0]));d_name_str := string(dirp.d_name[:d_name_length]);strings.write_string(&path_builder, directory);append_to_path(&path_builder, d_name_str);path := strings.to_string(path_builder);fileInfo, info_err := os.stat(path);if info_err != os.ERROR_NONE {fmt.printf("Error stating file '%s': %s\n", path, info_err);continue;}if os.S_ISREG(fileInfo.mode) || os.S_ISLNK(fileInfo.mode) {copy_path := strings.clone(path);globalData.path_hash[strings.clone(d_name_str)] = copy_path; // TODOncure.write_string(ncure.ForegroundColor.Red, ":");// fmt.println(d_name_str);}}// fmt.println(globalData.path_hash);}append_to_path :: proc(builder: ^strings.Builder, sarr: ..string) {for s in sarr {if !os.is_path_separator(rune(peek_byte(builder))) do strings.write_rune(builder, linux.get_path_separator());// if peek_byte(builder) != '/' do strings.write_rune(builder, '/');strings.write_string(builder, s);}}
package mainimport "linux"import "ncure"import "core:strings"import "core:path"defaultCallProc :: proc(self: ^Call, globalData: ^GlobalData) {// ncure.printf("%s\n", self^);// Check if command is in hashtable// TODO: command is a slice that can be of multiple tokenscommandPath, isHashed := globalData.path_hash[self.command[0].str];if !isHashed {commandPath = self.command[0].str;// Check that file exists, is a file, and is executableexists := path.is_file(commandPath);ncure.println(commandPath);if !exists {ncure.write_string(ncure.ForegroundColor.Red, "Error: File doesn't exist");ncure.newLine();return;}}amt := 1;if self.hasSubcommand || !self.help {amt += 1;}args := make([dynamic]cstring, amt, len(self.params) + amt);args[0] = strings.clone_to_cstring(commandPath);if self.hasSubcommand do args[1] = strings.clone_to_cstring(self.subcommand[0].str); // TODOelse if !self.help do args[1] = strings.clone_to_cstring("default");defer delete(args);builder := strings.make_builder();for i in 0..<len(self.params) {param := &self.params[i];if param.name != nil {strings.write_string(&builder, param.name.str);strings.write_byte(&builder, '=');append(&args, strings.clone_to_cstring(strings.to_string(builder)));strings.reset_builder(&builder);}valueString, _ := strings.replace_all(param.value.str, "\"", "");strings.write_string(&builder, valueString); // TODOappend(&args, strings.clone_to_cstring(strings.to_string(builder)));strings.reset_builder(&builder);}append(&args, nil);commandPath_cstr := strings.clone_to_cstring(commandPath);defer delete(commandPath_cstr);executeProgram(globalData, commandPath_cstr, &args[0]);}executeProgram :: proc(globalData: ^GlobalData, path: cstring, args: ^cstring) {pid, wpid: linux.pid_t;status: int;pid = linux.fork();if pid == 0 {// Child process// Set terminal stop and interactive attention signals to defaultlinux.signal(linux.SIGINT, linux.SIG_DFL);linux.signal(linux.SIGTSTP, linux.SIG_DFL);// Ignore SIGHUP to allow program to continue// running even if the controlling terminal/paled closes.// linux.signal(linux.SIGHUP, linux.SIG_IGN);result := linux._unix_execvp(path, args);if result == -1 {// TODO: Error}} else if pid < 0 {// TODO: Forking error} else {// Parent - Paledfor {wpid = linux.waitpid(pid, &status, linux.WUNTRACED);if linux.WIFEXITED(status) || linux.WIFSIGNALED(status) do break;}}}
package mainimport "core:os"import "core:container"import "tokenizer"import "ncure"import "linux"// -- Builtins --printVersionAndCopyright :: proc() {ncure.write_line(ncure.ForegroundColor.Blue, "Paled (odin) V0.5");ncure.write_line("Copyright (c) 2020 Christian Lee Seibold. MIT Licensed.");ncure.newLine();}BuiltinVersion :: proc(self: ^Builtin, globalData: ^GlobalData) {ncure.batch_start();defer ncure.batch_end();printVersionAndCopyright();}BuiltinHelp :: proc(self: ^Builtin, globalData: ^GlobalData) {ncure.batch_start();defer ncure.batch_end();// Version and CopyrightprintVersionAndCopyright();// Builtinsncure.write_line(ncure.ForegroundColor.Blue, "Builtins:");ncure.write_line("* tools - prints out list of all programs in tools directory");ncure.write_line("* cd - changes current directory");ncure.write_line("* motd - prints the message of the day");ncure.write_line("* getenv - prints the value of the given environment variable");ncure.write_line("* dhist - print the directory history");ncure.newLine();ncure.write_line("* sh");ncure.write_line("* builtins");ncure.write_line("* clear");ncure.write_line("* exit");ncure.newLine();// Syntaxncure.write_line(ncure.ForegroundColor.Blue, "Syntax:");ncure.write_line("The syntax for calling programs/binaries is much like C");ncure.write_line("and other programming languages:");ncure.write_line(ncure.ForegroundColor.Cyan, "> list(\".\")");ncure.newLine();ncure.write_line("Some programs support subcommands. The syntax for");ncure.write_line("calling a subcommand is:");ncure.write_line(ncure.ForegroundColor.Cyan, "> list.dirs(\".\")");ncure.newLine();ncure.write_line("You can see the documentation, including a list of");ncure.write_line("subcommands, for a program by typing the name without");ncure.write_line("parentheses:");ncure.write_line(ncure.ForegroundColor.Cyan, "> list");ncure.newLine();ncure.write_line("Named Parameters and Default Arguments are also");ncure.write_line("supported. Notice that list's documentation shows the");ncure.write_line("first parameter defaults to \".\" - this parameter is");ncure.write_line("optional.");ncure.write_line(ncure.ForegroundColor.Cyan, "> list(detail = true)");ncure.newLine();ncure.write_line("Lastly, Builtins do not need to use parentheses:");ncure.write_line(ncure.ForegroundColor.Cyan, "> cd ~");}BuiltinDebug :: proc(self: ^Builtin, globalData: ^GlobalData) {ncure.batch_start();defer ncure.batch_end();ncure.printf("History Count: %d", container.queue_len(globalData.commandHistory));ncure.newLine();}BuiltinExit :: proc(self: ^Builtin, globalData: ^GlobalData) {ncure.enableEcho();ncure.showCursor();os.exit(0);}BuiltinCd :: proc(self: ^Builtin, globalData: ^GlobalData) {linux.setenv("OLDPWD", globalData.current, true);ncure.println(self.rest);globalData.current = os.get_current_directory();linux.setenv("PWD", globalData.current, true);}BuiltinGetenv :: proc(self: ^Builtin, globaldata: ^GlobalData) {if self.rest[0].type == tokenizer.TokenType.Identifier {result, ok := linux.secure_getenv(self.rest[0].str);if ok {ncure.println(result);}}}BuiltinClear :: proc(self: ^Builtin, globalData: ^GlobalData) {ncure.batch_start();defer ncure.batch_end();ncure.clearScreen();ncure.setCursor_topleft();}BuiltinUnimplemented :: proc(self: ^Builtin, globalData: ^GlobalData) {ncure.batch_start();defer ncure.batch_end();ncure.write_string("Unimplemented.");ncure.newLine();}// -- Builtin Calls --
package mainimport "core:strings"import "core:unicode/utf8"peek_byte :: proc(b: ^strings.Builder) -> (r: byte) {if len(b.buf) == 0 {return 0;}r = b.buf[len(b.buf) - 1];return;}peek_rune :: proc(b: ^strings.Builder) -> (r: rune, width: int) {r, width = utf8.decode_last_rune(b.buf[:]);return;}