Initial commit

This commit is contained in:
2026-08-23 11:11:36 +03:00
commit 0548f8de26
97 changed files with 20841 additions and 0 deletions
+54
View File
@@ -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;
}
+20
View File
@@ -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);
}
+16
View File
@@ -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");
}
+3
View File
@@ -0,0 +1,3 @@
pub fn execute(running: *bool) void {
running.* = false;
}
+12
View File
@@ -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");
}
+33
View File
@@ -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,
);
}
View File
+12
View File
@@ -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
};
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void xsh_set_luau_flags(void);
#ifdef __cplusplus
}
#endif
+93
View File
@@ -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];
}
+63
View File
@@ -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;
}
+69
View File
@@ -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;
}
}
+24
View File
@@ -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);
}
+29
View File
@@ -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;
}
+29
View File
@@ -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,
};
}
View File