From 8b27c7385f83f3cea13a67bdd6a1a6f1ab988972 Mon Sep 17 00:00:00 2001 From: Enrico Lumetti Date: Sat, 29 May 2021 22:04:19 +0200 Subject: [PATCH] Implement header token parsing --- LICENSE | 2 +- README.md | 2 + build.zig | 27 +++++++ src/main.zig | 14 ++++ src/testUtil.zig | 36 +++++++++ src/zircon.zig | 190 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 build.zig create mode 100644 src/main.zig create mode 100644 src/testUtil.zig create mode 100644 src/zircon.zig diff --git a/LICENSE b/LICENSE index b0e20f5..16e5fd2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) All rights reserved. +Copyright (c) 2021 Enrico Lumetti All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index 66b898a..6af3759 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # zircon +Simple C11 compiler that uses ZIR as a target + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..4598e35 --- /dev/null +++ b/build.zig @@ -0,0 +1,27 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("zircon", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + 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); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..23b3270 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const fs = std.fs; +const zircon = @import("zircon.zig"); + +pub fn main() anyerror!void { + var cwd_path = try fs.realpathAlloc(std.heap.page_allocator, "."); + defer std.heap.page_allocator.free(cwd_path); + std.log.info("Running in {s}", .{cwd_path}); + + var f = try fs.cwd().openFile("test.c", fs.File.OpenFlags{ .read = true }); + defer f.close(); + + var pp = zircon.Preprocessor(fs.File.Reader).init(f.reader()); +} diff --git a/src/testUtil.zig b/src/testUtil.zig new file mode 100644 index 0000000..1061eb1 --- /dev/null +++ b/src/testUtil.zig @@ -0,0 +1,36 @@ +const zircon = @import("zircon.zig"); +const std = @import("std"); + +pub const TestStringReaderError = error { + EndOfString, + OutOfBound, +}; + +pub const TestStringReader = struct { + source: []const u8, + pos: usize, + + pub fn init(s: []const u8) TestStringReader { + return TestStringReader { + .source = s, + .pos = 0, + }; + } + + pub fn readByte(self: *TestStringReader) !u8 { + if (self.pos == self.source.len) { + return TestStringReaderError.EndOfString; + } + + var b = self.source[self.pos]; + self.pos += 1; + return b; + } + + pub fn seekBack(self: *TestStringReader, n: usize) !void { + if (n > self.pos) { + return TestStringReaderError.OutOfBound; + } + self.pos -= n; + } +}; diff --git a/src/zircon.zig b/src/zircon.zig new file mode 100644 index 0000000..08543e4 --- /dev/null +++ b/src/zircon.zig @@ -0,0 +1,190 @@ +const std = @import("std"); +const fs = std.fs; + +const SourceLoc = struct { + // source: []u8 + line: usize, + col: usize, + abs: usize, +}; + +const PPToken = struct { + // A.1.1 (6.4) + const Type = enum { + hHeaderName, + qHeaderName, + identifier, + ppNumber, + characterConstant, + stringLiteral, + punctuator, + nonBlank, + }; + start: SourceLoc, + end: SourceLoc, + kind: Type, +}; + +pub fn RewindableReader(comptime Seekable: type, comptime Reader: type, comptime getReaderFn: anytype, comptime seekByFn: anytype) type { + return struct { + const Self = @This(); + + reader: Reader, + source: Seekable, + + pub fn readByte(self: Self) !u8 { + self.reader.readByte(); + } + + pub fn seekBack(n: usize) !void { + seekByFn(self.source, -n); + self.reader = getReaderFn(self.source); + } + }; +} + +pub fn rewindableReaderFromFile(f: fs.File) RewindableReader(fs.File, fs.Reader, fs.File.reader, fs.File.seekBy) { + return RewindableReader(fs.File, fs.Reader, fs.File.reader, fs.File.seekBy) { + .reader = f.reader(), + .source = f, + }; +} + +pub fn Preprocessor(comptime RReader: type) type { + return struct { + const Self = @This(); + + reader: RReader, + loc: SourceLoc, + lastRead: u8, + + pub fn init(reader: RReader) Self { + return Self { + .reader = reader, + .lastRead = 0, + .loc = SourceLoc { + .line = 0, + .col = 0, + .abs = 0, + } + }; + } + + fn readByte(self: *Self) !u8 { + self.lastRead = try self.reader.readByte(); + self.loc.abs += 1; + + // TODO(enrico): support '\r\n' on windows? + if (self.lastRead == '\n') { + self.loc.col += 1; + self.loc.line = 0; + } else { + self.loc.col += 1; + } + + return self.lastRead; + } + + pub fn headerName(self: *Self) !?PPToken { + const rewindLoc = self.loc; + if (try self.headerNameDelim('<', '>')) |tok| { + return tok; + } + try self.rewindTo(rewindLoc); + if (try self.headerNameDelim('"', '"')) |tok| { + return tok; + } + return null; + } + + fn headerNameDelim(self: *Self, startDelimiter: u8, endDelimiter: u8) !?PPToken { + const tokenStart = self.loc; + if (!try self.expectChar(startDelimiter)) { + return null; + } + + while (true) { + // TODO(enrico): support '\r\n' on windows? + if (!try self.expectCharExceptAny(&.{'\n', endDelimiter})) { + break; + } + } + if (self.lastRead != endDelimiter) { + return null; + } + + return PPToken { + .kind = if (startDelimiter == '"') PPToken.Type.qHeaderName else PPToken.Type.hHeaderName, + .start = tokenStart, + .end = self.loc, + }; + } + + fn rewindTo(self: *Self, loc: SourceLoc) !void { + const diff = self.loc.abs - loc.abs; + self.loc = loc; + try self.reader.seekBack(diff); + } + + fn expectChar(self: *Self, c: u8) !bool { + var b = try self.readByte(); + return b == c; + } + + fn expectCharExceptAny(self: *Self, chars: []const u8) !bool { + var b = try self.readByte(); + for (chars) |c| { + if (b == c) { + return false; + } + } + return true; + } + }; +} + + +const testUtil = @import("testUtil.zig"); +const expect = std.testing.expect; + +test "h header name" { + var s = ""; + var reader = testUtil.TestStringReader.init(s[0..s.len]); + var pp = Preprocessor(testUtil.TestStringReader).init(reader); + + var tok = try pp.headerName(); + expect(std.meta.eql(tok.?, PPToken{ + .start = SourceLoc{ + .line = 0, + .col = 0, + .abs = 0, + }, + .end = SourceLoc{ + .line = 0, + .col = 8, + .abs = 8, + }, + .kind = PPToken.Type.hHeaderName, + })); +} + +test "q header name" { + var s = "\"test.h\""; + var reader = testUtil.TestStringReader.init(s[0..s.len]); + var pp = Preprocessor(testUtil.TestStringReader).init(reader); + + var tok = try pp.headerName(); + expect(std.meta.eql(tok.?, PPToken{ + .start = SourceLoc{ + .line = 0, + .col = 0, + .abs = 0, + }, + .end = SourceLoc{ + .line = 0, + .col = 8, + .abs = 8, + }, + .kind = PPToken.Type.qHeaderName, + })); +}