Initial commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
const std = @import("std");
|
||||
|
||||
const parser = @import("../shell/parser.zig");
|
||||
|
||||
const cd = @import("cmds/cd.zig");
|
||||
const echo = @import("cmds/echo.zig");
|
||||
const exit = @import("cmds/exit.zig");
|
||||
const pwd = @import("cmds/pwd.zig");
|
||||
const run = @import("cmds/run.zig");
|
||||
|
||||
pub const Shell = struct {
|
||||
running: bool = true,
|
||||
interrupted: bool = false,
|
||||
environ: *const std.process.Environ.Map,
|
||||
};
|
||||
|
||||
pub fn execute(
|
||||
io: std.Io,
|
||||
allocator: std.mem.Allocator,
|
||||
shell: *Shell,
|
||||
command: parser.Command,
|
||||
) !bool {
|
||||
if (command.argv.len == 0)
|
||||
return true;
|
||||
|
||||
const name = command.argv[0];
|
||||
|
||||
if (std.mem.eql(u8, name, "cd")) {
|
||||
try cd.execute(io, shell.environ, command.argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "pwd")) {
|
||||
try pwd.execute(io, allocator);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "run")) {
|
||||
try run.execute(io, allocator, command.argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "echo")) {
|
||||
try echo.execute(io, command.argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "exit")) {
|
||||
exit.execute(&shell.running);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn execute(
|
||||
io: std.Io,
|
||||
environ: *const std.process.Environ.Map,
|
||||
argv: []const []const u8,
|
||||
) !void {
|
||||
const path = if (argv.len >= 2)
|
||||
argv[1]
|
||||
else
|
||||
environ.get("USERPROFILE") orelse environ.get("HOME") orelse {
|
||||
std.debug.print("xsh: cd: cannot find home directory\n", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
var dir = try std.Io.Dir.cwd().openDir(io, path, .{});
|
||||
defer dir.close(io);
|
||||
|
||||
try std.process.setCurrentDir(io, dir);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn execute(
|
||||
io: std.Io,
|
||||
argv: []const []const u8,
|
||||
) !void {
|
||||
for (argv[1..], 0..) |argument, index| {
|
||||
if (index != 0) {
|
||||
try std.Io.File.stdout().writeStreamingAll(io, " ");
|
||||
}
|
||||
|
||||
try std.Io.File.stdout().writeStreamingAll(io, argument);
|
||||
}
|
||||
|
||||
try std.Io.File.stdout().writeStreamingAll(io, "\n");
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn execute(running: *bool) void {
|
||||
running.* = false;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn execute(
|
||||
io: std.Io,
|
||||
allocator: std.mem.Allocator,
|
||||
) !void {
|
||||
const path = try std.process.currentPathAlloc(io, allocator);
|
||||
defer allocator.free(path);
|
||||
|
||||
try std.Io.File.stdout().writeStreamingAll(io, path);
|
||||
try std.Io.File.stdout().writeStreamingAll(io, "\n");
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const std = @import("std");
|
||||
|
||||
const script = @import("../../script/script.zig");
|
||||
|
||||
pub fn execute(
|
||||
io: std.Io,
|
||||
allocator: std.mem.Allocator,
|
||||
argv: []const []const u8,
|
||||
) !void {
|
||||
if (argv.len < 2) {
|
||||
std.debug.print("xsh: run: missing script path\n", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
var file = try std.Io.Dir.cwd().openFile(io, argv[1], .{});
|
||||
defer file.close(io);
|
||||
|
||||
var file_reader = file.reader(io, &.{});
|
||||
|
||||
const source = try file_reader.interface.allocRemaining(
|
||||
allocator,
|
||||
.limited(16 * 1024 * 1024),
|
||||
);
|
||||
|
||||
const name_z = try allocator.dupeZ(u8, argv[1]);
|
||||
|
||||
try script.run(
|
||||
allocator,
|
||||
io,
|
||||
name_z,
|
||||
source,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
pub const prompt = "xsh> ";
|
||||
|
||||
pub const max_input_size = 4096;
|
||||
pub const max_script_size = 16 * 1024 * 1024;
|
||||
pub const max_exec_args = 64;
|
||||
|
||||
pub const features = .{
|
||||
.luau = true,
|
||||
.aliases = false,
|
||||
.history = false,
|
||||
.autocomplete = false
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void xsh_set_luau_flags(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
const std = @import("std");
|
||||
|
||||
const lexer = @import("shell/lexer.zig");
|
||||
const parser = @import("shell/parser.zig");
|
||||
const execute = @import("shell/execute.zig");
|
||||
|
||||
const builtin = @import("builtin/builtin.zig");
|
||||
|
||||
const script = @import("script/script.zig");
|
||||
|
||||
const config = @import("config.zig");
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
const allocator = init.gpa;
|
||||
const io = init.io;
|
||||
|
||||
var shell = builtin.Shell{
|
||||
.environ = init.environ_map,
|
||||
};
|
||||
|
||||
var stderr_buffer: [1024]u8 = undefined;
|
||||
var stderr_file_writer = std.Io.File.stderr().writer(io, &stderr_buffer);
|
||||
const stderr = &stderr_file_writer.interface;
|
||||
|
||||
while (shell.running) {
|
||||
try std.Io.File.stdout().writeStreamingAll(io, config.prompt);
|
||||
|
||||
var input: [4096]u8 = undefined;
|
||||
|
||||
const line = readLine(io, &input) catch |err| {
|
||||
try stderr.print("xsh: error reading input: {}\n", .{err});
|
||||
try stderr_file_writer.flush();
|
||||
continue;
|
||||
} orelse break;
|
||||
|
||||
if (shell.interrupted) {
|
||||
try std.Io.File.stdout().writeStreamingAll(io, "^C\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
const source = std.mem.trim(u8, line, " \t\r\n");
|
||||
|
||||
if (source.len == 0)
|
||||
continue;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const arena_allocator = arena.allocator();
|
||||
|
||||
runPipeline(arena_allocator, io, &shell, source) catch |err| {
|
||||
try stderr.print("xsh error: {}\n", .{err});
|
||||
try stderr_file_writer.flush();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn runPipeline(allocator: std.mem.Allocator, io: std.Io, shell: *builtin.Shell, source: []const u8) !void {
|
||||
const tokens = try lexer.lex(allocator, source);
|
||||
const command = try parser.parse(allocator, tokens.items);
|
||||
|
||||
try execute.run(
|
||||
io,
|
||||
allocator,
|
||||
shell,
|
||||
command,
|
||||
);
|
||||
}
|
||||
|
||||
fn readLine(
|
||||
io: std.Io,
|
||||
buffer: []u8,
|
||||
) !?[]u8 {
|
||||
var reader_buffer: [1024]u8 = undefined;
|
||||
|
||||
var file_reader = std.Io.File.stdin().reader(
|
||||
io,
|
||||
&reader_buffer,
|
||||
);
|
||||
|
||||
const reader = &file_reader.interface;
|
||||
|
||||
const result = try reader.takeDelimiterInclusive('\n');
|
||||
|
||||
if (result.len == 0)
|
||||
return null;
|
||||
|
||||
const len = @min(result.len, buffer.len);
|
||||
|
||||
@memcpy(buffer[0..len], result[0..len]);
|
||||
|
||||
return buffer[0..len];
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
const std = @import("std");
|
||||
|
||||
const luau = @import("luau");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("../include/bridge.h");
|
||||
});
|
||||
|
||||
const State = luau.State;
|
||||
|
||||
var shell_io: std.Io = undefined;
|
||||
|
||||
pub fn init(L: *State, io: std.Io) !void {
|
||||
shell_io = io;
|
||||
|
||||
try L.newtable();
|
||||
|
||||
try L.Zpushfunction(exec, "exec");
|
||||
try L.setfield(-2, "exec");
|
||||
|
||||
try L.setglobal("xsh");
|
||||
}
|
||||
|
||||
fn exec(L: *State) i32 {
|
||||
const argc = L.gettop();
|
||||
|
||||
if (argc == 0) {
|
||||
L.pushinteger(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var argv: [64][]const u8 = undefined;
|
||||
|
||||
if (argc > argv.len) {
|
||||
L.pushinteger(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (0..@intCast(argc)) |index| {
|
||||
const arg = L.tostring(@intCast(index + 1)) orelse {
|
||||
L.pushinteger(1);
|
||||
return 1;
|
||||
};
|
||||
|
||||
argv[index] = arg;
|
||||
}
|
||||
var child = std.process.spawn(shell_io, .{
|
||||
.argv = argv[0..@intCast(argc)],
|
||||
}) catch {
|
||||
L.pushinteger(1);
|
||||
return 1;
|
||||
};
|
||||
|
||||
const result = child.wait(shell_io) catch {
|
||||
L.pushinteger(1);
|
||||
return 1;
|
||||
};
|
||||
|
||||
_ = result;
|
||||
|
||||
L.pushinteger(0);
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
const std = @import("std");
|
||||
const zluau = @import("luau");
|
||||
|
||||
const luau = @import("luau.zig");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("../include/bridge.h");
|
||||
});
|
||||
|
||||
pub fn run(
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
name: [:0]const u8,
|
||||
source: []const u8,
|
||||
) !void {
|
||||
c.xsh_set_luau_flags();
|
||||
|
||||
var L = try zluau.init(&allocator);
|
||||
defer L.deinit();
|
||||
|
||||
try L.Lopenlibs();
|
||||
|
||||
try luau.init(L, io);
|
||||
|
||||
const bytecode = zluau.compile(
|
||||
allocator,
|
||||
source,
|
||||
.{},
|
||||
) catch |err| {
|
||||
std.debug.print(
|
||||
"xsh: failed to compile '{s}': {}\n",
|
||||
.{ name, err },
|
||||
);
|
||||
return err;
|
||||
};
|
||||
|
||||
defer allocator.free(bytecode);
|
||||
|
||||
// try L.load(name, bytecode, 0);
|
||||
L.load(name, bytecode, 0) catch |err| {
|
||||
const message = L.tostring(-1) orelse "unknown Luau load error";
|
||||
|
||||
std.debug.print(
|
||||
"xsh: failed to load '{s}': {s} ({})\n",
|
||||
.{ name, message, err },
|
||||
);
|
||||
|
||||
L.pop(1);
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
const call = L.pcall(0, 0, 0);
|
||||
|
||||
if (call.check()) |_| {
|
||||
return;
|
||||
} else |err| {
|
||||
const message = L.tostring(-1) orelse "unknown Luau runtime error";
|
||||
|
||||
std.debug.print(
|
||||
"xsh: {s}\n",
|
||||
.{message},
|
||||
);
|
||||
|
||||
L.pop(1);
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
const std = @import("std");
|
||||
|
||||
const builtin = @import("../builtin/builtin.zig");
|
||||
|
||||
const parser = @import("parser.zig");
|
||||
|
||||
pub fn run(
|
||||
io: std.Io,
|
||||
allocator: std.mem.Allocator,
|
||||
shell: *builtin.Shell,
|
||||
command: parser.Command,
|
||||
) !void {
|
||||
if (try builtin.execute(io, allocator, shell, command))
|
||||
return;
|
||||
|
||||
var child = try std.process.spawn(
|
||||
io,
|
||||
.{
|
||||
.argv = command.argv,
|
||||
},
|
||||
);
|
||||
|
||||
_ = try child.wait(io);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Token = struct {
|
||||
text: []const u8,
|
||||
};
|
||||
|
||||
pub fn lex(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
) !std.ArrayList(Token) {
|
||||
var tokens: std.ArrayList(Token) = .empty;
|
||||
|
||||
var iterator = std.mem.tokenizeAny(
|
||||
u8,
|
||||
source,
|
||||
" \t\r\n",
|
||||
);
|
||||
|
||||
while (iterator.next()) |word| {
|
||||
try tokens.append(
|
||||
allocator,
|
||||
.{
|
||||
.text = word,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const std = @import("std");
|
||||
|
||||
const lexer = @import("lexer.zig");
|
||||
|
||||
pub const Command = struct {
|
||||
argv: []const []const u8,
|
||||
};
|
||||
|
||||
pub fn parse(
|
||||
allocator: std.mem.Allocator,
|
||||
tokens: []const lexer.Token,
|
||||
) !Command {
|
||||
if (tokens.len == 0) {
|
||||
return error.EmptyCommand;
|
||||
}
|
||||
|
||||
const argv = try allocator.alloc(
|
||||
[]const u8,
|
||||
tokens.len,
|
||||
);
|
||||
|
||||
for (tokens, 0..) |token, index| {
|
||||
argv[index] = token.text;
|
||||
}
|
||||
|
||||
return .{
|
||||
.argv = argv,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user