B4ZO42ZKQGSFEFBDJAO62P5T6KRXKWJA5S6SIXNGAPKFQNLHGHFQC 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);}}}
package ncure
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 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 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 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 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 ncure
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 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 '/';}