A rudimentary lisp-like language
lisp
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add skeleton code structure

Scaffold the basic modules for the program, and clean up unnecessary zig-init
template code. Pretty sketchy, but it's late and I don't have much time tonight
so it will have to do.

chell 86cb9d8e b5a662db

+78 -215
-104
build.zig
··· 1 1 const std = @import("std"); 2 2 3 - // Although this function looks imperative, it does not perform the build 4 - // directly and instead it mutates the build graph (`b`) that will be then 5 - // executed by an external runner. The functions in `std.Build` implement a DSL 6 - // for defining build steps and express dependencies between them, allowing the 7 - // build runner to parallelize the build automatically (and the cache system to 8 - // know when a step doesn't need to be re-run). 9 3 pub fn build(b: *std.Build) void { 10 - // Standard target options allow the person running `zig build` to choose 11 - // what target to build for. Here we do not override the defaults, which 12 - // means any target is allowed, and the default is native. Other options 13 - // for restricting supported target set are available. 14 4 const target = b.standardTargetOptions(.{}); 15 - // Standard optimization options allow the person running `zig build` to select 16 - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not 17 - // set a preferred release mode, allowing the user to decide how to optimize. 18 5 const optimize = b.standardOptimizeOption(.{}); 19 - // It's also possible to define more custom flags to toggle optional features 20 - // of this build script using `b.option()`. All defined flags (including 21 - // target and optimize options) will be listed when running `zig build --help` 22 - // in this directory. 23 6 24 - // This creates a module, which represents a collection of source files alongside 25 - // some compilation options, such as optimization mode and linked system libraries. 26 - // Zig modules are the preferred way of making Zig code available to consumers. 27 - // addModule defines a module that we intend to make available for importing 28 - // to our consumers. We must give it a name because a Zig package can expose 29 - // multiple modules and consumers will need to be able to specify which 30 - // module they want to access. 31 7 const mod = b.addModule("rat_lisp", .{ 32 - // The root source file is the "entry point" of this module. Users of 33 - // this module will only be able to access public declarations contained 34 - // in this file, which means that if you have declarations that you 35 - // intend to expose to consumers that were defined in other files part 36 - // of this module, you will have to make sure to re-export them from 37 - // the root file. 38 8 .root_source_file = b.path("src/root.zig"), 39 - // Later on we'll use this module as the root module of a test executable 40 - // which requires us to specify a target. 41 9 .target = target, 42 10 }); 43 11 44 - // Here we define an executable. An executable needs to have a root module 45 - // which needs to expose a `main` function. While we could add a main function 46 - // to the module defined above, it's sometimes preferable to split business 47 - // logic and the CLI into two separate modules. 48 - // 49 - // If your goal is to create a Zig library for others to use, consider if 50 - // it might benefit from also exposing a CLI tool. A parser library for a 51 - // data serialization format could also bundle a CLI syntax checker, for example. 52 - // 53 - // If instead your goal is to create an executable, consider if users might 54 - // be interested in also being able to embed the core functionality of your 55 - // program in their own executable in order to avoid the overhead involved in 56 - // subprocessing your CLI tool. 57 - // 58 - // If neither case applies to you, feel free to delete the declaration you 59 - // don't need and to put everything under a single module. 60 12 const exe = b.addExecutable(.{ 61 13 .name = "rat_lisp", 62 14 .root_module = b.createModule(.{ 63 - // b.createModule defines a new module just like b.addModule but, 64 - // unlike b.addModule, it does not expose the module to consumers of 65 - // this package, which is why in this case we don't have to give it a name. 66 15 .root_source_file = b.path("src/main.zig"), 67 - // Target and optimization levels must be explicitly wired in when 68 - // defining an executable or library (in the root module), and you 69 - // can also hardcode a specific target for an executable or library 70 - // definition if desireable (e.g. firmware for embedded devices). 71 16 .target = target, 72 17 .optimize = optimize, 73 - // List of modules available for import in source files part of the 74 - // root module. 75 18 .imports = &.{ 76 - // Here "rat_lisp" is the name you will use in your source code to 77 - // import this module (e.g. `@import("rat_lisp")`). The name is 78 - // repeated because you are allowed to rename your imports, which 79 - // can be extremely useful in case of collisions (which can happen 80 - // importing modules from different packages). 81 19 .{ .name = "rat_lisp", .module = mod }, 82 20 }, 83 21 }), 84 22 }); 85 23 86 - // This declares intent for the executable to be installed into the 87 - // install prefix when running `zig build` (i.e. when executing the default 88 - // step). By default the install prefix is `zig-out/` but can be overridden 89 - // by passing `--prefix` or `-p`. 90 24 b.installArtifact(exe); 91 25 92 - // This creates a top level step. Top level steps have a name and can be 93 - // invoked by name when running `zig build` (e.g. `zig build run`). 94 - // This will evaluate the `run` step rather than the default step. 95 - // For a top level step to actually do something, it must depend on other 96 - // steps (e.g. a Run step, as we will see in a moment). 97 26 const run_step = b.step("run", "Run the app"); 98 27 99 - // This creates a RunArtifact step in the build graph. A RunArtifact step 100 - // invokes an executable compiled by Zig. Steps will only be executed by the 101 - // runner if invoked directly by the user (in the case of top level steps) 102 - // or if another step depends on it, so it's up to you to define when and 103 - // how this Run step will be executed. In our case we want to run it when 104 - // the user runs `zig build run`, so we create a dependency link. 105 28 const run_cmd = b.addRunArtifact(exe); 106 29 run_step.dependOn(&run_cmd.step); 107 30 108 - // By making the run step depend on the default step, it will be run from the 109 - // installation directory rather than directly from within the cache directory. 110 31 run_cmd.step.dependOn(b.getInstallStep()); 111 32 112 - // This allows the user to pass arguments to the application in the build 113 - // command itself, like this: `zig build run -- arg1 arg2 etc` 114 33 if (b.args) |args| { 115 34 run_cmd.addArgs(args); 116 35 } 117 36 118 - // Creates an executable that will run `test` blocks from the provided module. 119 - // Here `mod` needs to define a target, which is why earlier we made sure to 120 - // set the releative field. 121 37 const mod_tests = b.addTest(.{ 122 38 .root_module = mod, 123 39 }); 124 40 125 - // A run step that will run the test executable. 126 41 const run_mod_tests = b.addRunArtifact(mod_tests); 127 42 128 - // Creates an executable that will run `test` blocks from the executable's 129 - // root module. Note that test executables only test one module at a time, 130 - // hence why we have to create two separate ones. 131 43 const exe_tests = b.addTest(.{ 132 44 .root_module = exe.root_module, 133 45 }); 134 46 135 - // A run step that will run the second test executable. 136 47 const run_exe_tests = b.addRunArtifact(exe_tests); 137 48 138 - // A top level step for running all tests. dependOn can be called multiple 139 - // times and since the two run steps do not depend on one another, this will 140 - // make the two of them run in parallel. 141 49 const test_step = b.step("test", "Run tests"); 142 50 test_step.dependOn(&run_mod_tests.step); 143 51 test_step.dependOn(&run_exe_tests.step); 144 - 145 - // Just like flags, top level steps are also listed in the `--help` menu. 146 - // 147 - // The Zig build system is entirely implemented in userland, which means 148 - // that it cannot hook into private compiler APIs. All compilation work 149 - // orchestrated by the build system will result in other Zig compiler 150 - // subcommands being invoked with the right flags defined. You can observe 151 - // these invocations when one fails (or you pass a flag to increase 152 - // verbosity) to validate assumptions and diagnose problems. 153 - // 154 - // Lastly, the Zig build system is relatively simple and self-contained, 155 - // and reading its source code will allow you to master it. 156 52 }
+1 -70
build.zig.zon
··· 1 1 .{ 2 - // This is the default name used by packages depending on this one. For 3 - // example, when a user runs `zig fetch --save <url>`, this field is used 4 - // as the key in the `dependencies` table. Although the user can choose a 5 - // different name, most users will stick with this provided value. 6 - // 7 - // It is redundant to include "zig" in this name because it is already 8 - // within the Zig package namespace. 9 2 .name = .rat_lisp, 10 - // This is a [Semantic Version](https://semver.org/). 11 - // In a future version of Zig it will be used for package deduplication. 12 3 .version = "0.0.0", 13 - // Together with name, this represents a globally unique package 14 - // identifier. This field is generated by the Zig toolchain when the 15 - // package is first created, and then *never changes*. This allows 16 - // unambiguous detection of one package being an updated version of 17 - // another. 18 - // 19 - // When forking a Zig project, this id should be regenerated (delete the 20 - // field and run `zig build`) if the upstream project is still maintained. 21 - // Otherwise, the fork is *hostile*, attempting to take control over the 22 - // original project's identity. Thus it is recommended to leave the comment 23 - // on the following line intact, so that it shows up in code reviews that 24 - // modify the field. 25 4 .fingerprint = 0xee87fa05ccae38f0, // Changing this has security and trust implications. 26 - // Tracks the earliest Zig version that the package considers to be a 27 - // supported use case. 28 5 .minimum_zig_version = "0.15.2", 29 - // This field is optional. 30 - // Each dependency must either provide a `url` and `hash`, or a `path`. 31 - // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. 32 - // Once all dependencies are fetched, `zig build` no longer requires 33 - // internet connectivity. 34 - .dependencies = .{ 35 - // See `zig fetch --save <url>` for a command-line interface for adding dependencies. 36 - //.example = .{ 37 - // // When updating this field to a new URL, be sure to delete the corresponding 38 - // // `hash`, otherwise you are communicating that you expect to find the old hash at 39 - // // the new URL. If the contents of a URL change this will result in a hash mismatch 40 - // // which will prevent zig from using it. 41 - // .url = "https://example.com/foo.tar.gz", 42 - // 43 - // // This is computed from the file contents of the directory of files that is 44 - // // obtained after fetching `url` and applying the inclusion rules given by 45 - // // `paths`. 46 - // // 47 - // // This field is the source of truth; packages do not come from a `url`; they 48 - // // come from a `hash`. `url` is just one of many possible mirrors for how to 49 - // // obtain a package matching this `hash`. 50 - // // 51 - // // Uses the [multihash](https://multiformats.io/multihash/) format. 52 - // .hash = "...", 53 - // 54 - // // When this is provided, the package is found in a directory relative to the 55 - // // build root. In this case the package's hash is irrelevant and therefore not 56 - // // computed. This field and `url` are mutually exclusive. 57 - // .path = "foo", 58 - // 59 - // // When this is set to `true`, a package is declared to be lazily 60 - // // fetched. This makes the dependency only get fetched if it is 61 - // // actually used. 62 - // .lazy = false, 63 - //}, 64 - }, 65 - // Specifies the set of files and directories that are included in this package. 66 - // Only files and directories listed here are included in the `hash` that 67 - // is computed for this package. Only files listed here will remain on disk 68 - // when using the zig package manager. As a rule of thumb, one should list 69 - // files required for compilation plus any license(s). 70 - // Paths are relative to the build root. Use the empty string (`""`) to refer to 71 - // the build root itself. 72 - // A directory listed here means that all files within, recursively, are included. 6 + .dependencies = .{}, 73 7 .paths = .{ 74 8 "build.zig", 75 9 "build.zig.zon", 76 10 "src", 77 - // For example... 78 - //"LICENSE", 79 - //"README.md", 80 11 }, 81 12 }
+2
src/eval.zig
··· 1 + //! TODO: Tree-walking s-expr evaluator. 2 + const std = @import("std");
+26
src/lex.zig
··· 1 + //! TODO: Lexing functionality. Intentionally separate from parsing. 2 + const std = @import("std"); 3 + 4 + // Unresolved questions: OOP stateful nonsense or can we just have A Function? 5 + 6 + /// TODO: Process a code string into a flat array of tokens. 7 + pub fn lex(allocator: std.mem.Allocator, code: []const u8) !std.ArrayList(Lexeme) { 8 + _ = allocator; 9 + _ = code; 10 + return error.Todo; 11 + } 12 + 13 + pub const Lexeme = struct { 14 + text: []const u8, 15 + kind: Kind, 16 + 17 + const Kind = enum { 18 + l_paren, 19 + r_paren, 20 + identifier, 21 + number, 22 + string, 23 + inert, 24 + ignore, 25 + }; 26 + };
+10 -23
src/main.zig
··· 1 1 const std = @import("std"); 2 - const rat_lisp = @import("rat_lisp"); 2 + const rl = @import("rat_lisp"); 3 3 4 4 pub fn main() !void { 5 - // Prints to stderr, ignoring potential errors. 6 - std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); 7 - try rat_lisp.bufferedPrint(); 8 - } 9 - 10 - test "simple test" { 11 - const gpa = std.testing.allocator; 12 - var list: std.ArrayList(i32) = .empty; 13 - defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! 14 - try list.append(gpa, 42); 15 - try std.testing.expectEqual(@as(i32, 42), list.pop()); 16 - } 17 - 18 - test "fuzz example" { 19 - const Context = struct { 20 - fn testOne(context: @This(), input: []const u8) anyerror!void { 21 - _ = context; 22 - // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! 23 - try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); 24 - } 25 - }; 26 - try std.testing.fuzz(Context{}, Context.testOne, .{}); 5 + var gpa: std.heap.DebugAllocator(.{}) = .init; 6 + const allocator = gpa.allocator(); 7 + // TODO: somehow turn this into a proper REPL 8 + // TODO: the prompt for the repl should be `:>` ofc 9 + const code = @embedFile("example.rats"); 10 + _ = try rl.read(allocator, code); 11 + // TODO: eval 12 + // TODO: print 13 + // TODO: loop! 27 14 }
+30
src/parse.zig
··· 1 + //! TODO: Parsing functionality. Intentionally separate from lexing. 2 + const std = @import("std"); 3 + 4 + const lex = @import("lex.zig"); 5 + 6 + pub fn parse(allocator: std.mem.Allocator, lexemes: std.ArrayList(lex.Lexeme)) !Ast { 7 + _ = allocator; 8 + _ = lexemes; 9 + return error.Todo; 10 + } 11 + 12 + // TODO: AST 13 + // the AST will be a flat array(list?) of nodes, indexed by a handle type. nodes that contain other 14 + // nodes will merely contain these handles. 15 + 16 + pub const Ast = struct { 17 + nodes: std.ArrayList(Node), 18 + 19 + pub const Node = union(enum) { 20 + pair: struct { car: Handle, cdr: Handle }, 21 + boolean: bool, 22 + number: f64, 23 + symbol: []const u8, // TODO: interning! 24 + string: []const u8, // TODO: interning? 25 + inert, 26 + ignore, 27 + 28 + pub const Handle = enum(u32) { _ }; 29 + }; 30 + };
+9 -18
src/root.zig
··· 1 - //! By convention, root.zig is the root source file when making a library. 1 + //! TODO: Root library module. 2 2 const std = @import("std"); 3 3 4 - pub fn bufferedPrint() !void { 5 - // Stdout is for the actual output of your application, for example if you 6 - // are implementing gzip, then only the compressed bytes should be sent to 7 - // stdout, not any debugging messages. 8 - var stdout_buffer: [1024]u8 = undefined; 9 - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); 10 - const stdout = &stdout_writer.interface; 4 + const lex = @import("lex.zig"); 5 + const parse = @import("parse.zig"); 6 + const eval = @import("eval.zig"); 11 7 12 - try stdout.print("Run `zig build test` to run the tests.\n", .{}); 8 + // TODO: io/reader function(s) 13 9 14 - try stdout.flush(); // Don't forget to flush! 15 - } 16 - 17 - pub fn add(a: i32, b: i32) i32 { 18 - return a + b; 19 - } 20 - 21 - test "basic add functionality" { 22 - try std.testing.expect(add(3, 7) == 10); 10 + /// TODO: Lex and parse a string of source code and return the AST. 11 + pub fn read(allocator: std.mem.Allocator, code: []const u8) !parse.Ast { 12 + const lexemes = try lex.lex(allocator, code); 13 + return parse.parse(allocator, lexemes); 23 14 }