summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNic Gaffney <gaffney_nic@protonmail.com>2024-06-27 01:58:31 -0500
committerNic Gaffney <gaffney_nic@protonmail.com>2024-06-27 01:58:31 -0500
commit36e990c5bdfffb145b7255b8159d3ac879344996 (patch)
treecd6ca3ef975cd6657ee3cd92d9938ebcfa14846e
downloadcalico-36e990c5bdfffb145b7255b8159d3ac879344996.tar.gz
Initial commit
-rw-r--r--.gitignore3
-rw-r--r--build.zig38
-rw-r--r--build.zig.zon67
-rw-r--r--examples/test1.gft1
-rw-r--r--src/main.zig64
-rw-r--r--src/tokenize.zig66
6 files changed, 239 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9ae02d6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+out/
+zig-out/
+zig-cache/
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..f04131f
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,38 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+
+ const optimize = b.standardOptimizeOption(.{});
+
+ const exe = b.addExecutable(.{
+ .name = "compiler",
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..39f7607
--- /dev/null
+++ b/build.zig.zon
@@ -0,0 +1,67 @@
+.{
+ .name = "compiler",
+ // This is a [Semantic Version](https://semver.org/).
+ // In a future version of Zig it will be used for package deduplication.
+ .version = "0.0.0",
+
+ // This field is optional.
+ // This is currently advisory only; Zig does not yet do anything
+ // with this value.
+ //.minimum_zig_version = "0.11.0",
+
+ // This field is optional.
+ // Each dependency must either provide a `url` and `hash`, or a `path`.
+ // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+ // Once all dependencies are fetched, `zig build` no longer requires
+ // internet connectivity.
+ .dependencies = .{
+ // See `zig fetch --save <url>` for a command-line interface for adding dependencies.
+ //.example = .{
+ // // When updating this field to a new URL, be sure to delete the corresponding
+ // // `hash`, otherwise you are communicating that you expect to find the old hash at
+ // // the new URL.
+ // .url = "https://example.com/foo.tar.gz",
+ //
+ // // This is computed from the file contents of the directory of files that is
+ // // obtained after fetching `url` and applying the inclusion rules given by
+ // // `paths`.
+ // //
+ // // This field is the source of truth; packages do not come from a `url`; they
+ // // come from a `hash`. `url` is just one of many possible mirrors for how to
+ // // obtain a package matching this `hash`.
+ // //
+ // // Uses the [multihash](https://multiformats.io/multihash/) format.
+ // .hash = "...",
+ //
+ // // When this is provided, the package is found in a directory relative to the
+ // // build root. In this case the package's hash is irrelevant and therefore not
+ // // computed. This field and `url` are mutually exclusive.
+ // .path = "foo",
+
+ // // When this is set to `true`, a package is declared to be lazily
+ // // fetched. This makes the dependency only get fetched if it is
+ // // actually used.
+ // .lazy = false,
+ //},
+ },
+
+ // Specifies the set of files and directories that are included in this package.
+ // Only files and directories listed here are included in the `hash` that
+ // is computed for this package.
+ // Paths are relative to the build root. Use the empty string (`""`) to refer to
+ // the build root itself.
+ // A directory listed here means that all files within, recursively, are included.
+ .paths = .{
+ // This makes *all* files, recursively, included in this package. It is generally
+ // better to explicitly list the files and directories instead, to insure that
+ // fetching from tarballs, file system paths, and version control all result
+ // in the same contents hash.
+ "",
+ // For example...
+ //"build.zig",
+ //"build.zig.zon",
+ //"src",
+ //"LICENSE",
+ //"README.md",
+ },
+}
diff --git a/examples/test1.gft b/examples/test1.gft
new file mode 100644
index 0000000..16f566f
--- /dev/null
+++ b/examples/test1.gft
@@ -0,0 +1 @@
+return 69;
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..18239b1
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,64 @@
+const std = @import("std");
+const tok = @import("tokenize.zig");
+
+const gftCompilerError = error{NoInputFile};
+
+pub fn main() !void {
+ if (std.os.argv.len != 2) return gftCompilerError.NoInputFile;
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ var args = std.process.args();
+ _ = args.skip();
+ const inputFileName = args.next();
+ const inputFile = try std.fs.cwd().openFile(inputFileName.?, .{});
+ defer inputFile.close();
+
+ std.fs.cwd().makeDir("out") catch |err| {
+ if (err != error.PathAlreadyExists) return err;
+ };
+ const outfile = try std.fs.cwd().createFile("out/out.asm", .{});
+ const outWriter = outfile.writer();
+ defer outfile.close();
+
+ // Logic here to compile language
+ const all = try inputFile.readToEndAlloc(gpa.allocator(), 2048);
+ defer gpa.allocator().free(all);
+
+ const toks = try tok.tokenize(gpa.allocator(), all);
+ defer gpa.allocator().free(toks);
+ var tokIter = tok.TokenIterator{ .tokens = toks };
+ try outWriter.print("global _start:\n", .{});
+ while (tokIter.next()) |t| {
+ switch (t) {
+ .ret => {
+ const num = tokIter.next();
+ switch (num.?) {
+ .intLit => {},
+ else => break,
+ }
+ switch (tokIter.next().?) {
+ .semiCol => {},
+ else => break,
+ }
+ try outWriter.print(
+ \\ mov rax, 60
+ \\ mov rdi, {}
+ \\ syscall
+ \\
+ , .{num.?.intLit});
+ gpa.allocator().free(t.ret);
+ },
+ else => {},
+ }
+ }
+
+ const nasmargv = [_][]const u8{ "nasm", "-felf64", "out/out.asm" };
+ const nasmproc = try std.ChildProcess.run(.{ .argv = &nasmargv, .allocator = gpa.allocator() });
+ defer gpa.allocator().free(nasmproc.stdout);
+ defer gpa.allocator().free(nasmproc.stderr);
+
+ const ldargv = [_][]const u8{ "ld", "-o", "out/out", "out/out.o" };
+ const ldproc = try std.ChildProcess.run(.{ .argv = &ldargv, .allocator = gpa.allocator() });
+ defer gpa.allocator().free(ldproc.stdout);
+ defer gpa.allocator().free(ldproc.stderr);
+}
diff --git a/src/tokenize.zig b/src/tokenize.zig
new file mode 100644
index 0000000..b5d5d23
--- /dev/null
+++ b/src/tokenize.zig
@@ -0,0 +1,66 @@
+const std = @import("std");
+
+const TokenError = error{UnknownToken};
+
+const Token = union(enum) {
+ ret: []const u8,
+ intLit: i32,
+ semiCol: u8,
+ nil: void,
+};
+
+pub const TokenIterator = struct {
+ tokens: []const Token,
+ index: usize = 0,
+
+ pub fn next(self: *TokenIterator) ?Token {
+ defer self.*.index = self.*.index + 1;
+ if (self.*.index >= self.*.tokens.len) return null;
+ return self.*.tokens[self.*.index];
+ }
+};
+
+pub fn tokenize(allocator: std.mem.Allocator, buff: []const u8) ![]const Token {
+ var toks = std.ArrayList(Token).init(allocator);
+ defer toks.deinit();
+ var str = std.ArrayList(u8).init(allocator);
+ defer str.deinit();
+
+ var i: u32 = 0;
+ while (i < buff.len) {
+ switch (buff[i]) {
+ ' ', '\n', '\t' => {
+ i = i + 1;
+ continue;
+ },
+ '0'...'9' => {
+ while (std.ascii.isDigit(buff[i])) {
+ try str.append(buff[i]);
+ i = i + 1;
+ }
+ const num: i32 = try std.fmt.parseInt(i32, str.items, 10);
+ try toks.append(.{ .intLit = num });
+ str.deinit();
+ str = std.ArrayList(u8).init(allocator);
+ },
+ 'a'...'z', 'A'...'Z' => {
+ while (std.ascii.isAlphanumeric(buff[i])) {
+ try str.append(buff[i]);
+ i = i + 1;
+ }
+ try toks.append(.{ .ret = try str.toOwnedSlice() });
+ str.deinit();
+ str = std.ArrayList(u8).init(allocator);
+ },
+ ';' => {
+ i = i + 1;
+ try toks.append(.{ .semiCol = ';' });
+ },
+ '+', '-', '*', '/' => {
+ // Process operator
+ },
+ else => {},
+ }
+ }
+ return toks.toOwnedSlice();
+}