B4ZO42ZKQGSFEFBDJAO62P5T6KRXKWJA5S6SIXNGAPKFQNLHGHFQC
package tokenizer
import "core:fmt"
import "core:strings"
import "core:unicode/utf8"
Set :: distinct map[string]bool;
// TODO: Use an arena allocator
Tokenizer :: struct {
source: string,
keywords: ^Set,
ident_allowWhitespace: bool,
tokenContext: TokenizerContext,
line, col: int,
newLine: bool, // TODO
backslashEscape: bool, // TODO
quoted: 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 characters
Null = 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/input
tok.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 token
endToken := 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 Whitespace
if 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 '-': // TODO
case '+': {
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 identifiers
if 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 strings
if 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 literal
handleChar :: 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* allowed
token := makeToken(TokenType.Number, source[tokenStart:currentIndex], line, col);
append(&tokens, token);
tokenContext = TokenizerContext.None;
handleNone(tok);
}
}
}
package ncure
package ncure
import "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 batch
savedCursor: 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 { // TODO
batchInfo: ^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 all
batch :: proc(p: #type proc(batchInfo: ^BatchInfo, args: ..any), args: ..any) {
// State for Batch Build: builder, cursor, and termsize
batchInfo: 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 settings
prev, _ := 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 response
response := strings.make_builder();
defer strings.destroy_builder(&response);
data: byte;
for {
data = getch();
strings.write_byte(&response, data);
if data == 'R' do break;
}
// Parse response
response_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 set
moveCursor_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 characters
write_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/println
write_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 separator
print_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 builder
strings.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 colors
TermSize :: 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 ncure
Input :: enum u8 {
CTRL_C = 3,
CTRL_L = 12,
CTRL_O = 15,
CTRL_X = 24,
ESC = 27,
SPECIAL1 = -32, // TODO
SPECIAL2 = 224
}
isSpecial :: proc(c: byte) -> bool {
if c == SPECIAL1 || c == SPECIAL2 {
return true;
}
return false;
}
package ncure
import "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 {
// Error
fmt.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 {
// Error
fmt.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 {
// Error
fmt.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 {
// Error
fmt.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 main
import 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 test
termSize = 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_at
ncure.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 bounds
ncure.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 ncure
import "core:os"
import "core:strings"
import "core:strconv"
RESET_COLORS :: "\e[0m"; // TODO: \e[39;49m
setColor_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 linux
import "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 file
d_off: off_t,
d_reclen: c.ushort, // Size in bytes of the returned record
d_type: c.uchar, // File type
d_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 modes
c_oflag: tcflag_t, // Output modes
c_cflag: tcflag_t, // Control modes
c_lflag: tcflag_t, // Local modes
c_line: cc_t,
c_cc: [NCCS]cc_t, // Special characters
c_ispeed: speed_t, // Input speed
c_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
// #endif
CSIZE :: 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
// #endif
B57600 :: 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
// #endif
ECHO: 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 Options
WNOHANG :: 1; // Return immediately if no child has exited
WUNTRACED :: 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 SIGCONT
WIFEXITED :: inline proc(status: int) -> bool {
return (((status) & 0x7f) == 0);
}
WIFSIGNALED :: inline proc(status: int) -> bool {
return (((byte) (((status) & 0x7f) + 1) >> 1) > 0);
}
// Signal Handling
sighandler_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 Linux
SIGSTKFLT :: 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 occured
return nil, err;
} else if result == nil { // If errno not changed, end of directory stream
return 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-specific
secure_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: Tetralux
for {
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 '/';
}