srctree

Gregory Mullen parent e0e87235
init commit

filename was Deleted added: 553, removed: 5, total 548
@@ -0,0 +1,2 @@
zig-cache
zig-out
 
filename was Deleted added: 553, removed: 5, total 548
@@ -0,0 +1,18 @@
# hsh
Don't use this, it's not ready!
 
## Install
install zig<br>
clone<br>
`zig build run`<br>
 
## TODO
- [x] basic parsing
- [ ] tab complete
- [ ] history
- [ ] complex parsing
- [ ] built ins
- [ ] globs
- [ ] script support?
- [ ] env
 
 
filename was Deleted added: 553, removed: 5, total 548
@@ -0,0 +1,33 @@
const std = @import("std");
 
// near default build.zig from 0.11.0-dev.1908+06b263825a
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
 
const optimize = b.standardOptimizeOption(.{});
 
const exe = b.addExecutable(.{
.name = "hsh",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
 
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
 
const exe_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
 
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}
 
filename was Deleted added: 553, removed: 5, total 548
@@ -0,0 +1,318 @@
const std = @import("std");
const mem = std.mem;
const fs = std.fs;
const io = std.io;
const os = std.os;
const Allocator = mem.Allocator;
const ArrayList = std.ArrayList;
const File = fs.File;
const Reader = io.Reader(File, File.ReadError, File.read);
const TTY_ = @import("tty.zig");
const TTY = TTY_.TTY;
const tty_codes = TTY_.OpCodes;
 
const TokenType = enum(u8) {
Unknown,
String,
};
 
const Token = struct {
raw: []const u8,
type: TokenType = TokenType.Unknown,
};
 
const Tokenizer = struct {
alloc: Allocator,
raw: ArrayList(u8),
tokens: ArrayList(Token),
 
pub const TokenError = error{
None,
Unknown,
LineTooLong,
ParseError,
};
 
const Builtin = [_][]const u8{
"alias",
"which",
"echo",
};
 
pub fn init(a: Allocator) Tokenizer {
return Tokenizer{
.alloc = a,
.raw = ArrayList(u8).init(a),
.tokens = ArrayList(Token).init(a),
};
}
 
pub fn raze(self: Tokenizer) void {
self.alloc.deinit();
}
 
pub fn parse_string(self: *Tokenizer, src: []const u8) TokenError!Token {
_ = self;
var end: usize = 0;
for (src, 0..) |s, i| {
end = i;
if (s == ' ') {
break;
}
} else end += 1;
return Token{
.raw = src[0..end],
.type = TokenType.String,
};
}
 
pub fn parse(self: *Tokenizer) TokenError!void {
self.tokens.clearAndFree();
var start: usize = 0;
while (start < self.raw.items.len) {
const t = self.parse_string(self.raw.items[start..]);
if (t) |tt| {
if (tt.raw.len > 0) {
self.tokens.append(tt) catch unreachable;
start += tt.raw.len;
} else {
start += 1;
}
} else |_| {
return TokenError.ParseError;
}
}
}
 
pub fn dump_parsed(self: Tokenizer) !void {
std.debug.print("\n\n", .{});
for (self.tokens.items) |i| {
std.debug.print("{}\n", .{i});
std.debug.print("{s}\n", .{i.raw});
}
}
 
pub fn tab(self: Tokenizer) !bool {
_ = self;
return true;
}
 
pub fn pop(self: *Tokenizer) TokenError!void {
_ = self.raw.popOrNull();
}
pub fn consumec(self: *Tokenizer, c: u8) TokenError!void {
self.raw.append(c) catch return TokenError.Unknown;
}
 
pub fn clear(self: *Tokenizer) void {
self.raw.clearAndFree();
self.tokens.clearAndFree();
}
 
pub fn consumes(self: *Tokenizer, r: Reader) TokenError!void {
var buf: [2 ^ 8]u8 = undefined;
var line = r.readUntilDelimiterOrEof(&buf, '\n') catch |e| {
if (e == error.StreamTooLong) {
return TokenError.LineTooLong;
}
return TokenError.Unknown;
};
self.raw.appendSlice(line.?) catch return TokenError.Unknown;
}
};
 
fn prompt(tty: *TTY, tkn: *Tokenizer) !void {
try tty.prompt("\r{s}@{s}({})({}) # {s}", .{
"username",
"host",
tkn.raw.items.len,
tkn.tokens.items.len,
tkn.raw.items,
});
}
 
pub fn csi(tty: *TTY) !void {
var buffer: [1]u8 = undefined;
_ = try os.read(tty.tty, &buffer);
if (buffer[0] == 'D') {
tty.chadj += 1;
} else if (buffer[0] == 'C') {
tty.chadj -= 1;
} else {
try tty.print("\r\nCSI next: \r\n", .{});
try tty.printAfter(" {x} {s}", .{ buffer[0], buffer });
}
}
 
pub fn loop(tty: *TTY, tkn: *Tokenizer) !bool {
while (true) {
try prompt(tty, tkn);
var buffer: [1]u8 = undefined;
_ = try os.read(tty.tty, &buffer);
switch (buffer[0]) {
'\x1B' => {
_ = try os.read(tty.tty, &buffer);
if (buffer[0] == '[') {
try csi(tty);
} else {
try tty.print("\r\ninput: escape {s} {}\n", .{ buffer, buffer[0] });
}
},
'\x08' => try tty.print("\r\ninput: backspace\r\n", .{}),
'\x09' => {
if (tkn.tab()) |tab| {
if (tab) {} else {}
} else |err| {
_ = err;
unreachable;
}
},
'\x7F' => try tkn.pop(),
'\x17' => try tty.print("\r\ninput: ^w\r\n", .{}),
'\x03' => {
if (tkn.raw.items.len >= 0) {
try tty.print("^C\r\n", .{});
tkn.clear();
} else {
try tty.print("\r\nExit caught... Bye ()\r\n", .{});
return false;
}
},
'\x04' => |b| {
try tty.print("\r\nExit caught... Bye ({})\r\n", .{b});
return false;
},
'\n', '\r' => {
try tty.print("\r\n", .{});
try tkn.parse();
try tkn.dump_parsed();
if (tkn.tokens.items.len > 0) {
return true;
}
},
else => |b| {
try tkn.consumec(b);
try tty.printAfter(" {} {s}", .{ b, buffer });
},
}
}
}
 
test "c memory" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const a = arena.allocator();
 
var tkn = Tokenizer.init(a);
for ("ls -la") |c| {
try tkn.consumec(c);
}
try tkn.parse();
 
var argv: [:null]?[*:0]u8 = undefined;
var list = ArrayList(?[*:0]u8).init(a);
try std.testing.expect(tkn.tokens.items.len == 2);
try std.testing.expect(mem.eql(u8, tkn.tokens.items[0].raw, "ls"));
for (tkn.tokens.items) |token| {
var arg = a.alloc(u8, token.raw.len + 1) catch unreachable;
mem.copy(u8, arg, token.raw);
arg[token.raw.len] = 0;
try list.append(@ptrCast(?[*:0]u8, arg.ptr));
}
try std.testing.expect(list.items.len == 2);
argv = list.toOwnedSliceSentinel(null) catch unreachable;
 
try std.testing.expect(mem.eql(u8, argv[0].?[0..2 :0], "ls"));
try std.testing.expect(mem.eql(u8, argv[1].?[0..3 :0], "-la"));
try std.testing.expect(argv[2] == null);
}
 
pub fn exec(tty: *TTY, tkn: *Tokenizer) !void {
_ = tty;
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const a = arena.allocator();
 
var argv: [:null]?[*:0]u8 = undefined;
var list = ArrayList(?[*:0]u8).init(a);
for (tkn.tokens.items) |token| {
var arg = a.alloc(u8, token.raw.len + 1) catch unreachable;
mem.copy(u8, arg, token.raw);
arg[token.raw.len] = 0;
try list.append(@ptrCast(?[*:0]u8, arg.ptr));
}
argv = list.toOwnedSliceSentinel(null) catch unreachable;
 
const fork_pid = try std.os.fork();
if (fork_pid == 0) {
// TODO manage env
const res = std.os.execvpeZ(argv[0].?, argv, @ptrCast([*:null]?[*:0]u8, std.os.environ));
std.debug.print("exec error {}", .{res});
unreachable;
} else {
const res = std.os.waitpid(fork_pid, 0);
std.debug.print("fork res {}", .{res.status});
}
}
 
pub fn main() !void {
std.debug.print("All your {s} are belong to us.\n\n", .{"codebase"});
var tty = TTY.init() catch unreachable;
defer tty.raze();
 
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var t = Tokenizer.init(arena.allocator());
 
while (true) {
if (loop(&tty, &t)) |l| {
if (l) {
try exec(&tty, &t);
t.clear();
} else {
break;
}
} else |err| {
std.debug.print("unexpected error {}\n", .{err});
unreachable;
}
}
}
 
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
 
test "alloc" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const a = arena.allocator();
 
var t = Tokenizer.init(a);
try expect(std.mem.eql(u8, t.raw.items, ""));
}
 
test "tokens" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const a = arena.allocator();
 
var parsed = Tokenizer.init(a);
for ("token") |c| {
try parsed.consumec(c);
}
try parsed.parse();
try expect(std.mem.eql(u8, parsed.raw.items, "token"));
}
 
test "parse string" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const a = arena.allocator();
 
var t = Tokenizer.init(a);
var tkn = t.parse_string("string is true");
if (tkn) |tk| {
try expect(std.mem.eql(u8, tk.raw, "string"));
try expect(tk.raw.len == 6);
} else |_| {}
}
 
filename was Deleted added: 553, removed: 5, total 548
@@ -0,0 +1,177 @@
const std = @import("std");
const os = std.os;
const mem = std.mem;
const fs = std.fs;
const File = fs.File;
const io = std.io;
const Reader = fs.File.Reader;
//io.Reader(File, File.ReadError, File.read);
const Writer = fs.File.Writer;
//io.Writer(File, File.ReadError, File.read);
//const Writer = std.io.Writer;
 
const Point = struct {
x: usize,
y: usize,
};
 
pub const OpCodes = enum {
EraseInLine,
CurPosGet,
CurPosSet,
CurMvUp,
CurMvDn,
CurMvLe,
CurMvRi,
CurHorzAbs,
};
 
pub const TTY = struct {
tty: i32,
in: Reader,
out: Writer,
orig: os.termios,
 
cpos: Point,
size: Point,
chadj: i32 = 0,
cvadj: i32 = 0,
 
pub fn init() !TTY {
const tty = try os.open("/dev/tty", os.linux.O.RDWR, 0);
const orig = try os.tcgetattr(tty);
 
try push_tty(tty, orig);
return TTY{
.tty = tty,
.in = std.io.getStdIn().reader(),
.out = std.io.getStdOut().writer(),
.orig = orig,
.cpos = cpos(tty) catch unreachable,
.size = geom(tty) catch unreachable,
};
}
 
fn push_tty(tty: i32, tos: os.termios) !void {
var raw = tos;
raw.lflag &= ~@as(
os.linux.tcflag_t,
os.linux.ECHO | os.linux.ICANON | os.linux.ISIG | os.linux.IEXTEN,
);
raw.iflag &= ~@as(
os.linux.tcflag_t,
os.linux.IXON | os.linux.ICRNL | os.linux.BRKINT | os.linux.INPCK | os.linux.ISTRIP,
);
raw.cc[os.system.V.TIME] = 0;
raw.cc[os.system.V.MIN] = 1;
try os.tcsetattr(tty, .FLUSH, raw);
}
 
pub fn write(tty: TTY, string: []const u8) !usize {
return try tty.out.write(string);
}
 
pub fn writeAll(tty: TTY, string: []const u8) !void {
try tty.out.writeAll(string);
}
 
pub fn prompt(tty: TTY, comptime fmt: []const u8, args: anytype) !void {
try tty.print(fmt, args);
try tty.opcode(OpCodes.EraseInLine, null);
var move = tty.chadj;
while (move > 0) : (move -= 1) {
try tty.opcode(OpCodes.CurMvLe, null);
}
}
 
pub fn print(tty: TTY, comptime fmt: []const u8, args: anytype) !void {
try tty.out.print(fmt, args);
}
 
pub fn printAfter(tty: TTY, comptime fmt: []const u8, args: anytype) !void {
// TODO count cursor moves
// or TODO save and restore tty screen?
 
//try tty.opcode(OpCodes.CurHorzAbs, null);
//try tty.opcode(OpCodes.CurMvDn, null);
_ = try tty.write("\r\n");
try tty.print(fmt, args);
try tty.opcode(OpCodes.EraseInLine, null);
try tty.opcode(OpCodes.CurMvUp, null);
}
 
pub fn opcode(tty: TTY, comptime code: OpCodes, args: anytype) !void {
// TODO fetch info back out :/
_ = args;
switch (code) {
OpCodes.EraseInLine => try tty.writeAll("\x1B[K"),
OpCodes.CurPosGet => try tty.print("\x1B[6n"),
OpCodes.CurMvUp => try tty.writeAll("\x1B[A"),
OpCodes.CurMvDn => try tty.writeAll("\x1B[B"),
OpCodes.CurMvLe => try tty.writeAll("\x1B[D"),
OpCodes.CurMvRi => try tty.writeAll("\x1B[C"),
OpCodes.CurHorzAbs => try tty.writeAll("\x1B[G"),
else => unreachable,
}
}
 
fn cpos(tty: i32) !Point {
std.debug.print("\x1B[6n", .{});
var buffer: [10]u8 = undefined;
const len = try os.read(tty, &buffer);
var splits = mem.split(u8, buffer[2..], ";");
var x: usize = std.fmt.parseInt(usize, splits.next().?, 10) catch 0;
var y: usize = 0;
if (splits.next()) |thing| {
y = std.fmt.parseInt(usize, thing[0 .. len - 3], 10) catch 0;
}
return Point{
.x = x,
.y = y,
};
}
 
fn geom(tty: i32) !Point {
var size = mem.zeroes(os.linux.winsize);
const err = os.system.ioctl(tty, os.linux.T.IOCGWINSZ, @ptrToInt(&size));
if (os.errno(err) != .SUCCESS) {
return os.unexpectedErrno(@intToEnum(os.system.E, err));
}
return Point{
.x = size.ws_row,
.y = size.ws_col,
};
}
 
pub fn raze(tty: TTY) void {
os.tcsetattr(tty.tty, .FLUSH, tty.orig) catch |err| {
std.debug.print(
"\r\n\nTTY ERROR RAZE encountered, {} when attempting to raze.\r\n\n",
.{err},
);
};
}
};
 
const expect = std.testing.expect;
test "split" {
var s = "\x1B[86;1R";
var splits = std.mem.split(u8, s[2..], ";");
var x: usize = std.fmt.parseInt(usize, splits.next().?, 10) catch 0;
var y: usize = 0;
if (splits.next()) |thing| {
y = std.fmt.parseInt(usize, thing[0 .. thing.len - 1], 10) catch unreachable;
}
try expect(x == 86);
try expect(y == 1);
}
 
test "CSI format" {
// For Control Sequence Introducer, or CSI, commands, the ESC [ (written as
// \e[ or \033[ in several programming and scripting languages) is followed
// by any number (including none) of "parameter bytes" in the range
// 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of "intermediate bytes"
// in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./), then finally by
// a single "final byte" in the range 0x40–0x7E (ASCII
// @A–Z[\]^_`a–z{|}~).[5]: 5.4 
}