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; } }