OEM6DXKKM4FWAE6KJUB2VLFO2DSYUB7JWCHZQYTOZ5LBBBEXKLFQC B4ZO42ZKQGSFEFBDJAO62P5T6KRXKWJA5S6SIXNGAPKFQNLHGHFQC HPLAXKEJSIBY5D5BHQKRCCVOCAEK7UTBRLSRGHCS46HIUZ7WIQ3QC MAI6OUZJ2PBCEGOSHE5X6ZRC7OMW5CM3KYZKRDVZVP57WXOSXKCAC Z4CAZF3XFSRLORAUMMCZMXTI55A5W7UHSWBRDFRKFEH6C6SW7XNQC JVDQD62CLUEW45NY6ODHUFWJSPSTFWZVEBSSYCXMRW2GQQGP53PAC DZMW5PRBTJV4XFWSDSYNR4AWK5LIRBHW5TMN5E4N633AOT5KMLCAC The MIT License (MIT)Copyright (c) 2019-2020 Christian Lee SeiboldPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package tokenizerimport "core:fmt"import "core:strings"import "core:unicode/utf8"Set :: distinct map[string]bool;// TODO: Use an arena allocatorTokenizer :: struct {source: string,keywords: ^Set,ident_allowWhitespace: bool,tokenContext: TokenizerContext,line, col: int,newLine: bool, // TODObackslashEscape: bool, // TODOquoted: bool,currentIndex: int,currentRune: rune,currentRuneSize: int,tokenStart: int,tokens: [dynamic]Token,}TokenizerContext :: enum u8 {None,Number,Identifier,QuotedIdentifier,Char,String,RawString,}TokenType :: enum u16 {// 0 - 255 are the ascii charactersNull = 0,HorizontalTab = 9,ESC = 27,Exclamation = 33,Hash = 35,DollarSign = 36,Percent = 37,Ampersand = 38,LeftParen = 40,RightParen = 41,Astrisk = 42,Plus = 43,Comma = 44,Dash = 45,Dot = 46,Slash = 47,Colon = 58,Semicolon = 59,LessThan = 60,Equal = 61,GreaterThan = 62,QuestionMark = 63,At = 64,LeftBracket = 91,Backslash = 92,RightBracket = 93,Hat = 94, // ^Unerscore = 95,Backtick = 96, // `LeftCurlyBracket = 123,VerticalBar = 124, // |RightCurlyBracket = 125,Whitespace = 256,Identifier,QuotedIdentifier,Keyword,Number,String,RawString,Char,End,}Token :: struct {type: TokenType,str: string,line, col: int,}makeToken :: proc(type: TokenType, str: string, line, col: int) -> Token {token: Token;token.type = type;token.str = str;token.line = line;token.col = col - len(str) + 1;return token;}makeTokenizer :: proc(source: string, keywords: ^Set, ident_allowWhitespace: bool = false) -> Tokenizer {tokenizer: Tokenizer;tokenizer.tokenContext = TokenizerContext.None;tokenizer.source = source;tokenizer.line = 1;tokenizer.col = 0;tokenizer.tokens = make([dynamic]Token, 0);tokenizer.keywords = keywords;tokenizer.ident_allowWhitespace = ident_allowWhitespace;return tokenizer;}destroyTokenizer :: proc(tok: ^Tokenizer) {delete(tok.tokens);}is_newline :: proc(r: rune) -> bool {switch r {case '\n', '\r': return true;case: return false;}}// -----tokenize :: proc(tok: ^Tokenizer) {newLine: bool = false;for r, i in tok.source {tok.currentIndex = i;tok.currentRune = r;tok.currentRuneSize = utf8.rune_size(r);if is_newline(r) && !newLine {tok.line += 1;tok.col = 0;newLine = true;continue;} else {tok.col += 1;newLine = false;}switch tok.tokenContext {case .None: handleNone(tok);case .Number: handleNumber(tok);case .Identifier: handleIdentifier(tok);case .QuotedIdentifier: handleIdentifier(tok, true);case .Char: handleChar(tok);case .String: handleString(tok, false);case .RawString: handleString(tok, true);}}// End of file/inputtok.currentIndex += 1;tok.currentRune = '\x00';tok.currentRuneSize = 1;switch tok.tokenContext {case .None: handleNone(tok);case .Number: handleNumber(tok);case .Identifier: handleIdentifier(tok);case .QuotedIdentifier: handleIdentifier(tok, true);case .Char: handleChar(tok);case .String: handleString(tok, false);case .RawString: handleString(tok, true);}// End tokenendToken := makeToken(TokenType.End, tok.source[tok.currentIndex:], tok.line, tok.col);append(&tok.tokens, endToken);}printTokens :: proc(tok: ^Tokenizer) {for token in tok.tokens {fmt.println(token);if token.type == TokenType.Semicolon {fmt.println("");}}}handleNone :: proc(using tok: ^Tokenizer) {// Skip Whitespaceif strings.is_space(currentRune) do return;switch currentRune {case 'a'..'z', 'A'..'Z': {tokenStart = currentIndex;if quoted do tokenContext = TokenizerContext.QuotedIdentifier;else do tokenContext = TokenizerContext.Identifier;}case '(': {token := makeToken(TokenType.LeftParen, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case ')': {token := makeToken(TokenType.RightParen, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '{': {token := makeToken(TokenType.LeftCurlyBracket, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '}': {token := makeToken(TokenType.RightCurlyBracket, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case ':': {token := makeToken(TokenType.Colon, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case ';': {token := makeToken(TokenType.Semicolon, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case ',': {token := makeToken(TokenType.Comma, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '"': {tokenStart = currentIndex;tokenContext = TokenizerContext.String;}case '`': {tokenStart = currentIndex;tokenContext = TokenizerContext.RawString;}case '\'': {tokenStart = currentIndex;tokenContext = TokenizerContext.Char;}case '0'..'9': {tokenStart = currentIndex;tokenContext = TokenizerContext.Number;}case '-': // TODOcase '+': {token := makeToken(TokenType.Plus, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '*': {token := makeToken(TokenType.Astrisk, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '/': {token := makeToken(TokenType.Slash, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '\\': {token := makeToken(TokenType.Backslash, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '^': {token := makeToken(TokenType.Hat, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '.': {token := makeToken(TokenType.Dot, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '=': {token := makeToken(TokenType.Equal, source[currentIndex:currentIndex + 1], line, col);append(&tokens, token);}case '$': {quoted = true;return;}case: {}}if quoted do quoted = false;}handleIdentifier :: proc(using tok: ^Tokenizer, quotedIdentifier: bool = false) {// Allow whitespace in identifiersif tok.ident_allowWhitespace && strings.is_space(currentRune) do return;switch(currentRune) {case 'a'..'z', 'A'..'Z', '0'..'9', '_', '-': {return;}case: {type: TokenType = TokenType.Identifier;if quotedIdentifier do type = TokenType.QuotedIdentifier;str := source[tokenStart:currentIndex];if tok.keywords[str] {type = TokenType.Keyword;}token := makeToken(type, str, line, col);append(&tokens, token);tokenContext = TokenizerContext.None;handleNone(tok);}}}handleString :: proc(using tok: ^Tokenizer, raw: bool = false) {// Allow whitespace in stringsif strings.is_space(currentRune) do return;if currentRune == '"' && !raw {token := makeToken(TokenType.String, source[tokenStart:currentIndex + 1], line, col);append(&tokens, token);tokenContext = TokenizerContext.None;} else if currentRune == '`' && raw {token := makeToken(TokenType.RawString, source[tokenStart:currentIndex + 1], line, col);append(&tokens, token);tokenContext = TokenizerContext.None;}}// TODO: Error on more than one character in char literalhandleChar :: proc(using tok: ^Tokenizer) {if currentRune == '\'' {token := makeToken(TokenType.Char, source[tokenStart:currentIndex + 1], line, col);append(&tokens, token);tokenContext = TokenizerContext.None;}}handleNumber :: proc(using tok: ^Tokenizer) {switch currentRune {case '0'..'9', '.': {return;}case: { // Note: Whitespace *not* allowedtoken := makeToken(TokenType.Number, source[tokenStart:currentIndex], line, col);append(&tokens, token);tokenContext = TokenizerContext.None;handleNone(tok);}}}
# 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 ncureimport "core:os"import "core:strings"import "core:strconv"RESET_COLORS :: "\e[0m"; // TODO: \e[39;49msetColor_foreground :: proc(fg: ForegroundColor) {new_builder: strings.Builder;b: ^strings.Builder;if _batch {b = &_batchInfo.builder;} else {new_builder = strings.make_builder(0, len(SEQUENCE_START));b = &new_builder;}strings.write_string(b, SEQUENCE_START); // ESC[buf: [129]byte;strings.write_string(b, strconv.itoa(buf[:], int(fg)));strings.write_rune(b, 'm');if !_batch {os.write_string(os.stdout, strings.to_string(b^));strings.destroy_builder(b);}}setColor_background :: proc(bg: BackgroundColor) {new_builder: strings.Builder;b: ^strings.Builder;if _batch {b = &_batchInfo.builder;} else {new_builder = strings.make_builder(0, len(SEQUENCE_START));b = &new_builder;}strings.write_string(b, SEQUENCE_START); // ESC[buf: [129]byte;strings.write_string(b, strconv.itoa(buf[:], int(bg)));strings.write_rune(b, 'm');if !_batch {os.write_string(os.stdout, strings.to_string(b^));strings.destroy_builder(b);}}setColor_fg_bg :: proc(fg: ForegroundColor, bg: BackgroundColor) {new_builder: strings.Builder;b: ^strings.Builder;if _batch {b = &_batchInfo.builder;} else {new_builder = strings.make_builder(0, len(SEQUENCE_START));b = &new_builder;}strings.write_string(b, SEQUENCE_START); // ESC[buf: [129]byte;strings.write_string(b, strconv.itoa(buf[:], int(fg)));strings.write_rune(b, ';');strings.write_string(b, strconv.itoa(buf[:], int(bg)));strings.write_rune(b, 'm');if !_batch {os.write_string(os.stdout, strings.to_string(b^));strings.destroy_builder(b);}}setColor :: proc{setColor_foreground, setColor_background, setColor_fg_bg};resetColors :: proc() {if _batch {strings.write_string(&_batchInfo.builder, RESET_COLORS);} else {os.write_string(os.stdout, RESET_COLORS);}}
package mainimport ncure ".."import "core:strconv"import "core:time"main :: proc() {ncure.disableEcho(false);defer ncure.enableEcho();itoa_buf: [129]byte;termSize := ncure.getTermSize();ncure.batch_start();{ncure.clearScreen();ncure.setCursor_topleft();ncure.write_strings(ncure.ForegroundColor.Magenta, "Current Terminal Size: (", strconv.itoa(itoa_buf[:], termSize.width), ", ", strconv.itoa(itoa_buf[:], termSize.height), ")");ncure.setCursor_topright();str_topRight := "Hello!";ncure.moveCursor_left(len(str_topRight));ncure.write_string(str_topRight);ncure.setCursor(5, 4);ncure.write_string(ncure.ForegroundColor.Cyan, "Set cursor to (5, 4)");ncure.moveCursor_down();ncure.moveCursor_right(2);ncure.write_string(ncure.ForegroundColor.Red, "Gone down one and right two!");ncure.moveCursor_up(2);ncure.write_string(ncure.ForegroundColor.Red, "Gone up two lines!");ncure.moveCursor_down(3);ncure.moveCursor_start();ncure.write_string(ncure.ForegroundColor.Green, "Down 3 and Back at start!");ncure.moveCursor_down();}ncure.batch_end();pos := ncure.getCursor();ncure.batch_start();{ncure.write_strings(ncure.ForegroundColor.Blue, "Cursor pos at start of this text: (", strconv.itoa(itoa_buf[:], pos.x), ", ", strconv.itoa(itoa_buf[:], pos.y), ")");ncure.newLine();ncure.moveCursor_end();ncure.write_string("Cursor moved to end of line. Blahhhhh");ncure.moveCursor_left(8);ncure.clearLine_right();ncure.newLine();ncure.write_rune('x');ncure.newLine();}ncure.batch_end();pos = ncure.getCursor();ncure.batch_start();{ncure.setCursor_bottomleft();ncure.write_string("Testing bottom left");ncure.setCursor_bottomright();str_bottomRight := "Testing bottom right";ncure.moveCursor_left(len(str_bottomRight));ncure.write_string(str_bottomRight);ncure.setCursor(pos);ncure.write_string(ncure.ForegroundColor.Green, "Going back to saved cursor position");ncure.newLine();}ncure.batch_end();// Progress bar testtermSize = ncure.getTermSize();division := 10;ncure.batch_start();{ncure.hideCursor();ncure.moveCursor_right((termSize.width / division) + 1);ncure.write_byte('|');ncure.moveCursor_start();ncure.write_byte('|');}ncure.batch_end();for i in 0..<(termSize.width / division) {ncure.write_string(ncure.ForegroundColor.Cyan, "=");time.sleep(1 * time.Second);}ncure.newLine();// Progress bar test 2// with clearLine and write_byte_atncure.moveCursor_right((termSize.width / division) + 1);rightPos := ncure.getCursor();startPos := ncure.CursorPos { 1, rightPos.y };ncure.batch_start();{ncure.write_byte('|');ncure.moveCursor_start();ncure.write_byte('|');}ncure.batch_end();for i in 0..<(termSize.width / division) {ncure.batch_start();ncure.clearLine();// Redraw boundsncure.write_byte_at(rightPos, '|');ncure.write_byte_at(startPos, '|');ncure.write_string(ncure.ForegroundColor.Cyan, "=");ncure.batch_end();time.sleep(1 * time.Second);}ncure.newLine();ncure.showCursor();}
package ncureimport "core:fmt"import "core:os"import "../linux"Input :: enum u8 {CTRL_C = 3,CTRL_D = 4,END_INPUT = 4,CTRL_BACKSPACE = 8,ENTER = 10,CTRL_L = 12,CTRL_O = 15,CTRL_X = 24,CTRL_Z = 26,ESC = 27,SPECIAL1 = 27,SPECIAL2 = 91,LEFT = 68,RIGHT = 67,UP = 65,DOWN = 66,DELETE1 = 51,DELETE2 = 126,END = 70,HOME = 72,BACKSPACE = 127}isSpecial :: proc(c: byte) -> bool {if Input(c) == Input.SPECIAL1 {next := getch();if Input(next) == Input.SPECIAL2 {return true;} else {// TODO}}return false;}disableEcho :: proc(nonblocking := false) -> (prev: linux.termios, current: linux.termios) {if get_error := linux.tcgetattr(os.stdin, &prev); get_error != os.ERROR_NONE {// Errorfmt.println("Error getting terminal info: %s\n", get_error);}current = prev;current.c_lflag &= ~linux.ICANON;current.c_lflag &= ~linux.ECHO;if nonblocking do current.c_cc[linux.VMIN] = 0;else do current.c_cc[linux.VMIN] = 1;current.c_cc[linux.VTIME] = 0;if set_error := linux.tcsetattr(os.stdin, linux.TCSANOW, ¤t); set_error != os.ERROR_NONE {fmt.println("Error setting terminal info: %s\n", set_error);}return prev, current;}enableEcho :: proc() {term: linux.termios;if get_error := linux.tcgetattr(os.stdin, &term); get_error != os.ERROR_NONE {// Errorfmt.println("Error getting terminal info: %s\n", get_error);}term.c_lflag |= linux.ICANON;term.c_lflag |= linux.ECHO;if set_error := linux.tcsetattr(os.stdin, linux.TCSANOW, &term); set_error != os.ERROR_NONE {fmt.println("Error setting terminal info: %s\n", set_error);}}enableBlocking :: proc() {term: linux.termios;if get_error := linux.tcgetattr(os.stdin, &term); get_error != os.ERROR_NONE {// Errorfmt.println("Error getting terminal info: %s\n", get_error);}term.c_cc[linux.VMIN] = 1;term.c_cc[linux.VTIME] = 0;if set_error := linux.tcsetattr(os.stdin, linux.TCSANOW, &term); set_error != os.ERROR_NONE {fmt.println("Error setting terminal info: %s\n", set_error);}}disableBlocking :: proc() {term: linux.termios;if get_error := linux.tcgetattr(os.stdin, &term); get_error != os.ERROR_NONE {// Errorfmt.println("Error getting terminal info: %s\n", get_error);}term.c_cc[linux.VMIN] = 0;term.c_cc[linux.VTIME] = 0;if set_error := linux.tcsetattr(os.stdin, linux.TCSANOW, &term); set_error != os.ERROR_NONE {fmt.println("Error setting terminal info: %s\n", set_error);}}getch :: proc() -> (byte) {data: [1]byte;if bytes_read, _ := os.read(os.stdin, data[:]); bytes_read < 0 {fmt.println("Error reading Input");}return data[0];}
package ncureInput :: enum u8 {CTRL_C = 3,CTRL_L = 12,CTRL_O = 15,CTRL_X = 24,ESC = 27,SPECIAL1 = -32, // TODOSPECIAL2 = 224}isSpecial :: proc(c: byte) -> bool {if c == SPECIAL1 || c == SPECIAL2 {return true;}return false;}
package ncure// TODO: Implement 256 colors, and perhaps true 24-bit colorsTermSize :: struct {width: int,height: int,}CursorPos :: [2]int;ForegroundColor :: enum u8 {Red = 31,BrightRed = 91,Green = 32,BrightGreen = 92,Blue = 34,BrightBlue = 94,Yellow = 33,BrightYellow = 93,Cyan = 36,BrightCyan = 96,Magenta = 35,BrightMagenta = 96,White = 37,BrightWhite = 97,Black = 30,Grey = 90,}BackgroundColor :: enum u8 {Red = 41,BrightRed = 101,Green = 42,BrightGreen = 102,Blue = 44,BrightBlue = 104,Yellow = 43,BrightYellow = 103,Cyan = 46,BrightCyan = 106,Magenta = 45,BrightMagenta = 105,White = 47,BrightWhite = 107,Black = 40,Grey = 100,}
package ncureimport "core:strings"import "core:strconv"import "core:os"import "core:fmt"import "core:mem"import "../linux"ESC :: "\e";SEQUENCE_START :: "\e[";NEWLINE :: "\n";CLEAR :: "\e[2J";CLEAR_DOWN :: "\e[J";CLEAR_UP :: "\e[1J";CLEAR_LINE :: "\e[2K";CLEAR_LINE_RIGHT :: "\e[K";CLEAR_LINE_LEFT :: "\e[1K";TOP_LEFT :: "\e[1;1H";GET_CURSOR :: "\e[6n";HIDE_CURSOR :: "\e[?25l";SHOW_CURSOR :: "\e[?25h";SAVE_CURSOR :: "\e7";RESTORE_CURSOR :: "\e8";MOVE_UP :: "\e[1A";MOVE_DOWN :: "\e[1B";MOVE_LEFT :: "\e[1D";MOVE_RIGHT :: "\e[1C";BatchInfo :: struct {builder: strings.Builder,cursor: CursorPos, // Current cursor position at latest ncure call. NOTE: Doesn't necessarily work properly atm.cursor_start: CursorPos, // Cursor position at the start of a batchsavedCursor: bool,savedCursorPos: CursorPos, // NOTE: Doesn't necessarily work atm.termSize: TermSize,}@private_batch := false;@private_batchInfo: ^BatchInfo = nil; // TODO: Switch to a stack thing so we can have nested Batches@private_createBatchInfo :: proc(batchInfo: ^BatchInfo) {batchInfo.builder = strings.make_builder();batchInfo.cursor = getCursor();batchInfo.cursor_start = batchInfo.cursor;batchInfo.termSize = getTermSize();batchInfo.savedCursor = false;_batchInfo = batchInfo;}@private_destroyBatchInfo :: proc(batchInfo: ^BatchInfo) {strings.destroy_builder(&batchInfo.builder);}batch_start :: proc() -> ^BatchInfo { // TODObatchInfo: ^BatchInfo = cast(^BatchInfo) mem.alloc(size_of(BatchInfo));_createBatchInfo(batchInfo);_batch = true;return batchInfo;}batch_end :: proc() {if _batch != true do return;os.write_string(os.stdout, strings.to_string(_batchInfo.builder));_destroyBatchInfo(_batchInfo);_batch = false;}// TODO: This isn't thread-safe at allbatch :: proc(p: #type proc(batchInfo: ^BatchInfo, args: ..any), args: ..any) {// State for Batch Build: builder, cursor, and termsizebatchInfo: BatchInfo;_createBatchInfo(&batchInfo);defer _destroyBatchInfo(&batchInfo);_batch = true;p(&batchInfo, ..args);os.write_string(os.stdout, strings.to_string(batchInfo.builder));_batch = false;}getTermSize :: proc() -> (termSize: TermSize) {w: linux.winsize;if _, err := linux.ioctl(os.stdout, linux.TIOCGWINSZ, &w); err != os.ERROR_NONE {// Error}termSize.width = int(w.ws_col);termSize.height = int(w.ws_row);return termSize;}getCursor :: proc() -> CursorPos {if _batch do return _batchInfo.cursor;cursor: CursorPos;// Disable Echo, send request, then switch terminal// back to previous settingsprev, _ := disableEcho(false);os.write_string(os.stdout, GET_CURSOR);if set_error := linux.tcsetattr(os.stdin, linux.TCSANOW, &prev); set_error != os.ERROR_NONE {fmt.println("Error setting terminal info: %s\n", set_error);}// Get responseresponse := strings.make_builder();defer strings.destroy_builder(&response);data: byte;for {data = getch();strings.write_byte(&response, data);if data == 'R' do break;}// Parse responseresponse_str := strings.to_string(response);arg1_start: int;arg1_end: int;arg2_start: int;arg2_end: int;for c, i in response_str {if c == '[' do arg1_start = i + 1;if c == ';' {arg1_end = i;arg2_start = i + 1;}if c == 'R' {arg2_end = i;}}arg1 := response_str[arg1_start:arg1_end];arg2 := response_str[arg2_start:arg2_end];cursor.y = strconv.atoi(arg1);cursor.x = strconv.atoi(arg2);return cursor;}getCursor_topleft :: proc() -> CursorPos {return CursorPos {1, 1};}getCursor_topright :: proc(termSize: ^TermSize = nil) -> CursorPos {new_ts: TermSize;if _batch {new_ts = _batchInfo.termSize;} else {new_ts = getTermSize();if termSize != nil do termSize^ = new_ts;}return CursorPos {new_ts.width, 1};}getCursor_bottomleft :: proc(termSize: ^TermSize = nil) -> CursorPos {new_ts: TermSize;if _batch {new_ts = _batchInfo.termSize;} else {new_ts = getTermSize();if termSize != nil do termSize^ = new_ts;}return CursorPos {1, new_ts.height};}getCursor_bottomright :: proc(termSize: ^TermSize = nil) -> CursorPos {new_ts: TermSize;if _batch {new_ts = _batchInfo.termSize;} else {new_ts = getTermSize();if termSize != nil do termSize^ = new_ts;}return CursorPos {new_ts.width, new_ts.height};}hideCursor :: proc() {if _batch {strings.write_string(&_batchInfo.builder, HIDE_CURSOR);} else {os.write_string(os.stdout, HIDE_CURSOR);}}showCursor :: proc() {if _batch {strings.write_string(&_batchInfo.builder, SHOW_CURSOR);} else {os.write_string(os.stdout, SHOW_CURSOR);}}saveCursor :: proc(overwrite := false) {if !overwrite {assert(!_batchInfo.savedCursor, "A cursor has already been saved without being restored.");}if _batch {strings.write_string(&_batchInfo.builder, SAVE_CURSOR);// Set savedCursor so that subsequent commands know when a saved cursor will be overridden_batchInfo.savedCursor = true;_batchInfo.savedCursorPos = _batchInfo.cursor;} else {os.write_string(os.stdout, SAVE_CURSOR);}}restoreCursor :: proc() {if _batch {strings.write_string(&_batchInfo.builder, RESTORE_CURSOR);// Set savedCursor so that subsequent commands know when a saved cursor is being overridden_batchInfo.savedCursor = false;_batchInfo.cursor = _batchInfo.savedCursorPos;} else {os.write_string(os.stdout, RESTORE_CURSOR);}}// TODO: Add option to do something like this in the batching stuff??save_restore :: proc(cursor: CursorPos, f: #type proc()) {saveCursor();setCursor(cursor);f();restoreCursor();}getSequence_set :: proc(x, y: int, b: ^strings.Builder = nil) -> string {if x == 1 && y == 1 {if b != nil {strings.write_string(b, TOP_LEFT);return strings.to_string(b^);}return strings.clone(TOP_LEFT);}buf: [129]byte;builder_new: strings.Builder;builder: ^strings.Builder = b;if b == nil {// Create new builder for this sequence only if not// being added to a pre-existing builder.builder_new = strings.make_builder();builder = &builder_new;}strings.write_string(builder, SEQUENCE_START);if y == 1 do strings.write_string(builder, "1;");else {strings.write_string(builder, strconv.itoa(buf[:], y));strings.write_rune(builder, ';');}if x == 1 do strings.write_string(builder, "1H");else {strings.write_string(builder, strconv.itoa(buf[:], x));strings.write_rune(builder, 'H');}return strings.to_string(builder^);}getSequence_moveup :: proc(amt: int, b: ^strings.Builder = nil) -> string {if amt == 1 {if b != nil {strings.write_string(b, MOVE_UP);return strings.to_string(b^);}return strings.clone(MOVE_UP);}builder_new: strings.Builder;builder: ^strings.Builder = b;if b == nil {// Create new builder for this sequence only if not// being added to a pre-existing builder.builder_new = strings.make_builder();builder = &builder_new;}strings.write_string(builder, SEQUENCE_START);buf: [129]byte;strings.write_string(builder, strconv.itoa(buf[:], amt));strings.write_rune(builder, 'A');return strings.to_string(builder^);}getSequence_movedown :: proc(amt: int, b: ^strings.Builder = nil) -> string {if amt == 1 {if b != nil {strings.write_string(b, MOVE_DOWN);return strings.to_string(b^);}return strings.clone(MOVE_DOWN);}builder_new: strings.Builder;builder: ^strings.Builder = b;if b == nil {// Create new builder for this sequence only if not// being added to a pre-existing builder.builder_new = strings.make_builder();builder = &builder_new;}strings.write_string(builder, SEQUENCE_START);buf: [129]byte;strings.write_string(builder, strconv.itoa(buf[:], amt));strings.write_rune(builder, 'B');return strings.to_string(builder^);}getSequence_moveleft :: proc(amt: int, b: ^strings.Builder = nil) -> string {if amt == 1 {if b != nil {strings.write_string(b, MOVE_LEFT);return strings.to_string(b^);}return strings.clone(MOVE_LEFT);}builder_new: strings.Builder;builder: ^strings.Builder = b;if b == nil {// Create new builder for this sequence only if not// being added to a pre-existing builder.builder_new = strings.make_builder();builder = &builder_new;}strings.write_string(builder, SEQUENCE_START);buf: [129]byte;strings.write_string(builder, strconv.itoa(buf[:], amt));strings.write_rune(builder, 'D');return strings.to_string(builder^);}getSequence_moveright :: proc(amt: int, b: ^strings.Builder = nil) -> string {if amt == 1 {if b != nil {strings.write_string(b, MOVE_RIGHT);return strings.to_string(b^);}return strings.clone(MOVE_RIGHT);}builder_new: strings.Builder;builder: ^strings.Builder = b;if b == nil {// Create new builder for this sequence only if not// being added to a pre-existing builder.builder_new = strings.make_builder();builder = &builder_new;}strings.write_string(builder, SEQUENCE_START);buf: [129]byte;strings.write_string(builder, strconv.itoa(buf[:], amt));strings.write_rune(builder, 'C');return strings.to_string(builder^);}setCursor_xy :: proc(x, y: int, cursor: ^CursorPos = nil, savePrev := false) {str: string;defer delete(str);if savePrev {saveCursor();}if _batch {str := getSequence_set(x, y, &_batchInfo.builder);_batchInfo.cursor.x = x;_batchInfo.cursor.y = y;} else {str := getSequence_set(x, y);defer delete(str);os.write_string(os.stdout, str);}if cursor != nil {cursor.x = x;cursor.y = y;}}setCursor_cursor :: proc(cursor: CursorPos, savePrev := false) {setCursor_xy(x = cursor.x, y = cursor.y, savePrev = savePrev);}setCursor :: proc{setCursor_xy, setCursor_cursor};setCursor_topleft :: proc(cursor: ^CursorPos = nil, savePrev := false) {if savePrev {saveCursor();}if _batch {strings.write_string(&_batchInfo.builder, TOP_LEFT);_batchInfo.cursor.x = 1;_batchInfo.cursor.y = 1;} else {os.write_string(os.stdout, TOP_LEFT);}if cursor != nil {cursor.x = 1;cursor.y = 1;}}setCursor_topright :: proc(termSize: ^TermSize = nil, cursor: ^CursorPos = nil, savePrev := false) {if savePrev {saveCursor();}c := getCursor_topright(termSize);setCursor(c);if cursor != nil do cursor^ = c;}setCursor_bottomleft :: proc(termSize: ^TermSize = nil, cursor: ^CursorPos = nil, savePrev := false) {if savePrev {saveCursor();}c := getCursor_bottomleft(termSize);setCursor(c);if cursor != nil do cursor^ = c;}setCursor_bottomright :: proc(termSize: ^TermSize = nil, cursor: ^CursorPos = nil, savePrev := false) {if savePrev {saveCursor();}c := getCursor_bottomright(termSize);setCursor(c);if cursor != nil do cursor^ = c;}// TODO: Add optional cursor argument to be setmoveCursor_up :: proc(amt: int = 1) {if _batch {str := getSequence_moveup(amt, &_batchInfo.builder);_batchInfo.cursor.y -= amt;} else {str := getSequence_moveup(amt);defer delete(str);os.write_string(os.stdout, str);}}moveCursor_down :: proc(amt: int = 1) {if _batch {str := getSequence_movedown(amt, &_batchInfo.builder);_batchInfo.cursor.y += amt;} else {str := getSequence_movedown(amt);defer delete(str);os.write_string(os.stdout, str);}}moveCursor_left :: proc(amt: int = 1) {if _batch {str := getSequence_moveleft(amt, &_batchInfo.builder);_batchInfo.cursor.x -= amt;} else {str := getSequence_moveleft(amt);defer delete(str);os.write_string(os.stdout, str);}}moveCursor_right :: proc(amt: int = 1) {if _batch {str := getSequence_moveright(amt, &_batchInfo.builder);_batchInfo.cursor.x += amt;} else {str := getSequence_moveright(amt);defer delete(str);os.write_string(os.stdout, str);}}moveCursor_start :: proc() {if _batch {strings.write_byte(&_batchInfo.builder, '\r');_batchInfo.cursor.x = 1;} else {os.write_byte(os.stdout, '\r');}}moveCursor_end :: proc(termSize: ^TermSize = nil) {new_ts: TermSize;moveCursor_start();if _batch {new_ts = _batchInfo.termSize;getSequence_moveright(new_ts.width, &_batchInfo.builder);_batchInfo.cursor.x = new_ts.width;} else {new_ts = getTermSize();if termSize != nil do termSize^ = new_ts;str := getSequence_moveright(new_ts.width);os.write_string(os.stdout, str);}}// TODO: The write and print functions don't change the cursor position correctly// due to needing to scan the string for escape sequences, new lines, \b,// non-printable characters, and combinational utf-8 characterswrite_string_nocolor :: proc(s: string) {if _batch {strings.write_string(&_batchInfo.builder, s);_batchInfo.cursor.x += len(s); // TODO: This would not work with \b, non-printable chars, and escape sequences within the string} else {os.write_string(os.stdout, s);}}write_string_at_nocolor :: proc(cursor: CursorPos, s: string) {saveCursor();setCursor(cursor);write_string_nocolor(s);restoreCursor();}write_string_color :: proc(fg: ForegroundColor, s: string) {setColor(fg);if _batch {strings.write_string(&_batchInfo.builder, s);_batchInfo.cursor.x += len(s); // TODO: This would not work with \b, non-printable chars, and escape sequences within the string} else {os.write_string(os.stdout, s);}resetColors();}write_string_at_color :: proc(cursor: CursorPos, fg: ForegroundColor, s: string) {saveCursor();setCursor(cursor);write_string_color(fg, s);restoreCursor();}write_string :: proc{write_string_nocolor, write_string_color, write_string_at_nocolor, write_string_at_color};// TODO: write_strings functions with ..string arg, but doesn't use print/printf/printlnwrite_strings_nocolor :: proc(args: ..string) {for s in args {write_string(s);}}write_strings_at_nocolor :: proc(cursor: CursorPos, args: ..string) {saveCursor();setCursor(cursor);write_strings_nocolor(..args);restoreCursor();}write_strings_color :: proc(fg: ForegroundColor, args: ..string) {for s in args {write_string(fg, s);}}write_strings_at_color :: proc(cursor: CursorPos, fg: ForegroundColor, args: ..string) {saveCursor();setCursor(cursor);write_strings_color(fg, ..args);restoreCursor();}write_strings :: proc{write_strings_nocolor, write_strings_color, write_strings_at_nocolor, write_strings_at_color};write_line_nocolor :: proc(s: string) {if _batch {strings.write_string(&_batchInfo.builder, s);} else {os.write_string(os.stdout, s);}newLine();}write_line_at_nocolor :: proc(cursor: CursorPos, s: string) {saveCursor();setCursor(cursor);write_line_nocolor(s);restoreCursor();}write_line_color :: proc(fg: ForegroundColor, s: string) {setColor(fg);if _batch {strings.write_string(&_batchInfo.builder, s);} else {os.write_string(os.stdout, s);}resetColors();newLine();}write_line_at_color :: proc(cursor: CursorPos, fg: ForegroundColor, s: string) {saveCursor();setCursor(cursor);write_line_color(fg, s);restoreCursor();}write_line :: proc{write_line_nocolor, write_line_color, write_line_at_nocolor, write_line_at_color};write_byte_current :: proc(b: byte) {if _batch {strings.write_byte(&_batchInfo.builder, b);_batchInfo.cursor.x += 1;} else {os.write_byte(os.stdout, b);}}write_byte_at :: proc(cursor: CursorPos, b: byte) {saveCursor();setCursor(cursor);write_byte_current(b);restoreCursor();}write_byte :: proc{write_byte_current, write_byte_at};write_rune_current :: proc(r: rune) {if _batch {strings.write_rune(&_batchInfo.builder, r);_batchInfo.cursor.x += 1; // TODO: non-printable/combinational rune} else {os.write_rune(os.stdout, r);}}write_rune_at :: proc(cursor: CursorPos, r: rune) {saveCursor();setCursor(cursor);write_rune_current(r);restoreCursor();}write_rune :: proc{write_rune_current, write_rune_at};// TODO: Not sure how to handle separatorprint_nocolor :: proc(args: ..any, sep := " ") {if _batch {fmt.sbprint(&_batchInfo.builder, ..args);} else {fmt.print(..args);}}print_at_nocolor :: proc(cursor: CursorPos, args: ..any, sep := " ") {saveCursor();setCursor(cursor);print_nocolor(..args);restoreCursor();}print_color :: proc(fg: ForegroundColor, args: ..any, sep := " ") {setColor(fg);if _batch {fmt.sbprint(&_batchInfo.builder, ..args);} else {fmt.print(..args);}resetColors();}print_at_color :: proc(cursor: CursorPos, fg: ForegroundColor, args: ..any, sep := " ") {saveCursor();setCursor(cursor);print_color(fg, ..args);restoreCursor();}print :: proc{print_nocolor, print_color, print_at_nocolor, print_at_color};println_nocolor :: proc(args: ..any, sep := " ") {if _batch {fmt.sbprintln(&_batchInfo.builder, ..args);_batchInfo.cursor.y += 1; // For the last newline} else {fmt.println(..args);}}println_at_nocolor :: proc(cursor: CursorPos, args: ..any, sep := " ") {saveCursor();setCursor(cursor);println_nocolor(..args);restoreCursor();}println_color :: proc(fg: ForegroundColor, args: ..any, sep := " ") {setColor(fg);if _batch {fmt.sbprintln(&_batchInfo.builder, ..args);_batchInfo.cursor.y += 1; // For the last newline} else {fmt.println(..args);}resetColors();}println_at_color :: proc(cursor: CursorPos, fg: ForegroundColor, args: ..any, sep := " ") {saveCursor();setCursor(cursor);println_color(fg, ..args);restoreCursor();}println :: proc{println_nocolor, println_color, println_at_nocolor, println_at_color};printf_nocolor :: proc(format: string, args: ..any) {if _batch {fmt.sbprintf(&_batchInfo.builder, format, ..args);} else {fmt.printf(format, ..args);}}printf_at_nocolor :: proc(cursor: CursorPos, format: string, args: ..any) {saveCursor();setCursor(cursor);printf_nocolor(format, ..args);restoreCursor();}printf_color :: proc(fg: ForegroundColor, format: string, args: ..any) {setColor(fg);if _batch {fmt.sbprintf(&_batchInfo.builder, format, ..args);} else {fmt.printf(format, ..args);}resetColors();}printf_at_color :: proc(cursor: CursorPos, fg: ForegroundColor, format: string, args: ..any) {saveCursor();setCursor(cursor);printf_color(fg, format, ..args);restoreCursor();}printf :: proc{printf_nocolor, printf_color, printf_at_nocolor, printf_at_color};newLine :: proc(amt: int = 1) {if _batch {for i in 0..<amt {strings.write_string(&_batchInfo.builder, NEWLINE);}_batchInfo.cursor.x = 1;_batchInfo.cursor.y += amt;} else {for i in 0..<amt {os.write_string(os.stdout, NEWLINE);}}}clearScreen :: proc() {if _batch {// Clearing the screen with erase everything before it.// Therefore, we can reset everything that was already in// the string builderstrings.reset_builder(&_batchInfo.builder);strings.write_string(&_batchInfo.builder, CLEAR);} else {os.write_string(os.stdout, CLEAR);}}clearLine :: proc() {if _batch {strings.write_string(&_batchInfo.builder, CLEAR_LINE);} else {os.write_string(os.stdout, CLEAR_LINE);}}clearLine_right :: proc() {if _batch {strings.write_string(&_batchInfo.builder, CLEAR_LINE_RIGHT);} else {os.write_string(os.stdout, CLEAR_LINE_RIGHT);}}clearLine_left :: proc() {if _batch {strings.write_string(&_batchInfo.builder, CLEAR_LINE_LEFT);} else {os.write_string(os.stdout, CLEAR_LINE_LEFT);}}backspace :: proc(amt := 1, clear := true) {if _batch {// TODO: This doesn't handle escape sequences, non-printable characters, or combinational characters// TODO: Problem - doing a backspace after a backspace that has added escape sequences will result// in the deletion of some of the previous backspace, potentially./*for i in 0..<min(amt, strings.builder_len(_batchInfo.builder)) {strings.pop_rune(&_batchInfo.builder);}*/// If trying to backspace more than what was buffered, then// just add new escape sequences to the buffer to do this.// diff := amt - strings.builder_len(_batchInfo.builder);diff := amt;if (diff > 0) {moveCursor_left(diff);if clear do clearLine_right();else {for i in 0..<diff {os.write_string(os.stdout, " ");}moveCursor_left(diff);}}} else {moveCursor_left(amt);if clear do clearLine_right();else {for i in 0..<amt {os.write_string(os.stdout, " ");}moveCursor_left(amt);}}}
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 linuximport "core:c"import "core:strings"import "core:os"import "core:fmt"foreign import libc "system:c"pid_t :: #type u32;uid_t :: #type u32;gid_t :: #type u32;passwd :: struct {pw_name: cstring,pw_passwd: cstring,pw_uid: uid_t,pw_gid: gid_t,pw_gecos: cstring,pw_dir: cstring,pw_shell: cstring,}DIR :: opaque [0]byte;ino_t :: distinct c.ulong;off_t :: distinct c.long;// NOTE: The only fields in the dirent structure that are mandated by POSIX.1 are d_name and d_ino. The other fields are unstandardized, and not present on all systems.// See Notes in `man 3 readdir` for more info about structure and size of structure.dirent :: struct { // TODO: Make this a raw version and another struct that's easier to use.d_ino: ino_t, // Inode number of the filed_off: off_t,d_reclen: c.ushort, // Size in bytes of the returned recordd_type: c.uchar, // File typed_name: [256]c.char, // Name of file, null-terminated, can exceede 256 chars (in which case d_reclen exceedes the size of this struct)}// -- termios stuff --cc_t :: distinct c.uchar;speed_t :: distinct c.uint;tcflag_t :: distinct c.uint;NCCS :: 32;termios :: struct {c_iflag: tcflag_t, // Input modesc_oflag: tcflag_t, // Output modesc_cflag: tcflag_t, // Control modesc_lflag: tcflag_t, // Local modesc_line: cc_t,c_cc: [NCCS]cc_t, // Special charactersc_ispeed: speed_t, // Input speedc_ospeed: speed_t // Output speed}/* c_cc characters */VINTR :: 0;VQUIT :: 1;VERASE :: 2;VKILL :: 3;VEOF :: 4;VTIME :: 5;VMIN :: 6;VSWTC :: 7;VSTART :: 8;VSTOP :: 9;VSUSP :: 10;VEOL :: 11;VREPRINT :: 12;VDISCARD :: 13;VWERASE :: 14;VLNEXT :: 15;VEOL2 :: 16;/* c_iflag bits */IGNBRK: tcflag_t : 0000001;BRKINT: tcflag_t : 0000002;IGNPAR: tcflag_t : 0000004;PARMRK: tcflag_t : 0000010;INPCK: tcflag_t : 0000020;ISTRIP: tcflag_t : 0000040;INLCR: tcflag_t : 0000100;IGNCR: tcflag_t : 0000200;ICRNL: tcflag_t : 0000400;IUCLC: tcflag_t : 0001000;IXON: tcflag_t : 0002000;IXANY: tcflag_t : 0004000;IXOFF: tcflag_t : 0010000;IMAXBEL :: 0020000;IUTF8 :: 0040000;/* c_oflag bits */OPOST :: 0000001;OLCUC :: 0000002;ONLCR :: 0000004;OCRNL :: 0000010;ONOCR :: 0000020;ONLRET :: 0000040;OFILL :: 0000100;OFDEL :: 0000200;/*#if defined __USE_MISC || defined __USE_XOPEN# define NLDLY 0000400# define NL0 0000000# define NL1 0000400# define CRDLY 0003000# define CR0 0000000# define CR1 0001000# define CR2 0002000# define CR3 0003000# define TABDLY 0014000# define TAB0 0000000# define TAB1 0004000# define TAB2 0010000# define TAB3 0014000# define BSDLY 0020000# define BS0 0000000# define BS1 0020000# define FFDLY 0100000# define FF0 0000000# define FF1 0100000#endif*/VTDLY :: 0040000;VT0 :: 0000000;VT1 :: 0040000;/*#ifdef __USE_MISC# define XTABS 0014000#endif*//* c_cflag bit meaning *//*#ifdef __USE_MISC# define CBAUD 0010017#endif*/B0 :: 0000000; /* hang up */B50 :: 0000001;B75 :: 0000002;B110 :: 0000003;B134 :: 0000004;B150 :: 0000005;B200 :: 0000006;B300 :: 0000007;B600 :: 0000010;B1200 :: 0000011;B1800 :: 0000012;B2400 :: 0000013;B4800 :: 0000014;B9600 :: 0000015;B19200 :: 0000016;B38400 :: 0000017;// #ifdef __USE_MISC// # define EXTA B19200// # define EXTB B38400// #endifCSIZE :: 0000060;CS5 :: 0000000;CS6 :: 0000020;CS7 :: 0000040;CS8 :: 0000060;CSTOPB :: 0000100;CREAD :: 0000200;PARENB :: 0000400;PARODD :: 0001000;HUPCL :: 0002000;CLOCAL :: 0004000;// #ifdef __USE_MISC// # define CBAUDEX 0010000// #endifB57600 :: 0010001;B115200 :: 0010002;B230400 :: 0010003;B460800 :: 0010004;B500000 :: 0010005;B576000 :: 0010006;B921600 :: 0010007;B1000000 :: 0010010;B1152000 :: 0010011;B1500000 :: 0010012;B2000000 :: 0010013;B2500000 :: 0010014;B3000000 :: 0010015;B3500000 :: 0010016;B4000000 :: 0010017;__MAX_BAUD :: B4000000;// #ifdef __USE_MISC// # define CIBAUD 002003600000 /* input baud rate (not used) */// # define CMSPAR 010000000000 /* mark or space (stick) parity */// # define CRTSCTS 020000000000 /* flow control */// #endif/* c_lflag bits */ISIG :: 0000001;ICANON: tcflag_t : 0000002;// #if defined __USE_MISC || (defined __USE_XOPEN && !defined __USE_XOPEN2K)// # define XCASE 0000004// #endifECHO: tcflag_t : 0000010;ECHOE :: 0000020;ECHOK :: 0000040;ECHONL :: 0000100;NOFLSH :: 0000200;TOSTOP :: 0000400;/*#ifdef __USE_MISC# define ECHOCTL 0001000# define ECHOPRT 0002000# define ECHOKE 0004000# define FLUSHO 0010000# define PENDIN 0040000#endif*/IEXTEN :: 0100000;/*#ifdef __USE_MISC# define EXTPROC 0200000#endif*/TCSANOW :: 0;TCSADRAIN :: 1;TCSAFLUSH :: 2;// -- ioctl --winsize :: struct {ws_row: c.ushort,ws_col: c.ushort,ws_xpixel: c.ushort,ws_ypixel: c.ushort,}TIOCGWINSZ :: 21523;// wait & waitpid OptionsWNOHANG :: 1; // Return immediately if no child has exitedWUNTRACED :: 2; // Also return if a child has stopped (but not traced via ptrace).WCONTINUED :: 8; // Also return if a stopped child has been resumed by delivery of SIGCONTWIFEXITED :: inline proc(status: int) -> bool {return (((status) & 0x7f) == 0);}WIFSIGNALED :: inline proc(status: int) -> bool {return (((byte) (((status) & 0x7f) + 1) >> 1) > 0);}// Signal Handlingsighandler_t :: #type proc "c" (signum: c.int);SIG_IGN : uintptr : 1;SIG_DFL : uintptr : 0;SIG_ERR : uintptr : ~uintptr(0); // -1/* ISO C99 signals. */SIGINT :: 2; /* Interactive attention signal. */SIGILL :: 4; /* Illegal instruction. */SIGABRT :: 6; /* Abnormal termination. */SIGFPE :: 8; /* Erroneous arithmetic operation. */SIGSEGV :: 11; /* Invalid access to storage. */SIGTERM :: 15; /* Termination request. *//* Historical signals specified by POSIX. */SIGHUP :: 1; /* Hangup. */SIGQUIT :: 3; /* Quit. */SIGTRAP :: 5; /* Trace/breakpoint trap. */SIGKILL :: 9; /* Killed. */// SIGBUS :: 10; /* Bus error. */// SIGSYS :: 12; /* Bad system call. */SIGPIPE :: 13; /* Broken pipe. */SIGALRM :: 14; /* Alarm clock. *//* New(er) POSIX signals (1003.1-2008, 1003.1-2013). */// SIGURG :: 16; /* Urgent data is available at a socket. */// SIGSTOP :: 17; /* Stop, unblockable. */// SIGTSTP :: 18; /* Keyboard stop. */// SIGCONT :: 19; /* Continue. */SIGCHLD_ISO :: 20; /* Child terminated or stopped. */SIGTTIN :: 21; /* Background read from control terminal. */SIGTTOU :: 22; /* Background write to control terminal. */SIGPOLL_ISO :: 23; /* Pollable event occurred (System V). */SIGXCPU :: 24; /* CPU time limit exceeded. */SIGXFSZ :: 25; /* File size limit exceeded. */SIGVTALRM :: 26; /* Virtual timer expired. */SIGPROF :: 27; /* Profiling timer expired. */// SIGUSR1 :: 30; /* User-defined signal 1. */// SIGUSR2 :: 31; /* User-defined signal 2. *//* Nonstandard signals found in all modern POSIX systems(including both BSD and Linux). */SIGWINCH :: 28; /* Window size change (4.3 BSD, Sun). *//* Archaic names for compatibility. */SIGIO :: SIGPOLL_ISO; /* I/O now possible (4.2 BSD). */SIGIOT :: SIGABRT; /* IOT instruction, abort() on a PDP-11. */SIGCLD :: SIGCHLD_ISO; /* Old System V name */// Signal Adjustment for LinuxSIGSTKFLT :: 16; /* Stack fault (obsolete). */SIGPWR :: 30; /* Power failure imminent. */SIGBUS :: 7;SIGUSR1 :: 10;SIGUSR2 :: 12;SIGCHLD :: 17;SIGCONT :: 18;SIGSTOP :: 19;SIGTSTP :: 20; /* terminal stop - Ctrl+Z */SIGURG :: 23;SIGPOLL :: 29;SIGSYS :: 31;foreign libc {@(link_name="getuid") getuid :: proc() -> uid_t ---;@(link_name="getpwnam") _unix_getpwnam :: proc(name: cstring) -> ^passwd ---;@(link_name="getpwuid") getpwuid :: proc(uid: uid_t) -> ^passwd ---;@(link_name="readlink") _unix_readlink :: proc(pathname: cstring, buf: cstring, bufsiz: c.size_t) -> c.ssize_t ---;@(link_name="opendir") _unix_opendir :: proc(name: cstring) -> ^DIR ---; // TODO: Returns ^DIR (which is defined as __dirstream in dirent.h)@(link_name="readdir") _unix_readdir :: proc(dirp: ^DIR) -> ^dirent ---;@(link_name="closedir") _unix_closedir :: proc(dirp: ^DIR) -> c.int ---;@(link_name="setenv") _unix_setenv :: proc(name: cstring, value: cstring, overwrite: c.int) -> c.int ---;@(link_name="unsetenv") _unix_unsetenv :: proc(name: cstring) -> c.int ---;@(link_name="secure_getenv") _unix_secure_getenv :: proc(name: cstring) -> cstring ---; // NOTE: GNU-specific@(link_name="tcgetattr") _unix_tcgetattr :: proc(fd: os.Handle, termios_p: ^termios) -> c.int ---;@(link_name="tcsetattr") _unix_tcsetattr :: proc(fd: os.Handle, optional_actions: c.int, termios_p: ^termios) -> c.int ---;@(link_name="ioctl") _unix_ioctl :: proc(fd: os.Handle, request: c.ulong, argp: rawptr) -> c.int ---;@(link_name="fork") fork :: proc() -> pid_t ---;@(link_name="execvp") _unix_execvp :: proc(file: cstring, argv: ^cstring) -> c.int ---;@(link_name="waitpid") _unix_waitpid :: proc(pid: pid_t, wstatus: ^c.int, options: c.int) -> pid_t ---;@(link_name="signal") _unix_signal :: proc(signum: c.int, handler: c.uintptr_t) -> c.uintptr_t ---;}getpwnam :: proc(name: string) -> ^passwd {cstr := strings.clone_to_cstring(name);defer delete(cstr);return _unix_getpwnam(cstr);}readlink :: proc(pathname: string, buf: []u8) -> (int, os.Errno) {cstr_pathname := strings.clone_to_cstring(pathname);defer delete(cstr_pathname);bytes_written := _unix_readlink(cstr_pathname, cstring(#no_bounds_check &buf[0]), c.size_t(len(buf)));if bytes_written == -1 {return -1, os.Errno(os.get_last_error());}return int(bytes_written), os.ERROR_NONE;}opendir :: proc(name: string) -> (^DIR, os.Errno) {cstr := strings.clone_to_cstring(name);defer delete(cstr);result := _unix_opendir(cstr);if result == nil {return nil, os.Errno(os.get_last_error());}return result, os.ERROR_NONE;}readdir :: proc(dirp: ^DIR) -> (^dirent, os.Errno) {previous := os.Errno(os.get_last_error());result := _unix_readdir(dirp);err := os.Errno(os.get_last_error());if result == nil && previous != err { // If nil and errno changed, err occuredreturn nil, err;} else if result == nil { // If errno not changed, end of directory streamreturn nil, os.ERROR_NONE;}return result, os.ERROR_NONE;}closedir :: proc(dirp: ^DIR) -> os.Errno {result := _unix_closedir(dirp);if result == 0 {return os.ERROR_NONE;} else {return os.Errno(os.get_last_error());}}setenv :: proc(name: string, value: string, overwrite: bool) -> os.Errno {name_str := strings.clone_to_cstring(name);defer delete(name_str);value_str := strings.clone_to_cstring(value);defer delete(value_str);result := _unix_setenv(name_str, value_str, overwrite ? 1 : 0);if result == -1 {return os.Errno(os.get_last_error());}return os.ERROR_NONE;}unsetenv :: proc(name: string) -> os.Errno {name_str := strings.clone_to_cstring(name);defer delete(name_str);result := _unix_unsetenv(name_str);if result == -1 {return os.Errno(os.get_last_error());}return os.ERROR_NONE;}// NOTE: GNU-specificsecure_getenv :: proc(name: string) -> (string, bool) {path_str := strings.clone_to_cstring(name);defer delete(path_str);cstr := _unix_secure_getenv(path_str);if cstr == nil {return "", false;}return string(cstr), true;}tcgetattr :: proc(fd: os.Handle, termios_p: ^termios) -> os.Errno {result := _unix_tcgetattr(fd, termios_p);if result == -1 {return os.Errno(os.get_last_error());}return os.ERROR_NONE;}tcsetattr :: proc(fd: os.Handle, optional_actions: int, termios_p: ^termios) -> os.Errno {result := _unix_tcsetattr(fd, c.int(optional_actions), termios_p);if result == -1 {return os.Errno(os.get_last_error());}return os.ERROR_NONE;}ioctl :: proc(fd: os.Handle, request: u64, argp: rawptr) -> (int, os.Errno) {result := _unix_ioctl(fd, c.ulong(request), argp);if result == -1 {return -1, os.Errno(os.get_last_error());}return int(result), os.ERROR_NONE;}/*execvp :: proc(file: string, args: []string) -> os.Errno {file_str := strings.clone_to_cstring(file);defer delete(file_str);result := _unix_execvp(file_str, );if result == -1 {return os.Errno(os.get_last_error());}return os.ERROR_NONE;}*/waitpid :: proc(pid: pid_t, wstatus: ^int, options: int) -> pid_t {return _unix_waitpid(pid, cast(^c.int) wstatus, c.int(options));}signal_handler :: proc(signum: int, handler: sighandler_t) -> (uintptr, os.Errno) {result := _unix_signal(c.int(signum), c.uintptr_t(uintptr(rawptr(handler))));if c.uintptr_t(result) == c.uintptr_t(SIG_ERR) {return uintptr(result), os.Errno(os.get_last_error());}return uintptr(result), os.ERROR_NONE;}signal_int :: proc(signum: int, handler: uintptr) -> (uintptr, os.Errno) {result := _unix_signal(c.int(signum), c.uintptr_t(handler));if result == SIG_ERR {return uintptr(result), os.Errno(os.get_last_error());}return uintptr(result), os.ERROR_NONE;}signal :: proc{signal_handler, signal_int};// TODO: Doesn't work on any BSDs or MacOS// TODO: Look at 'realpath'get_executable_path :: proc() -> string {pathname :: "/proc/self/exe";page_size := os.get_page_size();buf := make([dynamic]u8, page_size);// Credit for this loop technique: Tetraluxfor {bytes_written, error := readlink(pathname, buf[:]);if error == os.ERROR_NONE {resize(&buf, bytes_written);return string(buf[:]);}if error != os.ERANGE {return "";}resize(&buf, len(buf)+page_size);}unreachable();}get_path_separator :: proc() -> rune {return '/';}
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;}