nativity/src/main.nat
David Gonzalez Martin 40c570f3f1 More self hosted work
2024-04-24 07:41:37 -06:00

318 lines
9.1 KiB
Plaintext

const std = #import("std");
const Arena = std.Arena;
const c_slice = std.c_slice;
const byte_equal = std.byte_equal;
const print = std.print;
const print_usize = std.print_usize;
const exit = std.os.exit;
const Parser = struct{
text: [&]const u8,
index: u32 = 0,
length: u32,
current_line: u32 = 0,
current_line_offset: u32 = 0,
const expect_byte = fn(parser: &Parser, byte: u8) void {
const current_ch = parser.text[parser.index];
if (current_ch != byte) {
print("Expected '");
var a = [1]u8{byte};
print(a.&);
print("', got '");
a[0] = current_ch;
print(a.&);
print("'\n");
exit(1);
}
}
const skip_whitespace = fn (parser: &Parser) void {
const length = parser.length;
const pointer = parser.text;
while (parser.index < length) {
const ch = pointer[parser.index];
const new_line = ch == '\n';
const is_space = ch == ' ' or ch == '\t' or new_line or ch == '\r';
if (new_line) {
parser.current_line += 1;
parser.current_line_offset = parser.index + 1;
}
if (!is_space) {
break;
}
parser.index += 1;
}
}
const raw_string = fn (parser: &Parser) u32 {
const start_index = parser.index;
const text = parser.text;
var index: u32 = parser.index;
while (index < parser.length) {
const ch = text[index];
switch (ch) {
'a'...'z', 'A'...'Z', '_' => index += 1,
else => break,
}
}
parser.index = index;
return start_index;
}
const identifier = fn (parser: &Parser) []const u8 {
const start_index = parser.raw_string();
const slice = parser.text[start_index.. parser.index];
// TODO: check if the identifier matches keywords
return slice;
}
};
const parse = fn (arena: &Arena, bytes: []const u8) *!void {
if (bytes.length >= 0xffffffff) {
unreachable;
}
const length: u32 = #cast(bytes.length);
var parser = Parser{
.text = bytes.pointer,
.length = length,
};
while (parser.index < length) {
parser.skip_whitespace();
const current_index = parser.index;
if (current_index == length) {
break;
}
const slice = bytes[current_index..];
const is_const = byte_equal(slice[0.."const".length], "const");
const is_var = byte_equal(slice[0.."var".length], "var");
const is_test = byte_equal(slice[0.."test".length], "test");
const is_comptime = byte_equal(slice[0.."comptime".length], "comptime");
if (is_const) {
const space_index: u32 = "const".length;
const ch = slice[space_index];
const next_ch = slice[space_index + 1];
const is_normal_space = (ch == ' ' or ch == '\n') or (ch == '\t' or ch == '\r');
const is_comment = ch == '/' and next_ch == '/';
const is_space = is_normal_space or is_comment;
if (!is_space) {
exit(1);
}
parser.index += space_index;
parser.skip_whitespace();
const identifier = parser.identifier();
parser.skip_whitespace();
parser.expect_byte('=');
parser.skip_whitespace();
exit(0);
} else if (is_var) {
const space_index: u32 = "var".length;
const ch = slice[space_index];
const next_ch = slice[space_index + 1];
const is_normal_space = (ch == ' ' or ch == '\n') or (ch == '\t' or ch == '\r');
const is_comment = ch == '/' and next_ch == '/';
const is_space = is_normal_space or is_comment;
if (!is_space) {
exit(1);
}
parser.index += space_index;
parser.skip_whitespace();
exit(0);
} else if (is_test) {
const space_index: u32 = "test".length;
const ch = slice[space_index];
const next_ch = slice[space_index + 1];
const is_normal_space = (ch == ' ' or ch == '\n') or (ch == '\t' or ch == '\r');
const is_comment = ch == '/' and next_ch == '/';
const is_space = is_normal_space or is_comment;
if (!is_space) {
exit(1);
}
parser.index += space_index;
parser.skip_whitespace();
exit(0);
} else if (is_comptime) {
const space_index: u32 = "comptime".length;
const ch = slice[space_index];
const next_ch = slice[space_index + 1];
const is_normal_space = (ch == ' ' or ch == '\n') or (ch == '\t' or ch == '\r');
const is_comment = ch == '/' and next_ch == '/';
const is_space = is_normal_space or is_comment;
if (!is_space) {
exit(1);
}
parser.index += space_index;
parser.skip_whitespace();
exit(0);
} else {
exit(1);
}
}
}
const FileStartToken = enum{
"comptime",
"test",
"const",
"var",
};
const ArgumentProcessingError = error{
no_arguments,
};
const Token = struct {
const Id = enum(u8) {
invalid,
keyword_unsigned_integer,
keyword_signed_integer,
identifier,
};
};
const FixedKeyword = enum{
"comptime",
"const",
"var",
"void",
"noreturn",
"while",
"bool",
"true",
"false",
"fn",
"unreachable",
"return",
"ssize",
"usize",
""switch",
"if",
"else",
"struct",
"enum",
"null",
"align",
"for",
"undefined",
"break",
"test",
"catch",
"try",
"orelse",
"error",
"and",
"or",
"bitfield",
"Self",
"any",
"type",
"continue",
};
const get_argument = fn (real_argument: []const u8, wanted_argument: []const u8, command_arguments: []const [&:0]const u8, i_ptr: &usize) ?[]const u8 {
const i = i_ptr.@;
const are_equal = byte_equal(real_argument, wanted_argument);
if (are_equal) {
if (i < command_arguments.length) {
const new_i = i + 1;
const argument = c_slice(command_arguments[new_i]);
i_ptr.@ = new_i;
return argument;
} else {
print("error: unterminated argument: '");
print(real_argument);
print("'\n");
exit(1);
}
} else {
return null;
}
}
const command_exe = fn (arena: &Arena, command_arguments: []const [&:0]const u8) *!void {
var i: usize = 0;
var maybe_output_argument: ?[]const u8 = null;
var maybe_main_source_file: ?[]const u8 = null;
var maybe_main_executable_name: ?[]const u8 = null;
while (i < command_arguments.length) {
const command_argument = c_slice(command_arguments[i]);
if (get_argument(command_argument, "-o", command_arguments, i.&)) |out_arg| {
maybe_output_argument = out_arg;
} else if (get_argument(command_argument, "-main_source_file", command_arguments, i.&)) |src_arg| {
maybe_main_source_file = src_arg;
} else {
print("error: unhandled argument: '");
print(command_argument);
print("'\n");
exit(1);
}
i += 1;
}
const main_source_file = maybe_main_source_file orelse {
print("error: no main source file specified\n");
exit(1);
};
const main_executable_name = maybe_main_executable_name orelse (std.os.basename(main_source_file[0..main_source_file.length - 9]) orelse unreachable); // 9 => "/main.nat".length
const file_descriptor = try std.os.open(#cast(main_source_file.pointer), .{});
const file_size = try file_descriptor.get_size();
const file_buffer = try arena.new_array($u8, file_size);
file_descriptor.read_all(file_buffer);
parse(arena, file_buffer);
}
const main = fn() *!void {
const arena = try Arena.init(std.megabytes(64));
const argument_count = std.start.argument_count;
if (argument_count <= 1) {
return ArgumentProcessingError.no_arguments;
}
const argument_values = std.start.argument_values;
const arguments = argument_values[0..argument_count];
const command = c_slice(arguments[1]);
const command_arguments = arguments[2..];
if (byte_equal(command, "build")) {
print("TODO: build");
} else if (byte_equal(command, "clang") or byte_equal(command, "-cc1") or byte_equal(command, "-cc1as")) {
unreachable;
} else if (byte_equal(command, "cc")) {
unreachable;
} else if (byte_equal(command, "c++")) {
unreachable;
} else if (byte_equal(command, "exe")) {
command_exe(arena, command_arguments);
} else if (byte_equal(command, "lib")) {
unreachable;
} else if (byte_equal(command, "obj")) {
unreachable;
} else if (byte_equal(command, "test")) {
print("TODO: test");
} else {
unreachable;
}
}