diff options
| -rwxr-xr-x | ci/travis_linux_script | 2 | ||||
| -rw-r--r-- | deps/lld/ELF/MarkLive.cpp | 9 | ||||
| -rw-r--r-- | src-self-hosted/arg.zig | 284 | ||||
| -rw-r--r-- | src-self-hosted/introspect.zig | 57 | ||||
| -rw-r--r-- | src-self-hosted/main.zig | 1581 | ||||
| -rw-r--r-- | src-self-hosted/module.zig | 23 | ||||
| -rw-r--r-- | src/codegen.cpp | 2 | ||||
| -rw-r--r-- | src/ir.cpp | 23 | ||||
| -rw-r--r-- | src/main.cpp | 33 | ||||
| -rw-r--r-- | std/c/index.zig | 1 | ||||
| -rw-r--r-- | std/os/darwin.zig | 9 | ||||
| -rw-r--r-- | std/os/file.zig | 45 | ||||
| -rw-r--r-- | std/os/linux/index.zig | 9 | ||||
| -rw-r--r-- | std/os/test.zig | 17 | ||||
| -rw-r--r-- | std/os/windows/index.zig | 2 | ||||
| -rw-r--r-- | std/zig/ast.zig | 455 | ||||
| -rw-r--r-- | std/zig/parser.zig | 4634 | ||||
| -rw-r--r-- | test/cases/fn.zig | 17 | ||||
| -rw-r--r-- | test/compile_errors.zig | 11 |
19 files changed, 4034 insertions, 3180 deletions
diff --git a/ci/travis_linux_script b/ci/travis_linux_script index 9b43dd20fb..f2f4bd95d3 100755 --- a/ci/travis_linux_script +++ b/ci/travis_linux_script @@ -19,5 +19,5 @@ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then echo "secret_key = $AWS_SECRET_ACCESS_KEY" >> ~/.s3cfg s3cmd put -P $TRAVIS_BUILD_DIR/artifacts/* s3://ziglang.org/builds/ touch empty - s3cmd put -P empty s3://ziglang.org/builds/zig-linux-x86_64-$TRAVIS_BRANCH.tar.xz --add-header=x-amz-website-redirect-location:/builds/$(ls $TRAVIS_BUILD_DIR/artifacts) + s3cmd put -P empty s3://ziglang.org/builds/zig-linux-x86_64-$TRAVIS_BRANCH.tar.xz --add-header="Cache-Control: max-age=0, must-revalidate" --add-header=x-amz-website-redirect-location:/builds/$(ls $TRAVIS_BUILD_DIR/artifacts) fi diff --git a/deps/lld/ELF/MarkLive.cpp b/deps/lld/ELF/MarkLive.cpp index 88f558c7a3..9fca927437 100644 --- a/deps/lld/ELF/MarkLive.cpp +++ b/deps/lld/ELF/MarkLive.cpp @@ -301,6 +301,15 @@ template <class ELFT> void elf::markLive() { // Follow the graph to mark all live sections. doGcSections<ELFT>(); + // If all references to a DSO happen to be weak, the DSO is removed from + // DT_NEEDED, which creates dangling shared symbols to non-existent DSO. + // We'll replace such symbols with undefined ones to fix it. + for (Symbol *Sym : Symtab->getSymbols()) + if (auto *S = dyn_cast<SharedSymbol>(Sym)) + if (S->isWeak() && !S->getFile<ELFT>().IsNeeded) + replaceSymbol<Undefined>(S, nullptr, S->getName(), STB_WEAK, S->StOther, + S->Type); + // Report garbage-collected sections. if (Config->PrintGcSections) for (InputSectionBase *Sec : InputSections) diff --git a/src-self-hosted/arg.zig b/src-self-hosted/arg.zig new file mode 100644 index 0000000000..707f208287 --- /dev/null +++ b/src-self-hosted/arg.zig @@ -0,0 +1,284 @@ +const std = @import("std"); +const debug = std.debug; +const mem = std.mem; + +const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; +const HashMap = std.HashMap; + +fn trimStart(slice: []const u8, ch: u8) []const u8 { + var i: usize = 0; + for (slice) |b| { + if (b != '-') break; + i += 1; + } + + return slice[i..]; +} + +fn argInAllowedSet(maybe_set: ?[]const []const u8, arg: []const u8) bool { + if (maybe_set) |set| { + for (set) |possible| { + if (mem.eql(u8, arg, possible)) { + return true; + } + } + return false; + } else { + return true; + } +} + +// Modifies the current argument index during iteration +fn readFlagArguments(allocator: &Allocator, args: []const []const u8, required: usize, + allowed_set: ?[]const []const u8, index: &usize) !FlagArg { + + switch (required) { + 0 => return FlagArg { .None = undefined }, // TODO: Required to force non-tag but value? + 1 => { + if (*index + 1 >= args.len) { + return error.MissingFlagArguments; + } + + *index += 1; + const arg = args[*index]; + + if (!argInAllowedSet(allowed_set, arg)) { + return error.ArgumentNotInAllowedSet; + } + + return FlagArg { .Single = arg }; + }, + else => |needed| { + var extra = ArrayList([]const u8).init(allocator); + errdefer extra.deinit(); + + var j: usize = 0; + while (j < needed) : (j += 1) { + if (*index + 1 >= args.len) { + return error.MissingFlagArguments; + } + + *index += 1; + const arg = args[*index]; + + if (!argInAllowedSet(allowed_set, arg)) { + return error.ArgumentNotInAllowedSet; + } + + try extra.append(arg); + } + + return FlagArg { .Many = extra }; + }, + } +} + +const HashMapFlags = HashMap([]const u8, FlagArg, std.hash.Fnv1a_32.hash, mem.eql_slice_u8); + +// A store for querying found flags and positional arguments. +pub const Args = struct { + flags: HashMapFlags, + positionals: ArrayList([]const u8), + + pub fn parse(allocator: &Allocator, comptime spec: []const Flag, args: []const []const u8) !Args { + var parsed = Args { + .flags = HashMapFlags.init(allocator), + .positionals = ArrayList([]const u8).init(allocator), + }; + + var i: usize = 0; + next: while (i < args.len) : (i += 1) { + const arg = args[i]; + + if (arg.len != 0 and arg[0] == '-') { + // TODO: hashmap, although the linear scan is okay for small argument sets as is + for (spec) |flag| { + if (mem.eql(u8, arg, flag.name)) { + const flag_name_trimmed = trimStart(flag.name, '-'); + const flag_args = readFlagArguments(allocator, args, flag.required, flag.allowed_set, &i) catch |err| { + switch (err) { + error.ArgumentNotInAllowedSet => { + std.debug.warn("argument '{}' is invalid for flag '{}'\n", args[i], arg); + std.debug.warn("allowed options are "); + for (??flag.allowed_set) |possible| { + std.debug.warn("'{}' ", possible); + } + std.debug.warn("\n"); + }, + error.MissingFlagArguments => { + std.debug.warn("missing argument for flag: {}\n", arg); + }, + else => {}, + } + + return err; + }; + + if (flag.mergable) { + var prev = + if (parsed.flags.get(flag_name_trimmed)) |entry| + entry.value.Many + else + ArrayList([]const u8).init(allocator); + + // MergeN creation disallows 0 length flag entry (doesn't make sense) + switch (flag_args) { + FlagArg.None => unreachable, + FlagArg.Single => |inner| try prev.append(inner), + FlagArg.Many => |inner| try prev.appendSlice(inner.toSliceConst()), + } + + _ = try parsed.flags.put(flag_name_trimmed, FlagArg { .Many = prev }); + } else { + _ = try parsed.flags.put(flag_name_trimmed, flag_args); + } + + continue :next; + } + } + + // TODO: Better errors with context, global error state and return is sufficient. + std.debug.warn("could not match flag: {}\n", arg); + return error.UnknownFlag; + } else { + try parsed.positionals.append(arg); + } + } + + return parsed; + } + + pub fn deinit(self: &Args) void { + self.flags.deinit(); + self.positionals.deinit(); + } + + // e.g. --help + pub fn present(self: &Args, name: []const u8) bool { + return self.flags.contains(name); + } + + // e.g. --name value + pub fn single(self: &Args, name: []const u8) ?[]const u8 { + if (self.flags.get(name)) |entry| { + switch (entry.value) { + FlagArg.Single => |inner| { return inner; }, + else => @panic("attempted to retrieve flag with wrong type"), + } + } else { + return null; + } + } + + // e.g. --names value1 value2 value3 + pub fn many(self: &Args, name: []const u8) ?[]const []const u8 { + if (self.flags.get(name)) |entry| { + switch (entry.value) { + FlagArg.Many => |inner| { return inner.toSliceConst(); }, + else => @panic("attempted to retrieve flag with wrong type"), + } + } else { + return null; + } + } +}; + +// Arguments for a flag. e.g. arg1, arg2 in `--command arg1 arg2`. +const FlagArg = union(enum) { + None, + Single: []const u8, + Many: ArrayList([]const u8), +}; + +// Specification for how a flag should be parsed. +pub const Flag = struct { + name: []const u8, + required: usize, + mergable: bool, + allowed_set: ?[]const []const u8, + + pub fn Bool(comptime name: []const u8) Flag { + return ArgN(name, 0); + } + + pub fn Arg1(comptime name: []const u8) Flag { + return ArgN(name, 1); + } + + pub fn ArgN(comptime name: []const u8, comptime n: usize) Flag { + return Flag { + .name = name, + .required = n, + .mergable = false, + .allowed_set = null, + }; + } + + pub fn ArgMergeN(comptime name: []const u8, comptime n: usize) Flag { + if (n == 0) { + @compileError("n must be greater than 0"); + } + + return Flag { + .name = name, + .required = n, + .mergable = true, + .allowed_set = null, + }; + } + + pub fn Option(comptime name: []const u8, comptime set: []const []const u8) Flag { + return Flag { + .name = name, + .required = 1, + .mergable = false, + .allowed_set = set, + }; + } +}; + +test "parse arguments" { + const spec1 = comptime []const Flag { + Flag.Bool("--help"), + Flag.Bool("--init"), + Flag.Arg1("--build-file"), + Flag.Option("--color", []const []const u8 { "on", "off", "auto" }), + Flag.ArgN("--pkg-begin", 2), + Flag.ArgMergeN("--object", 1), + Flag.ArgN("--library", 1), + }; + + const cliargs = []const []const u8 { + "build", + "--help", + "pos1", + "--build-file", "build.zig", + "--object", "obj1", + "--object", "obj2", + "--library", "lib1", + "--library", "lib2", + "--color", "on", + "pos2", + }; + + var args = try Args.parse(std.debug.global_allocator, spec1, cliargs); + + debug.assert(args.present("help")); + debug.assert(!args.present("help2")); + debug.assert(!args.present("init")); + + debug.assert(mem.eql(u8, ??args.single("build-file"), "build.zig")); + debug.assert(mem.eql(u8, ??args.single("color"), "on")); + + const objects = ??args.many("object"); + debug.assert(mem.eql(u8, objects[0], "obj1")); + debug.assert(mem.eql(u8, objects[1], "obj2")); + + debug.assert(mem.eql(u8, ??args.single("library"), "lib2")); + + const pos = args.positionals.toSliceConst(); + debug.assert(mem.eql(u8, pos[0], "build")); + debug.assert(mem.eql(u8, pos[1], "pos1")); + debug.assert(mem.eql(u8, pos[2], "pos2")); +} diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig new file mode 100644 index 0000000000..3f1fefdd5a --- /dev/null +++ b/src-self-hosted/introspect.zig @@ -0,0 +1,57 @@ +// Introspection and determination of system libraries needed by zig. + +const std = @import("std"); +const mem = std.mem; +const os = std.os; + +const warn = std.debug.warn; + +/// Caller must free result +pub fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 { + const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig"); + errdefer allocator.free(test_zig_dir); + + const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig"); + defer allocator.free(test_index_file); + + var file = try os.File.openRead(allocator, test_index_file); + file.close(); + + return test_zig_dir; +} + +/// Caller must free result +pub fn findZigLibDir(allocator: &mem.Allocator) ![]u8 { + const self_exe_path = try os.selfExeDirPath(allocator); + defer allocator.free(self_exe_path); + + var cur_path: []const u8 = self_exe_path; + while (true) { + const test_dir = os.path.dirname(cur_path); + + if (mem.eql(u8, test_dir, cur_path)) { + break; + } + + return testZigInstallPrefix(allocator, test_dir) catch |err| { + cur_path = test_dir; + continue; + }; + } + + return error.FileNotFound; +} + +pub fn resolveZigLibDir(allocator: &mem.Allocator) ![]u8 { + return findZigLibDir(allocator) catch |err| { + warn( + \\Unable to find zig lib directory: {}. + \\Reinstall Zig or use --zig-install-prefix. + \\ + , + @errorName(err) + ); + + return error.ZigLibDirNotFound; + }; +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index d125b05b24..c1a6bbe99a 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,613 +1,165 @@ const std = @import("std"); -const mem = std.mem; -const io = std.io; -const os = std.os; -const heap = std.heap; -const warn = std.debug.warn; -const assert = std.debug.assert; -const target = @import("target.zig"); -const Target = target.Target; -const Module = @import("module.zig").Module; -const ErrColor = Module.ErrColor; -const Emit = Module.Emit; const builtin = @import("builtin"); -const ArrayList = std.ArrayList; -const c = @import("c.zig"); -const default_zig_cache_name = "zig-cache"; +const os = std.os; +const io = std.io; +const mem = std.mem; +const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; +const Buffer = std.Buffer; -const Cmd = enum { - None, - Build, - Test, - Version, - Zen, - TranslateC, - Targets, +const arg = @import("arg.zig"); +const c = @import("c.zig"); +const introspect = @import("introspect.zig"); +const Args = arg.Args; +const Flag = arg.Flag; +const Module = @import("module.zig").Module; +const Target = @import("target.zig").Target; + +var stderr: &io.OutStream(io.FileOutStream.Error) = undefined; +var stdout: &io.OutStream(io.FileOutStream.Error) = undefined; + +const usage = + \\usage: zig [command] [options] + \\ + \\Commands: + \\ + \\ build Build project from build.zig + \\ build-exe [source] Create executable from source or object files + \\ build-lib [source] Create library from source or object files + \\ build-obj [source] Create object from source or assembly + \\ fmt [source] Parse file and render in canonical zig format + \\ run [source] Create executable and run immediately + \\ targets List available compilation targets + \\ test [source] Create and run a test build + \\ translate-c [source] Convert c code to zig code + \\ version Print version number and exit + \\ zen Print zen of zig and exit + \\ + \\ + ; + +const Command = struct { + name: []const u8, + exec: fn(&Allocator, []const []const u8) error!void, }; -fn badArgs(comptime format: []const u8, args: ...) noreturn { - var stderr = io.getStdErr() catch std.os.exit(1); - var stderr_stream_adapter = io.FileOutStream.init(&stderr); - const stderr_stream = &stderr_stream_adapter.stream; - stderr_stream.print(format ++ "\n\n", args) catch std.os.exit(1); - printUsage(&stderr_stream_adapter.stream) catch std.os.exit(1); - std.os.exit(1); -} - pub fn main() !void { - const allocator = std.heap.c_allocator; + var allocator = std.heap.c_allocator; + + var stdout_file = try std.io.getStdOut(); + var stdout_out_stream = std.io.FileOutStream.init(&stdout_file); + stdout = &stdout_out_stream.stream; + + var stderr_file = try std.io.getStdErr(); + var stderr_out_stream = std.io.FileOutStream.init(&stderr_file); + stderr = &stderr_out_stream.stream; const args = try os.argsAlloc(allocator); defer os.argsFree(allocator, args); - if (args.len >= 2 and mem.eql(u8, args[1], "build")) { - return buildMain(allocator, args[2..]); - } - - if (args.len >= 2 and mem.eql(u8, args[1], "fmt")) { - return fmtMain(allocator, args[2..]); - } - - var cmd = Cmd.None; - var build_kind: Module.Kind = undefined; - var build_mode: builtin.Mode = builtin.Mode.Debug; - var color = ErrColor.Auto; - var emit_file_type = Emit.Binary; - - var strip = false; - var is_static = false; - var verbose_tokenize = false; - var verbose_ast_tree = false; - var verbose_ast_fmt = false; - var verbose_link = false; - var verbose_ir = false; - var verbose_llvm_ir = false; - var verbose_cimport = false; - var mwindows = false; - var mconsole = false; - var rdynamic = false; - var each_lib_rpath = false; - var timing_info = false; - - var in_file_arg: ?[]u8 = null; - var out_file: ?[]u8 = null; - var out_file_h: ?[]u8 = null; - var out_name_arg: ?[]u8 = null; - var libc_lib_dir_arg: ?[]u8 = null; - var libc_static_lib_dir_arg: ?[]u8 = null; - var libc_include_dir_arg: ?[]u8 = null; - var msvc_lib_dir_arg: ?[]u8 = null; - var kernel32_lib_dir_arg: ?[]u8 = null; - var zig_install_prefix: ?[]u8 = null; - var dynamic_linker_arg: ?[]u8 = null; - var cache_dir_arg: ?[]const u8 = null; - var target_arch: ?[]u8 = null; - var target_os: ?[]u8 = null; - var target_environ: ?[]u8 = null; - var mmacosx_version_min: ?[]u8 = null; - var mios_version_min: ?[]u8 = null; - var linker_script_arg: ?[]u8 = null; - var test_name_prefix_arg: ?[]u8 = null; - - var test_filters = ArrayList([]const u8).init(allocator); - defer test_filters.deinit(); - - var lib_dirs = ArrayList([]const u8).init(allocator); - defer lib_dirs.deinit(); - - var clang_argv = ArrayList([]const u8).init(allocator); - defer clang_argv.deinit(); - - var llvm_argv = ArrayList([]const u8).init(allocator); - defer llvm_argv.deinit(); - - var link_libs = ArrayList([]const u8).init(allocator); - defer link_libs.deinit(); - - var frameworks = ArrayList([]const u8).init(allocator); - defer frameworks.deinit(); - - var objects = ArrayList([]const u8).init(allocator); - defer objects.deinit(); - - var asm_files = ArrayList([]const u8).init(allocator); - defer asm_files.deinit(); - - var rpath_list = ArrayList([]const u8).init(allocator); - defer rpath_list.deinit(); - - var ver_major: u32 = 0; - var ver_minor: u32 = 0; - var ver_patch: u32 = 0; - - var arg_i: usize = 1; - while (arg_i < args.len) : (arg_i += 1) { - const arg = args[arg_i]; - - if (arg.len != 0 and arg[0] == '-') { - if (mem.eql(u8, arg, "--release-fast")) { - build_mode = builtin.Mode.ReleaseFast; - } else if (mem.eql(u8, arg, "--release-safe")) { - build_mode = builtin.Mode.ReleaseSafe; - } else if (mem.eql(u8, arg, "--strip")) { - strip = true; - } else if (mem.eql(u8, arg, "--static")) { - is_static = true; - } else if (mem.eql(u8, arg, "--verbose-tokenize")) { - verbose_tokenize = true; - } else if (mem.eql(u8, arg, "--verbose-ast-tree")) { - verbose_ast_tree = true; - } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) { - verbose_ast_fmt = true; - } else if (mem.eql(u8, arg, "--verbose-link")) { - verbose_link = true; - } else if (mem.eql(u8, arg, "--verbose-ir")) { - verbose_ir = true; - } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { - verbose_llvm_ir = true; - } else if (mem.eql(u8, arg, "--verbose-cimport")) { - verbose_cimport = true; - } else if (mem.eql(u8, arg, "-mwindows")) { - mwindows = true; - } else if (mem.eql(u8, arg, "-mconsole")) { - mconsole = true; - } else if (mem.eql(u8, arg, "-rdynamic")) { - rdynamic = true; - } else if (mem.eql(u8, arg, "--each-lib-rpath")) { - each_lib_rpath = true; - } else if (mem.eql(u8, arg, "--enable-timing-info")) { - timing_info = true; - } else if (mem.eql(u8, arg, "--test-cmd-bin")) { - @panic("TODO --test-cmd-bin"); - } else if (arg[1] == 'L' and arg.len > 2) { - // alias for --library-path - try lib_dirs.append(arg[1..]); - } else if (mem.eql(u8, arg, "--pkg-begin")) { - @panic("TODO --pkg-begin"); - } else if (mem.eql(u8, arg, "--pkg-end")) { - @panic("TODO --pkg-end"); - } else if (arg_i + 1 >= args.len) { - badArgs("expected another argument after {}", arg); - } else { - arg_i += 1; - if (mem.eql(u8, arg, "--output")) { - out_file = args[arg_i]; - } else if (mem.eql(u8, arg, "--output-h")) { - out_file_h = args[arg_i]; - } else if (mem.eql(u8, arg, "--color")) { - if (mem.eql(u8, args[arg_i], "auto")) { - color = ErrColor.Auto; - } else if (mem.eql(u8, args[arg_i], "on")) { - color = ErrColor.On; - } else if (mem.eql(u8, args[arg_i], "off")) { - color = ErrColor.Off; - } else { - badArgs("--color options are 'auto', 'on', or 'off'"); - } - } else if (mem.eql(u8, arg, "--emit")) { - if (mem.eql(u8, args[arg_i], "asm")) { - emit_file_type = Emit.Assembly; - } else if (mem.eql(u8, args[arg_i], "bin")) { - emit_file_type = Emit.Binary; - } else if (mem.eql(u8, args[arg_i], "llvm-ir")) { - emit_file_type = Emit.LlvmIr; - } else { - badArgs("--emit options are 'asm', 'bin', or 'llvm-ir'"); - } - } else if (mem.eql(u8, arg, "--name")) { - out_name_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--libc-lib-dir")) { - libc_lib_dir_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--libc-static-lib-dir")) { - libc_static_lib_dir_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--libc-include-dir")) { - libc_include_dir_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--msvc-lib-dir")) { - msvc_lib_dir_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--kernel32-lib-dir")) { - kernel32_lib_dir_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--zig-install-prefix")) { - zig_install_prefix = args[arg_i]; - } else if (mem.eql(u8, arg, "--dynamic-linker")) { - dynamic_linker_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "-isystem")) { - try clang_argv.append("-isystem"); - try clang_argv.append(args[arg_i]); - } else if (mem.eql(u8, arg, "-dirafter")) { - try clang_argv.append("-dirafter"); - try clang_argv.append(args[arg_i]); - } else if (mem.eql(u8, arg, "-mllvm")) { - try clang_argv.append("-mllvm"); - try clang_argv.append(args[arg_i]); - - try llvm_argv.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--library-path") or mem.eql(u8, arg, "-L")) { - try lib_dirs.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--library")) { - try link_libs.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--object")) { - try objects.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--assembly")) { - try asm_files.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--cache-dir")) { - cache_dir_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--target-arch")) { - target_arch = args[arg_i]; - } else if (mem.eql(u8, arg, "--target-os")) { - target_os = args[arg_i]; - } else if (mem.eql(u8, arg, "--target-environ")) { - target_environ = args[arg_i]; - } else if (mem.eql(u8, arg, "-mmacosx-version-min")) { - mmacosx_version_min = args[arg_i]; - } else if (mem.eql(u8, arg, "-mios-version-min")) { - mios_version_min = args[arg_i]; - } else if (mem.eql(u8, arg, "-framework")) { - try frameworks.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--linker-script")) { - linker_script_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "-rpath")) { - try rpath_list.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--test-filter")) { - try test_filters.append(args[arg_i]); - } else if (mem.eql(u8, arg, "--test-name-prefix")) { - test_name_prefix_arg = args[arg_i]; - } else if (mem.eql(u8, arg, "--ver-major")) { - ver_major = try std.fmt.parseUnsigned(u32, args[arg_i], 10); - } else if (mem.eql(u8, arg, "--ver-minor")) { - ver_minor = try std.fmt.parseUnsigned(u32, args[arg_i], 10); - } else if (mem.eql(u8, arg, "--ver-patch")) { - ver_patch = try std.fmt.parseUnsigned(u32, args[arg_i], 10); - } else if (mem.eql(u8, arg, "--test-cmd")) { - @panic("TODO --test-cmd"); - } else { - badArgs("invalid argument: {}", arg); - } - } - } else if (cmd == Cmd.None) { - if (mem.eql(u8, arg, "build-obj")) { - cmd = Cmd.Build; - build_kind = Module.Kind.Obj; - } else if (mem.eql(u8, arg, "build-exe")) { - cmd = Cmd.Build; - build_kind = Module.Kind.Exe; - } else if (mem.eql(u8, arg, "build-lib")) { - cmd = Cmd.Build; - build_kind = Module.Kind.Lib; - } else if (mem.eql(u8, arg, "version")) { - cmd = Cmd.Version; - } else if (mem.eql(u8, arg, "zen")) { - cmd = Cmd.Zen; - } else if (mem.eql(u8, arg, "translate-c")) { - cmd = Cmd.TranslateC; - } else if (mem.eql(u8, arg, "test")) { - cmd = Cmd.Test; - build_kind = Module.Kind.Exe; - } else { - badArgs("unrecognized command: {}", arg); - } - } else switch (cmd) { - Cmd.Build, Cmd.TranslateC, Cmd.Test => { - if (in_file_arg == null) { - in_file_arg = arg; - } else { - badArgs("unexpected extra parameter: {}", arg); - } - }, - Cmd.Version, Cmd.Zen, Cmd.Targets => { - badArgs("unexpected extra parameter: {}", arg); - }, - Cmd.None => unreachable, - } + if (args.len <= 1) { + try stderr.write(usage); + os.exit(1); } - target.initializeAll(); - - // TODO -// ZigTarget alloc_target; -// ZigTarget *target; -// if (!target_arch && !target_os && !target_environ) { -// target = nullptr; -// } else { -// target = &alloc_target; -// get_unknown_target(target); -// if (target_arch) { -// if (parse_target_arch(target_arch, &target->arch)) { -// fprintf(stderr, "invalid --target-arch argument\n"); -// return usage(arg0); -// } -// } -// if (target_os) { -// if (parse_target_os(target_os, &target->os)) { -// fprintf(stderr, "invalid --target-os argument\n"); -// return usage(arg0); -// } -// } -// if (target_environ) { -// if (parse_target_environ(target_environ, &target->env_type)) { -// fprintf(stderr, "invalid --target-environ argument\n"); -// return usage(arg0); -// } -// } -// } - - switch (cmd) { - Cmd.None => badArgs("expected command"), - Cmd.Zen => return printZen(), - Cmd.Build, Cmd.Test, Cmd.TranslateC => { - if (cmd == Cmd.Build and in_file_arg == null and objects.len == 0 and asm_files.len == 0) { - badArgs("expected source file argument or at least one --object or --assembly argument"); - } else if ((cmd == Cmd.TranslateC or cmd == Cmd.Test) and in_file_arg == null) { - badArgs("expected source file argument"); - } else if (cmd == Cmd.Build and build_kind == Module.Kind.Obj and objects.len != 0) { - badArgs("When building an object file, --object arguments are invalid"); - } - - const root_name = switch (cmd) { - Cmd.Build, Cmd.TranslateC => x: { - if (out_name_arg) |out_name| { - break :x out_name; - } else if (in_file_arg) |in_file_path| { - const basename = os.path.basename(in_file_path); - var it = mem.split(basename, "."); - break :x it.next() ?? badArgs("file name cannot be empty"); - } else { - badArgs("--name [name] not provided and unable to infer"); - } - }, - Cmd.Test => "test", - else => unreachable, - }; - - const zig_root_source_file = if (cmd == Cmd.TranslateC) null else in_file_arg; - - const chosen_cache_dir = cache_dir_arg ?? default_zig_cache_name; - const full_cache_dir = try os.path.resolve(allocator, ".", chosen_cache_dir); - defer allocator.free(full_cache_dir); - - const zig_lib_dir = try resolveZigLibDir(allocator, zig_install_prefix); - errdefer allocator.free(zig_lib_dir); - - const module = try Module.create(allocator, root_name, zig_root_source_file, - Target.Native, build_kind, build_mode, zig_lib_dir, full_cache_dir); - defer module.destroy(); - - module.version_major = ver_major; - module.version_minor = ver_minor; - module.version_patch = ver_patch; - - module.is_test = cmd == Cmd.Test; - if (linker_script_arg) |linker_script| { - module.linker_script = linker_script; - } - module.each_lib_rpath = each_lib_rpath; - module.clang_argv = clang_argv.toSliceConst(); - module.llvm_argv = llvm_argv.toSliceConst(); - module.strip = strip; - module.is_static = is_static; - - if (libc_lib_dir_arg) |libc_lib_dir| { - module.libc_lib_dir = libc_lib_dir; - } - if (libc_static_lib_dir_arg) |libc_static_lib_dir| { - module.libc_static_lib_dir = libc_static_lib_dir; - } - if (libc_include_dir_arg) |libc_include_dir| { - module.libc_include_dir = libc_include_dir; - } - if (msvc_lib_dir_arg) |msvc_lib_dir| { - module.msvc_lib_dir = msvc_lib_dir; - } - if (kernel32_lib_dir_arg) |kernel32_lib_dir| { - module.kernel32_lib_dir = kernel32_lib_dir; - } - if (dynamic_linker_arg) |dynamic_linker| { - module.dynamic_linker = dynamic_linker; - } - module.verbose_tokenize = verbose_tokenize; - module.verbose_ast_tree = verbose_ast_tree; - module.verbose_ast_fmt = verbose_ast_fmt; - module.verbose_link = verbose_link; - module.verbose_ir = verbose_ir; - module.verbose_llvm_ir = verbose_llvm_ir; - module.verbose_cimport = verbose_cimport; - - module.err_color = color; - - module.lib_dirs = lib_dirs.toSliceConst(); - module.darwin_frameworks = frameworks.toSliceConst(); - module.rpath_list = rpath_list.toSliceConst(); - - for (link_libs.toSliceConst()) |name| { - _ = try module.addLinkLib(name, true); - } - - module.windows_subsystem_windows = mwindows; - module.windows_subsystem_console = mconsole; - module.linker_rdynamic = rdynamic; - - if (mmacosx_version_min != null and mios_version_min != null) { - badArgs("-mmacosx-version-min and -mios-version-min options not allowed together"); - } - - if (mmacosx_version_min) |ver| { - module.darwin_version_min = Module.DarwinVersionMin { .MacOS = ver }; - } else if (mios_version_min) |ver| { - module.darwin_version_min = Module.DarwinVersionMin { .Ios = ver }; - } - - module.test_filters = test_filters.toSliceConst(); - module.test_name_prefix = test_name_prefix_arg; - module.out_h_path = out_file_h; - - // TODO - //add_package(g, cur_pkg, g->root_package); - - switch (cmd) { - Cmd.Build => { - module.emit_file_type = emit_file_type; - - module.link_objects = objects.toSliceConst(); - module.assembly_files = asm_files.toSliceConst(); - - try module.build(); - try module.link(out_file); - }, - Cmd.TranslateC => @panic("TODO translate-c"), - Cmd.Test => @panic("TODO test cmd"), - else => unreachable, - } - }, - Cmd.Version => { - var stdout_file = try io.getStdErr(); - try stdout_file.write(std.cstr.toSliceConst(c.ZIG_VERSION_STRING)); - try stdout_file.write("\n"); - }, - Cmd.Targets => @panic("TODO zig targets"), + const commands = []Command { + Command { .name = "build", .exec = cmdBuild }, + Command { .name = "build-exe", .exec = cmdBuildExe }, + Command { .name = "build-lib", .exec = cmdBuildLib }, + Command { .name = "build-obj", .exec = cmdBuildObj }, + Command { .name = "fmt", .exec = cmdFmt }, + Command { .name = "run", .exec = cmdRun }, + Command { .name = "targets", .exec = cmdTargets }, + Command { .name = "test", .exec = cmdTest }, + Command { .name = "translate-c", .exec = cmdTranslateC }, + Command { .name = "version", .exec = cmdVersion }, + Command { .name = "zen", .exec = cmdZen }, + + // undocumented commands + Command { .name = "help", .exec = cmdHelp }, + Command { .name = "internal", .exec = cmdInternal }, + }; + + for (commands) |command| { + if (mem.eql(u8, command.name, args[1])) { + try command.exec(allocator, args[2..]); + return; + } } -} -fn printUsage(stream: var) !void { - try stream.write( - \\Usage: zig [command] [options] - \\ - \\Commands: - \\ build build project from build.zig - \\ build-exe [source] create executable from source or object files - \\ build-lib [source] create library from source or object files - \\ build-obj [source] create object from source or assembly - \\ fmt [file] parse file and render in canonical zig format - \\ translate-c [source] convert c code to zig code - \\ targets list available compilation targets - \\ test [source] create and run a test build - \\ version print version number and exit - \\ zen print zen of zig and exit - \\Compile Options: - \\ --assembly [source] add assembly file to build - \\ --cache-dir [path] override the cache directory - \\ --color [auto|off|on] enable or disable colored error messages - \\ --emit [filetype] emit a specific file format as compilation output - \\ --enable-timing-info print timing diagnostics - \\ --libc-include-dir [path] directory where libc stdlib.h resides - \\ --name [name] override output name - \\ --output [file] override destination path - \\ --output-h [file] override generated header file path - \\ --pkg-begin [name] [path] make package available to import and push current pkg - \\ --pkg-end pop current pkg - \\ --release-fast build with optimizations on and safety off - \\ --release-safe build with optimizations on and safety on - \\ --static output will be statically linked - \\ --strip exclude debug symbols - \\ --target-arch [name] specify target architecture - \\ --target-environ [name] specify target environment - \\ --target-os [name] specify target operating system - \\ --verbose-tokenize enable compiler debug info: tokenization - \\ --verbose-ast-tree enable compiler debug info: parsing into an AST (treeview) - \\ --verbose-ast-fmt enable compiler debug info: parsing into an AST (render source) - \\ --verbose-cimport enable compiler debug info: C imports - \\ --verbose-ir enable compiler debug info: Zig IR - \\ --verbose-llvm-ir enable compiler debug info: LLVM IR - \\ --verbose-link enable compiler debug info: linking - \\ --zig-install-prefix [path] override directory where zig thinks it is installed - \\ -dirafter [dir] same as -isystem but do it last - \\ -isystem [dir] add additional search path for other .h files - \\ -mllvm [arg] additional arguments to forward to LLVM's option processing - \\Link Options: - \\ --ar-path [path] set the path to ar - \\ --dynamic-linker [path] set the path to ld.so - \\ --each-lib-rpath add rpath for each used dynamic library - \\ --libc-lib-dir [path] directory where libc crt1.o resides - \\ --libc-static-lib-dir [path] directory where libc crtbegin.o resides - \\ --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides - \\ --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides - \\ --library [lib] link against lib - \\ --library-path [dir] add a directory to the library search path - \\ --linker-script [path] use a custom linker script - \\ --object [obj] add object file to build - \\ -L[dir] alias for --library-path - \\ -rdynamic add all symbols to the dynamic symbol table - \\ -rpath [path] add directory to the runtime library search path - \\ -mconsole (windows) --subsystem console to the linker - \\ -mwindows (windows) --subsystem windows to the linker - \\ -framework [name] (darwin) link against framework - \\ -mios-version-min [ver] (darwin) set iOS deployment target - \\ -mmacosx-version-min [ver] (darwin) set Mac OS X deployment target - \\ --ver-major [ver] dynamic library semver major version - \\ --ver-minor [ver] dynamic library semver minor version - \\ --ver-patch [ver] dynamic library semver patch version - \\Test Options: - \\ --test-filter [text] skip tests that do not match filter - \\ --test-name-prefix [text] add prefix to all tests - \\ --test-cmd [arg] specify test execution command one arg at a time - \\ --test-cmd-bin appends test binary path to test cmd args - \\ - ); -} - -fn printZen() !void { - var stdout_file = try io.getStdErr(); - try stdout_file.write( - \\ - \\ * Communicate intent precisely. - \\ * Edge cases matter. - \\ * Favor reading code over writing code. - \\ * Only one obvious way to do things. - \\ * Runtime crashes are better than bugs. - \\ * Compile errors are better than runtime crashes. - \\ * Incremental improvements. - \\ * Avoid local maximums. - \\ * Reduce the amount one must remember. - \\ * Minimize energy spent on coding style. - \\ * Together we serve end users. - \\ - \\ - ); + try stderr.print("unknown command: {}\n\n", args[1]); + try stderr.write(usage); } -fn buildMain(allocator: &mem.Allocator, argv: []const []const u8) !void { - var build_file: [] const u8 = "build.zig"; - var cache_dir: ?[] const u8 = null; - var zig_install_prefix: ?[] const u8 = null; - var asked_for_help = false; - var asked_for_init = false; - - var args = ArrayList([] const u8).init(allocator); - defer args.deinit(); - - var zig_exe_path = try os.selfExePath(allocator); - defer allocator.free(zig_exe_path); - - try args.append(""); // Placeholder for zig-cache/build - try args.append(""); // Placeholder for zig_exe_path - try args.append(""); // Placeholder for build_file_dirname - try args.append(""); // Placeholder for full_cache_dir +// cmd:build /////////////////////////////////////////////////////////////////////////////////////// + +const usage_build = + \\usage: zig build <options> + \\ + \\General Options: + \\ --help Print this help and exit + \\ --init Generate a build.zig template + \\ --build-file [file] Override path to build.zig + \\ --cache-dir [path] Override path to cache directory + \\ --verbose Print commands before executing them + \\ --prefix [path] Override default install prefix + \\ + \\Project-Specific Options: + \\ + \\ Project-specific options become available when the build file is found. + \\ + \\Advanced Options: + \\ --build-file [file] Override path to build.zig + \\ --cache-dir [path] Override path to cache directory + \\ --verbose-tokenize Enable compiler debug output for tokenization + \\ --verbose-ast Enable compiler debug output for parsing into an AST + \\ --verbose-link Enable compiler debug output for linking + \\ --verbose-ir Enable compiler debug output for Zig IR + \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR + \\ --verbose-cimport Enable compiler debug output for C imports + \\ + \\ + ; + +const args_build_spec = []Flag { + Flag.Bool("--help"), + Flag.Bool("--init"), + Flag.Arg1("--build-file"), + Flag.Arg1("--cache-dir"), + Flag.Bool("--verbose"), + Flag.Arg1("--prefix"), + + Flag.Arg1("--build-file"), + Flag.Arg1("--cache-dir"), + Flag.Bool("--verbose-tokenize"), + Flag.Bool("--verbose-ast"), + Flag.Bool("--verbose-link"), + Flag.Bool("--verbose-ir"), + Flag.Bool("--verbose-llvm-ir"), + Flag.Bool("--verbose-cimport"), +}; - var i: usize = 0; - while (i < argv.len) : (i += 1) { - var arg = argv[i]; - if (mem.eql(u8, arg, "--help")) { - asked_for_help = true; - try args.append(argv[i]); - } else if (mem.eql(u8, arg, "--init")) { - asked_for_init = true; - try args.append(argv[i]); - } else if (i + 1 < argv.len and mem.eql(u8, arg, "--build-file")) { - build_file = argv[i + 1]; - i += 1; - } else if (i + 1 < argv.len and mem.eql(u8, arg, "--cache-dir")) { - cache_dir = argv[i + 1]; - i += 1; - } else if (i + 1 < argv.len and mem.eql(u8, arg, "--zig-install-prefix")) { - try args.append(arg); - i += 1; - zig_install_prefix = argv[i]; - try args.append(argv[i]); - } else { - try args.append(arg); - } +const missing_build_file = + \\No 'build.zig' file found. + \\ + \\Initialize a 'build.zig' template file with `zig build --init`, + \\or build an executable directly with `zig build-exe $FILENAME.zig`. + \\ + \\See: `zig build --help` or `zig help` for more options. + \\ + ; + +fn cmdBuild(allocator: &Allocator, args: []const []const u8) !void { + var flags = try Args.parse(allocator, args_build_spec, args); + defer flags.deinit(); + + if (flags.present("help")) { + try stderr.write(usage_build); + os.exit(0); } - const zig_lib_dir = try resolveZigLibDir(allocator, zig_install_prefix); + const zig_lib_dir = try introspect.resolveZigLibDir(allocator); defer allocator.free(zig_lib_dir); const zig_std_dir = try os.path.join(allocator, zig_lib_dir, "std"); @@ -619,113 +171,502 @@ fn buildMain(allocator: &mem.Allocator, argv: []const []const u8) !void { const build_runner_path = try os.path.join(allocator, special_dir, "build_runner.zig"); defer allocator.free(build_runner_path); - // g = codegen_create(build_runner_path, ...) - // codegen_set_out_name(g, "build") - + const build_file = flags.single("build-file") ?? "build.zig"; const build_file_abs = try os.path.resolve(allocator, ".", build_file); defer allocator.free(build_file_abs); - const build_file_basename = os.path.basename(build_file_abs); - const build_file_dirname = os.path.dirname(build_file_abs); + const build_file_exists = os.File.access(allocator, build_file_abs, os.default_file_mode) catch false; - var full_cache_dir: []u8 = undefined; - if (cache_dir == null) { - full_cache_dir = try os.path.join(allocator, build_file_dirname, "zig-cache"); - } else { - full_cache_dir = try os.path.resolve(allocator, ".", ??cache_dir, full_cache_dir); - } - defer allocator.free(full_cache_dir); + if (flags.present("init")) { + if (build_file_exists) { + try stderr.print("build.zig already exists\n"); + os.exit(1); + } - const path_to_build_exe = try os.path.join(allocator, full_cache_dir, "build"); - defer allocator.free(path_to_build_exe); - // codegen_set_cache_dir(g, full_cache_dir) + // need a new scope for proper defer scope finalization on exit + { + const build_template_path = try os.path.join(allocator, special_dir, "build_file_template.zig"); + defer allocator.free(build_template_path); - args.items[0] = path_to_build_exe; - args.items[1] = zig_exe_path; - args.items[2] = build_file_dirname; - args.items[3] = full_cache_dir; + try os.copyFile(allocator, build_template_path, build_file_abs); + try stderr.print("wrote build.zig template\n"); + } - var build_file_exists: bool = undefined; - if (os.File.openRead(allocator, build_file_abs)) |*file| { - file.close(); - build_file_exists = true; - } else |_| { - build_file_exists = false; + os.exit(0); } - if (!build_file_exists and asked_for_help) { - // TODO(bnoordhuis) Print help message from std/special/build_runner.zig - return; + if (!build_file_exists) { + try stderr.write(missing_build_file); + os.exit(1); } - if (!build_file_exists and asked_for_init) { - const build_template_path = try os.path.join(allocator, special_dir, "build_file_template.zig"); - defer allocator.free(build_template_path); - - var srcfile = try os.File.openRead(allocator, build_template_path); - defer srcfile.close(); + // TODO: Invoke build.zig entrypoint directly? + var zig_exe_path = try os.selfExePath(allocator); + defer allocator.free(zig_exe_path); - var dstfile = try os.File.openWrite(allocator, build_file_abs); - defer dstfile.close(); + var build_args = ArrayList([]const u8).init(allocator); + defer build_args.deinit(); - while (true) { - var buffer: [4096]u8 = undefined; - const n = try srcfile.read(buffer[0..]); - if (n == 0) break; - try dstfile.write(buffer[0..n]); - } + const build_file_basename = os.path.basename(build_file_abs); + const build_file_dirname = os.path.dirname(build_file_abs); - return; + var full_cache_dir: []u8 = undefined; + if (flags.single("cache-dir")) |cache_dir| { + full_cache_dir = try os.path.resolve(allocator, ".", cache_dir, full_cache_dir); + } else { + full_cache_dir = try os.path.join(allocator, build_file_dirname, "zig-cache"); } + defer allocator.free(full_cache_dir); - if (!build_file_exists) { - warn( - \\No 'build.zig' file found. - \\Initialize a 'build.zig' template file with `zig build --init`, - \\or build an executable directly with `zig build-exe $FILENAME.zig`. - \\See: `zig build --help` or `zig help` for more options. - \\ - ); - os.exit(1); - } + const path_to_build_exe = try os.path.join(allocator, full_cache_dir, "build"); + defer allocator.free(path_to_build_exe); - // codegen_build(g) - // codegen_link(g, path_to_build_exe) - // codegen_destroy(g) + try build_args.append(path_to_build_exe); + try build_args.append(zig_exe_path); + try build_args.append(build_file_dirname); + try build_args.append(full_cache_dir); - var proc = try os.ChildProcess.init(args.toSliceConst(), allocator); + var proc = try os.ChildProcess.init(build_args.toSliceConst(), allocator); defer proc.deinit(); var term = try proc.spawnAndWait(); switch (term) { os.ChildProcess.Term.Exited => |status| { if (status != 0) { - warn("{} exited with status {}\n", args.at(0), status); + try stderr.print("{} exited with status {}\n", build_args.at(0), status); os.exit(1); } }, os.ChildProcess.Term.Signal => |signal| { - warn("{} killed by signal {}\n", args.at(0), signal); + try stderr.print("{} killed by signal {}\n", build_args.at(0), signal); os.exit(1); }, os.ChildProcess.Term.Stopped => |signal| { - warn("{} stopped by signal {}\n", args.at(0), signal); + try stderr.print("{} stopped by signal {}\n", build_args.at(0), signal); os.exit(1); }, os.ChildProcess.Term.Unknown => |status| { - warn("{} encountered unknown failure {}\n", args.at(0), status); + try stderr.print("{} encountered unknown failure {}\n", build_args.at(0), status); + os.exit(1); + }, + } +} + +// cmd:build-exe /////////////////////////////////////////////////////////////////////////////////// + +const usage_build_generic = + \\usage: zig build-exe <options> [file] + \\ zig build-lib <options> [file] + \\ zig build-obj <options> [file] + \\ + \\General Options: + \\ --help Print this help and exit + \\ --color [auto|off|on] Enable or disable colored error messages + \\ + \\Compile Options: + \\ --assembly [source] Add assembly file to build + \\ --cache-dir [path] Override the cache directory + \\ --emit [filetype] Emit a specific file format as compilation output + \\ --enable-timing-info Print timing diagnostics + \\ --libc-include-dir [path] Directory where libc stdlib.h resides + \\ --name [name] Override output name + \\ --output [file] Override destination path + \\ --output-h [file] Override generated header file path + \\ --pkg-begin [name] [path] Make package available to import and push current pkg + \\ --pkg-end Pop current pkg + \\ --release-fast Build with optimizations on and safety off + \\ --release-safe Build with optimizations on and safety on + \\ --static Output will be statically linked + \\ --strip Exclude debug symbols + \\ --target-arch [name] Specify target architecture + \\ --target-environ [name] Specify target environment + \\ --target-os [name] Specify target operating system + \\ --verbose-tokenize Turn on compiler debug output for tokenization + \\ --verbose-ast-tree Turn on compiler debug output for parsing into an AST (tree view) + \\ --verbose-ast-fmt Turn on compiler debug output for parsing into an AST (render source) + \\ --verbose-link Turn on compiler debug output for linking + \\ --verbose-ir Turn on compiler debug output for Zig IR + \\ --verbose-llvm-ir Turn on compiler debug output for LLVM IR + \\ --verbose-cimport Turn on compiler debug output for C imports + \\ -dirafter [dir] Same as -isystem but do it last + \\ -isystem [dir] Add additional search path for other .h files + \\ -mllvm [arg] Additional arguments to forward to LLVM's option processing + \\ + \\Link Options: + \\ --ar-path [path] Set the path to ar + \\ --dynamic-linker [path] Set the path to ld.so + \\ --each-lib-rpath Add rpath for each used dynamic library + \\ --libc-lib-dir [path] Directory where libc crt1.o resides + \\ --libc-static-lib-dir [path] Directory where libc crtbegin.o resides + \\ --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides + \\ --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides + \\ --library [lib] Link against lib + \\ --forbid-library [lib] Make it an error to link against lib + \\ --library-path [dir] Add a directory to the library search path + \\ --linker-script [path] Use a custom linker script + \\ --object [obj] Add object file to build + \\ -rdynamic Add all symbols to the dynamic symbol table + \\ -rpath [path] Add directory to the runtime library search path + \\ -mconsole (windows) --subsystem console to the linker + \\ -mwindows (windows) --subsystem windows to the linker + \\ -framework [name] (darwin) link against framework + \\ -mios-version-min [ver] (darwin) set iOS deployment target + \\ -mmacosx-version-min [ver] (darwin) set Mac OS X deployment target + \\ --ver-major [ver] Dynamic library semver major version + \\ --ver-minor [ver] Dynamic library semver minor version + \\ --ver-patch [ver] Dynamic library semver patch version + \\ + \\ + ; + +const args_build_generic = []Flag { + Flag.Bool("--help"), + Flag.Option("--color", []const []const u8 { "auto", "off", "on" }), + + Flag.ArgMergeN("--assembly", 1), + Flag.Arg1("--cache-dir"), + Flag.Option("--emit", []const []const u8 { "asm", "bin", "llvm-ir" }), + Flag.Bool("--enable-timing-info"), + Flag.Arg1("--libc-include-dir"), + Flag.Arg1("--name"), + Flag.Arg1("--output"), + Flag.Arg1("--output-h"), + // NOTE: Parsed manually after initial check + Flag.ArgN("--pkg-begin", 2), + Flag.Bool("--pkg-end"), + Flag.Bool("--release-fast"), + Flag.Bool("--release-safe"), + Flag.Bool("--static"), + Flag.Bool("--strip"), + Flag.Arg1("--target-arch"), + Flag.Arg1("--target-environ"), + Flag.Arg1("--target-os"), + Flag.Bool("--verbose-tokenize"), + Flag.Bool("--verbose-ast-tree"), + Flag.Bool("--verbose-ast-fmt"), + Flag.Bool("--verbose-link"), + Flag.Bool("--verbose-ir"), + Flag.Bool("--verbose-llvm-ir"), + Flag.Bool("--verbose-cimport"), + Flag.Arg1("-dirafter"), + Flag.ArgMergeN("-isystem", 1), + Flag.Arg1("-mllvm"), + + Flag.Arg1("--ar-path"), + Flag.Arg1("--dynamic-linker"), + Flag.Bool("--each-lib-rpath"), + Flag.Arg1("--libc-lib-dir"), + Flag.Arg1("--libc-static-lib-dir"), + Flag.Arg1("--msvc-lib-dir"), + Flag.Arg1("--kernel32-lib-dir"), + Flag.ArgMergeN("--library", 1), + Flag.ArgMergeN("--forbid-library", 1), + Flag.ArgMergeN("--library-path", 1), + Flag.Arg1("--linker-script"), + Flag.ArgMergeN("--object", 1), + // NOTE: Removed -L since it would need to be special-cased and we have an alias in library-path + Flag.Bool("-rdynamic"), + Flag.Arg1("-rpath"), + Flag.Bool("-mconsole"), + Flag.Bool("-mwindows"), + Flag.ArgMergeN("-framework", 1), + Flag.Arg1("-mios-version-min"), + Flag.Arg1("-mmacosx-version-min"), + Flag.Arg1("--ver-major"), + Flag.Arg1("--ver-minor"), + Flag.Arg1("--ver-patch"), +}; + +fn buildOutputType(allocator: &Allocator, args: []const []const u8, out_type: Module.Kind) !void { + var flags = try Args.parse(allocator, args_build_generic, args); + defer flags.deinit(); + + if (flags.present("help")) { + try stderr.write(usage_build_generic); + os.exit(0); + } + + var build_mode = builtin.Mode.Debug; + if (flags.present("release-fast")) { + build_mode = builtin.Mode.ReleaseFast; + } else if (flags.present("release-safe")) { + build_mode = builtin.Mode.ReleaseSafe; + } + + var color = Module.ErrColor.Auto; + if (flags.single("color")) |color_flag| { + if (mem.eql(u8, color_flag, "auto")) { + color = Module.ErrColor.Auto; + } else if (mem.eql(u8, color_flag, "on")) { + color = Module.ErrColor.On; + } else if (mem.eql(u8, color_flag, "off")) { + color = Module.ErrColor.Off; + } else { + unreachable; + } + } + + var emit_type = Module.Emit.Binary; + if (flags.single("emit")) |emit_flag| { + if (mem.eql(u8, emit_flag, "asm")) { + emit_type = Module.Emit.Assembly; + } else if (mem.eql(u8, emit_flag, "bin")) { + emit_type = Module.Emit.Binary; + } else if (mem.eql(u8, emit_flag, "llvm-ir")) { + emit_type = Module.Emit.LlvmIr; + } else { + unreachable; + } + } + + var cur_pkg = try Module.CliPkg.init(allocator, "", "", null); // TODO: Need a path, name? + defer cur_pkg.deinit(); + + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg_name = args[i]; + if (mem.eql(u8, "--pkg-begin", arg_name)) { + // following two arguments guaranteed to exist due to arg parsing + i += 1; + const new_pkg_name = args[i]; + i += 1; + const new_pkg_path = args[i]; + + var new_cur_pkg = try Module.CliPkg.init(allocator, new_pkg_name, new_pkg_path, cur_pkg); + try cur_pkg.children.append(new_cur_pkg); + cur_pkg = new_cur_pkg; + } else if (mem.eql(u8, "--pkg-end", arg_name)) { + if (cur_pkg.parent == null) { + try stderr.print("encountered --pkg-end with no matching --pkg-begin\n"); + os.exit(1); + } + cur_pkg = ??cur_pkg.parent; + } + } + + if (cur_pkg.parent != null) { + try stderr.print("unmatched --pkg-begin\n"); + os.exit(1); + } + + var in_file: ?[]const u8 = undefined; + switch (flags.positionals.len) { + 0 => { + try stderr.write("--name [name] not provided and unable to infer\n"); os.exit(1); }, + 1 => { + in_file = flags.positionals.at(0); + }, + else => { + try stderr.write("only one zig input file is accepted during build\n"); + os.exit(1); + }, + } + + const basename = os.path.basename(??in_file); + var it = mem.split(basename, "."); + const root_name = it.next() ?? { + try stderr.write("file name cannot be empty\n"); + os.exit(1); + }; + + const asm_a= flags.many("assembly"); + const obj_a = flags.many("object"); + if (in_file == null and (obj_a == null or (??obj_a).len == 0) and (asm_a == null or (??asm_a).len == 0)) { + try stderr.write("Expected source file argument or at least one --object or --assembly argument\n"); + os.exit(1); + } + + if (out_type == Module.Kind.Obj and (obj_a != null and (??obj_a).len != 0)) { + try stderr.write("When building an object file, --object arguments are invalid\n"); + os.exit(1); + } + + const zig_root_source_file = in_file; + + const full_cache_dir = os.path.resolve(allocator, ".", flags.single("cache-dir") ?? "zig-cache"[0..]) catch { + os.exit(1); + }; + defer allocator.free(full_cache_dir); + + const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch os.exit(1); + defer allocator.free(zig_lib_dir); + + var module = + try Module.create( + allocator, + root_name, + zig_root_source_file, + Target.Native, + out_type, + build_mode, + zig_lib_dir, + full_cache_dir + ); + defer module.destroy(); + + module.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") ?? "0", 10); + module.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") ?? "0", 10); + module.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") ?? "0", 10); + + module.is_test = false; + + if (flags.single("linker-script")) |linker_script| { + module.linker_script = linker_script; + } + + module.each_lib_rpath = flags.present("each-lib-rpath"); + + var clang_argv_buf = ArrayList([]const u8).init(allocator); + defer clang_argv_buf.deinit(); + if (flags.many("mllvm")) |mllvm_flags| { + for (mllvm_flags) |mllvm| { + try clang_argv_buf.append("-mllvm"); + try clang_argv_buf.append(mllvm); + } + + module.llvm_argv = mllvm_flags; + module.clang_argv = clang_argv_buf.toSliceConst(); + } + + module.strip = flags.present("strip"); + module.is_static = flags.present("static"); + + if (flags.single("libc-lib-dir")) |libc_lib_dir| { + module.libc_lib_dir = libc_lib_dir; + } + if (flags.single("libc-static-lib-dir")) |libc_static_lib_dir| { + module.libc_static_lib_dir = libc_static_lib_dir; + } + if (flags.single("libc-include-dir")) |libc_include_dir| { + module.libc_include_dir = libc_include_dir; + } + if (flags.single("msvc-lib-dir")) |msvc_lib_dir| { + module.msvc_lib_dir = msvc_lib_dir; + } + if (flags.single("kernel32-lib-dir")) |kernel32_lib_dir| { + module.kernel32_lib_dir = kernel32_lib_dir; + } + if (flags.single("dynamic-linker")) |dynamic_linker| { + module.dynamic_linker = dynamic_linker; + } + + module.verbose_tokenize = flags.present("verbose-tokenize"); + module.verbose_ast_tree = flags.present("verbose-ast-tree"); + module.verbose_ast_fmt = flags.present("verbose-ast-fmt"); + module.verbose_link = flags.present("verbose-link"); + module.verbose_ir = flags.present("verbose-ir"); + module.verbose_llvm_ir = flags.present("verbose-llvm-ir"); + module.verbose_cimport = flags.present("verbose-cimport"); + + module.err_color = color; + + if (flags.many("library-path")) |lib_dirs| { + module.lib_dirs = lib_dirs; + } + + if (flags.many("framework")) |frameworks| { + module.darwin_frameworks = frameworks; + } + + if (flags.many("rpath")) |rpath_list| { + module.rpath_list = rpath_list; } + + if (flags.single("output-h")) |output_h| { + module.out_h_path = output_h; + } + + module.windows_subsystem_windows = flags.present("mwindows"); + module.windows_subsystem_console = flags.present("mconsole"); + module.linker_rdynamic = flags.present("rdynamic"); + + if (flags.single("mmacosx-version-min") != null and flags.single("mios-version-min") != null) { + try stderr.write("-mmacosx-version-min and -mios-version-min options not allowed together\n"); + os.exit(1); + } + + if (flags.single("mmacosx-version-min")) |ver| { + module.darwin_version_min = Module.DarwinVersionMin { .MacOS = ver }; + } + if (flags.single("mios-version-min")) |ver| { + module.darwin_version_min = Module.DarwinVersionMin { .Ios = ver }; + } + + module.emit_file_type = emit_type; + if (flags.many("object")) |objects| { + module.link_objects = objects; + } + if (flags.many("assembly")) |assembly_files| { + module.assembly_files = assembly_files; + } + + try module.build(); + try module.link(flags.single("out-file") ?? null); + + if (flags.present("print-timing-info")) { + // codegen_print_timing_info(g, stderr); + } + + try stderr.print("building {}: {}\n", @tagName(out_type), in_file); +} + +fn cmdBuildExe(allocator: &Allocator, args: []const []const u8) !void { + try buildOutputType(allocator, args, Module.Kind.Exe); } -fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void { - for (file_paths) |file_path| { +// cmd:build-lib /////////////////////////////////////////////////////////////////////////////////// + +fn cmdBuildLib(allocator: &Allocator, args: []const []const u8) !void { + try buildOutputType(allocator, args, Module.Kind.Lib); +} + +// cmd:build-obj /////////////////////////////////////////////////////////////////////////////////// + +fn cmdBuildObj(allocator: &Allocator, args: []const []const u8) !void { + try buildOutputType(allocator, args, Module.Kind.Obj); +} + +// cmd:fmt ///////////////////////////////////////////////////////////////////////////////////////// + +const usage_fmt = + \\usage: zig fmt [file]... + \\ + \\ Formats the input files and modifies them in-place. + \\ + \\Options: + \\ --help Print this help and exit + \\ --keep-backups Retain backup entries for every file + \\ + \\ + ; + +const args_fmt_spec = []Flag { + Flag.Bool("--help"), + Flag.Bool("--keep-backups"), +}; + +fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void { + var flags = try Args.parse(allocator, args_fmt_spec, args); + defer flags.deinit(); + + if (flags.present("help")) { + try stderr.write(usage_fmt); + os.exit(0); + } + + if (flags.positionals.len == 0) { + try stderr.write("expected at least one source file argument\n"); + os.exit(1); + } + + for (flags.positionals.toSliceConst()) |file_path| { var file = try os.File.openRead(allocator, file_path); defer file.close(); const source_code = io.readFileAlloc(allocator, file_path) catch |err| { - warn("unable to open '{}': {}", file_path, err); + try stderr.print("unable to open '{}': {}", file_path, err); continue; }; defer allocator.free(source_code); @@ -734,72 +675,312 @@ fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void { var parser = std.zig.Parser.init(&tokenizer, allocator, file_path); defer parser.deinit(); - var tree = try parser.parse(); + var tree = parser.parse() catch |err| { + try stderr.print("error parsing file '{}': {}\n", file_path, err); + continue; + }; defer tree.deinit(); - const baf = try io.BufferedAtomicFile.create(allocator, file_path); - defer baf.destroy(); + var original_file_backup = try Buffer.init(allocator, file_path); + defer original_file_backup.deinit(); + try original_file_backup.append(".backup"); + + try os.rename(allocator, file_path, original_file_backup.toSliceConst()); - try parser.renderSource(baf.stream(), tree.root_node); - try baf.finish(); + try stderr.print("{}\n", file_path); + + // TODO: BufferedAtomicFile has some access problems. + var out_file = try os.File.openWrite(allocator, file_path); + defer out_file.close(); + + var out_file_stream = io.FileOutStream.init(&out_file); + try parser.renderSource(out_file_stream.stream, tree.root_node); + + if (!flags.present("keep-backups")) { + try os.deleteFile(allocator, original_file_backup.toSliceConst()); + } } } -/// Caller must free result -fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const u8) ![]u8 { - if (zig_install_prefix_arg) |zig_install_prefix| { - return testZigInstallPrefix(allocator, zig_install_prefix) catch |err| { - warn("No Zig installation found at prefix {}: {}\n", zig_install_prefix_arg, @errorName(err)); - return error.ZigInstallationNotFound; - }; - } else { - return findZigLibDir(allocator) catch |err| { - warn("Unable to find zig lib directory: {}.\nReinstall Zig or use --zig-install-prefix.\n", - @errorName(err)); - return error.ZigLibDirNotFound; - }; +// cmd:targets ///////////////////////////////////////////////////////////////////////////////////// + +fn cmdTargets(allocator: &Allocator, args: []const []const u8) !void { + try stdout.write("Architectures:\n"); + { + comptime var i: usize = 0; + inline while (i < @memberCount(builtin.Arch)) : (i += 1) { + comptime const arch_tag = @memberName(builtin.Arch, i); + // NOTE: Cannot use empty string, see #918. + comptime const native_str = + if (comptime mem.eql(u8, arch_tag, @tagName(builtin.arch))) " (native)\n" else "\n"; + + try stdout.print(" {}{}", arch_tag, native_str); + } + } + try stdout.write("\n"); + + try stdout.write("Operating Systems:\n"); + { + comptime var i: usize = 0; + inline while (i < @memberCount(builtin.Os)) : (i += 1) { + comptime const os_tag = @memberName(builtin.Os, i); + // NOTE: Cannot use empty string, see #918. + comptime const native_str = + if (comptime mem.eql(u8, os_tag, @tagName(builtin.os))) " (native)\n" else "\n"; + + try stdout.print(" {}{}", os_tag, native_str); + } + } + try stdout.write("\n"); + + try stdout.write("Environments:\n"); + { + comptime var i: usize = 0; + inline while (i < @memberCount(builtin.Environ)) : (i += 1) { + comptime const environ_tag = @memberName(builtin.Environ, i); + // NOTE: Cannot use empty string, see #918. + comptime const native_str = + if (comptime mem.eql(u8, environ_tag, @tagName(builtin.environ))) " (native)\n" else "\n"; + + try stdout.print(" {}{}", environ_tag, native_str); + } } } -/// Caller must free result -fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 { - const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig"); - errdefer allocator.free(test_zig_dir); +// cmd:version ///////////////////////////////////////////////////////////////////////////////////// + +fn cmdVersion(allocator: &Allocator, args: []const []const u8) !void { + try stdout.print("{}\n", std.cstr.toSliceConst(c.ZIG_VERSION_STRING)); +} - const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig"); - defer allocator.free(test_index_file); +// cmd:test //////////////////////////////////////////////////////////////////////////////////////// - var file = try os.File.openRead(allocator, test_index_file); - file.close(); +const usage_test = + \\usage: zig test [file]... + \\ + \\Options: + \\ --help Print this help and exit + \\ + \\ + ; + +const args_test_spec = []Flag { + Flag.Bool("--help"), +}; + + +fn cmdTest(allocator: &Allocator, args: []const []const u8) !void { + var flags = try Args.parse(allocator, args_build_spec, args); + defer flags.deinit(); + + if (flags.present("help")) { + try stderr.write(usage_test); + os.exit(0); + } - return test_zig_dir; + if (flags.positionals.len != 1) { + try stderr.write("expected exactly one zig source file\n"); + os.exit(1); + } + + // compile the test program into the cache and run + + // NOTE: May be overlap with buildOutput, take the shared part out. + try stderr.print("testing file {}\n", flags.positionals.at(0)); } -/// Caller must free result -fn findZigLibDir(allocator: &mem.Allocator) ![]u8 { - const self_exe_path = try os.selfExeDirPath(allocator); - defer allocator.free(self_exe_path); +// cmd:run ///////////////////////////////////////////////////////////////////////////////////////// + +// Run should be simple and not expose the full set of arguments provided by build-exe. If specific +// build requirements are need, the user should `build-exe` then `run` manually. +const usage_run = + \\usage: zig run [file] -- <runtime args> + \\ + \\Options: + \\ --help Print this help and exit + \\ + \\ + ; + +const args_run_spec = []Flag { + Flag.Bool("--help"), +}; - var cur_path: []const u8 = self_exe_path; - while (true) { - const test_dir = os.path.dirname(cur_path); - if (mem.eql(u8, test_dir, cur_path)) { +fn cmdRun(allocator: &Allocator, args: []const []const u8) !void { + var compile_args = args; + var runtime_args: []const []const u8 = []const []const u8 {}; + + for (args) |argv, i| { + if (mem.eql(u8, argv, "--")) { + compile_args = args[0..i]; + runtime_args = args[i+1..]; break; } + } + var flags = try Args.parse(allocator, args_run_spec, compile_args); + defer flags.deinit(); - return testZigInstallPrefix(allocator, test_dir) catch |err| { - cur_path = test_dir; - continue; - }; + if (flags.present("help")) { + try stderr.write(usage_run); + os.exit(0); + } + + if (flags.positionals.len != 1) { + try stderr.write("expected exactly one zig source file\n"); + os.exit(1); + } + + try stderr.print("runtime args:\n"); + for (runtime_args) |cargs| { + try stderr.print("{}\n", cargs); + } +} + +// cmd:translate-c ///////////////////////////////////////////////////////////////////////////////// + +const usage_translate_c = + \\usage: zig translate-c [file] + \\ + \\Options: + \\ --help Print this help and exit + \\ --enable-timing-info Print timing diagnostics + \\ --output [path] Output file to write generated zig file (default: stdout) + \\ + \\ + ; + +const args_translate_c_spec = []Flag { + Flag.Bool("--help"), + Flag.Bool("--enable-timing-info"), + Flag.Arg1("--libc-include-dir"), + Flag.Arg1("--output"), +}; + +fn cmdTranslateC(allocator: &Allocator, args: []const []const u8) !void { + var flags = try Args.parse(allocator, args_translate_c_spec, args); + defer flags.deinit(); + + if (flags.present("help")) { + try stderr.write(usage_translate_c); + os.exit(0); + } + + if (flags.positionals.len != 1) { + try stderr.write("expected exactly one c source file\n"); + os.exit(1); + } + + // set up codegen + + const zig_root_source_file = null; + + // NOTE: translate-c shouldn't require setting up the full codegen instance as it does in + // the C++ compiler. + + // codegen_create(g); + // codegen_set_out_name(g, null); + // codegen_translate_c(g, flags.positional.at(0)) + + var output_stream = stdout; + if (flags.single("output")) |output_file| { + var file = try os.File.openWrite(allocator, output_file); + defer file.close(); + + var file_stream = io.FileOutStream.init(&file); + // TODO: Not being set correctly, still stdout + output_stream = &file_stream.stream; } - // TODO look in hard coded installation path from configuration - //if (ZIG_INSTALL_PREFIX != nullptr) { - // if (test_zig_install_prefix(buf_create_from_str(ZIG_INSTALL_PREFIX), out_path)) { - // return 0; - // } - //} + // ast_render(g, output_stream, g->root_import->root, 4); + try output_stream.write("pub const example = 10;\n"); + + if (flags.present("enable-timing-info")) { + // codegen_print_timing_info(g, stdout); + try stderr.write("printing timing info for translate-c\n"); + } +} + +// cmd:help //////////////////////////////////////////////////////////////////////////////////////// - return error.FileNotFound; +fn cmdHelp(allocator: &Allocator, args: []const []const u8) !void { + try stderr.write(usage); +} + +// cmd:zen ///////////////////////////////////////////////////////////////////////////////////////// + +const info_zen = + \\ + \\ * Communicate intent precisely. + \\ * Edge cases matter. + \\ * Favor reading code over writing code. + \\ * Only one obvious way to do things. + \\ * Runtime crashes are better than bugs. + \\ * Compile errors are better than runtime crashes. + \\ * Incremental improvements. + \\ * Avoid local maximums. + \\ * Reduce the amount one must remember. + \\ * Minimize energy spent on coding style. + \\ * Together we serve end users. + \\ + \\ + ; + +fn cmdZen(allocator: &Allocator, args: []const []const u8) !void { + try stdout.write(info_zen); +} + +// cmd:internal //////////////////////////////////////////////////////////////////////////////////// + +const usage_internal = + \\usage: zig internal [subcommand] + \\ + \\Sub-Commands: + \\ build-info Print static compiler build-info + \\ + \\ + ; + +fn cmdInternal(allocator: &Allocator, args: []const []const u8) !void { + if (args.len == 0) { + try stderr.write(usage_internal); + os.exit(1); + } + + const sub_commands = []Command { + Command { .name = "build-info", .exec = cmdInternalBuildInfo }, + }; + + for (sub_commands) |sub_command| { + if (mem.eql(u8, sub_command.name, args[0])) { + try sub_command.exec(allocator, args[1..]); + return; + } + } + + try stderr.print("unknown sub command: {}\n\n", args[0]); + try stderr.write(usage_internal); +} + +fn cmdInternalBuildInfo(allocator: &Allocator, args: []const []const u8) !void { + try stdout.print( + \\ZIG_CMAKE_BINARY_DIR {} + \\ZIG_CXX_COMPILER {} + \\ZIG_LLVM_CONFIG_EXE {} + \\ZIG_LLD_INCLUDE_PATH {} + \\ZIG_LLD_LIBRARIES {} + \\ZIG_STD_FILES {} + \\ZIG_C_HEADER_FILES {} + \\ZIG_DIA_GUIDS_LIB {} + \\ + , + std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR), + std.cstr.toSliceConst(c.ZIG_CXX_COMPILER), + std.cstr.toSliceConst(c.ZIG_LLVM_CONFIG_EXE), + std.cstr.toSliceConst(c.ZIG_LLD_INCLUDE_PATH), + std.cstr.toSliceConst(c.ZIG_LLD_LIBRARIES), + std.cstr.toSliceConst(c.ZIG_STD_FILES), + std.cstr.toSliceConst(c.ZIG_C_HEADER_FILES), + std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB), + ); } diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig index 464737bbbb..eec30749e2 100644 --- a/src-self-hosted/module.zig +++ b/src-self-hosted/module.zig @@ -109,6 +109,29 @@ pub const Module = struct { LlvmIr, }; + pub const CliPkg = struct { + name: []const u8, + path: []const u8, + children: ArrayList(&CliPkg), + parent: ?&CliPkg, + + pub fn init(allocator: &mem.Allocator, name: []const u8, path: []const u8, parent: ?&CliPkg) !&CliPkg { + var pkg = try allocator.create(CliPkg); + pkg.name = name; + pkg.path = path; + pkg.children = ArrayList(&CliPkg).init(allocator); + pkg.parent = parent; + return pkg; + } + + pub fn deinit(self: &CliPkg) void { + for (self.children.toSliceConst()) |child| { + child.deinit(); + } + self.children.deinit(); + } + }; + pub fn create(allocator: &mem.Allocator, name: []const u8, root_src_path: ?[]const u8, target: &const Target, kind: Kind, build_mode: builtin.Mode, zig_lib_dir: []const u8, cache_dir: []const u8) !&Module { diff --git a/src/codegen.cpp b/src/codegen.cpp index 2aca143524..a58832f983 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -467,7 +467,7 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { fn_table_entry->llvm_value, buf_ptr(&fn_export->name)); } } - fn_table_entry->llvm_name = LLVMGetValueName(fn_table_entry->llvm_value); + fn_table_entry->llvm_name = strdup(LLVMGetValueName(fn_table_entry->llvm_value)); switch (fn_table_entry->fn_inline) { case FnInlineAlways: diff --git a/src/ir.cpp b/src/ir.cpp index 0b072cc696..3ba58a09bd 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -11395,7 +11395,19 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc } break; case VarClassRequiredAny: - // OK + if (casted_init_value->value.special == ConstValSpecialStatic && + casted_init_value->value.type->id == TypeTableEntryIdFn && + casted_init_value->value.data.x_ptr.data.fn.fn_entry->fn_inline == FnInlineAlways) + { + var_class_requires_const = true; + if (!var->src_is_const && !is_comptime_var) { + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("functions marked inline must be stored in const or comptime var")); + AstNode *proto_node = casted_init_value->value.data.x_ptr.data.fn.fn_entry->proto_node; + add_error_note(ira->codegen, msg, proto_node, buf_sprintf("declared here")); + result_type = ira->codegen->builtin_types.entry_invalid; + } + } break; } } @@ -11804,7 +11816,8 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod } } - bool comptime_arg = param_decl_node->data.param_decl.is_inline; + bool comptime_arg = param_decl_node->data.param_decl.is_inline || + casted_arg->value.type->id == TypeTableEntryIdNumLitInt || casted_arg->value.type->id == TypeTableEntryIdNumLitFloat; ConstExprValue *arg_val; @@ -11829,6 +11842,12 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod var->shadowable = !comptime_arg; *next_proto_i += 1; + } else if (casted_arg->value.type->id == TypeTableEntryIdNumLitInt || + casted_arg->value.type->id == TypeTableEntryIdNumLitFloat) + { + ir_add_error(ira, casted_arg, + buf_sprintf("compiler bug: integer and float literals in var args function must be casted. https://github.com/zig-lang/zig/issues/557")); + return false; } if (!comptime_arg) { diff --git a/src/main.cpp b/src/main.cpp index 63b077e833..35c7462f4b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,7 +54,6 @@ static int usage(const char *arg0) { " --verbose-ir turn on compiler debug output for Zig IR\n" " --verbose-llvm-ir turn on compiler debug output for LLVM IR\n" " --verbose-cimport turn on compiler debug output for C imports\n" - " --zig-install-prefix [path] override directory where zig thinks it is installed\n" " -dirafter [dir] same as -isystem but do it last\n" " -isystem [dir] add additional search path for other .h files\n" " -mllvm [arg] additional arguments to forward to LLVM's option processing\n" @@ -177,6 +176,7 @@ static int find_zig_lib_dir(Buf *out_path) { int err; Buf self_exe_path = BUF_INIT; + buf_resize(&self_exe_path, 0); if (!(err = os_self_exe_path(&self_exe_path))) { Buf *cur_path = &self_exe_path; @@ -199,23 +199,14 @@ static int find_zig_lib_dir(Buf *out_path) { return ErrorFileNotFound; } -static Buf *resolve_zig_lib_dir(const char *zig_install_prefix_arg) { +static Buf *resolve_zig_lib_dir(void) { int err; Buf *result = buf_alloc(); - if (zig_install_prefix_arg == nullptr) { - if ((err = find_zig_lib_dir(result))) { - fprintf(stderr, "Unable to find zig lib directory. Reinstall Zig or use --zig-install-prefix.\n"); - exit(EXIT_FAILURE); - } - return result; - } - Buf *zig_lib_dir_buf = buf_create_from_str(zig_install_prefix_arg); - if (test_zig_install_prefix(zig_lib_dir_buf, result)) { - return result; + if ((err = find_zig_lib_dir(result))) { + fprintf(stderr, "Unable to find zig lib directory\n"); + exit(EXIT_FAILURE); } - - fprintf(stderr, "No Zig installation found at prefix: %s\n", zig_install_prefix_arg); - exit(EXIT_FAILURE); + return result; } enum Cmd { @@ -299,7 +290,6 @@ int main(int argc, char **argv) { const char *libc_include_dir = nullptr; const char *msvc_lib_dir = nullptr; const char *kernel32_lib_dir = nullptr; - const char *zig_install_prefix = nullptr; const char *dynamic_linker = nullptr; ZigList<const char *> clang_argv = {0}; ZigList<const char *> llvm_argv = {0}; @@ -359,17 +349,12 @@ int main(int argc, char **argv) { } else if (i + 1 < argc && strcmp(argv[i], "--cache-dir") == 0) { cache_dir = argv[i + 1]; i += 1; - } else if (i + 1 < argc && strcmp(argv[i], "--zig-install-prefix") == 0) { - args.append(argv[i]); - i += 1; - zig_install_prefix = argv[i]; - args.append(zig_install_prefix); } else { args.append(argv[i]); } } - Buf *zig_lib_dir_buf = resolve_zig_lib_dir(zig_install_prefix); + Buf *zig_lib_dir_buf = resolve_zig_lib_dir(); Buf *zig_std_dir = buf_alloc(); os_path_join(zig_lib_dir_buf, buf_create_from_str("std"), zig_std_dir); @@ -590,8 +575,6 @@ int main(int argc, char **argv) { msvc_lib_dir = argv[i]; } else if (strcmp(arg, "--kernel32-lib-dir") == 0) { kernel32_lib_dir = argv[i]; - } else if (strcmp(arg, "--zig-install-prefix") == 0) { - zig_install_prefix = argv[i]; } else if (strcmp(arg, "--dynamic-linker") == 0) { dynamic_linker = argv[i]; } else if (strcmp(arg, "-isystem") == 0) { @@ -803,7 +786,7 @@ int main(int argc, char **argv) { full_cache_dir); } - Buf *zig_lib_dir_buf = resolve_zig_lib_dir(zig_install_prefix); + Buf *zig_lib_dir_buf = resolve_zig_lib_dir(); CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, zig_lib_dir_buf); codegen_set_out_name(g, buf_out_name); diff --git a/std/c/index.zig b/std/c/index.zig index 369ea2b358..02321f1f34 100644 --- a/std/c/index.zig +++ b/std/c/index.zig @@ -28,6 +28,7 @@ pub extern "c" fn unlink(path: &const u8) c_int; pub extern "c" fn getcwd(buf: &u8, size: usize) ?&u8; pub extern "c" fn waitpid(pid: c_int, stat_loc: &c_int, options: c_int) c_int; pub extern "c" fn fork() c_int; +pub extern "c" fn access(path: &const u8, mode: c_uint) c_int; pub extern "c" fn pipe(fds: &c_int) c_int; pub extern "c" fn mkdir(path: &const u8, mode: c_uint) c_int; pub extern "c" fn symlink(existing: &const u8, new: &const u8) c_int; diff --git a/std/os/darwin.zig b/std/os/darwin.zig index 40da55315c..42b9917210 100644 --- a/std/os/darwin.zig +++ b/std/os/darwin.zig @@ -41,6 +41,11 @@ pub const SA_64REGSET = 0x0200; /// signal handler with SA_SIGINFO args with 64 pub const O_LARGEFILE = 0x0000; pub const O_PATH = 0x0000; +pub const F_OK = 0; +pub const X_OK = 1; +pub const W_OK = 2; +pub const R_OK = 4; + pub const O_RDONLY = 0x0000; /// open for reading only pub const O_WRONLY = 0x0001; /// open for writing only pub const O_RDWR = 0x0002; /// open for reading and writing @@ -209,6 +214,10 @@ pub fn fork() usize { return errnoWrap(c.fork()); } +pub fn access(path: &const u8, mode: u32) usize { + return errnoWrap(c.access(path, mode)); +} + pub fn pipe(fds: &[2]i32) usize { comptime assert(i32.bit_count == c_int.bit_count); return errnoWrap(c.pipe(@ptrCast(&c_int, fds))); diff --git a/std/os/file.zig b/std/os/file.zig index eed3a443b9..61fc2b1455 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -85,6 +85,47 @@ pub const File = struct { }; } + pub fn access(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) !bool { + const path_with_null = try std.cstr.addNullByte(allocator, path); + defer allocator.free(path_with_null); + + if (is_posix) { + // mode is ignored and is always F_OK for now + const result = posix.access(path_with_null.ptr, posix.F_OK); + const err = posix.getErrno(result); + if (err > 0) { + return switch (err) { + posix.EACCES => error.PermissionDenied, + posix.EROFS => error.PermissionDenied, + posix.ELOOP => error.PermissionDenied, + posix.ETXTBSY => error.PermissionDenied, + posix.ENOTDIR => error.NotFound, + posix.ENOENT => error.NotFound, + + posix.ENAMETOOLONG => error.NameTooLong, + posix.EINVAL => error.BadMode, + posix.EFAULT => error.BadPathName, + posix.EIO => error.Io, + posix.ENOMEM => error.SystemResources, + else => os.unexpectedErrorPosix(err), + }; + } + return true; + } else if (is_windows) { + if (os.windows.PathFileExists(path_with_null.ptr) == os.windows.TRUE) { + return true; + } + + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.FILE_NOT_FOUND => error.NotFound, + windows.ERROR.ACCESS_DENIED => error.PermissionDenied, + else => os.unexpectedErrorWindows(err), + }; + } else { + @compileError("TODO implement access for this OS"); + } + } /// Upon success, the stream is in an uninitialized state. To continue using it, /// you must use the open() function. @@ -245,7 +286,9 @@ pub const File = struct { }; } - return stat.mode; + // TODO: we should be able to cast u16 to ModeError!u32, making this + // explicit cast not necessary + return os.FileMode(stat.mode); } else if (is_windows) { return {}; } else { diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig index aa2a6d85da..e100af7733 100644 --- a/std/os/linux/index.zig +++ b/std/os/linux/index.zig @@ -38,6 +38,11 @@ pub const MAP_STACK = 0x20000; pub const MAP_HUGETLB = 0x40000; pub const MAP_FILE = 0; +pub const F_OK = 0; +pub const X_OK = 1; +pub const W_OK = 2; +pub const R_OK = 4; + pub const WNOHANG = 1; pub const WUNTRACED = 2; pub const WSTOPPED = 2; @@ -705,6 +710,10 @@ pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) usize { return syscall4(SYS_pread, usize(fd), @ptrToInt(buf), count, offset); } +pub fn access(path: &const u8, mode: u32) usize { + return syscall2(SYS_access, @ptrToInt(path), mode); +} + pub fn pipe(fd: &[2]i32) usize { return pipe2(fd, 0); } diff --git a/std/os/test.zig b/std/os/test.zig index 9c718d5b6b..718d1ce2c8 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -23,3 +23,20 @@ test "makePath, put some files in it, deleteTree" { assert(err == error.PathNotFound); } } + +test "access file" { + if (builtin.os == builtin.Os.windows) { + return; + } + + try os.makePath(a, "os_test_tmp"); + if (os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) |ok| { + unreachable; + } else |err| { + assert(err == error.NotFound); + } + + try io.writeFile(a, "os_test_tmp/file.txt", ""); + assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true); + try os.deleteTree(a, "os_test_tmp"); +} diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index 2709cf2a78..aa02c27f39 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -78,6 +78,8 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem pub extern "kernel32" stdcallcc fn MoveFileExA(lpExistingFileName: LPCSTR, lpNewFileName: LPCSTR, dwFlags: DWORD) BOOL; +pub extern "kernel32" stdcallcc fn PathFileExists(pszPath: ?LPCTSTR) BOOL; + pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: &c_void, in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD, in_out_lpOverlapped: ?&OVERLAPPED) BOOL; diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 045548d624..17a19e6213 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -9,38 +9,34 @@ pub const Node = struct { comment: ?&NodeLineComment, pub const Id = enum { + // Top level Root, - VarDecl, Use, - ErrorSetDecl, - ContainerDecl, - StructField, - UnionTag, - EnumTag, - Identifier, - AsyncAttribute, - FnProto, - ParamDecl, - Block, + TestDecl, + + // Statements + VarDecl, Defer, - Comptime, - Payload, - PointerPayload, - PointerIndexPayload, - Else, + + // Operators + InfixOp, + PrefixOp, + SuffixOp, + + // Control flow Switch, - SwitchCase, - SwitchElse, While, For, If, - InfixOp, - PrefixOp, - SuffixOp, - GroupedExpression, ControlFlowExpression, Suspend, - FieldInitializer, + + // Type expressions + VarType, + ErrorType, + FnProto, + + // Primary expressions IntegerLiteral, FloatLiteral, StringLiteral, @@ -50,180 +46,143 @@ pub const Node = struct { NullLiteral, UndefinedLiteral, ThisLiteral, - Asm, - AsmInput, - AsmOutput, Unreachable, - ErrorType, - VarType, + Identifier, + GroupedExpression, BuiltinCall, + ErrorSetDecl, + ContainerDecl, + Asm, + Comptime, + Block, + + // Misc LineComment, - TestDecl, + SwitchCase, + SwitchElse, + Else, + Payload, + PointerPayload, + PointerIndexPayload, + StructField, + UnionTag, + EnumTag, + AsmInput, + AsmOutput, + AsyncAttribute, + ParamDecl, + FieldInitializer, + }; + + const IdTypePair = struct { + id: Id, + Type: type, + }; + + // TODO: When @field exists, we could generate this by iterating over all members of `Id`, + // and making an array of `IdTypePair { .id = @field(Id, @memberName(Id, i)), .Type = @field(ast, "Node" ++ @memberName(Id, i)) }` + const idTypeTable = []IdTypePair { + IdTypePair { .id = Id.Root, .Type = NodeRoot }, + IdTypePair { .id = Id.Use, .Type = NodeUse }, + IdTypePair { .id = Id.TestDecl, .Type = NodeTestDecl }, + + IdTypePair { .id = Id.VarDecl, .Type = NodeVarDecl }, + IdTypePair { .id = Id.Defer, .Type = NodeDefer }, + + IdTypePair { .id = Id.InfixOp, .Type = NodeInfixOp }, + IdTypePair { .id = Id.PrefixOp, .Type = NodePrefixOp }, + IdTypePair { .id = Id.SuffixOp, .Type = NodeSuffixOp }, + + IdTypePair { .id = Id.Switch, .Type = NodeSwitch }, + IdTypePair { .id = Id.While, .Type = NodeWhile }, + IdTypePair { .id = Id.For, .Type = NodeFor }, + IdTypePair { .id = Id.If, .Type = NodeIf }, + IdTypePair { .id = Id.ControlFlowExpression, .Type = NodeControlFlowExpression }, + IdTypePair { .id = Id.Suspend, .Type = NodeSuspend }, + + IdTypePair { .id = Id.VarType, .Type = NodeVarType }, + IdTypePair { .id = Id.ErrorType, .Type = NodeErrorType }, + IdTypePair { .id = Id.FnProto, .Type = NodeFnProto }, + + IdTypePair { .id = Id.IntegerLiteral, .Type = NodeIntegerLiteral }, + IdTypePair { .id = Id.FloatLiteral, .Type = NodeFloatLiteral }, + IdTypePair { .id = Id.StringLiteral, .Type = NodeStringLiteral }, + IdTypePair { .id = Id.MultilineStringLiteral, .Type = NodeMultilineStringLiteral }, + IdTypePair { .id = Id.CharLiteral, .Type = NodeCharLiteral }, + IdTypePair { .id = Id.BoolLiteral, .Type = NodeBoolLiteral }, + IdTypePair { .id = Id.NullLiteral, .Type = NodeNullLiteral }, + IdTypePair { .id = Id.UndefinedLiteral, .Type = NodeUndefinedLiteral }, + IdTypePair { .id = Id.ThisLiteral, .Type = NodeThisLiteral }, + IdTypePair { .id = Id.Unreachable, .Type = NodeUnreachable }, + IdTypePair { .id = Id.Identifier, .Type = NodeIdentifier }, + IdTypePair { .id = Id.GroupedExpression, .Type = NodeGroupedExpression }, + IdTypePair { .id = Id.BuiltinCall, .Type = NodeBuiltinCall }, + IdTypePair { .id = Id.ErrorSetDecl, .Type = NodeErrorSetDecl }, + IdTypePair { .id = Id.ContainerDecl, .Type = NodeContainerDecl }, + IdTypePair { .id = Id.Asm, .Type = NodeAsm }, + IdTypePair { .id = Id.Comptime, .Type = NodeComptime }, + IdTypePair { .id = Id.Block, .Type = NodeBlock }, + + IdTypePair { .id = Id.LineComment, .Type = NodeLineComment }, + IdTypePair { .id = Id.SwitchCase, .Type = NodeSwitchCase }, + IdTypePair { .id = Id.SwitchElse, .Type = NodeSwitchElse }, + IdTypePair { .id = Id.Else, .Type = NodeElse }, + IdTypePair { .id = Id.Payload, .Type = NodePayload }, + IdTypePair { .id = Id.PointerPayload, .Type = NodePointerPayload }, + IdTypePair { .id = Id.PointerIndexPayload, .Type = NodePointerIndexPayload }, + IdTypePair { .id = Id.StructField, .Type = NodeStructField }, + IdTypePair { .id = Id.UnionTag, .Type = NodeUnionTag }, + IdTypePair { .id = Id.EnumTag, .Type = NodeEnumTag }, + IdTypePair { .id = Id.AsmInput, .Type = NodeAsmInput }, + IdTypePair { .id = Id.AsmOutput, .Type = NodeAsmOutput }, + IdTypePair { .id = Id.AsyncAttribute, .Type = NodeAsyncAttribute }, + IdTypePair { .id = Id.ParamDecl, .Type = NodeParamDecl }, + IdTypePair { .id = Id.FieldInitializer, .Type = NodeFieldInitializer }, }; + pub fn IdToType(comptime id: Id) type { + inline for (idTypeTable) |id_type_pair| { + if (id == id_type_pair.id) + return id_type_pair.Type; + } + + unreachable; + } + + pub fn typeToId(comptime T: type) Id { + inline for (idTypeTable) |id_type_pair| { + if (T == id_type_pair.Type) + return id_type_pair.id; + } + + unreachable; + } + pub fn iterate(base: &Node, index: usize) ?&Node { - return switch (base.id) { - Id.Root => @fieldParentPtr(NodeRoot, "base", base).iterate(index), - Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).iterate(index), - Id.Use => @fieldParentPtr(NodeUse, "base", base).iterate(index), - Id.ErrorSetDecl => @fieldParentPtr(NodeErrorSetDecl, "base", base).iterate(index), - Id.ContainerDecl => @fieldParentPtr(NodeContainerDecl, "base", base).iterate(index), - Id.StructField => @fieldParentPtr(NodeStructField, "base", base).iterate(index), - Id.UnionTag => @fieldParentPtr(NodeUnionTag, "base", base).iterate(index), - Id.EnumTag => @fieldParentPtr(NodeEnumTag, "base", base).iterate(index), - Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).iterate(index), - Id.AsyncAttribute => @fieldParentPtr(NodeAsyncAttribute, "base", base).iterate(index), - Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).iterate(index), - Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).iterate(index), - Id.Block => @fieldParentPtr(NodeBlock, "base", base).iterate(index), - Id.Defer => @fieldParentPtr(NodeDefer, "base", base).iterate(index), - Id.Comptime => @fieldParentPtr(NodeComptime, "base", base).iterate(index), - Id.Payload => @fieldParentPtr(NodePayload, "base", base).iterate(index), - Id.PointerPayload => @fieldParentPtr(NodePointerPayload, "base", base).iterate(index), - Id.PointerIndexPayload => @fieldParentPtr(NodePointerIndexPayload, "base", base).iterate(index), - Id.Else => @fieldParentPtr(NodeSwitch, "base", base).iterate(index), - Id.Switch => @fieldParentPtr(NodeSwitch, "base", base).iterate(index), - Id.SwitchCase => @fieldParentPtr(NodeSwitchCase, "base", base).iterate(index), - Id.SwitchElse => @fieldParentPtr(NodeSwitchElse, "base", base).iterate(index), - Id.While => @fieldParentPtr(NodeWhile, "base", base).iterate(index), - Id.For => @fieldParentPtr(NodeFor, "base", base).iterate(index), - Id.If => @fieldParentPtr(NodeIf, "base", base).iterate(index), - Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).iterate(index), - Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).iterate(index), - Id.SuffixOp => @fieldParentPtr(NodeSuffixOp, "base", base).iterate(index), - Id.GroupedExpression => @fieldParentPtr(NodeGroupedExpression, "base", base).iterate(index), - Id.ControlFlowExpression => @fieldParentPtr(NodeControlFlowExpression, "base", base).iterate(index), - Id.Suspend => @fieldParentPtr(NodeSuspend, "base", base).iterate(index), - Id.FieldInitializer => @fieldParentPtr(NodeFieldInitializer, "base", base).iterate(index), - Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).iterate(index), - Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).iterate(index), - Id.StringLiteral => @fieldParentPtr(NodeStringLiteral, "base", base).iterate(index), - Id.MultilineStringLiteral => @fieldParentPtr(NodeMultilineStringLiteral, "base", base).iterate(index), - Id.CharLiteral => @fieldParentPtr(NodeCharLiteral, "base", base).iterate(index), - Id.BoolLiteral => @fieldParentPtr(NodeBoolLiteral, "base", base).iterate(index), - Id.NullLiteral => @fieldParentPtr(NodeNullLiteral, "base", base).iterate(index), - Id.UndefinedLiteral => @fieldParentPtr(NodeUndefinedLiteral, "base", base).iterate(index), - Id.ThisLiteral => @fieldParentPtr(NodeThisLiteral, "base", base).iterate(index), - Id.Asm => @fieldParentPtr(NodeAsm, "base", base).iterate(index), - Id.AsmInput => @fieldParentPtr(NodeAsmInput, "base", base).iterate(index), - Id.AsmOutput => @fieldParentPtr(NodeAsmOutput, "base", base).iterate(index), - Id.Unreachable => @fieldParentPtr(NodeUnreachable, "base", base).iterate(index), - Id.ErrorType => @fieldParentPtr(NodeErrorType, "base", base).iterate(index), - Id.VarType => @fieldParentPtr(NodeVarType, "base", base).iterate(index), - Id.BuiltinCall => @fieldParentPtr(NodeBuiltinCall, "base", base).iterate(index), - Id.LineComment => @fieldParentPtr(NodeLineComment, "base", base).iterate(index), - Id.TestDecl => @fieldParentPtr(NodeTestDecl, "base", base).iterate(index), - }; + inline for (idTypeTable) |id_type_pair| { + if (base.id == id_type_pair.id) + return @fieldParentPtr(id_type_pair.Type, "base", base).iterate(index); + } + + unreachable; } pub fn firstToken(base: &Node) Token { - return switch (base.id) { - Id.Root => @fieldParentPtr(NodeRoot, "base", base).firstToken(), - Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).firstToken(), - Id.Use => @fieldParentPtr(NodeUse, "base", base).firstToken(), - Id.ErrorSetDecl => @fieldParentPtr(NodeErrorSetDecl, "base", base).firstToken(), - Id.ContainerDecl => @fieldParentPtr(NodeContainerDecl, "base", base).firstToken(), - Id.StructField => @fieldParentPtr(NodeStructField, "base", base).firstToken(), - Id.UnionTag => @fieldParentPtr(NodeUnionTag, "base", base).firstToken(), - Id.EnumTag => @fieldParentPtr(NodeEnumTag, "base", base).firstToken(), - Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).firstToken(), - Id.AsyncAttribute => @fieldParentPtr(NodeAsyncAttribute, "base", base).firstToken(), - Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).firstToken(), - Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).firstToken(), - Id.Block => @fieldParentPtr(NodeBlock, "base", base).firstToken(), - Id.Defer => @fieldParentPtr(NodeDefer, "base", base).firstToken(), - Id.Comptime => @fieldParentPtr(NodeComptime, "base", base).firstToken(), - Id.Payload => @fieldParentPtr(NodePayload, "base", base).firstToken(), - Id.PointerPayload => @fieldParentPtr(NodePointerPayload, "base", base).firstToken(), - Id.PointerIndexPayload => @fieldParentPtr(NodePointerIndexPayload, "base", base).firstToken(), - Id.Else => @fieldParentPtr(NodeSwitch, "base", base).firstToken(), - Id.Switch => @fieldParentPtr(NodeSwitch, "base", base).firstToken(), - Id.SwitchCase => @fieldParentPtr(NodeSwitchCase, "base", base).firstToken(), - Id.SwitchElse => @fieldParentPtr(NodeSwitchElse, "base", base).firstToken(), - Id.While => @fieldParentPtr(NodeWhile, "base", base).firstToken(), - Id.For => @fieldParentPtr(NodeFor, "base", base).firstToken(), - Id.If => @fieldParentPtr(NodeIf, "base", base).firstToken(), - Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).firstToken(), - Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).firstToken(), - Id.SuffixOp => @fieldParentPtr(NodeSuffixOp, "base", base).firstToken(), - Id.GroupedExpression => @fieldParentPtr(NodeGroupedExpression, "base", base).firstToken(), - Id.ControlFlowExpression => @fieldParentPtr(NodeControlFlowExpression, "base", base).firstToken(), - Id.Suspend => @fieldParentPtr(NodeSuspend, "base", base).firstToken(), - Id.FieldInitializer => @fieldParentPtr(NodeFieldInitializer, "base", base).firstToken(), - Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).firstToken(), - Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).firstToken(), - Id.StringLiteral => @fieldParentPtr(NodeStringLiteral, "base", base).firstToken(), - Id.MultilineStringLiteral => @fieldParentPtr(NodeMultilineStringLiteral, "base", base).firstToken(), - Id.CharLiteral => @fieldParentPtr(NodeCharLiteral, "base", base).firstToken(), - Id.BoolLiteral => @fieldParentPtr(NodeBoolLiteral, "base", base).firstToken(), - Id.NullLiteral => @fieldParentPtr(NodeNullLiteral, "base", base).firstToken(), - Id.UndefinedLiteral => @fieldParentPtr(NodeUndefinedLiteral, "base", base).firstToken(), - Id.Unreachable => @fieldParentPtr(NodeUnreachable, "base", base).firstToken(), - Id.ThisLiteral => @fieldParentPtr(NodeThisLiteral, "base", base).firstToken(), - Id.Asm => @fieldParentPtr(NodeAsm, "base", base).firstToken(), - Id.AsmInput => @fieldParentPtr(NodeAsmInput, "base", base).firstToken(), - Id.AsmOutput => @fieldParentPtr(NodeAsmOutput, "base", base).firstToken(), - Id.ErrorType => @fieldParentPtr(NodeErrorType, "base", base).firstToken(), - Id.VarType => @fieldParentPtr(NodeVarType, "base", base).firstToken(), - Id.BuiltinCall => @fieldParentPtr(NodeBuiltinCall, "base", base).firstToken(), - Id.LineComment => @fieldParentPtr(NodeLineComment, "base", base).firstToken(), - Id.TestDecl => @fieldParentPtr(NodeTestDecl, "base", base).firstToken(), - }; + inline for (idTypeTable) |id_type_pair| { + if (base.id == id_type_pair.id) + return @fieldParentPtr(id_type_pair.Type, "base", base).firstToken(); + } + + unreachable; } pub fn lastToken(base: &Node) Token { - return switch (base.id) { - Id.Root => @fieldParentPtr(NodeRoot, "base", base).lastToken(), - Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).lastToken(), - Id.Use => @fieldParentPtr(NodeUse, "base", base).lastToken(), - Id.ErrorSetDecl => @fieldParentPtr(NodeErrorSetDecl, "base", base).lastToken(), - Id.ContainerDecl => @fieldParentPtr(NodeContainerDecl, "base", base).lastToken(), - Id.StructField => @fieldParentPtr(NodeStructField, "base", base).lastToken(), - Id.UnionTag => @fieldParentPtr(NodeUnionTag, "base", base).lastToken(), - Id.EnumTag => @fieldParentPtr(NodeEnumTag, "base", base).lastToken(), - Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).lastToken(), - Id.AsyncAttribute => @fieldParentPtr(NodeAsyncAttribute, "base", base).lastToken(), - Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).lastToken(), - Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).lastToken(), - Id.Block => @fieldParentPtr(NodeBlock, "base", base).lastToken(), - Id.Defer => @fieldParentPtr(NodeDefer, "base", base).lastToken(), - Id.Comptime => @fieldParentPtr(NodeComptime, "base", base).lastToken(), - Id.Payload => @fieldParentPtr(NodePayload, "base", base).lastToken(), - Id.PointerPayload => @fieldParentPtr(NodePointerPayload, "base", base).lastToken(), - Id.PointerIndexPayload => @fieldParentPtr(NodePointerIndexPayload, "base", base).lastToken(), - Id.Else => @fieldParentPtr(NodeElse, "base", base).lastToken(), - Id.Switch => @fieldParentPtr(NodeSwitch, "base", base).lastToken(), - Id.SwitchCase => @fieldParentPtr(NodeSwitchCase, "base", base).lastToken(), - Id.SwitchElse => @fieldParentPtr(NodeSwitchElse, "base", base).lastToken(), - Id.While => @fieldParentPtr(NodeWhile, "base", base).lastToken(), - Id.For => @fieldParentPtr(NodeFor, "base", base).lastToken(), - Id.If => @fieldParentPtr(NodeIf, "base", base).lastToken(), - Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).lastToken(), - Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).lastToken(), - Id.SuffixOp => @fieldParentPtr(NodeSuffixOp, "base", base).lastToken(), - Id.GroupedExpression => @fieldParentPtr(NodeGroupedExpression, "base", base).lastToken(), - Id.ControlFlowExpression => @fieldParentPtr(NodeControlFlowExpression, "base", base).lastToken(), - Id.Suspend => @fieldParentPtr(NodeSuspend, "base", base).lastToken(), - Id.FieldInitializer => @fieldParentPtr(NodeFieldInitializer, "base", base).lastToken(), - Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).lastToken(), - Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).lastToken(), - Id.StringLiteral => @fieldParentPtr(NodeStringLiteral, "base", base).lastToken(), - Id.MultilineStringLiteral => @fieldParentPtr(NodeMultilineStringLiteral, "base", base).lastToken(), - Id.CharLiteral => @fieldParentPtr(NodeCharLiteral, "base", base).lastToken(), - Id.BoolLiteral => @fieldParentPtr(NodeBoolLiteral, "base", base).lastToken(), - Id.NullLiteral => @fieldParentPtr(NodeNullLiteral, "base", base).lastToken(), - Id.UndefinedLiteral => @fieldParentPtr(NodeUndefinedLiteral, "base", base).lastToken(), - Id.ThisLiteral => @fieldParentPtr(NodeThisLiteral, "base", base).lastToken(), - Id.Asm => @fieldParentPtr(NodeAsm, "base", base).lastToken(), - Id.AsmInput => @fieldParentPtr(NodeAsmInput, "base", base).lastToken(), - Id.AsmOutput => @fieldParentPtr(NodeAsmOutput, "base", base).lastToken(), - Id.Unreachable => @fieldParentPtr(NodeUnreachable, "base", base).lastToken(), - Id.ErrorType => @fieldParentPtr(NodeErrorType, "base", base).lastToken(), - Id.VarType => @fieldParentPtr(NodeVarType, "base", base).lastToken(), - Id.BuiltinCall => @fieldParentPtr(NodeBuiltinCall, "base", base).lastToken(), - Id.LineComment => @fieldParentPtr(NodeLineComment, "base", base).lastToken(), - Id.TestDecl => @fieldParentPtr(NodeTestDecl, "base", base).lastToken(), - }; + inline for (idTypeTable) |id_type_pair| { + if (base.id == id_type_pair.id) + return @fieldParentPtr(id_type_pair.Type, "base", base).lastToken(); + } + + unreachable; } }; @@ -255,7 +214,7 @@ pub const NodeVarDecl = struct { eq_token: Token, mut_token: Token, comptime_token: ?Token, - extern_token: ?Token, + extern_export_token: ?Token, lib_name: ?&Node, type_node: ?&Node, align_node: ?&Node, @@ -286,7 +245,7 @@ pub const NodeVarDecl = struct { pub fn firstToken(self: &NodeVarDecl) Token { if (self.visib_token) |visib_token| return visib_token; if (self.comptime_token) |comptime_token| return comptime_token; - if (self.extern_token) |extern_token| return extern_token; + if (self.extern_export_token) |extern_export_token| return extern_export_token; assert(self.lib_name == null); return self.mut_token; } @@ -324,13 +283,13 @@ pub const NodeUse = struct { pub const NodeErrorSetDecl = struct { base: Node, error_token: Token, - decls: ArrayList(&NodeIdentifier), + decls: ArrayList(&Node), rbrace_token: Token, pub fn iterate(self: &NodeErrorSetDecl, index: usize) ?&Node { var i = index; - if (i < self.decls.len) return &self.decls.at(i).base; + if (i < self.decls.len) return self.decls.at(i); i -= self.decls.len; return null; @@ -401,6 +360,7 @@ pub const NodeContainerDecl = struct { pub const NodeStructField = struct { base: Node, + visib_token: ?Token, name_token: Token, type_expr: &Node, @@ -414,6 +374,7 @@ pub const NodeStructField = struct { } pub fn firstToken(self: &NodeStructField) Token { + if (self.visib_token) |visib_token| return visib_token; return self.name_token; } @@ -482,18 +443,18 @@ pub const NodeEnumTag = struct { pub const NodeIdentifier = struct { base: Node, - name_token: Token, + token: Token, pub fn iterate(self: &NodeIdentifier, index: usize) ?&Node { return null; } pub fn firstToken(self: &NodeIdentifier) Token { - return self.name_token; + return self.token; } pub fn lastToken(self: &NodeIdentifier) Token { - return self.name_token; + return self.token; } }; @@ -535,8 +496,7 @@ pub const NodeFnProto = struct { params: ArrayList(&Node), return_type: ReturnType, var_args_token: ?Token, - extern_token: ?Token, - inline_token: ?Token, + extern_export_inline_token: ?Token, cc_token: ?Token, async_attr: ?&NodeAsyncAttribute, body_node: ?&Node, @@ -586,9 +546,8 @@ pub const NodeFnProto = struct { pub fn firstToken(self: &NodeFnProto) Token { if (self.visib_token) |visib_token| return visib_token; - if (self.extern_token) |extern_token| return extern_token; + if (self.extern_export_inline_token) |extern_export_inline_token| return extern_export_inline_token; assert(self.lib_name == null); - if (self.inline_token) |inline_token| return inline_token; if (self.cc_token) |cc_token| return cc_token; return self.fn_token; } @@ -717,13 +676,13 @@ pub const NodeComptime = struct { pub const NodePayload = struct { base: Node, lpipe: Token, - error_symbol: &NodeIdentifier, + error_symbol: &Node, rpipe: Token, pub fn iterate(self: &NodePayload, index: usize) ?&Node { var i = index; - if (i < 1) return &self.error_symbol.base; + if (i < 1) return self.error_symbol; i -= 1; return null; @@ -741,14 +700,14 @@ pub const NodePayload = struct { pub const NodePointerPayload = struct { base: Node, lpipe: Token, - is_ptr: bool, - value_symbol: &NodeIdentifier, + ptr_token: ?Token, + value_symbol: &Node, rpipe: Token, pub fn iterate(self: &NodePointerPayload, index: usize) ?&Node { var i = index; - if (i < 1) return &self.value_symbol.base; + if (i < 1) return self.value_symbol; i -= 1; return null; @@ -766,19 +725,19 @@ pub const NodePointerPayload = struct { pub const NodePointerIndexPayload = struct { base: Node, lpipe: Token, - is_ptr: bool, - value_symbol: &NodeIdentifier, - index_symbol: ?&NodeIdentifier, + ptr_token: ?Token, + value_symbol: &Node, + index_symbol: ?&Node, rpipe: Token, pub fn iterate(self: &NodePointerIndexPayload, index: usize) ?&Node { var i = index; - if (i < 1) return &self.value_symbol.base; + if (i < 1) return self.value_symbol; i -= 1; if (self.index_symbol) |index_symbol| { - if (i < 1) return &index_symbol.base; + if (i < 1) return index_symbol; i -= 1; } @@ -797,14 +756,14 @@ pub const NodePointerIndexPayload = struct { pub const NodeElse = struct { base: Node, else_token: Token, - payload: ?&NodePayload, + payload: ?&Node, body: &Node, pub fn iterate(self: &NodeElse, index: usize) ?&Node { var i = index; if (self.payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } @@ -854,7 +813,7 @@ pub const NodeSwitch = struct { pub const NodeSwitchCase = struct { base: Node, items: ArrayList(&Node), - payload: ?&NodePointerPayload, + payload: ?&Node, expr: &Node, pub fn iterate(self: &NodeSwitchCase, index: usize) ?&Node { @@ -864,7 +823,7 @@ pub const NodeSwitchCase = struct { i -= self.items.len; if (self.payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } @@ -906,7 +865,7 @@ pub const NodeWhile = struct { inline_token: ?Token, while_token: Token, condition: &Node, - payload: ?&NodePointerPayload, + payload: ?&Node, continue_expr: ?&Node, body: &Node, @"else": ?&NodeElse, @@ -918,7 +877,7 @@ pub const NodeWhile = struct { i -= 1; if (self.payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } @@ -965,7 +924,7 @@ pub const NodeFor = struct { inline_token: ?Token, for_token: Token, array_expr: &Node, - payload: ?&NodePointerIndexPayload, + payload: ?&Node, body: &Node, @"else": ?&NodeElse, @@ -976,7 +935,7 @@ pub const NodeFor = struct { i -= 1; if (self.payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } @@ -1016,7 +975,7 @@ pub const NodeIf = struct { base: Node, if_token: Token, condition: &Node, - payload: ?&NodePointerPayload, + payload: ?&Node, body: &Node, @"else": ?&NodeElse, @@ -1027,7 +986,7 @@ pub const NodeIf = struct { i -= 1; if (self.payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } @@ -1089,7 +1048,7 @@ pub const NodeInfixOp = struct { BitXor, BoolAnd, BoolOr, - Catch: ?&NodePayload, + Catch: ?&Node, Div, EqualEqual, ErrorUnion, @@ -1117,7 +1076,7 @@ pub const NodeInfixOp = struct { switch (self.op) { InfixOp.Catch => |maybe_payload| { if (maybe_payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } }, @@ -1385,14 +1344,30 @@ pub const NodeControlFlowExpression = struct { rhs: ?&Node, const Kind = union(enum) { - Break: ?Token, - Continue: ?Token, + Break: ?&Node, + Continue: ?&Node, Return, }; pub fn iterate(self: &NodeControlFlowExpression, index: usize) ?&Node { var i = index; + switch (self.kind) { + Kind.Break => |maybe_label| { + if (maybe_label) |label| { + if (i < 1) return label; + i -= 1; + } + }, + Kind.Continue => |maybe_label| { + if (maybe_label) |label| { + if (i < 1) return label; + i -= 1; + } + }, + Kind.Return => {}, + } + if (self.rhs) |rhs| { if (i < 1) return rhs; i -= 1; @@ -1411,14 +1386,14 @@ pub const NodeControlFlowExpression = struct { } switch (self.kind) { - Kind.Break => |maybe_blk_token| { - if (maybe_blk_token) |blk_token| { - return blk_token; + Kind.Break => |maybe_label| { + if (maybe_label) |label| { + return label.lastToken(); } }, - Kind.Continue => |maybe_blk_token| { - if (maybe_blk_token) |blk_token| { - return blk_token; + Kind.Continue => |maybe_label| { + if (maybe_label) |label| { + return label.lastToken(); } }, Kind.Return => return self.ltoken, @@ -1431,14 +1406,14 @@ pub const NodeControlFlowExpression = struct { pub const NodeSuspend = struct { base: Node, suspend_token: Token, - payload: ?&NodePayload, + payload: ?&Node, body: ?&Node, pub fn iterate(self: &NodeSuspend, index: usize) ?&Node { var i = index; if (self.payload) |payload| { - if (i < 1) return &payload.base; + if (i < 1) return payload; i -= 1; } @@ -1646,8 +1621,8 @@ pub const NodeThisLiteral = struct { pub const NodeAsmOutput = struct { base: Node, - symbolic_name: &NodeIdentifier, - constraint: &NodeStringLiteral, + symbolic_name: &Node, + constraint: &Node, kind: Kind, const Kind = union(enum) { @@ -1658,10 +1633,10 @@ pub const NodeAsmOutput = struct { pub fn iterate(self: &NodeAsmOutput, index: usize) ?&Node { var i = index; - if (i < 1) return &self.symbolic_name.base; + if (i < 1) return self.symbolic_name; i -= 1; - if (i < 1) return &self.constraint.base; + if (i < 1) return self.constraint; i -= 1; switch (self.kind) { @@ -1692,17 +1667,17 @@ pub const NodeAsmOutput = struct { pub const NodeAsmInput = struct { base: Node, - symbolic_name: &NodeIdentifier, - constraint: &NodeStringLiteral, + symbolic_name: &Node, + constraint: &Node, expr: &Node, pub fn iterate(self: &NodeAsmInput, index: usize) ?&Node { var i = index; - if (i < 1) return &self.symbolic_name.base; + if (i < 1) return self.symbolic_name; i -= 1; - if (i < 1) return &self.constraint.base; + if (i < 1) return self.constraint; i -= 1; if (i < 1) return self.expr; @@ -1723,12 +1698,12 @@ pub const NodeAsmInput = struct { pub const NodeAsm = struct { base: Node, asm_token: Token, - is_volatile: bool, - template: Token, + volatile_token: ?Token, + template: &Node, //tokens: ArrayList(AsmToken), outputs: ArrayList(&NodeAsmOutput), inputs: ArrayList(&NodeAsmInput), - cloppers: ArrayList(&NodeStringLiteral), + cloppers: ArrayList(&Node), rparen: Token, pub fn iterate(self: &NodeAsm, index: usize) ?&Node { @@ -1740,7 +1715,7 @@ pub const NodeAsm = struct { if (i < self.inputs.len) return &self.inputs.at(index).base; i -= self.inputs.len; - if (i < self.cloppers.len) return &self.cloppers.at(index).base; + if (i < self.cloppers.len) return self.cloppers.at(index); i -= self.cloppers.len; return null; diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 11b551fec0..c0708581ea 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -55,33 +55,33 @@ pub const Parser = struct { const TopLevelDeclCtx = struct { decls: &ArrayList(&ast.Node), visib_token: ?Token, - extern_token: ?Token, + extern_export_inline_token: ?Token, lib_name: ?&ast.Node, }; - const ContainerExternCtx = struct { - dest_ptr: DestPtr, - ltoken: Token, - layout: ast.NodeContainerDecl.Layout, + const VarDeclCtx = struct { + mut_token: Token, + visib_token: ?Token, + comptime_token: ?Token, + extern_export_token: ?Token, + lib_name: ?&ast.Node, + list: &ArrayList(&ast.Node), }; - const DestPtr = union(enum) { - Field: &&ast.Node, - NullableField: &?&ast.Node, + const TopLevelExternOrFieldCtx = struct { + visib_token: Token, + container_decl: &ast.NodeContainerDecl, + }; - pub fn store(self: &const DestPtr, value: &ast.Node) void { - switch (*self) { - DestPtr.Field => |ptr| *ptr = value, - DestPtr.NullableField => |ptr| *ptr = value, - } - } + const ExternTypeCtx = struct { + opt_ctx: OptionalCtx, + extern_token: Token, + }; - pub fn get(self: &const DestPtr) &ast.Node { - switch (*self) { - DestPtr.Field => |ptr| return *ptr, - DestPtr.NullableField => |ptr| return ??*ptr, - } - } + const ContainerKindCtx = struct { + opt_ctx: OptionalCtx, + ltoken: Token, + layout: ast.NodeContainerDecl.Layout, }; const ExpectTokenSave = struct { @@ -89,13 +89,9 @@ pub const Parser = struct { ptr: &Token, }; - const RevertState = struct { - parser: Parser, - tokenizer: Tokenizer, - - // We expect, that if something is optional, then there is a field, - // that needs to be set to null, when we revert. - ptr: &?&ast.Node, + const OptionalTokenSave = struct { + id: Token.Id, + ptr: &?Token, }; const ExprListCtx = struct { @@ -104,11 +100,6 @@ pub const Parser = struct { ptr: &Token, }; - const ElseCtx = struct { - payload: ?DestPtr, - body: DestPtr, - }; - fn ListSave(comptime T: type) type { return struct { list: &ArrayList(T), @@ -116,117 +107,196 @@ pub const Parser = struct { }; } + const MaybeLabeledExpressionCtx = struct { + label: Token, + opt_ctx: OptionalCtx, + }; + const LabelCtx = struct { label: ?Token, - dest_ptr: DestPtr, + opt_ctx: OptionalCtx, }; const InlineCtx = struct { label: ?Token, inline_token: ?Token, - dest_ptr: DestPtr, + opt_ctx: OptionalCtx, }; const LoopCtx = struct { label: ?Token, inline_token: ?Token, loop_token: Token, - dest_ptr: DestPtr, + opt_ctx: OptionalCtx, }; const AsyncEndCtx = struct { - dest_ptr: DestPtr, + ctx: OptionalCtx, attribute: &ast.NodeAsyncAttribute, }; + const ErrorTypeOrSetDeclCtx = struct { + opt_ctx: OptionalCtx, + error_token: Token, + }; + + const ParamDeclEndCtx = struct { + fn_proto: &ast.NodeFnProto, + param_decl: &ast.NodeParamDecl, + }; + + const ComptimeStatementCtx = struct { + comptime_token: Token, + block: &ast.NodeBlock, + }; + + const OptionalCtx = union(enum) { + Optional: &?&ast.Node, + RequiredNull: &?&ast.Node, + Required: &&ast.Node, + + pub fn store(self: &const OptionalCtx, value: &ast.Node) void { + switch (*self) { + OptionalCtx.Optional => |ptr| *ptr = value, + OptionalCtx.RequiredNull => |ptr| *ptr = value, + OptionalCtx.Required => |ptr| *ptr = value, + } + } + + pub fn get(self: &const OptionalCtx) ?&ast.Node { + switch (*self) { + OptionalCtx.Optional => |ptr| return *ptr, + OptionalCtx.RequiredNull => |ptr| return ??*ptr, + OptionalCtx.Required => |ptr| return *ptr, + } + } + + pub fn toRequired(self: &const OptionalCtx) OptionalCtx { + switch (*self) { + OptionalCtx.Optional => |ptr| { + return OptionalCtx { .RequiredNull = ptr }; + }, + OptionalCtx.RequiredNull => |ptr| return *self, + OptionalCtx.Required => |ptr| return *self, + } + } + }; + const State = union(enum) { TopLevel, TopLevelExtern: TopLevelDeclCtx, + TopLevelLibname: TopLevelDeclCtx, TopLevelDecl: TopLevelDeclCtx, - ContainerExtern: ContainerExternCtx, + TopLevelExternOrField: TopLevelExternOrFieldCtx, + + ContainerKind: ContainerKindCtx, + ContainerInitArgStart: &ast.NodeContainerDecl, + ContainerInitArg: &ast.NodeContainerDecl, ContainerDecl: &ast.NodeContainerDecl, - SliceOrArrayAccess: &ast.NodeSuffixOp, - AddrOfModifiers: &ast.NodePrefixOp.AddrOfInfo, - VarDecl: &ast.NodeVarDecl, + + VarDecl: VarDeclCtx, VarDeclAlign: &ast.NodeVarDecl, VarDeclEq: &ast.NodeVarDecl, - IfToken: @TagType(Token.Id), - IfTokenSave: ExpectTokenSave, - ExpectToken: @TagType(Token.Id), - ExpectTokenSave: ExpectTokenSave, + + FnDef: &ast.NodeFnProto, FnProto: &ast.NodeFnProto, FnProtoAlign: &ast.NodeFnProto, FnProtoReturnType: &ast.NodeFnProto, + ParamDecl: &ast.NodeFnProto, - ParamDeclComma, - FnDef: &ast.NodeFnProto, + ParamDeclAliasOrComptime: &ast.NodeParamDecl, + ParamDeclName: &ast.NodeParamDecl, + ParamDeclEnd: ParamDeclEndCtx, + ParamDeclComma: &ast.NodeFnProto, + + MaybeLabeledExpression: MaybeLabeledExpressionCtx, LabeledExpression: LabelCtx, Inline: InlineCtx, While: LoopCtx, + WhileContinueExpr: &?&ast.Node, For: LoopCtx, - Block: &ast.NodeBlock, Else: &?&ast.NodeElse, - WhileContinueExpr: &?&ast.Node, + + Block: &ast.NodeBlock, Statement: &ast.NodeBlock, - Semicolon: &const &const ast.Node, + ComptimeStatement: ComptimeStatementCtx, + Semicolon: &&ast.Node, + AsmOutputItems: &ArrayList(&ast.NodeAsmOutput), + AsmOutputReturnOrType: &ast.NodeAsmOutput, AsmInputItems: &ArrayList(&ast.NodeAsmInput), - AsmClopperItems: &ArrayList(&ast.NodeStringLiteral), + AsmClopperItems: &ArrayList(&ast.Node), + ExprListItemOrEnd: ExprListCtx, ExprListCommaOrEnd: ExprListCtx, FieldInitListItemOrEnd: ListSave(&ast.NodeFieldInitializer), FieldInitListCommaOrEnd: ListSave(&ast.NodeFieldInitializer), FieldListCommaOrEnd: &ast.NodeContainerDecl, + IdentifierListItemOrEnd: ListSave(&ast.Node), + IdentifierListCommaOrEnd: ListSave(&ast.Node), SwitchCaseOrEnd: ListSave(&ast.NodeSwitchCase), - SuspendBody: &ast.NodeSuspend, - AsyncEnd: AsyncEndCtx, - Payload: &?&ast.NodePayload, - PointerPayload: &?&ast.NodePointerPayload, - PointerIndexPayload: &?&ast.NodePointerIndexPayload, SwitchCaseCommaOrEnd: ListSave(&ast.NodeSwitchCase), + SwitchCaseFirstItem: &ArrayList(&ast.Node), SwitchCaseItem: &ArrayList(&ast.Node), SwitchCaseItemCommaOrEnd: &ArrayList(&ast.Node), - /// A state that can be appended before any other State. If an error occures, - /// the parser will first try looking for the closest optional state. If an - /// optional state is found, the parser will revert to the state it was in - /// when the optional was added. This will polute the arena allocator with - /// "leaked" nodes. TODO: Figure out if it's nessesary to handle leaked nodes. - Optional: RevertState, - - Expression: DestPtr, - RangeExpressionBegin: DestPtr, - RangeExpressionEnd: DestPtr, - AssignmentExpressionBegin: DestPtr, - AssignmentExpressionEnd: DestPtr, - UnwrapExpressionBegin: DestPtr, - UnwrapExpressionEnd: DestPtr, - BoolOrExpressionBegin: DestPtr, - BoolOrExpressionEnd: DestPtr, - BoolAndExpressionBegin: DestPtr, - BoolAndExpressionEnd: DestPtr, - ComparisonExpressionBegin: DestPtr, - ComparisonExpressionEnd: DestPtr, - BinaryOrExpressionBegin: DestPtr, - BinaryOrExpressionEnd: DestPtr, - BinaryXorExpressionBegin: DestPtr, - BinaryXorExpressionEnd: DestPtr, - BinaryAndExpressionBegin: DestPtr, - BinaryAndExpressionEnd: DestPtr, - BitShiftExpressionBegin: DestPtr, - BitShiftExpressionEnd: DestPtr, - AdditionExpressionBegin: DestPtr, - AdditionExpressionEnd: DestPtr, - MultiplyExpressionBegin: DestPtr, - MultiplyExpressionEnd: DestPtr, - CurlySuffixExpressionBegin: DestPtr, - CurlySuffixExpressionEnd: DestPtr, - TypeExprBegin: DestPtr, - TypeExprEnd: DestPtr, - PrefixOpExpression: DestPtr, - SuffixOpExpressionBegin: DestPtr, - SuffixOpExpressionEnd: DestPtr, - PrimaryExpression: DestPtr, + SuspendBody: &ast.NodeSuspend, + AsyncAllocator: &ast.NodeAsyncAttribute, + AsyncEnd: AsyncEndCtx, + + ExternType: ExternTypeCtx, + SliceOrArrayAccess: &ast.NodeSuffixOp, + SliceOrArrayType: &ast.NodePrefixOp, + AddrOfModifiers: &ast.NodePrefixOp.AddrOfInfo, + + Payload: OptionalCtx, + PointerPayload: OptionalCtx, + PointerIndexPayload: OptionalCtx, + + Expression: OptionalCtx, + RangeExpressionBegin: OptionalCtx, + RangeExpressionEnd: OptionalCtx, + AssignmentExpressionBegin: OptionalCtx, + AssignmentExpressionEnd: OptionalCtx, + UnwrapExpressionBegin: OptionalCtx, + UnwrapExpressionEnd: OptionalCtx, + BoolOrExpressionBegin: OptionalCtx, + BoolOrExpressionEnd: OptionalCtx, + BoolAndExpressionBegin: OptionalCtx, + BoolAndExpressionEnd: OptionalCtx, + ComparisonExpressionBegin: OptionalCtx, + ComparisonExpressionEnd: OptionalCtx, + BinaryOrExpressionBegin: OptionalCtx, + BinaryOrExpressionEnd: OptionalCtx, + BinaryXorExpressionBegin: OptionalCtx, + BinaryXorExpressionEnd: OptionalCtx, + BinaryAndExpressionBegin: OptionalCtx, + BinaryAndExpressionEnd: OptionalCtx, + BitShiftExpressionBegin: OptionalCtx, + BitShiftExpressionEnd: OptionalCtx, + AdditionExpressionBegin: OptionalCtx, + AdditionExpressionEnd: OptionalCtx, + MultiplyExpressionBegin: OptionalCtx, + MultiplyExpressionEnd: OptionalCtx, + CurlySuffixExpressionBegin: OptionalCtx, + CurlySuffixExpressionEnd: OptionalCtx, + TypeExprBegin: OptionalCtx, + TypeExprEnd: OptionalCtx, + PrefixOpExpression: OptionalCtx, + SuffixOpExpressionBegin: OptionalCtx, + SuffixOpExpressionEnd: OptionalCtx, + PrimaryExpression: OptionalCtx, + + ErrorTypeOrSetDecl: ErrorTypeOrSetDeclCtx, + StringLiteral: OptionalCtx, + Identifier: OptionalCtx, + + + IfToken: @TagType(Token.Id), + IfTokenSave: ExpectTokenSave, + ExpectToken: @TagType(Token.Id), + ExpectTokenSave: ExpectTokenSave, + OptionalTokenSave: OptionalTokenSave, }; /// Returns an AST tree, allocated with the parser's allocator. @@ -240,7 +310,14 @@ pub const Parser = struct { errdefer arena_allocator.deinit(); const arena = &arena_allocator.allocator; - const root_node = try self.createRoot(arena); + const root_node = try self.createNode(arena, ast.NodeRoot, + ast.NodeRoot { + .base = undefined, + .decls = ArrayList(&ast.Node).init(arena), + // initialized when we get the eof token + .eof_token = undefined, + } + ); try stack.append(State.TopLevel); @@ -259,8 +336,7 @@ pub const Parser = struct { // look for line comments while (true) { - const token = self.getNextToken(); - if (token.id == Token.Id.LineComment) { + if (self.eatToken(Token.Id.LineComment)) |line_comment| { const node = blk: { if (self.pending_line_comment_node) |comment_node| { break :blk comment_node; @@ -277,10 +353,9 @@ pub const Parser = struct { break :blk comment_node; } }; - try node.lines.append(token); + try node.lines.append(line_comment); continue; } - self.putBackToken(token); break; } @@ -294,41 +369,74 @@ pub const Parser = struct { Token.Id.Keyword_test => { stack.append(State.TopLevel) catch unreachable; - const name_token = (try self.eatToken(&stack, Token.Id.StringLiteral)) ?? continue; - const lbrace = (try self.eatToken(&stack, Token.Id.LBrace)) ?? continue; - - const name = try self.createStringLiteral(arena, name_token); - const block = try self.createBlock(arena, (?Token)(null), token); - const test_decl = try self.createAttachTestDecl(arena, &root_node.decls, token, &name.base, block); + const block = try self.createNode(arena, ast.NodeBlock, + ast.NodeBlock { + .base = undefined, + .label = null, + .lbrace = undefined, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, + } + ); + const test_node = try self.createAttachNode(arena, &root_node.decls, ast.NodeTestDecl, + ast.NodeTestDecl { + .base = undefined, + .test_token = token, + .name = undefined, + .body_node = &block.base, + } + ); stack.append(State { .Block = block }) catch unreachable; + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.LBrace, + .ptr = &block.rbrace, + } + }); + try stack.append(State { .StringLiteral = OptionalCtx { .Required = &test_node.name } }); continue; }, Token.Id.Eof => { root_node.eof_token = token; return Tree {.root_node = root_node, .arena_allocator = arena_allocator}; }, - Token.Id.Keyword_pub, Token.Id.Keyword_export => { + Token.Id.Keyword_pub => { stack.append(State.TopLevel) catch unreachable; try stack.append(State { .TopLevelExtern = TopLevelDeclCtx { .decls = &root_node.decls, .visib_token = token, - .extern_token = null, + .extern_export_inline_token = null, .lib_name = null, } }); continue; }, Token.Id.Keyword_comptime => { - const node = try arena.create(ast.NodeComptime); - *node = ast.NodeComptime { - .base = self.initNode(ast.Node.Id.Comptime), - .comptime_token = token, - .expr = undefined, - }; - try root_node.decls.append(&node.base); + const block = try self.createNode(arena, ast.NodeBlock, + ast.NodeBlock { + .base = undefined, + .label = null, + .lbrace = undefined, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, + } + ); + const node = try self.createAttachNode(arena, &root_node.decls, ast.NodeComptime, + ast.NodeComptime { + .base = undefined, + .comptime_token = token, + .expr = &block.base, + } + ); stack.append(State.TopLevel) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.expr } }); + try stack.append(State { .Block = block }); + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.LBrace, + .ptr = &block.rbrace, + } + }); continue; }, else => { @@ -338,7 +446,7 @@ pub const Parser = struct { .TopLevelExtern = TopLevelDeclCtx { .decls = &root_node.decls, .visib_token = null, - .extern_token = null, + .extern_export_inline_token = null, .lib_name = null, } }); @@ -349,43 +457,24 @@ pub const Parser = struct { State.TopLevelExtern => |ctx| { const token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_use => { - const node = try arena.create(ast.NodeUse); - *node = ast.NodeUse { - .base = self.initNode(ast.Node.Id.Use), - .visib_token = ctx.visib_token, - .expr = undefined, - .semicolon_token = undefined, - }; - try ctx.decls.append(&node.base); - + Token.Id.Keyword_export, Token.Id.Keyword_inline => { stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Semicolon, - .ptr = &node.semicolon_token, - } + .TopLevelDecl = TopLevelDeclCtx { + .decls = ctx.decls, + .visib_token = ctx.visib_token, + .extern_export_inline_token = token, + .lib_name = null, + }, }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.expr } }); continue; }, Token.Id.Keyword_extern => { - const lib_name_token = self.getNextToken(); - const lib_name = blk: { - if (lib_name_token.id == Token.Id.StringLiteral) { - const res = try self.createStringLiteral(arena, lib_name_token); - break :blk &res.base; - } else { - self.putBackToken(lib_name_token); - break :blk null; - } - }; - stack.append(State { - .TopLevelDecl = TopLevelDeclCtx { + .TopLevelLibname = TopLevelDeclCtx { .decls = ctx.decls, .visib_token = ctx.visib_token, - .extern_token = token, - .lib_name = lib_name, + .extern_export_inline_token = token, + .lib_name = null, }, }) catch unreachable; continue; @@ -397,258 +486,302 @@ pub const Parser = struct { } } }, + State.TopLevelLibname => |ctx| { + const lib_name = blk: { + const lib_name_token = self.getNextToken(); + break :blk (try self.parseStringLiteral(arena, lib_name_token)) ?? { + self.putBackToken(lib_name_token); + break :blk null; + }; + }; + + stack.append(State { + .TopLevelDecl = TopLevelDeclCtx { + .decls = ctx.decls, + .visib_token = ctx.visib_token, + .extern_export_inline_token = ctx.extern_export_inline_token, + .lib_name = lib_name, + }, + }) catch unreachable; + continue; + }, State.TopLevelDecl => |ctx| { const token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_var, Token.Id.Keyword_const => { - // TODO shouldn't need these casts - const var_decl_node = try self.createAttachVarDecl(arena, ctx.decls, ctx.visib_token, - token, (?Token)(null), ctx.extern_token, ctx.lib_name); - stack.append(State { .VarDecl = var_decl_node }) catch unreachable; - continue; - }, - Token.Id.Keyword_fn => { - // TODO shouldn't need these casts - const fn_proto = try self.createAttachFnProto(arena, ctx.decls, token, - ctx.extern_token, ctx.lib_name, (?Token)(null), ctx.visib_token, (?Token)(null)); - stack.append(State { .FnDef = fn_proto }) catch unreachable; - try stack.append(State { .FnProto = fn_proto }); - continue; - }, - Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { - // TODO shouldn't need this cast - const fn_proto = try self.createAttachFnProto(arena, ctx.decls, Token(undefined), - ctx.extern_token, ctx.lib_name, (?Token)(token), (?Token)(null), (?Token)(null)); - stack.append(State { .FnDef = fn_proto }) catch unreachable; - try stack.append(State { .FnProto = fn_proto }); - try stack.append(State { + Token.Id.Keyword_use => { + if (ctx.extern_export_inline_token != null) { + return self.parseError(token, "Invalid token {}", @tagName((??ctx.extern_export_inline_token).id)); + } + + const node = try self.createAttachNode(arena, ctx.decls, ast.NodeUse, + ast.NodeUse { + .base = undefined, + .visib_token = ctx.visib_token, + .expr = undefined, + .semicolon_token = undefined, + } + ); + stack.append(State { .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Keyword_fn, - .ptr = &fn_proto.fn_token, + .id = Token.Id.Semicolon, + .ptr = &node.semicolon_token, } - }); + }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); continue; }, - Token.Id.Keyword_async => { - // TODO shouldn't need this cast - const fn_proto = try self.createAttachFnProto(arena, ctx.decls, Token(undefined), - ctx.extern_token, ctx.lib_name, (?Token)(null), (?Token)(null), (?Token)(null)); - - const async_node = try arena.create(ast.NodeAsyncAttribute); - *async_node = ast.NodeAsyncAttribute { - .base = self.initNode(ast.Node.Id.AsyncAttribute), - .async_token = token, - .allocator_type = null, - .rangle_bracket = null, - }; - - fn_proto.async_attr = async_node; - stack.append(State { .FnDef = fn_proto }) catch unreachable; - try stack.append(State { .FnProto = fn_proto }); - try stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Keyword_fn, - .ptr = &fn_proto.fn_token, + Token.Id.Keyword_var, Token.Id.Keyword_const => { + if (ctx.extern_export_inline_token) |extern_export_inline_token| { + if (extern_export_inline_token.id == Token.Id.Keyword_inline) { + return self.parseError(token, "Invalid token {}", @tagName(extern_export_inline_token.id)); } - }); - - const langle_bracket = self.getNextToken(); - if (langle_bracket.id != Token.Id.AngleBracketLeft) { - self.putBackToken(langle_bracket); - continue; } - async_node.rangle_bracket = Token(undefined); - try stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.AngleBracketRight, - .ptr = &??async_node.rangle_bracket, + stack.append(State { + .VarDecl = VarDeclCtx { + .visib_token = ctx.visib_token, + .lib_name = ctx.lib_name, + .comptime_token = null, + .extern_export_token = ctx.extern_export_inline_token, + .mut_token = token, + .list = ctx.decls } - }); - try stack.append(State { .TypeExprBegin = DestPtr { .NullableField = &async_node.allocator_type } }); + }) catch unreachable; continue; }, + Token.Id.Keyword_fn, Token.Id.Keyword_nakedcc, + Token.Id.Keyword_stdcallcc, Token.Id.Keyword_async => { + const fn_proto = try self.createAttachNode(arena, ctx.decls, ast.NodeFnProto, + ast.NodeFnProto { + .base = undefined, + .visib_token = ctx.visib_token, + .name_token = null, + .fn_token = undefined, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = ctx.extern_export_inline_token, + .cc_token = null, + .async_attr = null, + .body_node = null, + .lib_name = ctx.lib_name, + .align_expr = null, + } + ); + stack.append(State { .FnDef = fn_proto }) catch unreachable; + try stack.append(State { .FnProto = fn_proto }); + + switch (token.id) { + Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { + fn_proto.cc_token = token; + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Keyword_fn, + .ptr = &fn_proto.fn_token, + } + }); + continue; + }, + Token.Id.Keyword_async => { + const async_node = try self.createNode(arena, ast.NodeAsyncAttribute, + ast.NodeAsyncAttribute { + .base = undefined, + .async_token = token, + .allocator_type = null, + .rangle_bracket = null, + } + ); + fn_proto.async_attr = async_node; + + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Keyword_fn, + .ptr = &fn_proto.fn_token, + } + }); + try stack.append(State { .AsyncAllocator = async_node }); + continue; + }, + Token.Id.Keyword_fn => { + fn_proto.fn_token = token; + continue; + }, + else => unreachable, + } + }, else => { - try self.parseError(&stack, token, "expected variable declaration or function, found {}", @tagName(token.id)); - continue; + return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id)); }, } }, - State.VarDecl => |var_decl| { - stack.append(State { .VarDeclAlign = var_decl }) catch unreachable; - try stack.append(State { .TypeExprBegin = DestPtr {.NullableField = &var_decl.type_node} }); - try stack.append(State { .IfToken = Token.Id.Colon }); - try stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Identifier, - .ptr = &var_decl.name_token, - } - }); - continue; - }, - State.VarDeclAlign => |var_decl| { - stack.append(State { .VarDeclEq = var_decl }) catch unreachable; + State.TopLevelExternOrField => |ctx| { + if (self.eatToken(Token.Id.Identifier)) |identifier| { + std.debug.assert(ctx.container_decl.kind == ast.NodeContainerDecl.Kind.Struct); + const node = try self.createAttachNode(arena, &ctx.container_decl.fields_and_decls, ast.NodeStructField, + ast.NodeStructField { + .base = undefined, + .visib_token = ctx.visib_token, + .name_token = identifier, + .type_expr = undefined, + } + ); - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Keyword_align) { - try stack.append(State { .ExpectToken = Token.Id.RParen }); - try stack.append(State { .Expression = DestPtr{.NullableField = &var_decl.align_node} }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); + stack.append(State { .FieldListCommaOrEnd = ctx.container_decl }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.type_expr } }); + try stack.append(State { .ExpectToken = Token.Id.Colon }); continue; } - self.putBackToken(next_token); - continue; - }, - State.VarDeclEq => |var_decl| { - const token = self.getNextToken(); - if (token.id == Token.Id.Equal) { - var_decl.eq_token = token; - stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Semicolon, - .ptr = &var_decl.semicolon_token, - }, - }) catch unreachable; - try stack.append(State { - .Expression = DestPtr {.NullableField = &var_decl.init_node}, - }); - continue; - } - if (token.id == Token.Id.Semicolon) { - var_decl.semicolon_token = token; - continue; - } - try self.parseError(&stack, token, "expected '=' or ';', found {}", @tagName(token.id)); + stack.append(State{ .ContainerDecl = ctx.container_decl }) catch unreachable; + try stack.append(State { + .TopLevelExtern = TopLevelDeclCtx { + .decls = &ctx.container_decl.fields_and_decls, + .visib_token = ctx.visib_token, + .extern_export_inline_token = null, + .lib_name = null, + } + }); continue; }, - State.ContainerExtern => |ctx| { - const token = self.getNextToken(); - const node = try arena.create(ast.NodeContainerDecl); - *node = ast.NodeContainerDecl { - .base = self.initNode(ast.Node.Id.ContainerDecl), - .ltoken = ctx.ltoken, - .layout = ctx.layout, - .kind = switch (token.id) { - Token.Id.Keyword_struct => ast.NodeContainerDecl.Kind.Struct, - Token.Id.Keyword_union => ast.NodeContainerDecl.Kind.Union, - Token.Id.Keyword_enum => ast.NodeContainerDecl.Kind.Enum, - else => { - try self.parseError(&stack, token, "expected {}, {} or {}, found {}", - @tagName(Token.Id.Keyword_struct), - @tagName(Token.Id.Keyword_union), - @tagName(Token.Id.Keyword_enum), - @tagName(token.id)); - continue; + State.ContainerKind => |ctx| { + const token = self.getNextToken(); + const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeContainerDecl, + ast.NodeContainerDecl { + .base = undefined, + .ltoken = ctx.ltoken, + .layout = ctx.layout, + .kind = switch (token.id) { + Token.Id.Keyword_struct => ast.NodeContainerDecl.Kind.Struct, + Token.Id.Keyword_union => ast.NodeContainerDecl.Kind.Union, + Token.Id.Keyword_enum => ast.NodeContainerDecl.Kind.Enum, + else => { + return self.parseError(token, "expected {}, {} or {}, found {}", + @tagName(Token.Id.Keyword_struct), + @tagName(Token.Id.Keyword_union), + @tagName(Token.Id.Keyword_enum), + @tagName(token.id)); + }, }, - }, - .init_arg_expr = undefined, - .fields_and_decls = ArrayList(&ast.Node).init(arena), - .rbrace_token = undefined, - }; - ctx.dest_ptr.store(&node.base); + .init_arg_expr = ast.NodeContainerDecl.InitArg.None, + .fields_and_decls = ArrayList(&ast.Node).init(arena), + .rbrace_token = undefined, + } + ); stack.append(State { .ContainerDecl = node }) catch unreachable; try stack.append(State { .ExpectToken = Token.Id.LBrace }); + try stack.append(State { .ContainerInitArgStart = node }); + continue; + }, - const lparen = self.getNextToken(); - if (lparen.id != Token.Id.LParen) { - self.putBackToken(lparen); - node.init_arg_expr = ast.NodeContainerDecl.InitArg.None; + State.ContainerInitArgStart => |container_decl| { + if (self.eatToken(Token.Id.LParen) == null) { continue; } - try stack.append(State { .ExpectToken = Token.Id.RParen }); + stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable; + try stack.append(State { .ContainerInitArg = container_decl }); + continue; + }, + State.ContainerInitArg => |container_decl| { const init_arg_token = self.getNextToken(); switch (init_arg_token.id) { Token.Id.Keyword_enum => { - node.init_arg_expr = ast.NodeContainerDecl.InitArg.Enum; + container_decl.init_arg_expr = ast.NodeContainerDecl.InitArg.Enum; }, else => { self.putBackToken(init_arg_token); - node.init_arg_expr = ast.NodeContainerDecl.InitArg { .Type = undefined }; - try stack.append(State { - .Expression = DestPtr { - .Field = &node.init_arg_expr.Type - } - }); + container_decl.init_arg_expr = ast.NodeContainerDecl.InitArg { .Type = undefined }; + stack.append(State { .Expression = OptionalCtx { .Required = &container_decl.init_arg_expr.Type } }) catch unreachable; }, } continue; }, - State.ContainerDecl => |container_decl| { const token = self.getNextToken(); - switch (token.id) { Token.Id.Identifier => { switch (container_decl.kind) { ast.NodeContainerDecl.Kind.Struct => { - const node = try arena.create(ast.NodeStructField); - *node = ast.NodeStructField { - .base = self.initNode(ast.Node.Id.StructField), - .name_token = token, - .type_expr = undefined, - }; - try container_decl.fields_and_decls.append(&node.base); + const node = try self.createAttachNode(arena, &container_decl.fields_and_decls, ast.NodeStructField, + ast.NodeStructField { + .base = undefined, + .visib_token = null, + .name_token = token, + .type_expr = undefined, + } + ); stack.append(State { .FieldListCommaOrEnd = container_decl }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.type_expr } }); + try stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &node.type_expr } }); try stack.append(State { .ExpectToken = Token.Id.Colon }); continue; }, ast.NodeContainerDecl.Kind.Union => { - const node = try arena.create(ast.NodeUnionTag); - *node = ast.NodeUnionTag { - .base = self.initNode(ast.Node.Id.UnionTag), - .name_token = token, - .type_expr = null, - }; - try container_decl.fields_and_decls.append(&node.base); + const node = try self.createAttachNode(arena, &container_decl.fields_and_decls, ast.NodeUnionTag, + ast.NodeUnionTag { + .base = undefined, + .name_token = token, + .type_expr = null, + } + ); stack.append(State { .FieldListCommaOrEnd = container_decl }) catch unreachable; - - const next = self.getNextToken(); - if (next.id != Token.Id.Colon) { - self.putBackToken(next); - continue; - } - - try stack.append(State { .Expression = DestPtr { .NullableField = &node.type_expr } }); + try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &node.type_expr } }); + try stack.append(State { .IfToken = Token.Id.Colon }); continue; }, ast.NodeContainerDecl.Kind.Enum => { - const node = try arena.create(ast.NodeEnumTag); - *node = ast.NodeEnumTag { - .base = self.initNode(ast.Node.Id.EnumTag), - .name_token = token, - .value = null, - }; - try container_decl.fields_and_decls.append(&node.base); + const node = try self.createAttachNode(arena, &container_decl.fields_and_decls, ast.NodeEnumTag, + ast.NodeEnumTag { + .base = undefined, + .name_token = token, + .value = null, + } + ); stack.append(State { .FieldListCommaOrEnd = container_decl }) catch unreachable; - - const next = self.getNextToken(); - if (next.id != Token.Id.Equal) { - self.putBackToken(next); - continue; - } - - try stack.append(State { .Expression = DestPtr { .NullableField = &node.value } }); + try stack.append(State { .Expression = OptionalCtx { .RequiredNull = &node.value } }); + try stack.append(State { .IfToken = Token.Id.Equal }); + continue; + }, + } + }, + Token.Id.Keyword_pub => { + switch (container_decl.kind) { + ast.NodeContainerDecl.Kind.Struct => { + try stack.append(State { + .TopLevelExternOrField = TopLevelExternOrFieldCtx { + .visib_token = token, + .container_decl = container_decl, + } + }); continue; }, + else => { + stack.append(State{ .ContainerDecl = container_decl }) catch unreachable; + try stack.append(State { + .TopLevelExtern = TopLevelDeclCtx { + .decls = &container_decl.fields_and_decls, + .visib_token = token, + .extern_export_inline_token = null, + .lib_name = null, + } + }); + continue; + } } }, - Token.Id.Keyword_pub, Token.Id.Keyword_export => { + Token.Id.Keyword_export => { stack.append(State{ .ContainerDecl = container_decl }) catch unreachable; try stack.append(State { .TopLevelExtern = TopLevelDeclCtx { .decls = &container_decl.fields_and_decls, .visib_token = token, - .extern_token = null, + .extern_export_inline_token = null, .lib_name = null, } }); @@ -665,7 +798,7 @@ pub const Parser = struct { .TopLevelExtern = TopLevelDeclCtx { .decls = &container_decl.fields_and_decls, .visib_token = null, - .extern_token = null, + .extern_export_inline_token = null, .lib_name = null, } }); @@ -674,161 +807,296 @@ pub const Parser = struct { } }, - State.ExpectToken => |token_id| { - _ = (try self.eatToken(&stack, token_id)) ?? continue; - continue; - }, - State.ExpectTokenSave => |expect_token_save| { - *expect_token_save.ptr = (try self.eatToken(&stack, expect_token_save.id)) ?? continue; - continue; - }, + State.VarDecl => |ctx| { + const var_decl = try self.createAttachNode(arena, ctx.list, ast.NodeVarDecl, + ast.NodeVarDecl { + .base = undefined, + .visib_token = ctx.visib_token, + .mut_token = ctx.mut_token, + .comptime_token = ctx.comptime_token, + .extern_export_token = ctx.extern_export_token, + .type_node = null, + .align_node = null, + .init_node = null, + .lib_name = ctx.lib_name, + // initialized later + .name_token = undefined, + .eq_token = undefined, + .semicolon_token = undefined, + } + ); - State.IfToken => |token_id| { - const token = self.getNextToken(); - if (@TagType(Token.Id)(token.id) != token_id) { - self.putBackToken(token); - _ = stack.pop(); - continue; - } + stack.append(State { .VarDeclAlign = var_decl }) catch unreachable; + try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &var_decl.type_node} }); + try stack.append(State { .IfToken = Token.Id.Colon }); + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Identifier, + .ptr = &var_decl.name_token, + } + }); continue; }, + State.VarDeclAlign => |var_decl| { + stack.append(State { .VarDeclEq = var_decl }) catch unreachable; - State.IfTokenSave => |if_token_save| { - const token = self.getNextToken(); - if (@TagType(Token.Id)(token.id) != if_token_save.id) { - self.putBackToken(token); - _ = stack.pop(); + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Keyword_align) { + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .RequiredNull = &var_decl.align_node} }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); continue; } - *if_token_save.ptr = token; + self.putBackToken(next_token); continue; }, - - State.Optional => { }, - - State.Expression => |dest_ptr| { + State.VarDeclEq => |var_decl| { const token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_try => { - const node = try self.createPrefixOp(arena, token, ast.NodePrefixOp.PrefixOp.Try); - dest_ptr.store(&node.base); - - stack.append(State { .Expression = DestPtr { .Field = &node.rhs } }) catch unreachable; - continue; - }, - Token.Id.Keyword_return => { - const node = try self.createControlFlowExpr(arena, token, ast.NodeControlFlowExpression.Kind.Return); - dest_ptr.store(&node.base); - + Token.Id.Equal => { + var_decl.eq_token = token; stack.append(State { - .Optional = RevertState { - .parser = *self, - .tokenizer = *self.tokenizer, - .ptr = &node.rhs, - } + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Semicolon, + .ptr = &var_decl.semicolon_token, + }, }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .NullableField = &node.rhs } }); + try stack.append(State { .Expression = OptionalCtx { .RequiredNull = &var_decl.init_node } }); continue; }, - Token.Id.Keyword_break => { - const label = blk: { - const colon = self.getNextToken(); - if (colon.id != Token.Id.Colon) { - self.putBackToken(colon); - break :blk null; - } + Token.Id.Semicolon => { + var_decl.semicolon_token = token; + continue; + }, + else => { + return self.parseError(token, "expected '=' or ';', found {}", @tagName(token.id)); + } + } + }, - break :blk (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - }; - const node = try self.createControlFlowExpr(arena, token, - ast.NodeControlFlowExpression.Kind { - .Break = label, + State.FnDef => |fn_proto| { + const token = self.getNextToken(); + switch(token.id) { + Token.Id.LBrace => { + const block = try self.createNode(arena, ast.NodeBlock, + ast.NodeBlock { + .base = undefined, + .label = null, + .lbrace = token, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, } ); - dest_ptr.store(&node.base); + fn_proto.body_node = &block.base; + stack.append(State { .Block = block }) catch unreachable; + continue; + }, + Token.Id.Semicolon => continue, + else => { + return self.parseError(token, "expected ';' or '{{', found {}", @tagName(token.id)); + }, + } + }, + State.FnProto => |fn_proto| { + stack.append(State { .FnProtoAlign = fn_proto }) catch unreachable; + try stack.append(State { .ParamDecl = fn_proto }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + + if (self.eatToken(Token.Id.Identifier)) |name_token| { + fn_proto.name_token = name_token; + } + continue; + }, + State.FnProtoAlign => |fn_proto| { + stack.append(State { .FnProtoReturnType = fn_proto }) catch unreachable; + if (self.eatToken(Token.Id.Keyword_align)) |align_token| { + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .RequiredNull = &fn_proto.align_expr } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + } + continue; + }, + State.FnProtoReturnType => |fn_proto| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Bang => { + fn_proto.return_type = ast.NodeFnProto.ReturnType { .InferErrorSet = undefined }; stack.append(State { - .Optional = RevertState { - .parser = *self, - .tokenizer = *self.tokenizer, - .ptr = &node.rhs, - } + .TypeExprBegin = OptionalCtx { .Required = &fn_proto.return_type.InferErrorSet }, }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .NullableField = &node.rhs } }); continue; }, - Token.Id.Keyword_continue => { - const label = blk: { - const colon = self.getNextToken(); - if (colon.id != Token.Id.Colon) { - self.putBackToken(colon); - break :blk null; + else => { + // TODO: this is a special case. Remove this when #760 is fixed + if (token.id == Token.Id.Keyword_error) { + if (self.isPeekToken(Token.Id.LBrace)) { + fn_proto.return_type = ast.NodeFnProto.ReturnType { + .Explicit = &(try self.createLiteral(arena, ast.NodeErrorType, token)).base + }; + continue; } + } - break :blk (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - }; + self.putBackToken(token); + fn_proto.return_type = ast.NodeFnProto.ReturnType { .Explicit = undefined }; + stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &fn_proto.return_type.Explicit }, }) catch unreachable; + continue; + }, + } + }, + + + State.ParamDecl => |fn_proto| { + if (self.eatToken(Token.Id.RParen)) |_| { + continue; + } + const param_decl = try self.createAttachNode(arena, &fn_proto.params, ast.NodeParamDecl, + ast.NodeParamDecl { + .base = undefined, + .comptime_token = null, + .noalias_token = null, + .name_token = null, + .type_node = undefined, + .var_args_token = null, + }, + ); + + stack.append(State { + .ParamDeclEnd = ParamDeclEndCtx { + .param_decl = param_decl, + .fn_proto = fn_proto, + } + }) catch unreachable; + try stack.append(State { .ParamDeclName = param_decl }); + try stack.append(State { .ParamDeclAliasOrComptime = param_decl }); + continue; + }, + State.ParamDeclAliasOrComptime => |param_decl| { + if (self.eatToken(Token.Id.Keyword_comptime)) |comptime_token| { + param_decl.comptime_token = comptime_token; + } else if (self.eatToken(Token.Id.Keyword_noalias)) |noalias_token| { + param_decl.noalias_token = noalias_token; + } + continue; + }, + State.ParamDeclName => |param_decl| { + // TODO: Here, we eat two tokens in one state. This means that we can't have + // comments between these two tokens. + if (self.eatToken(Token.Id.Identifier)) |ident_token| { + if (self.eatToken(Token.Id.Colon)) |_| { + param_decl.name_token = ident_token; + } else { + self.putBackToken(ident_token); + } + } + continue; + }, + State.ParamDeclEnd => |ctx| { + if (self.eatToken(Token.Id.Ellipsis3)) |ellipsis3| { + ctx.param_decl.var_args_token = ellipsis3; + stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable; + continue; + } - const node = try self.createControlFlowExpr(arena, token, - ast.NodeControlFlowExpression.Kind { - .Continue = label, + try stack.append(State { .ParamDeclComma = ctx.fn_proto }); + try stack.append(State { + .TypeExprBegin = OptionalCtx { .Required = &ctx.param_decl.type_node } + }); + continue; + }, + State.ParamDeclComma => |fn_proto| { + if ((try self.expectCommaOrEnd(Token.Id.RParen)) == null) { + stack.append(State { .ParamDecl = fn_proto }) catch unreachable; + } + continue; + }, + + State.MaybeLabeledExpression => |ctx| { + if (self.eatToken(Token.Id.Colon)) |_| { + stack.append(State { + .LabeledExpression = LabelCtx { + .label = ctx.label, + .opt_ctx = ctx.opt_ctx, + } + }) catch unreachable; + continue; + } + + _ = try self.createToCtxLiteral(arena, ctx.opt_ctx, ast.NodeIdentifier, ctx.label); + continue; + }, + State.LabeledExpression => |ctx| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.LBrace => { + const block = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeBlock, + ast.NodeBlock { + .base = undefined, + .label = ctx.label, + .lbrace = token, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, } ); - dest_ptr.store(&node.base); + stack.append(State { .Block = block }) catch unreachable; continue; }, - Token.Id.Keyword_cancel => { - const cancel_node = try self.createPrefixOp(arena, token, ast.NodePrefixOp.PrefixOp.Cancel); - dest_ptr.store(&cancel_node.base); - stack.append(State { .Expression = DestPtr { .Field = &cancel_node.rhs } }) catch unreachable; + Token.Id.Keyword_while => { + stack.append(State { + .While = LoopCtx { + .label = ctx.label, + .inline_token = null, + .loop_token = token, + .opt_ctx = ctx.opt_ctx.toRequired(), + } + }) catch unreachable; + continue; }, - Token.Id.Keyword_resume => { - const resume_node = try self.createPrefixOp(arena, token, ast.NodePrefixOp.PrefixOp.Resume); - dest_ptr.store(&resume_node.base); - stack.append(State { .Expression = DestPtr { .Field = &resume_node.rhs } }) catch unreachable; + Token.Id.Keyword_for => { + stack.append(State { + .For = LoopCtx { + .label = ctx.label, + .inline_token = null, + .loop_token = token, + .opt_ctx = ctx.opt_ctx.toRequired(), + } + }) catch unreachable; + continue; }, - Token.Id.Keyword_suspend => { - const node = try arena.create(ast.NodeSuspend); - *node = ast.NodeSuspend { - .base = self.initNode(ast.Node.Id.Suspend), - .suspend_token = token, - .payload = null, - .body = null, - }; - dest_ptr.store(&node.base); - stack.append(State { .SuspendBody = node }) catch unreachable; - try stack.append(State { .Payload = &node.payload }); + Token.Id.Keyword_inline => { + stack.append(State { + .Inline = InlineCtx { + .label = ctx.label, + .inline_token = token, + .opt_ctx = ctx.opt_ctx.toRequired(), + } + }) catch unreachable; continue; }, - Token.Id.Keyword_if => { - const node = try arena.create(ast.NodeIf); - *node = ast.NodeIf { - .base = self.initNode(ast.Node.Id.If), - .if_token = token, - .condition = undefined, - .payload = null, - .body = undefined, - .@"else" = null, - }; - dest_ptr.store(&node.base); + else => { + if (ctx.opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected 'while', 'for', 'inline' or '{{', found {}", @tagName(token.id)); + } - stack.append(State { .Else = &node.@"else" }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.body } }); - try stack.append(State { .PointerPayload = &node.payload }); - try stack.append(State { .ExpectToken = Token.Id.RParen }); - try stack.append(State { .Expression = DestPtr { .Field = &node.condition } }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); + self.putBackToken(token); continue; }, + } + }, + State.Inline => |ctx| { + const token = self.getNextToken(); + switch (token.id) { Token.Id.Keyword_while => { stack.append(State { .While = LoopCtx { - .label = null, - .inline_token = null, + .inline_token = ctx.inline_token, + .label = ctx.label, .loop_token = token, - .dest_ptr = dest_ptr, + .opt_ctx = ctx.opt_ctx.toRequired(), } }) catch unreachable; continue; @@ -836,181 +1104,602 @@ pub const Parser = struct { Token.Id.Keyword_for => { stack.append(State { .For = LoopCtx { - .label = null, - .inline_token = null, + .inline_token = ctx.inline_token, + .label = ctx.label, .loop_token = token, - .dest_ptr = dest_ptr, + .opt_ctx = ctx.opt_ctx.toRequired(), } }) catch unreachable; continue; }, - Token.Id.Keyword_switch => { - const node = try arena.create(ast.NodeSwitch); - *node = ast.NodeSwitch { - .base = self.initNode(ast.Node.Id.Switch), - .switch_token = token, - .expr = undefined, - .cases = ArrayList(&ast.NodeSwitchCase).init(arena), - .rbrace = undefined, - }; - dest_ptr.store(&node.base); + else => { + if (ctx.opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected 'while' or 'for', found {}", @tagName(token.id)); + } + self.putBackToken(token); + continue; + }, + } + }, + State.While => |ctx| { + const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeWhile, + ast.NodeWhile { + .base = undefined, + .label = ctx.label, + .inline_token = ctx.inline_token, + .while_token = ctx.loop_token, + .condition = undefined, + .payload = null, + .continue_expr = null, + .body = undefined, + .@"else" = null, + } + ); + stack.append(State { .Else = &node.@"else" }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.body } }); + try stack.append(State { .WhileContinueExpr = &node.continue_expr }); + try stack.append(State { .IfToken = Token.Id.Colon }); + try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } }); + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.condition } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + continue; + }, + State.WhileContinueExpr => |dest| { + stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable; + try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .RequiredNull = dest } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + continue; + }, + State.For => |ctx| { + const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeFor, + ast.NodeFor { + .base = undefined, + .label = ctx.label, + .inline_token = ctx.inline_token, + .for_token = ctx.loop_token, + .array_expr = undefined, + .payload = null, + .body = undefined, + .@"else" = null, + } + ); + stack.append(State { .Else = &node.@"else" }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.body } }); + try stack.append(State { .PointerIndexPayload = OptionalCtx { .Optional = &node.payload } }); + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.array_expr } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + continue; + }, + State.Else => |dest| { + if (self.eatToken(Token.Id.Keyword_else)) |else_token| { + const node = try self.createNode(arena, ast.NodeElse, + ast.NodeElse { + .base = undefined, + .else_token = else_token, + .payload = null, + .body = undefined, + } + ); + *dest = node; + + stack.append(State { .Expression = OptionalCtx { .Required = &node.body } }) catch unreachable; + try stack.append(State { .Payload = OptionalCtx { .Optional = &node.payload } }); + continue; + } else { + continue; + } + }, + + + State.Block => |block| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.RBrace => { + block.rbrace = token; + continue; + }, + else => { + self.putBackToken(token); + stack.append(State { .Block = block }) catch unreachable; + try stack.append(State { .Statement = block }); + continue; + }, + } + }, + State.Statement => |block| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_comptime => { stack.append(State { - .SwitchCaseOrEnd = ListSave(&ast.NodeSwitchCase) { - .list = &node.cases, - .ptr = &node.rbrace, - }, + .ComptimeStatement = ComptimeStatementCtx { + .comptime_token = token, + .block = block, + } }) catch unreachable; - try stack.append(State { .ExpectToken = Token.Id.LBrace }); - try stack.append(State { .ExpectToken = Token.Id.RParen }); - try stack.append(State { .Expression = DestPtr { .Field = &node.expr } }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); + continue; }, - Token.Id.Keyword_comptime => { - const node = try arena.create(ast.NodeComptime); - *node = ast.NodeComptime { - .base = self.initNode(ast.Node.Id.Comptime), - .comptime_token = token, - .expr = undefined, - }; - dest_ptr.store(&node.base); - try stack.append(State { .Expression = DestPtr { .Field = &node.expr } }); + Token.Id.Keyword_var, Token.Id.Keyword_const => { + stack.append(State { + .VarDecl = VarDeclCtx { + .visib_token = null, + .comptime_token = null, + .extern_export_token = null, + .lib_name = null, + .mut_token = token, + .list = &block.statements, + } + }) catch unreachable; + continue; + }, + Token.Id.Keyword_defer, Token.Id.Keyword_errdefer => { + const node = try self.createAttachNode(arena, &block.statements, ast.NodeDefer, + ast.NodeDefer { + .base = undefined, + .defer_token = token, + .kind = switch (token.id) { + Token.Id.Keyword_defer => ast.NodeDefer.Kind.Unconditional, + Token.Id.Keyword_errdefer => ast.NodeDefer.Kind.Error, + else => unreachable, + }, + .expr = undefined, + } + ); + stack.append(State { .Semicolon = &&node.base }) catch unreachable; + try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = &node.expr } }); continue; }, Token.Id.LBrace => { - const block = try self.createBlock(arena, (?Token)(null), token); - dest_ptr.store(&block.base); - - stack.append(State { .Block = block }) catch unreachable; + const inner_block = try self.createAttachNode(arena, &block.statements, ast.NodeBlock, + ast.NodeBlock { + .base = undefined, + .label = null, + .lbrace = token, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, + } + ); + stack.append(State { .Block = inner_block }) catch unreachable; continue; }, else => { self.putBackToken(token); - stack.append(State { .UnwrapExpressionBegin = dest_ptr }) catch unreachable; + const statememt = try block.statements.addOne(); + stack.append(State { .Semicolon = statememt }) catch unreachable; + try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = statememt } }); continue; } } }, - - State.RangeExpressionBegin => |dest_ptr| { - stack.append(State { .RangeExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .Expression = dest_ptr }); + State.ComptimeStatement => |ctx| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_var, Token.Id.Keyword_const => { + stack.append(State { + .VarDecl = VarDeclCtx { + .visib_token = null, + .comptime_token = ctx.comptime_token, + .extern_export_token = null, + .lib_name = null, + .mut_token = token, + .list = &ctx.block.statements, + } + }) catch unreachable; + continue; + }, + else => { + self.putBackToken(token); + self.putBackToken(ctx.comptime_token); + const statememt = try ctx.block.statements.addOne(); + stack.append(State { .Semicolon = statememt }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = statememt } }); + continue; + } + } + }, + State.Semicolon => |node_ptr| { + const node = *node_ptr; + if (requireSemiColon(node)) { + stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable; + continue; + } continue; }, - State.RangeExpressionEnd => |dest_ptr| { + + State.AsmOutputItems => |items| { + const lbracket = self.getNextToken(); + if (lbracket.id != Token.Id.LBracket) { + self.putBackToken(lbracket); + continue; + } + + const node = try self.createNode(arena, ast.NodeAsmOutput, + ast.NodeAsmOutput { + .base = undefined, + .symbolic_name = undefined, + .constraint = undefined, + .kind = undefined, + } + ); + try items.append(node); + + stack.append(State { .AsmOutputItems = items }) catch unreachable; + try stack.append(State { .IfToken = Token.Id.Comma }); + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .AsmOutputReturnOrType = node }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + try stack.append(State { .StringLiteral = OptionalCtx { .Required = &node.constraint } }); + try stack.append(State { .ExpectToken = Token.Id.RBracket }); + try stack.append(State { .Identifier = OptionalCtx { .Required = &node.symbolic_name } }); + continue; + }, + State.AsmOutputReturnOrType => |node| { const token = self.getNextToken(); - if (token.id == Token.Id.Ellipsis3) { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.Range); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + switch (token.id) { + Token.Id.Identifier => { + node.kind = ast.NodeAsmOutput.Kind { .Variable = try self.createLiteral(arena, ast.NodeIdentifier, token) }; + continue; + }, + Token.Id.Arrow => { + node.kind = ast.NodeAsmOutput.Kind { .Return = undefined }; + try stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &node.kind.Return } }); + continue; + }, + else => { + return self.parseError(token, "expected '->' or {}, found {}", + @tagName(Token.Id.Identifier), + @tagName(token.id)); + }, + } + }, + State.AsmInputItems => |items| { + const lbracket = self.getNextToken(); + if (lbracket.id != Token.Id.LBracket) { + self.putBackToken(lbracket); + continue; + } + + const node = try self.createNode(arena, ast.NodeAsmInput, + ast.NodeAsmInput { + .base = undefined, + .symbolic_name = undefined, + .constraint = undefined, + .expr = undefined, + } + ); + try items.append(node); + + stack.append(State { .AsmInputItems = items }) catch unreachable; + try stack.append(State { .IfToken = Token.Id.Comma }); + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + try stack.append(State { .StringLiteral = OptionalCtx { .Required = &node.constraint } }); + try stack.append(State { .ExpectToken = Token.Id.RBracket }); + try stack.append(State { .Identifier = OptionalCtx { .Required = &node.symbolic_name } }); + continue; + }, + State.AsmClopperItems => |items| { + stack.append(State { .AsmClopperItems = items }) catch unreachable; + try stack.append(State { .IfToken = Token.Id.Comma }); + try stack.append(State { .StringLiteral = OptionalCtx { .Required = try items.addOne() } }); + continue; + }, + - stack.append(State { .Expression = DestPtr { .Field = &node.rhs } }) catch unreachable; + State.ExprListItemOrEnd => |list_state| { + if (self.eatToken(list_state.end)) |token| { + *list_state.ptr = token; + continue; + } + + stack.append(State { .ExprListCommaOrEnd = list_state }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = try list_state.list.addOne() } }); + continue; + }, + State.ExprListCommaOrEnd => |list_state| { + if (try self.expectCommaOrEnd(list_state.end)) |end| { + *list_state.ptr = end; continue; } else { - self.putBackToken(token); + stack.append(State { .ExprListItemOrEnd = list_state }) catch unreachable; + continue; + } + }, + State.FieldInitListItemOrEnd => |list_state| { + if (self.eatToken(Token.Id.RBrace)) |rbrace| { + *list_state.ptr = rbrace; + continue; + } + + const node = try self.createNode(arena, ast.NodeFieldInitializer, + ast.NodeFieldInitializer { + .base = undefined, + .period_token = undefined, + .name_token = undefined, + .expr = undefined, + } + ); + try list_state.list.append(node); + + stack.append(State { .FieldInitListCommaOrEnd = list_state }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx{ .Required = &node.expr } }); + try stack.append(State { .ExpectToken = Token.Id.Equal }); + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Identifier, + .ptr = &node.name_token, + } + }); + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Period, + .ptr = &node.period_token, + } + }); + continue; + }, + State.FieldInitListCommaOrEnd => |list_state| { + if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { + *list_state.ptr = end; + continue; + } else { + stack.append(State { .FieldInitListItemOrEnd = list_state }) catch unreachable; + continue; + } + }, + State.FieldListCommaOrEnd => |container_decl| { + if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { + container_decl.rbrace_token = end; + continue; + } else { + stack.append(State { .ContainerDecl = container_decl }) catch unreachable; continue; } }, + State.IdentifierListItemOrEnd => |list_state| { + if (self.eatToken(Token.Id.RBrace)) |rbrace| { + *list_state.ptr = rbrace; + continue; + } - State.AssignmentExpressionBegin => |dest_ptr| { - stack.append(State { .AssignmentExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .Expression = dest_ptr }); + stack.append(State { .IdentifierListCommaOrEnd = list_state }) catch unreachable; + try stack.append(State { .Identifier = OptionalCtx { .Required = try list_state.list.addOne() } }); continue; }, + State.IdentifierListCommaOrEnd => |list_state| { + if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { + *list_state.ptr = end; + continue; + } else { + stack.append(State { .IdentifierListItemOrEnd = list_state }) catch unreachable; + continue; + } + }, + State.SwitchCaseOrEnd => |list_state| { + if (self.eatToken(Token.Id.RBrace)) |rbrace| { + *list_state.ptr = rbrace; + continue; + } - State.AssignmentExpressionEnd => |dest_ptr| { + const node = try self.createNode(arena, ast.NodeSwitchCase, + ast.NodeSwitchCase { + .base = undefined, + .items = ArrayList(&ast.Node).init(arena), + .payload = null, + .expr = undefined, + } + ); + try list_state.list.append(node); + stack.append(State { .SwitchCaseCommaOrEnd = list_state }) catch unreachable; + try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .Required = &node.expr } }); + try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } }); + try stack.append(State { .SwitchCaseFirstItem = &node.items }); + continue; + }, + State.SwitchCaseCommaOrEnd => |list_state| { + if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { + *list_state.ptr = end; + continue; + } else { + stack.append(State { .SwitchCaseOrEnd = list_state }) catch unreachable; + continue; + } + }, + State.SwitchCaseFirstItem => |case_items| { const token = self.getNextToken(); - if (tokenIdToAssignment(token.id)) |ass_id| { - const node = try self.createInfixOp(arena, token, ass_id); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); - - stack.append(State { .AssignmentExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.rhs } }); + if (token.id == Token.Id.Keyword_else) { + const else_node = try self.createAttachNode(arena, case_items, ast.NodeSwitchElse, + ast.NodeSwitchElse { + .base = undefined, + .token = token, + } + ); + try stack.append(State { .ExpectToken = Token.Id.EqualAngleBracketRight }); continue; } else { self.putBackToken(token); + try stack.append(State { .SwitchCaseItem = case_items }); continue; } }, + State.SwitchCaseItem => |case_items| { + stack.append(State { .SwitchCaseItemCommaOrEnd = case_items }) catch unreachable; + try stack.append(State { .RangeExpressionBegin = OptionalCtx { .Required = try case_items.addOne() } }); + }, + State.SwitchCaseItemCommaOrEnd => |case_items| { + if ((try self.expectCommaOrEnd(Token.Id.EqualAngleBracketRight)) == null) { + stack.append(State { .SwitchCaseItem = case_items }) catch unreachable; + } + continue; + }, + - State.UnwrapExpressionBegin => |dest_ptr| { - stack.append(State { .UnwrapExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BoolOrExpressionBegin = dest_ptr }); + State.SuspendBody => |suspend_node| { + if (suspend_node.payload != null) { + try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .RequiredNull = &suspend_node.body } }); + } continue; }, + State.AsyncAllocator => |async_node| { + if (self.eatToken(Token.Id.AngleBracketLeft) == null) { + continue; + } - State.UnwrapExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_catch => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp { .Catch = null }); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); - - stack.append(State { .UnwrapExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.rhs } }); - try stack.append(State { .Payload = &node.op.Catch }); + async_node.rangle_bracket = Token(undefined); + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.AngleBracketRight, + .ptr = &??async_node.rangle_bracket, + } + }); + try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &async_node.allocator_type } }); + continue; + }, + State.AsyncEnd => |ctx| { + const node = ctx.ctx.get() ?? continue; + + switch (node.id) { + ast.Node.Id.FnProto => { + const fn_proto = @fieldParentPtr(ast.NodeFnProto, "base", node); + fn_proto.async_attr = ctx.attribute; continue; }, - Token.Id.QuestionMarkQuestionMark => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.UnwrapMaybe); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + ast.Node.Id.SuffixOp => { + const suffix_op = @fieldParentPtr(ast.NodeSuffixOp, "base", node); + if (suffix_op.op == ast.NodeSuffixOp.SuffixOp.Call) { + suffix_op.op.Call.async_attr = ctx.attribute; + continue; + } - stack.append(State { .UnwrapExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.rhs } }); - continue; + return self.parseError(node.firstToken(), "expected {}, found {}.", + @tagName(ast.NodeSuffixOp.SuffixOp.Call), + @tagName(suffix_op.op)); }, else => { - self.putBackToken(token); - continue; - }, + return self.parseError(node.firstToken(), "expected {} or {}, found {}.", + @tagName(ast.NodeSuffixOp.SuffixOp.Call), + @tagName(ast.Node.Id.FnProto), + @tagName(node.id)); + } } }, - State.BoolOrExpressionBegin => |dest_ptr| { - stack.append(State { .BoolOrExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BoolAndExpressionBegin = dest_ptr }); + + State.ExternType => |ctx| { + if (self.eatToken(Token.Id.Keyword_fn)) |fn_token| { + const fn_proto = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeFnProto, + ast.NodeFnProto { + .base = undefined, + .visib_token = null, + .name_token = null, + .fn_token = fn_token, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = ctx.extern_token, + .cc_token = null, + .async_attr = null, + .body_node = null, + .lib_name = null, + .align_expr = null, + } + ); + stack.append(State { .FnProto = fn_proto }) catch unreachable; + continue; + } + + stack.append(State { + .ContainerKind = ContainerKindCtx { + .opt_ctx = ctx.opt_ctx, + .ltoken = ctx.extern_token, + .layout = ast.NodeContainerDecl.Layout.Extern, + }, + }) catch unreachable; continue; }, - - State.BoolOrExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); + State.SliceOrArrayAccess => |node| { + var token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_or => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.BoolOr); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + Token.Id.Ellipsis2 => { + const start = node.op.ArrayAccess; + node.op = ast.NodeSuffixOp.SuffixOp { + .Slice = ast.NodeSuffixOp.SliceRange { + .start = start, + .end = null, + } + }; - stack.append(State { .BoolOrExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BoolAndExpressionBegin = DestPtr { .Field = &node.rhs } }); + stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.RBracket, + .ptr = &node.rtoken, + } + }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Optional = &node.op.Slice.end } }); continue; }, - else => { - self.putBackToken(token); + Token.Id.RBracket => { + node.rtoken = token; continue; }, + else => { + return self.parseError(token, "expected ']' or '..', found {}", @tagName(token.id)); + } } }, + State.SliceOrArrayType => |node| { + if (self.eatToken(Token.Id.RBracket)) |_| { + node.op = ast.NodePrefixOp.PrefixOp { + .SliceType = ast.NodePrefixOp.AddrOfInfo { + .align_expr = null, + .bit_offset_start_token = null, + .bit_offset_end_token = null, + .const_token = null, + .volatile_token = null, + } + }; + stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &node.rhs } }) catch unreachable; + try stack.append(State { .AddrOfModifiers = &node.op.SliceType }); + continue; + } - State.BoolAndExpressionBegin => |dest_ptr| { - stack.append(State { .BoolAndExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .ComparisonExpressionBegin = dest_ptr }); + node.op = ast.NodePrefixOp.PrefixOp { .ArrayType = undefined }; + stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &node.rhs } }) catch unreachable; + try stack.append(State { .ExpectToken = Token.Id.RBracket }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.op.ArrayType } }); continue; }, - - State.BoolAndExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); + State.AddrOfModifiers => |addr_of_info| { + var token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_and => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.BoolAnd); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); - - stack.append(State { .BoolAndExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .ComparisonExpressionBegin = DestPtr { .Field = &node.rhs } }); + Token.Id.Keyword_align => { + stack.append(state) catch unreachable; + if (addr_of_info.align_expr != null) { + return self.parseError(token, "multiple align qualifiers"); + } + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .RequiredNull = &addr_of_info.align_expr} }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + continue; + }, + Token.Id.Keyword_const => { + stack.append(state) catch unreachable; + if (addr_of_info.const_token != null) { + return self.parseError(token, "duplicate qualifier: const"); + } + addr_of_info.const_token = token; + continue; + }, + Token.Id.Keyword_volatile => { + stack.append(state) catch unreachable; + if (addr_of_info.volatile_token != null) { + return self.parseError(token, "duplicate qualifier: volatile"); + } + addr_of_info.volatile_token = token; continue; }, else => { @@ -1020,118 +1709,336 @@ pub const Parser = struct { } }, - State.ComparisonExpressionBegin => |dest_ptr| { - stack.append(State { .ComparisonExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BinaryOrExpressionBegin = dest_ptr }); - continue; - }, - State.ComparisonExpressionEnd => |dest_ptr| { + State.Payload => |opt_ctx| { const token = self.getNextToken(); - if (tokenIdToComparison(token.id)) |comp_id| { - const node = try self.createInfixOp(arena, token, comp_id); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + if (token.id != Token.Id.Pipe) { + if (opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected {}, found {}.", + @tagName(Token.Id.Pipe), + @tagName(token.id)); + } - stack.append(State { .ComparisonExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BinaryOrExpressionBegin = DestPtr { .Field = &node.rhs } }); + self.putBackToken(token); continue; - } else { + } + + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodePayload, + ast.NodePayload { + .base = undefined, + .lpipe = token, + .error_symbol = undefined, + .rpipe = undefined + } + ); + + stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Pipe, + .ptr = &node.rpipe, + } + }) catch unreachable; + try stack.append(State { .Identifier = OptionalCtx { .Required = &node.error_symbol } }); + continue; + }, + State.PointerPayload => |opt_ctx| { + const token = self.getNextToken(); + if (token.id != Token.Id.Pipe) { + if (opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected {}, found {}.", + @tagName(Token.Id.Pipe), + @tagName(token.id)); + } + self.putBackToken(token); continue; } + + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodePointerPayload, + ast.NodePointerPayload { + .base = undefined, + .lpipe = token, + .ptr_token = null, + .value_symbol = undefined, + .rpipe = undefined + } + ); + + stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Pipe, + .ptr = &node.rpipe, + } + }) catch unreachable; + try stack.append(State { .Identifier = OptionalCtx { .Required = &node.value_symbol } }); + try stack.append(State { + .OptionalTokenSave = OptionalTokenSave { + .id = Token.Id.Asterisk, + .ptr = &node.ptr_token, + } + }); + continue; }, + State.PointerIndexPayload => |opt_ctx| { + const token = self.getNextToken(); + if (token.id != Token.Id.Pipe) { + if (opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected {}, found {}.", + @tagName(Token.Id.Pipe), + @tagName(token.id)); + } - State.BinaryOrExpressionBegin => |dest_ptr| { - stack.append(State { .BinaryOrExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BinaryXorExpressionBegin = dest_ptr }); + self.putBackToken(token); + continue; + } + + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodePointerIndexPayload, + ast.NodePointerIndexPayload { + .base = undefined, + .lpipe = token, + .ptr_token = null, + .value_symbol = undefined, + .index_symbol = null, + .rpipe = undefined + } + ); + + stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Pipe, + .ptr = &node.rpipe, + } + }) catch unreachable; + try stack.append(State { .Identifier = OptionalCtx { .RequiredNull = &node.index_symbol } }); + try stack.append(State { .IfToken = Token.Id.Comma }); + try stack.append(State { .Identifier = OptionalCtx { .Required = &node.value_symbol } }); + try stack.append(State { + .OptionalTokenSave = OptionalTokenSave { + .id = Token.Id.Asterisk, + .ptr = &node.ptr_token, + } + }); continue; }, - State.BinaryOrExpressionEnd => |dest_ptr| { + + State.Expression => |opt_ctx| { const token = self.getNextToken(); switch (token.id) { - Token.Id.Pipe => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.BitOr); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + Token.Id.Keyword_return, Token.Id.Keyword_break, Token.Id.Keyword_continue => { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeControlFlowExpression, + ast.NodeControlFlowExpression { + .base = undefined, + .ltoken = token, + .kind = undefined, + .rhs = null, + } + ); + + stack.append(State { .Expression = OptionalCtx { .Optional = &node.rhs } }) catch unreachable; - stack.append(State { .BinaryOrExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BinaryXorExpressionBegin = DestPtr { .Field = &node.rhs } }); + switch (token.id) { + Token.Id.Keyword_break => { + node.kind = ast.NodeControlFlowExpression.Kind { .Break = null }; + try stack.append(State { .Identifier = OptionalCtx { .RequiredNull = &node.kind.Break } }); + try stack.append(State { .IfToken = Token.Id.Colon }); + }, + Token.Id.Keyword_continue => { + node.kind = ast.NodeControlFlowExpression.Kind { .Continue = null }; + try stack.append(State { .Identifier = OptionalCtx { .RequiredNull = &node.kind.Continue } }); + try stack.append(State { .IfToken = Token.Id.Colon }); + }, + Token.Id.Keyword_return => { + node.kind = ast.NodeControlFlowExpression.Kind.Return; + }, + else => unreachable, + } continue; }, - else => { - self.putBackToken(token); + Token.Id.Keyword_try, Token.Id.Keyword_cancel, Token.Id.Keyword_resume => { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodePrefixOp, + ast.NodePrefixOp { + .base = undefined, + .op_token = token, + .op = switch (token.id) { + Token.Id.Keyword_try => ast.NodePrefixOp.PrefixOp { .Try = void{} }, + Token.Id.Keyword_cancel => ast.NodePrefixOp.PrefixOp { .Cancel = void{} }, + Token.Id.Keyword_resume => ast.NodePrefixOp.PrefixOp { .Resume = void{} }, + else => unreachable, + }, + .rhs = undefined, + } + ); + + stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } }) catch unreachable; continue; }, + else => { + if (!try self.parseBlockExpr(&stack, arena, opt_ctx, token)) { + self.putBackToken(token); + stack.append(State { .UnwrapExpressionBegin = opt_ctx }) catch unreachable; + } + continue; + } } }, + State.RangeExpressionBegin => |opt_ctx| { + stack.append(State { .RangeExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .Expression = opt_ctx }); + continue; + }, + State.RangeExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Ellipsis3)) |ellipsis3| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = ellipsis3, + .op = ast.NodeInfixOp.InfixOp.Range, + .rhs = undefined, + } + ); + stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } }) catch unreachable; + continue; + } + }, + State.AssignmentExpressionBegin => |opt_ctx| { + stack.append(State { .AssignmentExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .Expression = opt_ctx }); + continue; + }, - State.BinaryXorExpressionBegin => |dest_ptr| { - stack.append(State { .BinaryXorExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BinaryAndExpressionBegin = dest_ptr }); + State.AssignmentExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + const token = self.getNextToken(); + if (tokenIdToAssignment(token.id)) |ass_id| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = ass_id, + .rhs = undefined, + } + ); + stack.append(State { .AssignmentExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } }); + continue; + } else { + self.putBackToken(token); + continue; + } + }, + + State.UnwrapExpressionBegin => |opt_ctx| { + stack.append(State { .UnwrapExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .BoolOrExpressionBegin = opt_ctx }); continue; }, - State.BinaryXorExpressionEnd => |dest_ptr| { + State.UnwrapExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + const token = self.getNextToken(); - switch (token.id) { - Token.Id.Caret => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.BitXor); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + if (tokenIdToUnwrapExpr(token.id)) |unwrap_id| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = unwrap_id, + .rhs = undefined, + } + ); - stack.append(State { .BinaryXorExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BinaryAndExpressionBegin = DestPtr { .Field = &node.rhs } }); - continue; - }, - else => { - self.putBackToken(token); - continue; - }, + stack.append(State { .UnwrapExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } }); + + if (node.op == ast.NodeInfixOp.InfixOp.Catch) { + try stack.append(State { .Payload = OptionalCtx { .Optional = &node.op.Catch } }); + } + continue; + } else { + self.putBackToken(token); + continue; } }, - State.BinaryAndExpressionBegin => |dest_ptr| { - stack.append(State { .BinaryAndExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BitShiftExpressionBegin = dest_ptr }); + State.BoolOrExpressionBegin => |opt_ctx| { + stack.append(State { .BoolOrExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .BoolAndExpressionBegin = opt_ctx }); continue; }, - State.BinaryAndExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Ampersand => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.BitAnd); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.BoolOrExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Keyword_or)) |or_token| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = or_token, + .op = ast.NodeInfixOp.InfixOp.BoolOr, + .rhs = undefined, + } + ); + stack.append(State { .BoolOrExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .BoolAndExpressionBegin = OptionalCtx { .Required = &node.rhs } }); + continue; + } + }, - stack.append(State { .BinaryAndExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .BitShiftExpressionBegin = DestPtr { .Field = &node.rhs } }); - continue; - }, - else => { - self.putBackToken(token); - continue; - }, + State.BoolAndExpressionBegin => |opt_ctx| { + stack.append(State { .BoolAndExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .ComparisonExpressionBegin = opt_ctx }); + continue; + }, + + State.BoolAndExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Keyword_and)) |and_token| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = and_token, + .op = ast.NodeInfixOp.InfixOp.BoolAnd, + .rhs = undefined, + } + ); + stack.append(State { .BoolAndExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .ComparisonExpressionBegin = OptionalCtx { .Required = &node.rhs } }); + continue; } }, - State.BitShiftExpressionBegin => |dest_ptr| { - stack.append(State { .BitShiftExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .AdditionExpressionBegin = dest_ptr }); + State.ComparisonExpressionBegin => |opt_ctx| { + stack.append(State { .ComparisonExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .BinaryOrExpressionBegin = opt_ctx }); continue; }, - State.BitShiftExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); - if (tokenIdToBitShift(token.id)) |bitshift_id| { - const node = try self.createInfixOp(arena, token, bitshift_id); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.ComparisonExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; - stack.append(State { .BitShiftExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .AdditionExpressionBegin = DestPtr { .Field = &node.rhs } }); + const token = self.getNextToken(); + if (tokenIdToComparison(token.id)) |comp_id| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = comp_id, + .rhs = undefined, + } + ); + stack.append(State { .ComparisonExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .BinaryOrExpressionBegin = OptionalCtx { .Required = &node.rhs } }); continue; } else { self.putBackToken(token); @@ -1139,21 +2046,103 @@ pub const Parser = struct { } }, - State.AdditionExpressionBegin => |dest_ptr| { - stack.append(State { .AdditionExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .MultiplyExpressionBegin = dest_ptr }); + State.BinaryOrExpressionBegin => |opt_ctx| { + stack.append(State { .BinaryOrExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .BinaryXorExpressionBegin = opt_ctx }); continue; }, - State.AdditionExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); - if (tokenIdToAddition(token.id)) |add_id| { - const node = try self.createInfixOp(arena, token, add_id); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.BinaryOrExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Pipe)) |pipe| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = pipe, + .op = ast.NodeInfixOp.InfixOp.BitOr, + .rhs = undefined, + } + ); + stack.append(State { .BinaryOrExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .BinaryXorExpressionBegin = OptionalCtx { .Required = &node.rhs } }); + continue; + } + }, + + State.BinaryXorExpressionBegin => |opt_ctx| { + stack.append(State { .BinaryXorExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .BinaryAndExpressionBegin = opt_ctx }); + continue; + }, + + State.BinaryXorExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Caret)) |caret| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = caret, + .op = ast.NodeInfixOp.InfixOp.BitXor, + .rhs = undefined, + } + ); + stack.append(State { .BinaryXorExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .BinaryAndExpressionBegin = OptionalCtx { .Required = &node.rhs } }); + continue; + } + }, + + State.BinaryAndExpressionBegin => |opt_ctx| { + stack.append(State { .BinaryAndExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .BitShiftExpressionBegin = opt_ctx }); + continue; + }, - stack.append(State { .AdditionExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .MultiplyExpressionBegin = DestPtr { .Field = &node.rhs } }); + State.BinaryAndExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Ampersand)) |ampersand| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = ampersand, + .op = ast.NodeInfixOp.InfixOp.BitAnd, + .rhs = undefined, + } + ); + stack.append(State { .BinaryAndExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .BitShiftExpressionBegin = OptionalCtx { .Required = &node.rhs } }); + continue; + } + }, + + State.BitShiftExpressionBegin => |opt_ctx| { + stack.append(State { .BitShiftExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .AdditionExpressionBegin = opt_ctx }); + continue; + }, + + State.BitShiftExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + const token = self.getNextToken(); + if (tokenIdToBitShift(token.id)) |bitshift_id| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = bitshift_id, + .rhs = undefined, + } + ); + stack.append(State { .BitShiftExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .AdditionExpressionBegin = OptionalCtx { .Required = &node.rhs } }); continue; } else { self.putBackToken(token); @@ -1161,21 +2150,28 @@ pub const Parser = struct { } }, - State.MultiplyExpressionBegin => |dest_ptr| { - stack.append(State { .MultiplyExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .CurlySuffixExpressionBegin = dest_ptr }); + State.AdditionExpressionBegin => |opt_ctx| { + stack.append(State { .AdditionExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .MultiplyExpressionBegin = opt_ctx }); continue; }, - State.MultiplyExpressionEnd => |dest_ptr| { - const token = self.getNextToken(); - if (tokenIdToMultiply(token.id)) |mult_id| { - const node = try self.createInfixOp(arena, token, mult_id); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.AdditionExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; - stack.append(State { .MultiplyExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .CurlySuffixExpressionBegin = DestPtr { .Field = &node.rhs } }); + const token = self.getNextToken(); + if (tokenIdToAddition(token.id)) |add_id| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = add_id, + .rhs = undefined, + } + ); + stack.append(State { .AdditionExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .MultiplyExpressionBegin = OptionalCtx { .Required = &node.rhs } }); continue; } else { self.putBackToken(token); @@ -1183,162 +2179,199 @@ pub const Parser = struct { } }, - State.CurlySuffixExpressionBegin => |dest_ptr| { - stack.append(State { .CurlySuffixExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .TypeExprBegin = dest_ptr }); + State.MultiplyExpressionBegin => |opt_ctx| { + stack.append(State { .MultiplyExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .CurlySuffixExpressionBegin = opt_ctx }); continue; }, - State.CurlySuffixExpressionEnd => |dest_ptr| { + State.MultiplyExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + const token = self.getNextToken(); - if (token.id != Token.Id.LBrace) { + if (tokenIdToMultiply(token.id)) |mult_id| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = mult_id, + .rhs = undefined, + } + ); + stack.append(State { .MultiplyExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .CurlySuffixExpressionBegin = OptionalCtx { .Required = &node.rhs } }); + continue; + } else { self.putBackToken(token); continue; } + }, - const next = self.getNextToken(); - switch (next.id) { - Token.Id.Period => { - const node = try self.createSuffixOp(arena, ast.NodeSuffixOp.SuffixOp { - .StructInitializer = ArrayList(&ast.NodeFieldInitializer).init(arena), - }); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.CurlySuffixExpressionBegin => |opt_ctx| { + stack.append(State { .CurlySuffixExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .IfToken = Token.Id.LBrace }); + try stack.append(State { .TypeExprBegin = opt_ctx }); + continue; + }, - stack.append(State { .CurlySuffixExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { - .FieldInitListItemOrEnd = ListSave(&ast.NodeFieldInitializer) { - .list = &node.op.StructInitializer, - .ptr = &node.rtoken, - } - }); - self.putBackToken(next); - continue; - }, - else => { - const node = try self.createSuffixOp(arena, ast.NodeSuffixOp.SuffixOp { - .ArrayInitializer = ArrayList(&ast.Node).init(arena), - }); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.CurlySuffixExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; - stack.append(State { .CurlySuffixExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { - .ExprListItemOrEnd = ExprListCtx { - .list = &node.op.ArrayInitializer, - .end = Token.Id.RBrace, - .ptr = &node.rtoken, - } - }); - self.putBackToken(next); - continue; - }, + if (self.isPeekToken(Token.Id.Period)) { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeSuffixOp, + ast.NodeSuffixOp { + .base = undefined, + .lhs = lhs, + .op = ast.NodeSuffixOp.SuffixOp { + .StructInitializer = ArrayList(&ast.NodeFieldInitializer).init(arena), + }, + .rtoken = undefined, + } + ); + stack.append(State { .CurlySuffixExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .IfToken = Token.Id.LBrace }); + try stack.append(State { + .FieldInitListItemOrEnd = ListSave(&ast.NodeFieldInitializer) { + .list = &node.op.StructInitializer, + .ptr = &node.rtoken, + } + }); + continue; } - }, - State.TypeExprBegin => |dest_ptr| { - stack.append(State { .TypeExprEnd = dest_ptr }) catch unreachable; - try stack.append(State { .PrefixOpExpression = dest_ptr }); + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeSuffixOp, + ast.NodeSuffixOp { + .base = undefined, + .lhs = lhs, + .op = ast.NodeSuffixOp.SuffixOp { + .ArrayInitializer = ArrayList(&ast.Node).init(arena), + }, + .rtoken = undefined, + } + ); + stack.append(State { .CurlySuffixExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .IfToken = Token.Id.LBrace }); + try stack.append(State { + .ExprListItemOrEnd = ExprListCtx { + .list = &node.op.ArrayInitializer, + .end = Token.Id.RBrace, + .ptr = &node.rtoken, + } + }); continue; }, - State.TypeExprEnd => |dest_ptr| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Bang => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.ErrorUnion); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); + State.TypeExprBegin => |opt_ctx| { + stack.append(State { .TypeExprEnd = opt_ctx }) catch unreachable; + try stack.append(State { .PrefixOpExpression = opt_ctx }); + continue; + }, - stack.append(State { .TypeExprEnd = dest_ptr }) catch unreachable; - try stack.append(State { .PrefixOpExpression = DestPtr { .Field = &node.rhs } }); - continue; - }, - else => { - self.putBackToken(token); - continue; - }, + State.TypeExprEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + + if (self.eatToken(Token.Id.Bang)) |bang| { + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = bang, + .op = ast.NodeInfixOp.InfixOp.ErrorUnion, + .rhs = undefined, + } + ); + stack.append(State { .TypeExprEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .PrefixOpExpression = OptionalCtx { .Required = &node.rhs } }); + continue; } }, - State.PrefixOpExpression => |dest_ptr| { + State.PrefixOpExpression => |opt_ctx| { const token = self.getNextToken(); if (tokenIdToPrefixOp(token.id)) |prefix_id| { - const node = try self.createPrefixOp(arena, token, prefix_id); - dest_ptr.store(&node.base); + var node = try self.createToCtxNode(arena, opt_ctx, ast.NodePrefixOp, + ast.NodePrefixOp { + .base = undefined, + .op_token = token, + .op = prefix_id, + .rhs = undefined, + } + ); + + // Treat '**' token as two derefs + if (token.id == Token.Id.AsteriskAsterisk) { + const child = try self.createNode(arena, ast.NodePrefixOp, + ast.NodePrefixOp { + .base = undefined, + .op_token = token, + .op = prefix_id, + .rhs = undefined, + } + ); + node.rhs = &child.base; + node = child; + } - stack.append(State { .TypeExprBegin = DestPtr { .Field = &node.rhs } }) catch unreachable; + stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &node.rhs } }) catch unreachable; if (node.op == ast.NodePrefixOp.PrefixOp.AddrOf) { try stack.append(State { .AddrOfModifiers = &node.op.AddrOf }); } continue; } else { self.putBackToken(token); - stack.append(State { .SuffixOpExpressionBegin = dest_ptr }) catch unreachable; + stack.append(State { .SuffixOpExpressionBegin = opt_ctx }) catch unreachable; continue; } }, - State.SuffixOpExpressionBegin => |dest_ptr| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_async => { - const async_node = try arena.create(ast.NodeAsyncAttribute); - *async_node = ast.NodeAsyncAttribute { - .base = self.initNode(ast.Node.Id.AsyncAttribute), - .async_token = token, + State.SuffixOpExpressionBegin => |opt_ctx| { + if (self.eatToken(Token.Id.Keyword_async)) |async_token| { + const async_node = try self.createNode(arena, ast.NodeAsyncAttribute, + ast.NodeAsyncAttribute { + .base = undefined, + .async_token = async_token, .allocator_type = null, .rangle_bracket = null, - }; - - stack.append(State { - .AsyncEnd = AsyncEndCtx { - .dest_ptr = dest_ptr, - .attribute = async_node, - } - }) catch unreachable; - try stack.append(State { .SuffixOpExpressionEnd = dest_ptr }); - try stack.append(State { .PrimaryExpression = dest_ptr }); - - const langle_bracket = self.getNextToken(); - if (langle_bracket.id != Token.Id.AngleBracketLeft) { - self.putBackToken(langle_bracket); - continue; } - - async_node.rangle_bracket = Token(undefined); - try stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.AngleBracketRight, - .ptr = &??async_node.rangle_bracket, - } - }); - try stack.append(State { .TypeExprBegin = DestPtr { .NullableField = &async_node.allocator_type } }); - continue; - }, - else => { - self.putBackToken(token); - stack.append(State { .SuffixOpExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .PrimaryExpression = dest_ptr }); - continue; - } + ); + stack.append(State { + .AsyncEnd = AsyncEndCtx { + .ctx = opt_ctx, + .attribute = async_node, + } + }) catch unreachable; + try stack.append(State { .SuffixOpExpressionEnd = opt_ctx.toRequired() }); + try stack.append(State { .PrimaryExpression = opt_ctx.toRequired() }); + try stack.append(State { .AsyncAllocator = async_node }); + continue; } + + stack.append(State { .SuffixOpExpressionEnd = opt_ctx }) catch unreachable; + try stack.append(State { .PrimaryExpression = opt_ctx }); + continue; }, - State.SuffixOpExpressionEnd => |dest_ptr| { + State.SuffixOpExpressionEnd => |opt_ctx| { + const lhs = opt_ctx.get() ?? continue; + const token = self.getNextToken(); switch (token.id) { Token.Id.LParen => { - const node = try self.createSuffixOp(arena, ast.NodeSuffixOp.SuffixOp { - .Call = ast.NodeSuffixOp.CallInfo { - .params = ArrayList(&ast.Node).init(arena), - .async_attr = null, + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeSuffixOp, + ast.NodeSuffixOp { + .base = undefined, + .lhs = lhs, + .op = ast.NodeSuffixOp.SuffixOp { + .Call = ast.NodeSuffixOp.CallInfo { + .params = ArrayList(&ast.Node).init(arena), + .async_attr = null, + } + }, + .rtoken = undefined, } - }); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); - - stack.append(State { .SuffixOpExpressionEnd = dest_ptr }) catch unreachable; + ); + stack.append(State { .SuffixOpExpressionEnd = opt_ctx.toRequired() }) catch unreachable; try stack.append(State { .ExprListItemOrEnd = ExprListCtx { .list = &node.op.Call.params, @@ -1349,30 +2382,33 @@ pub const Parser = struct { continue; }, Token.Id.LBracket => { - const node = try arena.create(ast.NodeSuffixOp); - *node = ast.NodeSuffixOp { - .base = self.initNode(ast.Node.Id.SuffixOp), - .lhs = undefined, - .op = ast.NodeSuffixOp.SuffixOp { - .ArrayAccess = undefined, - }, - .rtoken = undefined, - }; - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); - - stack.append(State { .SuffixOpExpressionEnd = dest_ptr }) catch unreachable; + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeSuffixOp, + ast.NodeSuffixOp { + .base = undefined, + .lhs = lhs, + .op = ast.NodeSuffixOp.SuffixOp { + .ArrayAccess = undefined, + }, + .rtoken = undefined + } + ); + stack.append(State { .SuffixOpExpressionEnd = opt_ctx.toRequired() }) catch unreachable; try stack.append(State { .SliceOrArrayAccess = node }); - try stack.append(State { .Expression = DestPtr { .Field = &node.op.ArrayAccess }}); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.op.ArrayAccess }}); continue; }, Token.Id.Period => { - const node = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.Period); - node.lhs = dest_ptr.get(); - dest_ptr.store(&node.base); - - stack.append(State { .SuffixOpExpressionEnd = dest_ptr }) catch unreachable; - try stack.append(State { .SuffixOpExpressionBegin = DestPtr { .Field = &node.rhs }}); + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp, + ast.NodeInfixOp { + .base = undefined, + .lhs = lhs, + .op_token = token, + .op = ast.NodeInfixOp.InfixOp.Period, + .rhs = undefined, + } + ); + stack.append(State { .SuffixOpExpressionEnd = opt_ctx.toRequired() }) catch unreachable; + try stack.append(State { .Identifier = OptionalCtx { .Required = &node.rhs } }); continue; }, else => { @@ -1382,125 +2418,76 @@ pub const Parser = struct { } }, - State.PrimaryExpression => |dest_ptr| { + State.PrimaryExpression => |opt_ctx| { const token = self.getNextToken(); switch (token.id) { Token.Id.IntegerLiteral => { - dest_ptr.store(&(try self.createIntegerLiteral(arena, token)).base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeStringLiteral, token); continue; }, Token.Id.FloatLiteral => { - dest_ptr.store(&(try self.createFloatLiteral(arena, token)).base); - continue; - }, - Token.Id.StringLiteral => { - dest_ptr.store(&(try self.createStringLiteral(arena, token)).base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeFloatLiteral, token); continue; }, Token.Id.CharLiteral => { - const node = try arena.create(ast.NodeCharLiteral); - *node = ast.NodeCharLiteral { - .base = self.initNode(ast.Node.Id.CharLiteral), - .token = token, - }; - dest_ptr.store(&node.base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeCharLiteral, token); continue; }, Token.Id.Keyword_undefined => { - dest_ptr.store(&(try self.createUndefined(arena, token)).base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeUndefinedLiteral, token); continue; }, Token.Id.Keyword_true, Token.Id.Keyword_false => { - const node = try arena.create(ast.NodeBoolLiteral); - *node = ast.NodeBoolLiteral { - .base = self.initNode(ast.Node.Id.BoolLiteral), - .token = token, - }; - dest_ptr.store(&node.base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeBoolLiteral, token); continue; }, Token.Id.Keyword_null => { - const node = try arena.create(ast.NodeNullLiteral); - *node = ast.NodeNullLiteral { - .base = self.initNode(ast.Node.Id.NullLiteral), - .token = token, - }; - dest_ptr.store(&node.base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeNullLiteral, token); continue; }, Token.Id.Keyword_this => { - const node = try arena.create(ast.NodeThisLiteral); - *node = ast.NodeThisLiteral { - .base = self.initNode(ast.Node.Id.ThisLiteral), - .token = token, - }; - dest_ptr.store(&node.base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeThisLiteral, token); continue; }, Token.Id.Keyword_var => { - const node = try arena.create(ast.NodeVarType); - *node = ast.NodeVarType { - .base = self.initNode(ast.Node.Id.VarType), - .token = token, - }; - dest_ptr.store(&node.base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeVarType, token); + continue; }, Token.Id.Keyword_unreachable => { - const node = try arena.create(ast.NodeUnreachable); - *node = ast.NodeUnreachable { - .base = self.initNode(ast.Node.Id.Unreachable), - .token = token, - }; - dest_ptr.store(&node.base); + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeUnreachable, token); continue; }, - Token.Id.MultilineStringLiteralLine => { - const node = try arena.create(ast.NodeMultilineStringLiteral); - *node = ast.NodeMultilineStringLiteral { - .base = self.initNode(ast.Node.Id.MultilineStringLiteral), - .tokens = ArrayList(Token).init(arena), - }; - dest_ptr.store(&node.base); - try node.tokens.append(token); - - while (true) { - const multiline_str = self.getNextToken(); - if (multiline_str.id != Token.Id.MultilineStringLiteralLine) { - self.putBackToken(multiline_str); - break; - } - - try node.tokens.append(multiline_str); - } + Token.Id.StringLiteral, Token.Id.MultilineStringLiteralLine => { + opt_ctx.store((try self.parseStringLiteral(arena, token)) ?? unreachable); continue; }, Token.Id.LParen => { - const node = try arena.create(ast.NodeGroupedExpression); - *node = ast.NodeGroupedExpression { - .base = self.initNode(ast.Node.Id.GroupedExpression), - .lparen = token, - .expr = undefined, - .rparen = undefined, - }; - dest_ptr.store(&node.base); + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeGroupedExpression, + ast.NodeGroupedExpression { + .base = undefined, + .lparen = token, + .expr = undefined, + .rparen = undefined, + } + ); stack.append(State { .ExpectTokenSave = ExpectTokenSave { .id = Token.Id.RParen, .ptr = &node.rparen, } }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.expr } }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); continue; }, Token.Id.Builtin => { - const node = try arena.create(ast.NodeBuiltinCall); - *node = ast.NodeBuiltinCall { - .base = self.initNode(ast.Node.Id.BuiltinCall), - .builtin_token = token, - .params = ArrayList(&ast.Node).init(arena), - .rparen_token = undefined, - }; - dest_ptr.store(&node.base); + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeBuiltinCall, + ast.NodeBuiltinCall { + .base = undefined, + .builtin_token = token, + .params = ArrayList(&ast.Node).init(arena), + .rparen_token = undefined, + } + ); stack.append(State { .ExprListItemOrEnd = ExprListCtx { .list = &node.params, @@ -1512,195 +2499,127 @@ pub const Parser = struct { continue; }, Token.Id.LBracket => { - const rbracket_token = self.getNextToken(); - if (rbracket_token.id == Token.Id.RBracket) { - const node = try self.createPrefixOp(arena, token, ast.NodePrefixOp.PrefixOp{ - .SliceType = ast.NodePrefixOp.AddrOfInfo { - .align_expr = null, - .bit_offset_start_token = null, - .bit_offset_end_token = null, - .const_token = null, - .volatile_token = null, - } - }); - dest_ptr.store(&node.base); - stack.append(State { .TypeExprBegin = DestPtr { .Field = &node.rhs } }) catch unreachable; - try stack.append(State { .AddrOfModifiers = &node.op.SliceType }); - continue; - } - - self.putBackToken(rbracket_token); - - const node = try self.createPrefixOp(arena, token, ast.NodePrefixOp.PrefixOp{ - .ArrayType = undefined, - }); - dest_ptr.store(&node.base); - stack.append(State { .TypeExprBegin = DestPtr { .Field = &node.rhs } }) catch unreachable; - try stack.append(State { .ExpectToken = Token.Id.RBracket }); - try stack.append(State { .Expression = DestPtr { .Field = &node.op.ArrayType } }); - + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodePrefixOp, + ast.NodePrefixOp { + .base = undefined, + .op_token = token, + .op = undefined, + .rhs = undefined, + } + ); + stack.append(State { .SliceOrArrayType = node }) catch unreachable; + continue; }, Token.Id.Keyword_error => { - const next = self.getNextToken(); - - if (next.id != Token.Id.LBrace) { - self.putBackToken(next); - const node = try arena.create(ast.NodeErrorType); - *node = ast.NodeErrorType { - .base = self.initNode(ast.Node.Id.ErrorType), - .token = token, - }; - dest_ptr.store(&node.base); - continue; - } - - const node = try arena.create(ast.NodeErrorSetDecl); - *node = ast.NodeErrorSetDecl { - .base = self.initNode(ast.Node.Id.ErrorSetDecl), - .error_token = token, - .decls = ArrayList(&ast.NodeIdentifier).init(arena), - .rbrace_token = undefined, - }; - dest_ptr.store(&node.base); - - while (true) { - const t = self.getNextToken(); - switch (t.id) { - Token.Id.RBrace => { - node.rbrace_token = t; - break; - }, - Token.Id.Identifier => { - try node.decls.append( - try self.createIdentifier(arena, t) - ); - }, - else => { - try self.parseError(&stack, token, "expected {} or {}, found {}", - @tagName(Token.Id.RBrace), - @tagName(Token.Id.Identifier), - @tagName(token.id)); - continue; - } - } - - const t2 = self.getNextToken(); - switch (t2.id) { - Token.Id.RBrace => { - node.rbrace_token = t; - break; - }, - Token.Id.Comma => continue, - else => { - try self.parseError(&stack, token, "expected {} or {}, found {}", - @tagName(Token.Id.RBrace), - @tagName(Token.Id.Comma), - @tagName(token.id)); - continue; - } + stack.append(State { + .ErrorTypeOrSetDecl = ErrorTypeOrSetDeclCtx { + .error_token = token, + .opt_ctx = opt_ctx } - } + }) catch unreachable; continue; }, Token.Id.Keyword_packed => { stack.append(State { - .ContainerExtern = ContainerExternCtx { - .dest_ptr = dest_ptr, + .ContainerKind = ContainerKindCtx { + .opt_ctx = opt_ctx, .ltoken = token, .layout = ast.NodeContainerDecl.Layout.Packed, }, }) catch unreachable; + continue; }, Token.Id.Keyword_extern => { - const next = self.getNextToken(); - if (next.id == Token.Id.Keyword_fn) { - // TODO shouldn't need this cast - const fn_proto = try self.createFnProto(arena, next, - (?Token)(token), (?&ast.Node)(null), (?Token)(null), (?Token)(null), (?Token)(null)); - dest_ptr.store(&fn_proto.base); - stack.append(State { .FnProto = fn_proto }) catch unreachable; - continue; - } - - self.putBackToken(next); stack.append(State { - .ContainerExtern = ContainerExternCtx { - .dest_ptr = dest_ptr, - .ltoken = token, - .layout = ast.NodeContainerDecl.Layout.Extern, + .ExternType = ExternTypeCtx { + .opt_ctx = opt_ctx, + .extern_token = token, }, }) catch unreachable; + continue; }, Token.Id.Keyword_struct, Token.Id.Keyword_union, Token.Id.Keyword_enum => { self.putBackToken(token); stack.append(State { - .ContainerExtern = ContainerExternCtx { - .dest_ptr = dest_ptr, + .ContainerKind = ContainerKindCtx { + .opt_ctx = opt_ctx, .ltoken = token, .layout = ast.NodeContainerDecl.Layout.Auto, }, }) catch unreachable; + continue; }, Token.Id.Identifier => { - const next = self.getNextToken(); - if (next.id != Token.Id.Colon) { - self.putBackToken(next); - dest_ptr.store(&(try self.createIdentifier(arena, token)).base); - continue; - } - stack.append(State { - .LabeledExpression = LabelCtx { + .MaybeLabeledExpression = MaybeLabeledExpressionCtx { .label = token, - .dest_ptr = dest_ptr + .opt_ctx = opt_ctx } }) catch unreachable; continue; }, Token.Id.Keyword_fn => { - // TODO shouldn't need these casts - const fn_proto = try self.createFnProto(arena, token, - (?Token)(null), (?&ast.Node)(null), (?Token)(null), (?Token)(null), (?Token)(null)); - dest_ptr.store(&fn_proto.base); + const fn_proto = try self.createToCtxNode(arena, opt_ctx, ast.NodeFnProto, + ast.NodeFnProto { + .base = undefined, + .visib_token = null, + .name_token = null, + .fn_token = token, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = null, + .cc_token = null, + .async_attr = null, + .body_node = null, + .lib_name = null, + .align_expr = null, + } + ); stack.append(State { .FnProto = fn_proto }) catch unreachable; continue; }, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { - const fn_token = (try self.eatToken(&stack, Token.Id.Keyword_fn)) ?? continue; - // TODO shouldn't need this cast - const fn_proto = try self.createFnProto(arena, fn_token, - (?Token)(null), (?&ast.Node)(null), (?Token)(token), (?Token)(null), (?Token)(null)); - dest_ptr.store(&fn_proto.base); + const fn_proto = try self.createToCtxNode(arena, opt_ctx, ast.NodeFnProto, + ast.NodeFnProto { + .base = undefined, + .visib_token = null, + .name_token = null, + .fn_token = undefined, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = null, + .cc_token = token, + .async_attr = null, + .body_node = null, + .lib_name = null, + .align_expr = null, + } + ); stack.append(State { .FnProto = fn_proto }) catch unreachable; + try stack.append(State { + .ExpectTokenSave = ExpectTokenSave { + .id = Token.Id.Keyword_fn, + .ptr = &fn_proto.fn_token + } + }); continue; }, Token.Id.Keyword_asm => { - const is_volatile = blk: { - const volatile_token = self.getNextToken(); - if (volatile_token.id != Token.Id.Keyword_volatile) { - self.putBackToken(volatile_token); - break :blk false; + const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeAsm, + ast.NodeAsm { + .base = undefined, + .asm_token = token, + .volatile_token = null, + .template = undefined, + //.tokens = ArrayList(ast.NodeAsm.AsmToken).init(arena), + .outputs = ArrayList(&ast.NodeAsmOutput).init(arena), + .inputs = ArrayList(&ast.NodeAsmInput).init(arena), + .cloppers = ArrayList(&ast.Node).init(arena), + .rparen = undefined, } - break :blk true; - }; - _ = (try self.eatToken(&stack, Token.Id.LParen)) ?? continue; - const template = (try self.eatToken(&stack, Token.Id.StringLiteral)) ?? continue; - // TODO parse template - - const node = try arena.create(ast.NodeAsm); - *node = ast.NodeAsm { - .base = self.initNode(ast.Node.Id.Asm), - .asm_token = token, - .is_volatile = is_volatile, - .template = template, - //.tokens = ArrayList(ast.NodeAsm.AsmToken).init(arena), - .outputs = ArrayList(&ast.NodeAsmOutput).init(arena), - .inputs = ArrayList(&ast.NodeAsmInput).init(arena), - .cloppers = ArrayList(&ast.NodeStringLiteral).init(arena), - .rparen = undefined, - }; - dest_ptr.store(&node.base); - + ); stack.append(State { .ExpectTokenSave = ExpectTokenSave { .id = Token.Id.RParen, @@ -1713,793 +2632,120 @@ pub const Parser = struct { try stack.append(State { .IfToken = Token.Id.Colon }); try stack.append(State { .AsmOutputItems = &node.outputs }); try stack.append(State { .IfToken = Token.Id.Colon }); + try stack.append(State { .StringLiteral = OptionalCtx { .Required = &node.template } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + try stack.append(State { + .OptionalTokenSave = OptionalTokenSave { + .id = Token.Id.Keyword_volatile, + .ptr = &node.volatile_token, + } + }); }, Token.Id.Keyword_inline => { stack.append(State { .Inline = InlineCtx { .label = null, .inline_token = token, - .dest_ptr = dest_ptr, + .opt_ctx = opt_ctx, } }) catch unreachable; continue; }, else => { - try self.parseError(&stack, token, "expected primary expression, found {}", @tagName(token.id)); - continue; - } - } - }, - - State.SliceOrArrayAccess => |node| { - var token = self.getNextToken(); - - switch (token.id) { - Token.Id.Ellipsis2 => { - const start = node.op.ArrayAccess; - node.op = ast.NodeSuffixOp.SuffixOp { - .Slice = ast.NodeSuffixOp.SliceRange { - .start = start, - .end = undefined, + if (!try self.parseBlockExpr(&stack, arena, opt_ctx, token)) { + self.putBackToken(token); + if (opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)); } - }; - - const rbracket_token = self.getNextToken(); - if (rbracket_token.id != Token.Id.RBracket) { - self.putBackToken(rbracket_token); - stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.RBracket, - .ptr = &node.rtoken, - } - }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .NullableField = &node.op.Slice.end } }); - } else { - node.rtoken = rbracket_token; } continue; - }, - Token.Id.RBracket => { - node.rtoken = token; - continue; - }, - else => { - try self.parseError(&stack, token, "expected ']' or '..', found {}", @tagName(token.id)); - continue; } } }, - State.AsmOutputItems => |items| { - const lbracket = self.getNextToken(); - if (lbracket.id != Token.Id.LBracket) { - self.putBackToken(lbracket); - continue; - } - - stack.append(State { .AsmOutputItems = items }) catch unreachable; - try stack.append(State { .IfToken = Token.Id.Comma }); - - const symbolic_name = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - _ = (try self.eatToken(&stack, Token.Id.RBracket)) ?? continue; - const constraint = (try self.eatToken(&stack, Token.Id.StringLiteral)) ?? continue; - - _ = (try self.eatToken(&stack, Token.Id.LParen)) ?? continue; - try stack.append(State { .ExpectToken = Token.Id.RParen }); - - const node = try arena.create(ast.NodeAsmOutput); - *node = ast.NodeAsmOutput { - .base = self.initNode(ast.Node.Id.AsmOutput), - .symbolic_name = try self.createIdentifier(arena, symbolic_name), - .constraint = try self.createStringLiteral(arena, constraint), - .kind = undefined, - }; - try items.append(node); - - const symbol_or_arrow = self.getNextToken(); - switch (symbol_or_arrow.id) { - Token.Id.Identifier => { - node.kind = ast.NodeAsmOutput.Kind { .Variable = try self.createIdentifier(arena, symbol_or_arrow) }; - }, - Token.Id.Arrow => { - node.kind = ast.NodeAsmOutput.Kind { .Return = undefined }; - try stack.append(State { .TypeExprBegin = DestPtr { .Field = &node.kind.Return } }); - }, - else => { - try self.parseError(&stack, symbol_or_arrow, "expected '->' or {}, found {}", - @tagName(Token.Id.Identifier), - @tagName(symbol_or_arrow.id)); - continue; - }, - } - }, - - State.AsmInputItems => |items| { - const lbracket = self.getNextToken(); - if (lbracket.id != Token.Id.LBracket) { - self.putBackToken(lbracket); - continue; - } - - stack.append(State { .AsmInputItems = items }) catch unreachable; - try stack.append(State { .IfToken = Token.Id.Comma }); - - const symbolic_name = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - _ = (try self.eatToken(&stack, Token.Id.RBracket)) ?? continue; - const constraint = (try self.eatToken(&stack, Token.Id.StringLiteral)) ?? continue; - - _ = (try self.eatToken(&stack, Token.Id.LParen)) ?? continue; - try stack.append(State { .ExpectToken = Token.Id.RParen }); - - const node = try arena.create(ast.NodeAsmInput); - *node = ast.NodeAsmInput { - .base = self.initNode(ast.Node.Id.AsmInput), - .symbolic_name = try self.createIdentifier(arena, symbolic_name), - .constraint = try self.createStringLiteral(arena, constraint), - .expr = undefined, - }; - try items.append(node); - try stack.append(State { .Expression = DestPtr { .Field = &node.expr } }); - }, - - State.AsmClopperItems => |items| { - const string = self.getNextToken(); - if (string.id != Token.Id.StringLiteral) { - self.putBackToken(string); - continue; - } - - try items.append(try self.createStringLiteral(arena, string)); - stack.append(State { .AsmClopperItems = items }) catch unreachable; - try stack.append(State { .IfToken = Token.Id.Comma }); - }, - - State.ExprListItemOrEnd => |list_state| { - var token = self.getNextToken(); - - const IdTag = @TagType(Token.Id); - if (IdTag(list_state.end) == token.id) { - *list_state.ptr = token; - continue; - } - - self.putBackToken(token); - stack.append(State { .ExprListCommaOrEnd = list_state }) catch unreachable; - try stack.append(State { .Expression = DestPtr{ .Field = try list_state.list.addOne() } }); - }, - - State.FieldInitListItemOrEnd => |list_state| { - var token = self.getNextToken(); - - if (token.id == Token.Id.RBrace){ - *list_state.ptr = token; + State.ErrorTypeOrSetDecl => |ctx| { + if (self.eatToken(Token.Id.LBrace) == null) { + _ = try self.createToCtxLiteral(arena, ctx.opt_ctx, ast.NodeErrorType, ctx.error_token); continue; } - self.putBackToken(token); - - const node = try arena.create(ast.NodeFieldInitializer); - *node = ast.NodeFieldInitializer { - .base = self.initNode(ast.Node.Id.FieldInitializer), - .period_token = undefined, - .name_token = undefined, - .expr = undefined, - }; - try list_state.list.append(node); - - stack.append(State { .FieldInitListCommaOrEnd = list_state }) catch unreachable; - try stack.append(State { .Expression = DestPtr{.Field = &node.expr} }); - try stack.append(State { .ExpectToken = Token.Id.Equal }); - try stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Identifier, - .ptr = &node.name_token, - } - }); - try stack.append(State { - .ExpectTokenSave = ExpectTokenSave { - .id = Token.Id.Period, - .ptr = &node.period_token, + const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeErrorSetDecl, + ast.NodeErrorSetDecl { + .base = undefined, + .error_token = ctx.error_token, + .decls = ArrayList(&ast.Node).init(arena), + .rbrace_token = undefined, } - }); - }, - - State.SwitchCaseOrEnd => |list_state| { - var token = self.getNextToken(); - - if (token.id == Token.Id.RBrace){ - *list_state.ptr = token; - continue; - } - - self.putBackToken(token); - - const node = try arena.create(ast.NodeSwitchCase); - *node = ast.NodeSwitchCase { - .base = self.initNode(ast.Node.Id.SwitchCase), - .items = ArrayList(&ast.Node).init(arena), - .payload = null, - .expr = undefined, - }; - try list_state.list.append(node); - stack.append(State { .SwitchCaseCommaOrEnd = list_state }) catch unreachable; - try stack.append(State { .Expression = DestPtr{ .Field = &node.expr } }); - try stack.append(State { .PointerPayload = &node.payload }); - - const maybe_else = self.getNextToken(); - if (maybe_else.id == Token.Id.Keyword_else) { - const else_node = try arena.create(ast.NodeSwitchElse); - *else_node = ast.NodeSwitchElse { - .base = self.initNode(ast.Node.Id.SwitchElse), - .token = maybe_else, - }; - try node.items.append(&else_node.base); - try stack.append(State { .ExpectToken = Token.Id.EqualAngleBracketRight }); - continue; - } else { - self.putBackToken(maybe_else); - try stack.append(State { .SwitchCaseItem = &node.items }); - continue; - } - }, - - State.SwitchCaseItem => |case_items| { - stack.append(State { .SwitchCaseItemCommaOrEnd = case_items }) catch unreachable; - try stack.append(State { .RangeExpressionBegin = DestPtr{ .Field = try case_items.addOne() } }); - }, - - State.ExprListCommaOrEnd => |list_state| { - try self.commaOrEnd(&stack, list_state.end, list_state.ptr, State { .ExprListItemOrEnd = list_state }); - continue; - }, - - State.FieldInitListCommaOrEnd => |list_state| { - try self.commaOrEnd(&stack, Token.Id.RBrace, list_state.ptr, State { .FieldInitListItemOrEnd = list_state }); - continue; - }, - - State.FieldListCommaOrEnd => |container_decl| { - try self.commaOrEnd(&stack, Token.Id.RBrace, &container_decl.rbrace_token, - State { .ContainerDecl = container_decl }); - continue; - }, - - State.SwitchCaseCommaOrEnd => |list_state| { - try self.commaOrEnd(&stack, Token.Id.RBrace, list_state.ptr, State { .SwitchCaseOrEnd = list_state }); - continue; - }, - - State.SwitchCaseItemCommaOrEnd => |case_items| { - try self.commaOrEnd(&stack, Token.Id.EqualAngleBracketRight, null, State { .SwitchCaseItem = case_items }); - continue; - }, - - State.Else => |dest| { - const else_token = self.getNextToken(); - if (else_token.id != Token.Id.Keyword_else) { - self.putBackToken(else_token); - continue; - } - - const node = try arena.create(ast.NodeElse); - *node = ast.NodeElse { - .base = self.initNode(ast.Node.Id.Else), - .else_token = else_token, - .payload = null, - .body = undefined, - }; - *dest = node; - - stack.append(State { .Expression = DestPtr { .Field = &node.body } }) catch unreachable; - try stack.append(State { .Payload = &node.payload }); - }, - - State.WhileContinueExpr => |dest| { - const colon = self.getNextToken(); - if (colon.id != Token.Id.Colon) { - self.putBackToken(colon); - continue; - } - - _ = (try self.eatToken(&stack, Token.Id.LParen)) ?? continue; - stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable; - try stack.append(State { .AssignmentExpressionBegin = DestPtr { .NullableField = dest } }); - }, + ); - State.SuspendBody => |suspend_node| { - if (suspend_node.payload != null) { - try stack.append(State { .AssignmentExpressionBegin = DestPtr { .NullableField = &suspend_node.body } }); - } + stack.append(State { + .IdentifierListItemOrEnd = ListSave(&ast.Node) { + .list = &node.decls, + .ptr = &node.rbrace_token, + } + }) catch unreachable; continue; }, - - State.AsyncEnd => |ctx| { - const node = ctx.dest_ptr.get(); - - switch (node.id) { - ast.Node.Id.FnProto => { - const fn_proto = @fieldParentPtr(ast.NodeFnProto, "base", node); - fn_proto.async_attr = ctx.attribute; - }, - ast.Node.Id.SuffixOp => { - const suffix_op = @fieldParentPtr(ast.NodeSuffixOp, "base", node); - if (suffix_op.op == ast.NodeSuffixOp.SuffixOp.Call) { - suffix_op.op.Call.async_attr = ctx.attribute; - continue; + State.StringLiteral => |opt_ctx| { + const token = self.getNextToken(); + opt_ctx.store( + (try self.parseStringLiteral(arena, token)) ?? { + self.putBackToken(token); + if (opt_ctx != OptionalCtx.Optional) { + return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)); } - try self.parseError(&stack, node.firstToken(), "expected call or fn proto, found {}.", - @tagName(suffix_op.op)); - continue; - }, - else => { - try self.parseError(&stack, node.firstToken(), "expected call or fn proto, found {}.", - @tagName(node.id)); continue; } - } + ); }, - - State.Payload => |dest| { - const lpipe = self.getNextToken(); - if (lpipe.id != Token.Id.Pipe) { - self.putBackToken(lpipe); + State.Identifier => |opt_ctx| { + if (self.eatToken(Token.Id.Identifier)) |ident_token| { + _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeIdentifier, ident_token); continue; } - const error_symbol = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - const rpipe = (try self.eatToken(&stack, Token.Id.Pipe)) ?? continue; - const node = try arena.create(ast.NodePayload); - *node = ast.NodePayload { - .base = self.initNode(ast.Node.Id.Payload), - .lpipe = lpipe, - .error_symbol = try self.createIdentifier(arena, error_symbol), - .rpipe = rpipe - }; - *dest = node; - }, - - State.PointerPayload => |dest| { - const lpipe = self.getNextToken(); - if (lpipe.id != Token.Id.Pipe) { - self.putBackToken(lpipe); - continue; + if (opt_ctx != OptionalCtx.Optional) { + const token = self.getNextToken(); + return self.parseError(token, "expected identifier, found {}", @tagName(token.id)); } - - const is_ptr = blk: { - const asterik = self.getNextToken(); - if (asterik.id == Token.Id.Asterisk) { - break :blk true; - } else { - self.putBackToken(asterik); - break :blk false; - } - }; - - const value_symbol = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - const rpipe = (try self.eatToken(&stack, Token.Id.Pipe)) ?? continue; - const node = try arena.create(ast.NodePointerPayload); - *node = ast.NodePointerPayload { - .base = self.initNode(ast.Node.Id.PointerPayload), - .lpipe = lpipe, - .is_ptr = is_ptr, - .value_symbol = try self.createIdentifier(arena, value_symbol), - .rpipe = rpipe - }; - *dest = node; }, - State.PointerIndexPayload => |dest| { - const lpipe = self.getNextToken(); - if (lpipe.id != Token.Id.Pipe) { - self.putBackToken(lpipe); - continue; - } - - const is_ptr = blk: { - const asterik = self.getNextToken(); - if (asterik.id == Token.Id.Asterisk) { - break :blk true; - } else { - self.putBackToken(asterik); - break :blk false; - } - }; - - const value_symbol = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - const index_symbol = blk: { - const comma = self.getNextToken(); - if (comma.id != Token.Id.Comma) { - self.putBackToken(comma); - break :blk null; - } - - const symbol = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue; - break :blk try self.createIdentifier(arena, symbol); - }; - - const rpipe = (try self.eatToken(&stack, Token.Id.Pipe)) ?? continue; - const node = try arena.create(ast.NodePointerIndexPayload); - *node = ast.NodePointerIndexPayload { - .base = self.initNode(ast.Node.Id.PointerIndexPayload), - .lpipe = lpipe, - .is_ptr = is_ptr, - .value_symbol = try self.createIdentifier(arena, value_symbol), - .index_symbol = index_symbol, - .rpipe = rpipe - }; - *dest = node; - }, - State.AddrOfModifiers => |addr_of_info| { - var token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_align => { - stack.append(state) catch unreachable; - if (addr_of_info.align_expr != null) { - try self.parseError(&stack, token, "multiple align qualifiers"); - continue; - } - try stack.append(State { .ExpectToken = Token.Id.RParen }); - try stack.append(State { .Expression = DestPtr{.NullableField = &addr_of_info.align_expr} }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); - continue; - }, - Token.Id.Keyword_const => { - stack.append(state) catch unreachable; - if (addr_of_info.const_token != null) { - try self.parseError(&stack, token, "duplicate qualifier: const"); - continue; - } - addr_of_info.const_token = token; - continue; - }, - Token.Id.Keyword_volatile => { - stack.append(state) catch unreachable; - if (addr_of_info.volatile_token != null) { - try self.parseError(&stack, token, "duplicate qualifier: volatile"); - continue; - } - addr_of_info.volatile_token = token; - continue; - }, - else => { - self.putBackToken(token); - continue; - }, - } - }, - - State.FnProto => |fn_proto| { - stack.append(State { .FnProtoAlign = fn_proto }) catch unreachable; - try stack.append(State { .ParamDecl = fn_proto }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); - - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Identifier) { - fn_proto.name_token = next_token; - continue; - } - self.putBackToken(next_token); - continue; - }, - - State.FnProtoAlign => |fn_proto| { - const token = self.getNextToken(); - if (token.id == Token.Id.Keyword_align) { - @panic("TODO fn proto align"); - } - self.putBackToken(token); - stack.append(State { - .FnProtoReturnType = fn_proto, - }) catch unreachable; + State.ExpectToken => |token_id| { + _ = try self.expectToken(token_id); continue; }, - - State.FnProtoReturnType => |fn_proto| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Bang => { - fn_proto.return_type = ast.NodeFnProto.ReturnType { .InferErrorSet = undefined }; - stack.append(State { - .TypeExprBegin = DestPtr {.Field = &fn_proto.return_type.InferErrorSet}, - }) catch unreachable; - }, - else => { - self.putBackToken(token); - fn_proto.return_type = ast.NodeFnProto.ReturnType { .Explicit = undefined }; - stack.append(State { - .TypeExprBegin = DestPtr {.Field = &fn_proto.return_type.Explicit}, - }) catch unreachable; - }, - } - if (token.id == Token.Id.Keyword_align) { - @panic("TODO fn proto align"); - } + State.ExpectTokenSave => |expect_token_save| { + *expect_token_save.ptr = try self.expectToken(expect_token_save.id); continue; }, - - State.ParamDecl => |fn_proto| { - var token = self.getNextToken(); - if (token.id == Token.Id.RParen) { - continue; - } - const param_decl = try self.createAttachParamDecl(arena, &fn_proto.params); - if (token.id == Token.Id.Keyword_comptime) { - param_decl.comptime_token = token; - token = self.getNextToken(); - } else if (token.id == Token.Id.Keyword_noalias) { - param_decl.noalias_token = token; - token = self.getNextToken(); - } - if (token.id == Token.Id.Identifier) { - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Colon) { - param_decl.name_token = token; - token = self.getNextToken(); - } else { - self.putBackToken(next_token); - } - } - if (token.id == Token.Id.Ellipsis3) { - param_decl.var_args_token = token; - stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable; + State.IfToken => |token_id| { + if (self.eatToken(token_id)) |_| { continue; - } else { - self.putBackToken(token); } - stack.append(State { .ParamDecl = fn_proto }) catch unreachable; - try stack.append(State.ParamDeclComma); - try stack.append(State { - .TypeExprBegin = DestPtr {.Field = ¶m_decl.type_node} - }); + _ = stack.pop(); continue; }, - - State.ParamDeclComma => { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.RParen => { - _ = stack.pop(); // pop off the ParamDecl - continue; - }, - Token.Id.Comma => continue, - else => { - try self.parseError(&stack, token, "expected ',' or ')', found {}", @tagName(token.id)); - continue; - }, - } - }, - - State.FnDef => |fn_proto| { - const token = self.getNextToken(); - switch(token.id) { - Token.Id.LBrace => { - const block = try self.createBlock(arena, (?Token)(null), token); - fn_proto.body_node = &block.base; - stack.append(State { .Block = block }) catch unreachable; - continue; - }, - Token.Id.Semicolon => continue, - else => { - try self.parseError(&stack, token, "expected ';' or '{{', found {}", @tagName(token.id)); - continue; - }, - } - }, - - State.LabeledExpression => |ctx| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.LBrace => { - const block = try self.createBlock(arena, (?Token)(ctx.label), token); - ctx.dest_ptr.store(&block.base); - - stack.append(State { .Block = block }) catch unreachable; - continue; - }, - Token.Id.Keyword_while => { - stack.append(State { - .While = LoopCtx { - .label = ctx.label, - .inline_token = null, - .loop_token = token, - .dest_ptr = ctx.dest_ptr, - } - }) catch unreachable; - continue; - }, - Token.Id.Keyword_for => { - stack.append(State { - .For = LoopCtx { - .label = ctx.label, - .inline_token = null, - .loop_token = token, - .dest_ptr = ctx.dest_ptr, - } - }) catch unreachable; - continue; - }, - Token.Id.Keyword_inline => { - stack.append(State { - .Inline = InlineCtx { - .label = ctx.label, - .inline_token = token, - .dest_ptr = ctx.dest_ptr, - } - }) catch unreachable; - continue; - }, - else => { - try self.parseError(&stack, token, "expected 'while', 'for', 'inline' or '{{', found {}", @tagName(token.id)); - continue; - }, - } - }, - - State.Inline => |ctx| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_while => { - stack.append(State { - .While = LoopCtx { - .inline_token = ctx.inline_token, - .label = ctx.label, - .loop_token = token, - .dest_ptr = ctx.dest_ptr, - } - }) catch unreachable; - continue; - }, - Token.Id.Keyword_for => { - stack.append(State { - .For = LoopCtx { - .inline_token = ctx.inline_token, - .label = ctx.label, - .loop_token = token, - .dest_ptr = ctx.dest_ptr, - } - }) catch unreachable; - continue; - }, - else => { - try self.parseError(&stack, token, "expected 'while' or 'for', found {}", @tagName(token.id)); - continue; - }, + State.IfTokenSave => |if_token_save| { + if (self.eatToken(if_token_save.id)) |token| { + *if_token_save.ptr = token; + continue; } - }, - - State.While => |ctx| { - const node = try arena.create(ast.NodeWhile); - *node = ast.NodeWhile { - .base = self.initNode(ast.Node.Id.While), - .label = ctx.label, - .inline_token = ctx.inline_token, - .while_token = ctx.loop_token, - .condition = undefined, - .payload = null, - .continue_expr = null, - .body = undefined, - .@"else" = null, - }; - ctx.dest_ptr.store(&node.base); - - stack.append(State { .Else = &node.@"else" }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.body } }); - try stack.append(State { .WhileContinueExpr = &node.continue_expr }); - try stack.append(State { .PointerPayload = &node.payload }); - try stack.append(State { .ExpectToken = Token.Id.RParen }); - try stack.append(State { .Expression = DestPtr { .Field = &node.condition } }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); - }, - - State.For => |ctx| { - const node = try arena.create(ast.NodeFor); - *node = ast.NodeFor { - .base = self.initNode(ast.Node.Id.For), - .label = ctx.label, - .inline_token = ctx.inline_token, - .for_token = ctx.loop_token, - .array_expr = undefined, - .payload = null, - .body = undefined, - .@"else" = null, - }; - ctx.dest_ptr.store(&node.base); - stack.append(State { .Else = &node.@"else" }) catch unreachable; - try stack.append(State { .Expression = DestPtr { .Field = &node.body } }); - try stack.append(State { .PointerIndexPayload = &node.payload }); - try stack.append(State { .ExpectToken = Token.Id.RParen }); - try stack.append(State { .Expression = DestPtr { .Field = &node.array_expr } }); - try stack.append(State { .ExpectToken = Token.Id.LParen }); - }, - - State.Block => |block| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.RBrace => { - block.rbrace = token; - continue; - }, - else => { - self.putBackToken(token); - stack.append(State { .Block = block }) catch unreachable; - try stack.append(State { .Statement = block }); - continue; - }, - } + _ = stack.pop(); + continue; }, - - State.Statement => |block| { - const next = self.getNextToken(); - switch (next.id) { - Token.Id.Keyword_comptime => { - const mut_token = self.getNextToken(); - if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { - // TODO shouldn't need these casts - const var_decl = try self.createAttachVarDecl(arena, &block.statements, (?Token)(null), - mut_token, (?Token)(next), (?Token)(null), null); - stack.append(State { .VarDecl = var_decl }) catch unreachable; - continue; - } else { - self.putBackToken(mut_token); - self.putBackToken(next); - const statememt = try block.statements.addOne(); - stack.append(State { .Semicolon = statememt }) catch unreachable; - try stack.append(State { .Expression = DestPtr{.Field = statememt } }); - } - }, - Token.Id.Keyword_var, Token.Id.Keyword_const => { - const var_decl = try self.createAttachVarDecl(arena, &block.statements, (?Token)(null), - next, (?Token)(null), (?Token)(null), null); - stack.append(State { .VarDecl = var_decl }) catch unreachable; - continue; - }, - Token.Id.Keyword_defer, Token.Id.Keyword_errdefer => { - const node = try arena.create(ast.NodeDefer); - *node = ast.NodeDefer { - .base = self.initNode(ast.Node.Id.Defer), - .defer_token = next, - .kind = switch (next.id) { - Token.Id.Keyword_defer => ast.NodeDefer.Kind.Unconditional, - Token.Id.Keyword_errdefer => ast.NodeDefer.Kind.Error, - else => unreachable, - }, - .expr = undefined, - }; - try block.statements.append(&node.base); - - stack.append(State { .Semicolon = &node.base }) catch unreachable; - try stack.append(State { .AssignmentExpressionBegin = DestPtr{.Field = &node.expr } }); - continue; - }, - Token.Id.LBrace => { - const inner_block = try self.createBlock(arena, (?Token)(null), next); - try block.statements.append(&inner_block.base); - - stack.append(State { .Block = inner_block }) catch unreachable; - continue; - }, - else => { - self.putBackToken(next); - const statememt = try block.statements.addOne(); - stack.append(State { .Semicolon = statememt }) catch unreachable; - try stack.append(State { .AssignmentExpressionBegin = DestPtr{.Field = statememt } }); - continue; - } + State.OptionalTokenSave => |optional_token_save| { + if (self.eatToken(optional_token_save.id)) |token| { + *optional_token_save.ptr = token; + continue; } + continue; }, - - State.Semicolon => |node_ptr| { - const node = *node_ptr; - if (requireSemiColon(node)) { - _ = (try self.eatToken(&stack, Token.Id.Semicolon)) ?? continue; - } - } } } } @@ -2530,7 +2776,7 @@ pub const Parser = struct { continue; } - n = while_node.body; + return while_node.body.id != ast.Node.Id.Block; }, ast.Node.Id.For => { const for_node = @fieldParentPtr(ast.NodeFor, "base", n); @@ -2539,7 +2785,7 @@ pub const Parser = struct { continue; } - n = for_node.body; + return for_node.body.id != ast.Node.Id.Block; }, ast.Node.Id.If => { const if_node = @fieldParentPtr(ast.NodeIf, "base", n); @@ -2548,25 +2794,25 @@ pub const Parser = struct { continue; } - n = if_node.body; + return if_node.body.id != ast.Node.Id.Block; }, ast.Node.Id.Else => { const else_node = @fieldParentPtr(ast.NodeElse, "base", n); n = else_node.body; + continue; }, ast.Node.Id.Defer => { const defer_node = @fieldParentPtr(ast.NodeDefer, "base", n); - n = defer_node.expr; + return defer_node.expr.id != ast.Node.Id.Block; }, ast.Node.Id.Comptime => { const comptime_node = @fieldParentPtr(ast.NodeComptime, "base", n); - n = comptime_node.expr; + return comptime_node.expr.id != ast.Node.Id.Block; }, ast.Node.Id.Suspend => { const suspend_node = @fieldParentPtr(ast.NodeSuspend, "base", n); if (suspend_node.body) |body| { - n = body; - continue; + return body.id != ast.Node.Id.Block; } return true; @@ -2576,22 +2822,158 @@ pub const Parser = struct { } } - fn commaOrEnd(self: &Parser, stack: &ArrayList(State), end: &const Token.Id, maybe_ptr: ?&Token, state_after_comma: &const State) !void { - var token = self.getNextToken(); + fn parseStringLiteral(self: &Parser, arena: &mem.Allocator, token: &const Token) !?&ast.Node { switch (token.id) { - Token.Id.Comma => { - stack.append(state_after_comma) catch unreachable; + Token.Id.StringLiteral => { + return &(try self.createLiteral(arena, ast.NodeStringLiteral, token)).base; }, - else => { - const IdTag = @TagType(Token.Id); - if (IdTag(*end) == token.id) { - if (maybe_ptr) |ptr| { - *ptr = token; + Token.Id.MultilineStringLiteralLine => { + const node = try self.createNode(arena, ast.NodeMultilineStringLiteral, + ast.NodeMultilineStringLiteral { + .base = undefined, + .tokens = ArrayList(Token).init(arena), } - return; + ); + try node.tokens.append(token); + while (true) { + const multiline_str = self.getNextToken(); + if (multiline_str.id != Token.Id.MultilineStringLiteralLine) { + self.putBackToken(multiline_str); + break; + } + + try node.tokens.append(multiline_str); + } + + return &node.base; + }, + // TODO: We shouldn't need a cast, but: + // zig: /home/jc/Documents/zig/src/ir.cpp:7962: TypeTableEntry* ir_resolve_peer_types(IrAnalyze*, AstNode*, IrInstruction**, size_t): Assertion `err_set_type != nullptr' failed. + else => return (?&ast.Node)(null), + } + } + + fn parseBlockExpr(self: &Parser, stack: &ArrayList(State), arena: &mem.Allocator, ctx: &const OptionalCtx, token: &const Token) !bool { + switch (token.id) { + Token.Id.Keyword_suspend => { + const node = try self.createToCtxNode(arena, ctx, ast.NodeSuspend, + ast.NodeSuspend { + .base = undefined, + .suspend_token = *token, + .payload = null, + .body = null, + } + ); + + stack.append(State { .SuspendBody = node }) catch unreachable; + try stack.append(State { .Payload = OptionalCtx { .Optional = &node.payload } }); + return true; + }, + Token.Id.Keyword_if => { + const node = try self.createToCtxNode(arena, ctx, ast.NodeIf, + ast.NodeIf { + .base = undefined, + .if_token = *token, + .condition = undefined, + .payload = null, + .body = undefined, + .@"else" = null, + } + ); + + stack.append(State { .Else = &node.@"else" }) catch unreachable; + try stack.append(State { .Expression = OptionalCtx { .Required = &node.body } }); + try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } }); + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.condition } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + return true; + }, + Token.Id.Keyword_while => { + stack.append(State { + .While = LoopCtx { + .label = null, + .inline_token = null, + .loop_token = *token, + .opt_ctx = *ctx, + } + }) catch unreachable; + return true; + }, + Token.Id.Keyword_for => { + stack.append(State { + .For = LoopCtx { + .label = null, + .inline_token = null, + .loop_token = *token, + .opt_ctx = *ctx, + } + }) catch unreachable; + return true; + }, + Token.Id.Keyword_switch => { + const node = try self.createToCtxNode(arena, ctx, ast.NodeSwitch, + ast.NodeSwitch { + .base = undefined, + .switch_token = *token, + .expr = undefined, + .cases = ArrayList(&ast.NodeSwitchCase).init(arena), + .rbrace = undefined, + } + ); + + stack.append(State { + .SwitchCaseOrEnd = ListSave(&ast.NodeSwitchCase) { + .list = &node.cases, + .ptr = &node.rbrace, + }, + }) catch unreachable; + try stack.append(State { .ExpectToken = Token.Id.LBrace }); + try stack.append(State { .ExpectToken = Token.Id.RParen }); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); + try stack.append(State { .ExpectToken = Token.Id.LParen }); + return true; + }, + Token.Id.Keyword_comptime => { + const node = try self.createToCtxNode(arena, ctx, ast.NodeComptime, + ast.NodeComptime { + .base = undefined, + .comptime_token = *token, + .expr = undefined, + } + ); + try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); + return true; + }, + Token.Id.LBrace => { + const block = try self.createToCtxNode(arena, ctx, ast.NodeBlock, + ast.NodeBlock { + .base = undefined, + .label = null, + .lbrace = *token, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, + } + ); + stack.append(State { .Block = block }) catch unreachable; + return true; + }, + else => { + return false; + } + } + } + + fn expectCommaOrEnd(self: &Parser, end: @TagType(Token.Id)) !?Token { + var token = self.getNextToken(); + switch (token.id) { + Token.Id.Comma => return null, + else => { + if (end == token.id) { + return token; } - try self.parseError(stack, token, "expected ',' or {}, found {}", @tagName(*end), @tagName(token.id)); + return self.parseError(token, "expected ',' or {}, found {}", @tagName(end), @tagName(token.id)); }, } } @@ -2600,84 +2982,82 @@ pub const Parser = struct { // TODO: We have to cast all cases because of this: // error: expected type '?InfixOp', found '?@TagType(InfixOp)' return switch (*id) { - Token.Id.AmpersandEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignBitAnd), - Token.Id.AngleBracketAngleBracketLeftEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignBitShiftLeft), - Token.Id.AngleBracketAngleBracketRightEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignBitShiftRight), - Token.Id.AsteriskEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignTimes), - Token.Id.AsteriskPercentEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignTimesWarp), - Token.Id.CaretEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignBitXor), - Token.Id.Equal => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.Assign), - Token.Id.MinusEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignMinus), - Token.Id.MinusPercentEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignMinusWrap), - Token.Id.PercentEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignMod), - Token.Id.PipeEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignBitOr), - Token.Id.PlusEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignPlus), - Token.Id.PlusPercentEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignPlusWrap), - Token.Id.SlashEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AssignDiv), + Token.Id.AmpersandEqual => ast.NodeInfixOp.InfixOp { .AssignBitAnd = void{} }, + Token.Id.AngleBracketAngleBracketLeftEqual => ast.NodeInfixOp.InfixOp { .AssignBitShiftLeft = void{} }, + Token.Id.AngleBracketAngleBracketRightEqual => ast.NodeInfixOp.InfixOp { .AssignBitShiftRight = void{} }, + Token.Id.AsteriskEqual => ast.NodeInfixOp.InfixOp { .AssignTimes = void{} }, + Token.Id.AsteriskPercentEqual => ast.NodeInfixOp.InfixOp { .AssignTimesWarp = void{} }, + Token.Id.CaretEqual => ast.NodeInfixOp.InfixOp { .AssignBitXor = void{} }, + Token.Id.Equal => ast.NodeInfixOp.InfixOp { .Assign = void{} }, + Token.Id.MinusEqual => ast.NodeInfixOp.InfixOp { .AssignMinus = void{} }, + Token.Id.MinusPercentEqual => ast.NodeInfixOp.InfixOp { .AssignMinusWrap = void{} }, + Token.Id.PercentEqual => ast.NodeInfixOp.InfixOp { .AssignMod = void{} }, + Token.Id.PipeEqual => ast.NodeInfixOp.InfixOp { .AssignBitOr = void{} }, + Token.Id.PlusEqual => ast.NodeInfixOp.InfixOp { .AssignPlus = void{} }, + Token.Id.PlusPercentEqual => ast.NodeInfixOp.InfixOp { .AssignPlusWrap = void{} }, + Token.Id.SlashEqual => ast.NodeInfixOp.InfixOp { .AssignDiv = void{} }, else => null, }; } - fn tokenIdToComparison(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp { - // TODO: We have to cast all cases because of this: - // error: expected type '?InfixOp', found '?@TagType(InfixOp)' - return switch (*id) { - Token.Id.BangEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.BangEqual), - Token.Id.EqualEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.EqualEqual), - Token.Id.AngleBracketLeft => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.LessThan), - Token.Id.AngleBracketLeftEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.LessOrEqual), - Token.Id.AngleBracketRight => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.GreaterThan), - Token.Id.AngleBracketRightEqual => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.GreaterOrEqual), + fn tokenIdToUnwrapExpr(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp { + return switch (id) { + Token.Id.Keyword_catch => ast.NodeInfixOp.InfixOp { .Catch = null }, + Token.Id.QuestionMarkQuestionMark => ast.NodeInfixOp.InfixOp { .UnwrapMaybe = void{} }, else => null, }; } - fn tokenIdToBitShift(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp { - // TODO: We have to cast all cases because of this: - // error: expected type '?InfixOp', found '?@TagType(InfixOp)' - return switch (*id) { - Token.Id.AngleBracketAngleBracketLeft => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.BitShiftLeft), - Token.Id.AngleBracketAngleBracketRight => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.BitShiftRight), + fn tokenIdToComparison(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp { + return switch (id) { + Token.Id.BangEqual => ast.NodeInfixOp.InfixOp { .BangEqual = void{} }, + Token.Id.EqualEqual => ast.NodeInfixOp.InfixOp { .EqualEqual = void{} }, + Token.Id.AngleBracketLeft => ast.NodeInfixOp.InfixOp { .LessThan = void{} }, + Token.Id.AngleBracketLeftEqual => ast.NodeInfixOp.InfixOp { .LessOrEqual = void{} }, + Token.Id.AngleBracketRight => ast.NodeInfixOp.InfixOp { .GreaterThan = void{} }, + Token.Id.AngleBracketRightEqual => ast.NodeInfixOp.InfixOp { .GreaterOrEqual = void{} }, else => null, }; } - fn tokenIdToAddition(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp { - // TODO: We have to cast all cases because of this: - // error: expected type '?InfixOp', found '?@TagType(InfixOp)' - return switch (*id) { - Token.Id.Minus => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.Sub), - Token.Id.MinusPercent => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.SubWrap), - Token.Id.Plus => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.Add), - Token.Id.PlusPercent => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.AddWrap), - Token.Id.PlusPlus => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.ArrayCat), + fn tokenIdToBitShift(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp { + return switch (id) { + Token.Id.AngleBracketAngleBracketLeft => ast.NodeInfixOp.InfixOp { .BitShiftLeft = void{} }, + Token.Id.AngleBracketAngleBracketRight => ast.NodeInfixOp.InfixOp { .BitShiftRight = void{} }, else => null, }; } - fn tokenIdToMultiply(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp { - // TODO: We have to cast all cases because of this: - // error: expected type '?InfixOp', found '?@TagType(InfixOp)' - return switch (*id) { - Token.Id.Slash => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.Div), - Token.Id.Asterisk => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.Mult), - Token.Id.AsteriskAsterisk => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.ArrayMult), - Token.Id.AsteriskPercent => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.MultWrap), - Token.Id.Percent => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.Mod), - Token.Id.PipePipe => (ast.NodeInfixOp.InfixOp)(ast.NodeInfixOp.InfixOp.MergeErrorSets), + fn tokenIdToAddition(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp { + return switch (id) { + Token.Id.Minus => ast.NodeInfixOp.InfixOp { .Sub = void{} }, + Token.Id.MinusPercent => ast.NodeInfixOp.InfixOp { .SubWrap = void{} }, + Token.Id.Plus => ast.NodeInfixOp.InfixOp { .Add = void{} }, + Token.Id.PlusPercent => ast.NodeInfixOp.InfixOp { .AddWrap = void{} }, + Token.Id.PlusPlus => ast.NodeInfixOp.InfixOp { .ArrayCat = void{} }, else => null, }; } - fn tokenIdToPrefixOp(id: &const Token.Id) ?ast.NodePrefixOp.PrefixOp { - // TODO: We have to cast all cases because of this: - // error: expected type '?InfixOp', found '?@TagType(InfixOp)' - return switch (*id) { - Token.Id.Bang => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.BoolNot), - Token.Id.Tilde => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.BitNot), - Token.Id.Minus => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.Negation), - Token.Id.MinusPercent => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.NegationWrap), - Token.Id.Asterisk => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.Deref), + fn tokenIdToMultiply(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp { + return switch (id) { + Token.Id.Slash => ast.NodeInfixOp.InfixOp { .Div = void{} }, + Token.Id.Asterisk => ast.NodeInfixOp.InfixOp { .Mult = void{} }, + Token.Id.AsteriskAsterisk => ast.NodeInfixOp.InfixOp { .ArrayMult = void{} }, + Token.Id.AsteriskPercent => ast.NodeInfixOp.InfixOp { .MultWrap = void{} }, + Token.Id.Percent => ast.NodeInfixOp.InfixOp { .Mod = void{} }, + Token.Id.PipePipe => ast.NodeInfixOp.InfixOp { .MergeErrorSets = void{} }, + else => null, + }; + } + + fn tokenIdToPrefixOp(id: @TagType(Token.Id)) ?ast.NodePrefixOp.PrefixOp { + return switch (id) { + Token.Id.Bang => ast.NodePrefixOp.PrefixOp { .BoolNot = void{} }, + Token.Id.Tilde => ast.NodePrefixOp.PrefixOp { .BitNot = void{} }, + Token.Id.Minus => ast.NodePrefixOp.PrefixOp { .Negation = void{} }, + Token.Id.MinusPercent => ast.NodePrefixOp.PrefixOp { .NegationWrap = void{} }, + Token.Id.Asterisk, Token.Id.AsteriskAsterisk => ast.NodePrefixOp.PrefixOp { .Deref = void{} }, Token.Id.Ampersand => ast.NodePrefixOp.PrefixOp { .AddrOf = ast.NodePrefixOp.AddrOfInfo { .align_expr = null, @@ -2687,307 +3067,93 @@ pub const Parser = struct { .volatile_token = null, }, }, - Token.Id.QuestionMark => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.MaybeType), - Token.Id.QuestionMarkQuestionMark => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.UnwrapMaybe), - Token.Id.Keyword_await => (ast.NodePrefixOp.PrefixOp)(ast.NodePrefixOp.PrefixOp.Await), + Token.Id.QuestionMark => ast.NodePrefixOp.PrefixOp { .MaybeType = void{} }, + Token.Id.QuestionMarkQuestionMark => ast.NodePrefixOp.PrefixOp { .UnwrapMaybe = void{} }, + Token.Id.Keyword_await => ast.NodePrefixOp.PrefixOp { .Await = void{} }, + Token.Id.Keyword_try => ast.NodePrefixOp.PrefixOp { .Try = void{ } }, else => null, }; } - fn initNode(self: &Parser, id: ast.Node.Id) ast.Node { - if (self.pending_line_comment_node) |comment_node| { - self.pending_line_comment_node = null; - return ast.Node {.id = id, .comment = comment_node}; - } - return ast.Node {.id = id, .comment = null }; - } - - fn createRoot(self: &Parser, arena: &mem.Allocator) !&ast.NodeRoot { - const node = try arena.create(ast.NodeRoot); - - *node = ast.NodeRoot { - .base = self.initNode(ast.Node.Id.Root), - .decls = ArrayList(&ast.Node).init(arena), - // initialized when we get the eof token - .eof_token = undefined, - }; - return node; - } - - fn createVarDecl(self: &Parser, arena: &mem.Allocator, visib_token: &const ?Token, mut_token: &const Token, - comptime_token: &const ?Token, extern_token: &const ?Token, lib_name: ?&ast.Node) !&ast.NodeVarDecl - { - const node = try arena.create(ast.NodeVarDecl); - - *node = ast.NodeVarDecl { - .base = self.initNode(ast.Node.Id.VarDecl), - .visib_token = *visib_token, - .mut_token = *mut_token, - .comptime_token = *comptime_token, - .extern_token = *extern_token, - .type_node = null, - .align_node = null, - .init_node = null, - .lib_name = lib_name, - // initialized later - .name_token = undefined, - .eq_token = undefined, - .semicolon_token = undefined, - }; - return node; - } - - fn createStringLiteral(self: &Parser, arena: &mem.Allocator, token: &const Token) !&ast.NodeStringLiteral { - const node = try arena.create(ast.NodeStringLiteral); - - assert(token.id == Token.Id.StringLiteral); - *node = ast.NodeStringLiteral { - .base = self.initNode(ast.Node.Id.StringLiteral), - .token = *token, - }; - return node; - } - - fn createTestDecl(self: &Parser, arena: &mem.Allocator, test_token: &const Token, name: &ast.Node, - block: &ast.NodeBlock) !&ast.NodeTestDecl - { - const node = try arena.create(ast.NodeTestDecl); - - *node = ast.NodeTestDecl { - .base = self.initNode(ast.Node.Id.TestDecl), - .test_token = *test_token, - .name = name, - .body_node = &block.base, - }; - return node; - } - - fn createFnProto(self: &Parser, arena: &mem.Allocator, fn_token: &const Token, extern_token: &const ?Token, - lib_name: ?&ast.Node, cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) !&ast.NodeFnProto - { - const node = try arena.create(ast.NodeFnProto); - - *node = ast.NodeFnProto { - .base = self.initNode(ast.Node.Id.FnProto), - .visib_token = *visib_token, - .name_token = null, - .fn_token = *fn_token, - .params = ArrayList(&ast.Node).init(arena), - .return_type = undefined, - .var_args_token = null, - .extern_token = *extern_token, - .inline_token = *inline_token, - .cc_token = *cc_token, - .async_attr = null, - .body_node = null, - .lib_name = lib_name, - .align_expr = null, - }; - return node; - } - - fn createParamDecl(self: &Parser, arena: &mem.Allocator) !&ast.NodeParamDecl { - const node = try arena.create(ast.NodeParamDecl); - - *node = ast.NodeParamDecl { - .base = self.initNode(ast.Node.Id.ParamDecl), - .comptime_token = null, - .noalias_token = null, - .name_token = null, - .type_node = undefined, - .var_args_token = null, - }; - return node; - } - - fn createBlock(self: &Parser, arena: &mem.Allocator, label: &const ?Token, lbrace: &const Token) !&ast.NodeBlock { - const node = try arena.create(ast.NodeBlock); - - *node = ast.NodeBlock { - .base = self.initNode(ast.Node.Id.Block), - .label = *label, - .lbrace = *lbrace, - .statements = ArrayList(&ast.Node).init(arena), - .rbrace = undefined, - }; - return node; - } - - fn createControlFlowExpr(self: &Parser, arena: &mem.Allocator, ltoken: &const Token, - kind: &const ast.NodeControlFlowExpression.Kind) !&ast.NodeControlFlowExpression - { - const node = try arena.create(ast.NodeControlFlowExpression); - *node = ast.NodeControlFlowExpression { - .base = self.initNode(ast.Node.Id.ControlFlowExpression), - .ltoken = *ltoken, - .kind = *kind, - .rhs = null, - }; - return node; - } - - fn createInfixOp(self: &Parser, arena: &mem.Allocator, op_token: &const Token, op: &const ast.NodeInfixOp.InfixOp) !&ast.NodeInfixOp { - const node = try arena.create(ast.NodeInfixOp); - - *node = ast.NodeInfixOp { - .base = self.initNode(ast.Node.Id.InfixOp), - .op_token = *op_token, - .lhs = undefined, - .op = *op, - .rhs = undefined, - }; - return node; - } - - fn createPrefixOp(self: &Parser, arena: &mem.Allocator, op_token: &const Token, op: &const ast.NodePrefixOp.PrefixOp) !&ast.NodePrefixOp { - const node = try arena.create(ast.NodePrefixOp); - - *node = ast.NodePrefixOp { - .base = self.initNode(ast.Node.Id.PrefixOp), - .op_token = *op_token, - .op = *op, - .rhs = undefined, - }; - return node; - } - - fn createSuffixOp(self: &Parser, arena: &mem.Allocator, op: &const ast.NodeSuffixOp.SuffixOp) !&ast.NodeSuffixOp { - const node = try arena.create(ast.NodeSuffixOp); - - *node = ast.NodeSuffixOp { - .base = self.initNode(ast.Node.Id.SuffixOp), - .lhs = undefined, - .op = *op, - .rtoken = undefined, - }; - return node; - } - - fn createIdentifier(self: &Parser, arena: &mem.Allocator, name_token: &const Token) !&ast.NodeIdentifier { - const node = try arena.create(ast.NodeIdentifier); - - *node = ast.NodeIdentifier { - .base = self.initNode(ast.Node.Id.Identifier), - .name_token = *name_token, + fn createNode(self: &Parser, arena: &mem.Allocator, comptime T: type, init_to: &const T) !&T { + const node = try arena.create(T); + *node = *init_to; + node.base = blk: { + const id = ast.Node.typeToId(T); + if (self.pending_line_comment_node) |comment_node| { + self.pending_line_comment_node = null; + break :blk ast.Node {.id = id, .comment = comment_node}; + } + break :blk ast.Node {.id = id, .comment = null }; }; - return node; - } - - fn createIntegerLiteral(self: &Parser, arena: &mem.Allocator, token: &const Token) !&ast.NodeIntegerLiteral { - const node = try arena.create(ast.NodeIntegerLiteral); - *node = ast.NodeIntegerLiteral { - .base = self.initNode(ast.Node.Id.IntegerLiteral), - .token = *token, - }; return node; } - fn createFloatLiteral(self: &Parser, arena: &mem.Allocator, token: &const Token) !&ast.NodeFloatLiteral { - const node = try arena.create(ast.NodeFloatLiteral); - - *node = ast.NodeFloatLiteral { - .base = self.initNode(ast.Node.Id.FloatLiteral), - .token = *token, - }; - return node; - } - - fn createUndefined(self: &Parser, arena: &mem.Allocator, token: &const Token) !&ast.NodeUndefinedLiteral { - const node = try arena.create(ast.NodeUndefinedLiteral); + fn createAttachNode(self: &Parser, arena: &mem.Allocator, list: &ArrayList(&ast.Node), comptime T: type, init_to: &const T) !&T { + const node = try self.createNode(arena, T, init_to); + try list.append(&node.base); - *node = ast.NodeUndefinedLiteral { - .base = self.initNode(ast.Node.Id.UndefinedLiteral), - .token = *token, - }; return node; } - fn createAttachIdentifier(self: &Parser, arena: &mem.Allocator, dest_ptr: &const DestPtr, name_token: &const Token) !&ast.NodeIdentifier { - const node = try self.createIdentifier(arena, name_token); - try dest_ptr.store(&node.base); - return node; - } + fn createToCtxNode(self: &Parser, arena: &mem.Allocator, opt_ctx: &const OptionalCtx, comptime T: type, init_to: &const T) !&T { + const node = try self.createNode(arena, T, init_to); + opt_ctx.store(&node.base); - fn createAttachParamDecl(self: &Parser, arena: &mem.Allocator, list: &ArrayList(&ast.Node)) !&ast.NodeParamDecl { - const node = try self.createParamDecl(arena); - try list.append(&node.base); return node; } - fn createAttachFnProto(self: &Parser, arena: &mem.Allocator, list: &ArrayList(&ast.Node), fn_token: &const Token, - extern_token: &const ?Token, lib_name: ?&ast.Node, cc_token: &const ?Token, visib_token: &const ?Token, - inline_token: &const ?Token) !&ast.NodeFnProto - { - const node = try self.createFnProto(arena, fn_token, extern_token, lib_name, cc_token, visib_token, inline_token); - try list.append(&node.base); - return node; + fn createLiteral(self: &Parser, arena: &mem.Allocator, comptime T: type, token: &const Token) !&T { + return self.createNode(arena, T, + T { + .base = undefined, + .token = *token, + } + ); } - fn createAttachVarDecl(self: &Parser, arena: &mem.Allocator, list: &ArrayList(&ast.Node), - visib_token: &const ?Token, mut_token: &const Token, comptime_token: &const ?Token, - extern_token: &const ?Token, lib_name: ?&ast.Node) !&ast.NodeVarDecl - { - const node = try self.createVarDecl(arena, visib_token, mut_token, comptime_token, extern_token, lib_name); - try list.append(&node.base); - return node; - } + fn createToCtxLiteral(self: &Parser, arena: &mem.Allocator, opt_ctx: &const OptionalCtx, comptime T: type, token: &const Token) !&T { + const node = try self.createLiteral(arena, T, token); + opt_ctx.store(&node.base); - fn createAttachTestDecl(self: &Parser, arena: &mem.Allocator, list: &ArrayList(&ast.Node), - test_token: &const Token, name: &ast.Node, block: &ast.NodeBlock) !&ast.NodeTestDecl - { - const node = try self.createTestDecl(arena, test_token, name, block); - try list.append(&node.base); return node; } - fn parseError(self: &Parser, stack: &ArrayList(State), token: &const Token, comptime fmt: []const u8, args: ...) !void { - // Before reporting an error. We pop the stack to see if our state was optional - self.revertIfOptional(stack) catch { - const loc = self.tokenizer.getTokenLocation(0, token); - warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); - warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]); - { - var i: usize = 0; - while (i < loc.column) : (i += 1) { - warn(" "); - } + fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) (error{ParseError}) { + const loc = self.tokenizer.getTokenLocation(0, token); + warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); + warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]); + { + var i: usize = 0; + while (i < loc.column) : (i += 1) { + warn(" "); } - { - const caret_count = token.end - token.start; - var i: usize = 0; - while (i < caret_count) : (i += 1) { - warn("~"); - } + } + { + const caret_count = token.end - token.start; + var i: usize = 0; + while (i < caret_count) : (i += 1) { + warn("~"); } - warn("\n"); - return error.ParseError; - }; + } + warn("\n"); + return error.ParseError; } - fn eatToken(self: &Parser, stack: &ArrayList(State), id: @TagType(Token.Id)) !?Token { + fn expectToken(self: &Parser, id: @TagType(Token.Id)) !Token { const token = self.getNextToken(); if (token.id != id) { - try self.parseError(stack, token, "expected {}, found {}", @tagName(id), @tagName(token.id)); - return null; + return self.parseError(token, "expected {}, found {}", @tagName(id), @tagName(token.id)); } return token; } - fn revertIfOptional(self: &Parser, stack: &ArrayList(State)) !void { - while (stack.popOrNull()) |state| { - switch (state) { - State.Optional => |revert| { - *self = revert.parser; - *self.tokenizer = revert.tokenizer; - *revert.ptr = null; - return; - }, - else => { } - } + fn eatToken(self: &Parser, id: @TagType(Token.Id)) ?Token { + if (self.isPeekToken(id)) { + return self.getNextToken(); } - - return error.NoOptionalStateFound; + return null; } fn putBackToken(self: &Parser, token: &const Token) void { @@ -3006,6 +3172,12 @@ pub const Parser = struct { } } + fn isPeekToken(self: &Parser, id: @TagType(Token.Id)) bool { + const token = self.getNextToken(); + defer self.putBackToken(token); + return id == token.id; + } + const RenderAstFrame = struct { node: &ast.Node, indent: usize, @@ -3040,7 +3212,6 @@ pub const Parser = struct { const RenderState = union(enum) { TopLevelDecl: &ast.Node, - FnProtoRParen: &ast.NodeFnProto, ParamDecl: &ast.Node, Text: []const u8, Expression: &ast.Node, @@ -3118,6 +3289,9 @@ pub const Parser = struct { }, ast.Node.Id.StructField => { const field = @fieldParentPtr(ast.NodeStructField, "base", decl); + if (field.visib_token) |visib_token| { + try stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); + } try stream.print("{}: ", self.tokenizer.getTokenSlice(field.name_token)); try stack.append(RenderState { .Expression = field.type_expr}); }, @@ -3179,13 +3353,13 @@ pub const Parser = struct { try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(comptime_token) }); } - if (var_decl.extern_token) |extern_token| { + if (var_decl.extern_export_token) |extern_export_token| { if (var_decl.lib_name != null) { try stack.append(RenderState { .Text = " " }); try stack.append(RenderState { .Expression = ??var_decl.lib_name }); } try stack.append(RenderState { .Text = " " }); - try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(extern_token) }); + try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(extern_export_token) }); } if (var_decl.visib_token) |visib_token| { @@ -3217,7 +3391,7 @@ pub const Parser = struct { RenderState.Expression => |base| switch (base.id) { ast.Node.Id.Identifier => { const identifier = @fieldParentPtr(ast.NodeIdentifier, "base", base); - try stream.print("{}", self.tokenizer.getTokenSlice(identifier.name_token)); + try stream.print("{}", self.tokenizer.getTokenSlice(identifier.token)); }, ast.Node.Id.Block => { const block = @fieldParentPtr(ast.NodeBlock, "base", base); @@ -3285,7 +3459,7 @@ pub const Parser = struct { } if (suspend_node.payload) |payload| { - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); try stack.append(RenderState { .Text = " " }); } }, @@ -3296,7 +3470,7 @@ pub const Parser = struct { if (prefix_op_node.op == ast.NodeInfixOp.InfixOp.Catch) { if (prefix_op_node.op.Catch) |payload| { try stack.append(RenderState { .Text = " " }); - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); } try stack.append(RenderState { .Text = " catch " }); } else { @@ -3440,50 +3614,70 @@ pub const Parser = struct { try stack.append(RenderState { .Expression = suffix_op.lhs }); }, ast.NodeSuffixOp.SuffixOp.StructInitializer => |field_inits| { - try stack.append(RenderState { .Text = " }"}); + if (field_inits.len == 0) { + try stack.append(RenderState { .Text = "{}" }); + try stack.append(RenderState { .Expression = suffix_op.lhs }); + continue; + } + try stack.append(RenderState { .Text = "}"}); + try stack.append(RenderState.PrintIndent); + try stack.append(RenderState { .Indent = indent }); var i = field_inits.len; while (i != 0) { i -= 1; const field_init = field_inits.at(i); + try stack.append(RenderState { .Text = ",\n" }); try stack.append(RenderState { .FieldInitializer = field_init }); - try stack.append(RenderState { .Text = " " }); - if (i != 0) { - try stack.append(RenderState { .Text = "," }); - } + try stack.append(RenderState.PrintIndent); } - try stack.append(RenderState { .Text = "{"}); + try stack.append(RenderState { .Indent = indent + indent_delta }); + try stack.append(RenderState { .Text = " {\n"}); try stack.append(RenderState { .Expression = suffix_op.lhs }); }, ast.NodeSuffixOp.SuffixOp.ArrayInitializer => |exprs| { - try stack.append(RenderState { .Text = " }"}); + if (exprs.len == 0) { + try stack.append(RenderState { .Text = "{}" }); + try stack.append(RenderState { .Expression = suffix_op.lhs }); + continue; + } + try stack.append(RenderState { .Text = "}"}); + try stack.append(RenderState.PrintIndent); + try stack.append(RenderState { .Indent = indent }); var i = exprs.len; while (i != 0) { i -= 1; const expr = exprs.at(i); + try stack.append(RenderState { .Text = ",\n" }); try stack.append(RenderState { .Expression = expr }); - try stack.append(RenderState { .Text = " " }); - if (i != 0) { - try stack.append(RenderState { .Text = "," }); - } + try stack.append(RenderState.PrintIndent); } - try stack.append(RenderState { .Text = "{"}); + try stack.append(RenderState { .Indent = indent + indent_delta }); + try stack.append(RenderState { .Text = " {\n"}); try stack.append(RenderState { .Expression = suffix_op.lhs }); }, } }, ast.Node.Id.ControlFlowExpression => { const flow_expr = @fieldParentPtr(ast.NodeControlFlowExpression, "base", base); + + if (flow_expr.rhs) |rhs| { + try stack.append(RenderState { .Expression = rhs }); + try stack.append(RenderState { .Text = " " }); + } + switch (flow_expr.kind) { - ast.NodeControlFlowExpression.Kind.Break => |maybe_blk_token| { + ast.NodeControlFlowExpression.Kind.Break => |maybe_label| { try stream.print("break"); - if (maybe_blk_token) |blk_token| { - try stream.print(" :{}", self.tokenizer.getTokenSlice(blk_token)); + if (maybe_label) |label| { + try stream.print(" :"); + try stack.append(RenderState { .Expression = label }); } }, - ast.NodeControlFlowExpression.Kind.Continue => |maybe_blk_token| { + ast.NodeControlFlowExpression.Kind.Continue => |maybe_label| { try stream.print("continue"); - if (maybe_blk_token) |blk_token| { - try stream.print(" :{}", self.tokenizer.getTokenSlice(blk_token)); + if (maybe_label) |label| { + try stream.print(" :"); + try stack.append(RenderState { .Expression = label }); } }, ast.NodeControlFlowExpression.Kind.Return => { @@ -3491,25 +3685,20 @@ pub const Parser = struct { }, } - - if (flow_expr.rhs) |rhs| { - try stream.print(" "); - try stack.append(RenderState { .Expression = rhs }); - } }, ast.Node.Id.Payload => { const payload = @fieldParentPtr(ast.NodePayload, "base", base); try stack.append(RenderState { .Text = "|"}); - try stack.append(RenderState { .Expression = &payload.error_symbol.base }); + try stack.append(RenderState { .Expression = payload.error_symbol }); try stack.append(RenderState { .Text = "|"}); }, ast.Node.Id.PointerPayload => { const payload = @fieldParentPtr(ast.NodePointerPayload, "base", base); try stack.append(RenderState { .Text = "|"}); - try stack.append(RenderState { .Expression = &payload.value_symbol.base }); + try stack.append(RenderState { .Expression = payload.value_symbol }); - if (payload.is_ptr) { - try stack.append(RenderState { .Text = "*"}); + if (payload.ptr_token) |ptr_token| { + try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(ptr_token) }); } try stack.append(RenderState { .Text = "|"}); @@ -3519,14 +3708,14 @@ pub const Parser = struct { try stack.append(RenderState { .Text = "|"}); if (payload.index_symbol) |index_symbol| { - try stack.append(RenderState { .Expression = &index_symbol.base }); + try stack.append(RenderState { .Expression = index_symbol }); try stack.append(RenderState { .Text = ", "}); } - try stack.append(RenderState { .Expression = &payload.value_symbol.base }); + try stack.append(RenderState { .Expression = payload.value_symbol }); - if (payload.is_ptr) { - try stack.append(RenderState { .Text = "*"}); + if (payload.ptr_token) |ptr_token| { + try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(ptr_token) }); } try stack.append(RenderState { .Text = "|"}); @@ -3607,6 +3796,14 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = fields_and_decls[i]; + switch (node.id) { + ast.Node.Id.StructField, + ast.Node.Id.UnionTag, + ast.Node.Id.EnumTag => { + try stack.append(RenderState { .Text = "," }); + }, + else => { } + } try stack.append(RenderState { .TopLevelDecl = node}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { @@ -3621,18 +3818,6 @@ pub const Parser = struct { break :blk "\n"; }, }); - - if (i != 0) { - const prev_node = fields_and_decls[i - 1]; - switch (prev_node.id) { - ast.Node.Id.StructField, - ast.Node.Id.UnionTag, - ast.Node.Id.EnumTag => { - try stack.append(RenderState { .Text = "," }); - }, - else => { } - } - } } try stack.append(RenderState { .Indent = indent + indent_delta}); try stack.append(RenderState { .Text = "{"}); @@ -3661,7 +3846,8 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = decls[i]; - try stack.append(RenderState { .Expression = &node.base}); + try stack.append(RenderState { .Text = "," }); + try stack.append(RenderState { .Expression = node }); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Text = blk: { @@ -3675,10 +3861,6 @@ pub const Parser = struct { break :blk "\n"; }, }); - - if (i != 0) { - try stack.append(RenderState { .Text = "," }); - } } try stack.append(RenderState { .Indent = indent + indent_delta}); try stack.append(RenderState { .Text = "{"}); @@ -3726,8 +3908,10 @@ pub const Parser = struct { }, } - if (fn_proto.align_expr != null) { - @panic("TODO"); + if (fn_proto.align_expr) |align_expr| { + try stack.append(RenderState { .Text = ") " }); + try stack.append(RenderState { .Expression = align_expr}); + try stack.append(RenderState { .Text = "align(" }); } try stack.append(RenderState { .Text = ") " }); @@ -3763,9 +3947,9 @@ pub const Parser = struct { try stack.append(RenderState { .Text = " " }); try stack.append(RenderState { .Expression = lib_name }); } - if (fn_proto.extern_token) |extern_token| { + if (fn_proto.extern_export_inline_token) |extern_export_inline_token| { try stack.append(RenderState { .Text = " " }); - try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(extern_token) }); + try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(extern_export_inline_token) }); } if (fn_proto.visib_token) |visib_token| { @@ -3789,6 +3973,7 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = cases[i]; + try stack.append(RenderState { .Text = ","}); try stack.append(RenderState { .Expression = &node.base}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { @@ -3803,10 +3988,6 @@ pub const Parser = struct { break :blk "\n"; }, }); - - if (i != 0) { - try stack.append(RenderState { .Text = "," }); - } } try stack.append(RenderState { .Indent = indent + indent_delta}); try stack.append(RenderState { .Text = ") {"}); @@ -3818,7 +3999,7 @@ pub const Parser = struct { try stack.append(RenderState { .Expression = switch_case.expr }); if (switch_case.payload) |payload| { try stack.append(RenderState { .Text = " " }); - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); } try stack.append(RenderState { .Text = " => "}); @@ -3829,7 +4010,8 @@ pub const Parser = struct { try stack.append(RenderState { .Expression = items[i] }); if (i != 0) { - try stack.append(RenderState { .Text = ", " }); + try stack.append(RenderState.PrintIndent); + try stack.append(RenderState { .Text = ",\n" }); } } }, @@ -3859,7 +4041,7 @@ pub const Parser = struct { if (else_node.payload) |payload| { try stack.append(RenderState { .Text = " " }); - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); } }, ast.Node.Id.While => { @@ -3904,7 +4086,7 @@ pub const Parser = struct { } if (while_node.payload) |payload| { - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); try stack.append(RenderState { .Text = " " }); } @@ -3947,7 +4129,7 @@ pub const Parser = struct { } if (for_node.payload) |payload| { - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); try stack.append(RenderState { .Text = " " }); } @@ -3980,7 +4162,7 @@ pub const Parser = struct { if (@"else".payload) |payload| { try stack.append(RenderState { .Text = " " }); - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); } try stack.append(RenderState { .Text = " " }); @@ -3994,7 +4176,7 @@ pub const Parser = struct { try stack.append(RenderState { .Text = " " }); if (if_node.payload) |payload| { - try stack.append(RenderState { .Expression = &payload.base }); + try stack.append(RenderState { .Expression = payload }); try stack.append(RenderState { .Text = " " }); } @@ -4006,12 +4188,10 @@ pub const Parser = struct { const asm_node = @fieldParentPtr(ast.NodeAsm, "base", base); try stream.print("{} ", self.tokenizer.getTokenSlice(asm_node.asm_token)); - if (asm_node.is_volatile) { - try stream.write("volatile "); + if (asm_node.volatile_token) |volatile_token| { + try stream.print("{} ", self.tokenizer.getTokenSlice(volatile_token)); } - try stream.print("({}", self.tokenizer.getTokenSlice(asm_node.template)); - try stack.append(RenderState { .Indent = indent }); try stack.append(RenderState { .Text = ")" }); { @@ -4019,7 +4199,7 @@ pub const Parser = struct { var i = cloppers.len; while (i != 0) { i -= 1; - try stack.append(RenderState { .Expression = &cloppers[i].base }); + try stack.append(RenderState { .Expression = cloppers[i] }); if (i != 0) { try stack.append(RenderState { .Text = ", " }); @@ -4088,6 +4268,8 @@ pub const Parser = struct { try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Indent = indent + indent_delta}); try stack.append(RenderState { .Text = "\n" }); + try stack.append(RenderState { .Expression = asm_node.template }); + try stack.append(RenderState { .Text = "(" }); }, ast.Node.Id.AsmInput => { const asm_input = @fieldParentPtr(ast.NodeAsmInput, "base", base); @@ -4095,9 +4277,9 @@ pub const Parser = struct { try stack.append(RenderState { .Text = ")"}); try stack.append(RenderState { .Expression = asm_input.expr}); try stack.append(RenderState { .Text = " ("}); - try stack.append(RenderState { .Expression = &asm_input.constraint.base}); + try stack.append(RenderState { .Expression = asm_input.constraint }); try stack.append(RenderState { .Text = "] "}); - try stack.append(RenderState { .Expression = &asm_input.symbolic_name.base}); + try stack.append(RenderState { .Expression = asm_input.symbolic_name }); try stack.append(RenderState { .Text = "["}); }, ast.Node.Id.AsmOutput => { @@ -4114,9 +4296,9 @@ pub const Parser = struct { }, } try stack.append(RenderState { .Text = " ("}); - try stack.append(RenderState { .Expression = &asm_output.constraint.base}); + try stack.append(RenderState { .Expression = asm_output.constraint }); try stack.append(RenderState { .Text = "] "}); - try stack.append(RenderState { .Expression = &asm_output.symbolic_name.base}); + try stack.append(RenderState { .Expression = asm_output.symbolic_name }); try stack.append(RenderState { .Text = "["}); }, @@ -4129,26 +4311,6 @@ pub const Parser = struct { ast.Node.Id.TestDecl, ast.Node.Id.ParamDecl => unreachable, }, - RenderState.FnProtoRParen => |fn_proto| { - try stream.print(")"); - if (fn_proto.align_expr != null) { - @panic("TODO"); - } - try stream.print(" "); - if (fn_proto.body_node) |body_node| { - try stack.append(RenderState { .Expression = body_node}); - try stack.append(RenderState { .Text = " "}); - } - switch (fn_proto.return_type) { - ast.NodeFnProto.ReturnType.Explicit => |node| { - try stack.append(RenderState { .Expression = node}); - }, - ast.NodeFnProto.ReturnType.InferErrorSet => |node| { - try stream.print("!"); - try stack.append(RenderState { .Expression = node}); - }, - } - }, RenderState.Statement => |base| { if (base.comment) |comment| { for (comment.lines.toSliceConst()) |line_token| { @@ -4441,10 +4603,10 @@ test "zig fmt: precedence" { \\ (a!b)(); \\ !a!b; \\ !(a!b); - \\ !a{ }; - \\ !(a{ }); - \\ a + b{ }; - \\ (a + b){ }; + \\ !a{}; + \\ !(a{}); + \\ a + b{}; + \\ (a + b){}; \\ a << b + c; \\ (a << b) + c; \\ a & b << c; @@ -4502,10 +4664,20 @@ test "zig fmt: var type" { ); } -test "zig fmt: extern function" { +test "zig fmt: functions" { try testCanonical( \\extern fn puts(s: &const u8) c_int; \\extern "c" fn puts(s: &const u8) c_int; + \\export fn puts(s: &const u8) c_int; + \\inline fn puts(s: &const u8) c_int; + \\pub extern fn puts(s: &const u8) c_int; + \\pub extern "c" fn puts(s: &const u8) c_int; + \\pub export fn puts(s: &const u8) c_int; + \\pub inline fn puts(s: &const u8) c_int; + \\pub extern fn puts(s: &const u8) align(2 + 2) c_int; + \\pub extern "c" fn puts(s: &const u8) align(2 + 2) c_int; + \\pub export fn puts(s: &const u8) align(2 + 2) c_int; + \\pub inline fn puts(s: &const u8) align(2 + 2) c_int; \\ ); } @@ -4565,26 +4737,27 @@ test "zig fmt: struct declaration" { \\const S = struct { \\ const Self = this; \\ f1: u8, + \\ pub f3: u8, \\ \\ fn method(self: &Self) Self { \\ return *self; \\ } \\ - \\ f2: u8 + \\ f2: u8, \\}; \\ \\const Ps = packed struct { \\ a: u8, - \\ b: u8, + \\ pub b: u8, \\ - \\ c: u8 + \\ c: u8, \\}; \\ \\const Es = extern struct { \\ a: u8, - \\ b: u8, + \\ pub b: u8, \\ - \\ c: u8 + \\ c: u8, \\}; \\ ); @@ -4594,25 +4767,25 @@ test "zig fmt: enum declaration" { try testCanonical( \\const E = enum { \\ Ok, - \\ SomethingElse = 0 + \\ SomethingElse = 0, \\}; \\ \\const E2 = enum(u8) { \\ Ok, \\ SomethingElse = 255, - \\ SomethingThird + \\ SomethingThird, \\}; \\ \\const Ee = extern enum { \\ Ok, \\ SomethingElse, - \\ SomethingThird + \\ SomethingThird, \\}; \\ \\const Ep = packed enum { \\ Ok, \\ SomethingElse, - \\ SomethingThird + \\ SomethingThird, \\}; \\ ); @@ -4624,35 +4797,35 @@ test "zig fmt: union declaration" { \\ Int: u8, \\ Float: f32, \\ None, - \\ Bool: bool + \\ Bool: bool, \\}; \\ \\const Ue = union(enum) { \\ Int: u8, \\ Float: f32, \\ None, - \\ Bool: bool + \\ Bool: bool, \\}; \\ \\const E = enum { \\ Int, \\ Float, \\ None, - \\ Bool + \\ Bool, \\}; \\ \\const Ue2 = union(E) { \\ Int: u8, \\ Float: f32, \\ None, - \\ Bool: bool + \\ Bool: bool, \\}; \\ \\const Eu = extern union { \\ Int: u8, \\ Float: f32, \\ None, - \\ Bool: bool + \\ Bool: bool, \\}; \\ ); @@ -4664,7 +4837,7 @@ test "zig fmt: error set declaration" { \\ A, \\ B, \\ - \\ C + \\ C, \\}; \\ ); @@ -4673,9 +4846,15 @@ test "zig fmt: error set declaration" { test "zig fmt: arrays" { try testCanonical( \\test "test array" { - \\ const a: [2]u8 = [2]u8{ 1, 2 }; - \\ const a: [2]u8 = []u8{ 1, 2 }; - \\ const a: [0]u8 = []u8{ }; + \\ const a: [2]u8 = [2]u8 { + \\ 1, + \\ 2, + \\ }; + \\ const a: [2]u8 = []u8 { + \\ 1, + \\ 2, + \\ }; + \\ const a: [0]u8 = []u8{}; \\} \\ ); @@ -4683,10 +4862,18 @@ test "zig fmt: arrays" { test "zig fmt: container initializers" { try testCanonical( - \\const a1 = []u8{ }; - \\const a2 = []u8{ 1, 2, 3, 4 }; - \\const s1 = S{ }; - \\const s2 = S{ .a = 1, .b = 2 }; + \\const a1 = []u8{}; + \\const a2 = []u8 { + \\ 1, + \\ 2, + \\ 3, + \\ 4, + \\}; + \\const s1 = S{}; + \\const s2 = S { + \\ .a = 1, + \\ .b = 2, + \\}; \\ ); } @@ -4730,30 +4917,34 @@ test "zig fmt: switch" { \\ switch (0) { \\ 0 => {}, \\ 1 => unreachable, - \\ 2, 3 => {}, + \\ 2, + \\ 3 => {}, \\ 4 ... 7 => {}, \\ 1 + 4 * 3 + 22 => {}, \\ else => { \\ const a = 1; \\ const b = a; - \\ } + \\ }, \\ } \\ \\ const res = switch (0) { \\ 0 => 0, \\ 1 => 2, - \\ else => 4 + \\ 1 => a = 4, + \\ else => 4, \\ }; \\ \\ const Union = union(enum) { \\ Int: i64, - \\ Float: f64 + \\ Float: f64, \\ }; \\ - \\ const u = Union{ .Int = 0 }; + \\ const u = Union { + \\ .Int = 0, + \\ }; \\ switch (u) { \\ Union.Int => |int| {}, - \\ Union.Float => |*float| unreachable + \\ Union.Float => |*float| unreachable, \\ } \\} \\ @@ -4829,7 +5020,11 @@ test "zig fmt: while" { test "zig fmt: for" { try testCanonical( \\test "for" { - \\ const a = []u8{ 1, 2, 3 }; + \\ const a = []u8 { + \\ 1, + \\ 2, + \\ 3, + \\ }; \\ for (a) |v| { \\ continue; \\ } @@ -4887,6 +5082,7 @@ test "zig fmt: if" { \\ } \\ \\ const is_world_broken = if (10 < 0) true else false; + \\ const some_number = 1 + if (10 < 0) 2 else 3; \\ \\ const a: ?u8 = 10; \\ const b: ?u8 = null; @@ -5000,6 +5196,7 @@ test "zig fmt: inline asm" { test "zig fmt: coroutines" { try testCanonical( \\async fn simpleAsyncFn() void { + \\ const a = async a.b(); \\ x += 1; \\ suspend; \\ x += 1; @@ -5047,3 +5244,22 @@ test "zig fmt: string identifier" { \\ ); } + +test "zig fmt: error return" { + try testCanonical( + \\fn err() error { + \\ call(); + \\ return error.InvalidArgs; + \\} + \\ + ); +} + +test "zig fmt: struct literals with fields on each line" { + try testCanonical( + \\var self = BufSet { + \\ .hash_map = BufSetHashMap.init(a), + \\}; + \\ + ); +} diff --git a/test/cases/fn.zig b/test/cases/fn.zig index e492f6036c..5388deac10 100644 --- a/test/cases/fn.zig +++ b/test/cases/fn.zig @@ -94,3 +94,20 @@ test "inline function call" { } fn add(a: i32, b: i32) i32 { return a + b; } + + +test "number literal as an argument" { + numberLiteralArg(3); + comptime numberLiteralArg(3); +} + +fn numberLiteralArg(a: var) void { + assert(a == 3); +} + +test "assign inline fn to const variable" { + const a = inlineFn; + a(); +} + +inline fn inlineFn() void { } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index bed5aa1b63..b22816a9a8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,15 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) void { + cases.add("assign inline fn to non-comptime var", + \\export fn entry() void { + \\ var a = b; + \\} + \\inline fn b() void { } + , + ".tmp_source.zig:2:5: error: functions marked inline must be stored in const or comptime var", + ".tmp_source.zig:4:8: note: declared here"); + cases.add("wrong type passed to @panic", \\export fn entry() void { \\ var e = error.Foo; @@ -1723,7 +1732,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\} \\ \\export fn entry() usize { return @sizeOf(@typeOf(bar)); } - , ".tmp_source.zig:10:16: error: parameter of type '(integer literal)' requires comptime"); + , ".tmp_source.zig:10:16: error: compiler bug: integer and float literals in var args function must be casted"); cases.add("assign too big number to u16", \\export fn foo() void { |
