590 lines
19 KiB
Zig
590 lines
19 KiB
Zig
const Compilation = @This();
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const equal = std.mem.eql;
|
|
const print = std.debug.print;
|
|
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const data_structures = @import("data_structures.zig");
|
|
const ArrayList = data_structures.ArrayList;
|
|
const AutoHashMap = data_structures.AutoHashMap;
|
|
const BlockList = data_structures.BlockList;
|
|
const HashMap = data_structures.HashMap;
|
|
const SegmentedList = data_structures.SegmentedList;
|
|
const StringHashMap = data_structures.StringHashMap;
|
|
const StringArrayHashMap = data_structures.StringArrayHashMap;
|
|
|
|
const lexical_analyzer = @import("frontend/lexical_analyzer.zig");
|
|
const syntactic_analyzer = @import("frontend/syntactic_analyzer.zig");
|
|
const Node = syntactic_analyzer.Node;
|
|
const semantic_analyzer = @import("frontend/semantic_analyzer.zig");
|
|
const intermediate_representation = @import("backend/intermediate_representation.zig");
|
|
const emit = @import("backend/emit.zig");
|
|
|
|
test {
|
|
_ = lexical_analyzer;
|
|
_ = syntactic_analyzer;
|
|
_ = semantic_analyzer;
|
|
_ = data_structures;
|
|
}
|
|
|
|
base_allocator: Allocator,
|
|
cwd_absolute_path: []const u8,
|
|
directory_absolute_path: []const u8,
|
|
executable_absolute_path: []const u8,
|
|
build_directory: std.fs.Dir,
|
|
|
|
const cache_dir_name = "cache";
|
|
const installation_dir_name = "installation";
|
|
|
|
pub fn init(allocator: Allocator) !*Compilation {
|
|
const compilation: *Compilation = try allocator.create(Compilation);
|
|
|
|
const self_exe_path = try std.fs.selfExePathAlloc(allocator);
|
|
const self_exe_dir_path = std.fs.path.dirname(self_exe_path).?;
|
|
compilation.* = .{
|
|
.base_allocator = allocator,
|
|
.cwd_absolute_path = try realpathAlloc(allocator, "."),
|
|
.executable_absolute_path = self_exe_path,
|
|
.directory_absolute_path = self_exe_dir_path,
|
|
.build_directory = try std.fs.cwd().makeOpenPath("nat", .{}),
|
|
};
|
|
|
|
try compilation.build_directory.makePath(cache_dir_name);
|
|
try compilation.build_directory.makePath(installation_dir_name);
|
|
|
|
return compilation;
|
|
}
|
|
|
|
pub const Struct = struct {
|
|
scope: Scope.Index,
|
|
fields: ArrayList(Field.Index) = .{},
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Type = union(enum) {
|
|
void,
|
|
noreturn,
|
|
bool,
|
|
integer: Type.Integer,
|
|
@"struct": Struct.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
|
|
pub const Integer = struct {
|
|
bit_count: u16,
|
|
signedness: Signedness,
|
|
pub const Signedness = enum(u1) {
|
|
unsigned = 0,
|
|
signed = 1,
|
|
};
|
|
|
|
pub fn getSize(integer: Type.Integer) u64 {
|
|
return integer.bit_count / @bitSizeOf(u8) + @intFromBool(integer.bit_count % @bitSizeOf(u8) != 0);
|
|
}
|
|
};
|
|
|
|
pub fn getSize(type_info: Type) u64 {
|
|
return switch (type_info) {
|
|
.integer => |integer| integer.getSize(),
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
}
|
|
|
|
pub fn getAlignment(type_info: Type) u64 {
|
|
return switch (type_info) {
|
|
.integer => |integer| @min(16, integer.getSize()),
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
}
|
|
};
|
|
|
|
/// A scope contains a bunch of declarations
|
|
pub const Scope = struct {
|
|
declarations: AutoHashMap(u32, Declaration.Index) = .{},
|
|
parent: Scope.Index,
|
|
file: File.Index,
|
|
type: Type.Index = Type.Index.invalid,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const ScopeType = enum(u1) {
|
|
local = 0,
|
|
global = 1,
|
|
};
|
|
|
|
pub const Mutability = enum(u1) {
|
|
@"const",
|
|
@"var",
|
|
};
|
|
|
|
pub const Declaration = struct {
|
|
scope_type: ScopeType,
|
|
mutability: Mutability,
|
|
init_value: Value.Index,
|
|
name: []const u8,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Function = struct {
|
|
body: Block.Index,
|
|
prototype: Prototype.Index,
|
|
|
|
pub const Prototype = struct {
|
|
arguments: ?[]const Field.Index,
|
|
return_type: Type.Index,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = Prototype.List.Index;
|
|
};
|
|
|
|
pub fn getBodyBlock(function: Function, module: *Module) *Block {
|
|
return module.blocks.get(function.body);
|
|
}
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Block = struct {
|
|
statements: ArrayList(Value.Index) = .{},
|
|
reaches_end: bool,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Field = struct {
|
|
foo: u32 = 0,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Loop = struct {
|
|
condition: Value.Index,
|
|
body: Value.Index,
|
|
breaks: bool,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
const Runtime = struct {
|
|
foo: u32 = 0,
|
|
};
|
|
|
|
const Unresolved = struct {
|
|
node_index: Node.Index,
|
|
};
|
|
|
|
pub const Assignment = struct {
|
|
store: Value.Index,
|
|
load: Value.Index,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Syscall = struct {
|
|
number: Value.Index,
|
|
arguments: [6]Value.Index,
|
|
argument_count: u8,
|
|
|
|
pub fn getArguments(syscall: Syscall) []const Value.Index {
|
|
return syscall.arguments[0..syscall.argument_count];
|
|
}
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Call = struct {
|
|
value: Value.Index,
|
|
arguments: ArgumentList.Index,
|
|
type: Type.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const ArgumentList = struct {
|
|
array: ArrayList(Value.Index),
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Return = struct {
|
|
value: Value.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Value = union(enum) {
|
|
unresolved: Unresolved,
|
|
declaration: Declaration.Index,
|
|
declaration_reference: Declaration.Index,
|
|
void,
|
|
bool: bool,
|
|
undefined,
|
|
@"unreachable",
|
|
loop: Loop.Index,
|
|
function: Function.Index,
|
|
block: Block.Index,
|
|
runtime: Runtime,
|
|
assign: Assignment.Index,
|
|
type: Type.Index,
|
|
integer: Integer,
|
|
syscall: Syscall.Index,
|
|
call: Call.Index,
|
|
argument_list: ArgumentList,
|
|
@"return": Return.Index,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
|
|
pub fn isComptime(value: Value) bool {
|
|
return switch (value) {
|
|
.bool, .void, .undefined, .function => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
pub fn getType(value: *Value, module: *Module) Type.Index {
|
|
return switch (value.*) {
|
|
.call => |call_index| module.calls.get(call_index).type,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Integer = struct {
|
|
value: u64,
|
|
type: Type.Integer,
|
|
};
|
|
|
|
pub const Module = struct {
|
|
main_package: *Package,
|
|
import_table: StringArrayHashMap(*File) = .{},
|
|
string_table: AutoHashMap(u32, []const u8) = .{},
|
|
declarations: BlockList(Declaration) = .{},
|
|
structs: BlockList(Struct) = .{},
|
|
scopes: BlockList(Scope) = .{},
|
|
files: BlockList(File) = .{},
|
|
values: BlockList(Value) = .{},
|
|
functions: BlockList(Function) = .{},
|
|
fields: BlockList(Field) = .{},
|
|
function_prototypes: BlockList(Function.Prototype) = .{},
|
|
types: BlockList(Type) = .{},
|
|
blocks: BlockList(Block) = .{},
|
|
loops: BlockList(Loop) = .{},
|
|
assignments: BlockList(Assignment) = .{},
|
|
syscalls: BlockList(Syscall) = .{},
|
|
calls: BlockList(Call) = .{},
|
|
argument_list: BlockList(ArgumentList) = .{},
|
|
returns: BlockList(Return) = .{},
|
|
entry_point: ?u32 = null,
|
|
|
|
pub const Descriptor = struct {
|
|
main_package_path: []const u8,
|
|
};
|
|
|
|
const ImportFileResult = struct {
|
|
ptr: *File,
|
|
index: File.Index,
|
|
is_new: bool,
|
|
};
|
|
|
|
const ImportPackageResult = struct {
|
|
file: ImportFileResult,
|
|
is_package: bool,
|
|
};
|
|
|
|
pub fn importFile(module: *Module, allocator: Allocator, current_file_index: File.Index, import_name: []const u8) !ImportPackageResult {
|
|
print("import: '{s}'\n", .{import_name});
|
|
if (equal(u8, import_name, "std")) {
|
|
return module.importPackage(allocator, module.main_package.dependencies.get("std").?);
|
|
}
|
|
|
|
if (equal(u8, import_name, "builtin")) {
|
|
return module.importPackage(allocator, module.main_package.dependencies.get("builtin").?);
|
|
}
|
|
|
|
if (equal(u8, import_name, "main")) {
|
|
return module.importPackage(allocator, module.main_package);
|
|
}
|
|
|
|
const current_file = module.files.get(current_file_index);
|
|
if (current_file.package.dependencies.get(import_name)) |package| {
|
|
return module.importPackage(allocator, package);
|
|
}
|
|
|
|
if (!std.mem.endsWith(u8, import_name, ".nat")) {
|
|
unreachable;
|
|
}
|
|
|
|
const full_path = try std.fs.path.join(allocator, &.{ current_file.package.directory.path, import_name });
|
|
const file_relative_path = std.fs.path.basename(full_path);
|
|
const package = current_file.package;
|
|
const import_file = try module.getFile(allocator, full_path, file_relative_path, package);
|
|
|
|
try import_file.ptr.addFileReference(allocator, current_file);
|
|
|
|
const result = ImportPackageResult{
|
|
.file = import_file,
|
|
.is_package = false,
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
fn lookupDeclaration(module: *Module, hashed: u32) !noreturn {
|
|
_ = hashed;
|
|
_ = module;
|
|
while (true) {}
|
|
}
|
|
|
|
fn getFile(module: *Module, allocator: Allocator, full_path: []const u8, relative_path: []const u8, package: *Package) !ImportFileResult {
|
|
const path_lookup = try module.import_table.getOrPut(allocator, full_path);
|
|
const file, const index = switch (path_lookup.found_existing) {
|
|
true => blk: {
|
|
const result = path_lookup.value_ptr.*;
|
|
const index = module.files.indexOf(result);
|
|
break :blk .{
|
|
result,
|
|
index,
|
|
};
|
|
},
|
|
false => blk: {
|
|
const file_allocation = try module.files.append(allocator, File{
|
|
.relative_path = relative_path,
|
|
.package = package,
|
|
});
|
|
std.debug.print("Adding file #{}: {s}\n", .{ file_allocation.index.uniqueInteger(), full_path });
|
|
path_lookup.value_ptr.* = file_allocation.ptr;
|
|
// break :blk file;
|
|
break :blk .{
|
|
file_allocation.ptr,
|
|
file_allocation.index,
|
|
};
|
|
},
|
|
};
|
|
|
|
return .{
|
|
.ptr = file,
|
|
.index = index,
|
|
.is_new = !path_lookup.found_existing,
|
|
};
|
|
}
|
|
|
|
pub fn importPackage(module: *Module, allocator: Allocator, package: *Package) !ImportPackageResult {
|
|
const full_path = try std.fs.path.resolve(allocator, &.{ package.directory.path, package.source_path });
|
|
const import_file = try module.getFile(allocator, full_path, package.source_path, package);
|
|
try import_file.ptr.addPackageReference(allocator, package);
|
|
|
|
return .{
|
|
.file = import_file,
|
|
.is_package = true,
|
|
};
|
|
}
|
|
|
|
pub fn generateAbstractSyntaxTreeForFile(module: *Module, allocator: Allocator, file: *File) !void {
|
|
_ = module;
|
|
const source_file = file.package.directory.handle.openFile(file.relative_path, .{}) catch |err| {
|
|
std.debug.panic("Can't find file {s} in directory {s} for error {s}", .{ file.relative_path, file.package.directory.path, @errorName(err) });
|
|
};
|
|
|
|
const file_size = try source_file.getEndPos();
|
|
var file_buffer = try allocator.alloc(u8, file_size);
|
|
|
|
const read_byte_count = try source_file.readAll(file_buffer);
|
|
assert(read_byte_count == file_size);
|
|
source_file.close();
|
|
|
|
//TODO: adjust file maximum size
|
|
file.source_code = file_buffer[0..read_byte_count];
|
|
file.status = .loaded_into_memory;
|
|
|
|
try file.lex(allocator);
|
|
try file.parse(allocator);
|
|
}
|
|
};
|
|
|
|
fn pathFromCwd(compilation: *const Compilation, relative_path: []const u8) ![]const u8 {
|
|
return std.fs.path.join(compilation.base_allocator, &.{ compilation.cwd_absolute_path, relative_path });
|
|
}
|
|
|
|
fn pathFromCompiler(compilation: *const Compilation, relative_path: []const u8) ![]const u8 {
|
|
return std.fs.path.join(compilation.base_allocator, &.{ compilation.directory_absolute_path, relative_path });
|
|
}
|
|
|
|
fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]const u8 {
|
|
var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const realpathInStack = try std.os.realpath(pathname, &path_buffer);
|
|
return allocator.dupe(u8, realpathInStack);
|
|
}
|
|
|
|
pub fn compileModule(compilation: *Compilation, descriptor: Module.Descriptor) !void {
|
|
// TODO: generate an actual file
|
|
const builtin_file_name = "builtin.nat";
|
|
var cache_dir = try compilation.build_directory.openDir("cache", .{});
|
|
const builtin_file = try cache_dir.createFile(builtin_file_name, .{ .truncate = false });
|
|
builtin_file.close();
|
|
|
|
const module: *Module = try compilation.base_allocator.create(Module);
|
|
module.* = Module{
|
|
.main_package = blk: {
|
|
const result = try compilation.base_allocator.create(Package);
|
|
const main_package_absolute_directory_path = try compilation.pathFromCwd(std.fs.path.dirname(descriptor.main_package_path).?);
|
|
result.* = .{
|
|
.directory = .{
|
|
.handle = try std.fs.openDirAbsolute(main_package_absolute_directory_path, .{}),
|
|
.path = main_package_absolute_directory_path,
|
|
},
|
|
.source_path = try compilation.base_allocator.dupe(u8, std.fs.path.basename(descriptor.main_package_path)),
|
|
};
|
|
break :blk result;
|
|
},
|
|
};
|
|
|
|
const std_package_dir = "lib/std";
|
|
|
|
const package_descriptors = [2]struct {
|
|
name: []const u8,
|
|
directory_path: []const u8,
|
|
}{
|
|
.{
|
|
.name = "std",
|
|
.directory_path = try compilation.pathFromCwd(std_package_dir),
|
|
},
|
|
.{
|
|
.name = "builtin",
|
|
.directory_path = blk: {
|
|
const result = try cache_dir.realpathAlloc(compilation.base_allocator, ".");
|
|
cache_dir.close();
|
|
break :blk result;
|
|
},
|
|
},
|
|
};
|
|
|
|
var packages: [package_descriptors.len]*Package = undefined;
|
|
for (package_descriptors, &packages) |package_descriptor, *package_ptr| {
|
|
const package = try compilation.base_allocator.create(Package);
|
|
package.* = .{
|
|
.directory = .{
|
|
.path = package_descriptor.directory_path,
|
|
.handle = try std.fs.openDirAbsolute(package_descriptor.directory_path, .{}),
|
|
},
|
|
.source_path = try std.mem.concat(compilation.base_allocator, u8, &.{ package_descriptor.name, ".nat" }),
|
|
};
|
|
|
|
try module.main_package.addDependency(compilation.base_allocator, package_descriptor.name, package);
|
|
|
|
package_ptr.* = package;
|
|
}
|
|
|
|
assert(module.main_package.dependencies.size == 2);
|
|
|
|
_ = try module.importPackage(compilation.base_allocator, module.main_package.dependencies.get("std").?);
|
|
|
|
for (module.import_table.values()) |import| {
|
|
try module.generateAbstractSyntaxTreeForFile(compilation.base_allocator, import);
|
|
}
|
|
|
|
const main_declaration = try semantic_analyzer.initialize(compilation, module, packages[0], .{ .block = 0, .index = 0 });
|
|
|
|
var ir = try intermediate_representation.initialize(compilation, module, packages[0], main_declaration);
|
|
|
|
try emit.get(.x86_64).initialize(compilation.base_allocator, &ir);
|
|
}
|
|
|
|
fn generateAST() !void {}
|
|
|
|
pub const Directory = struct {
|
|
handle: std.fs.Dir,
|
|
path: []const u8,
|
|
};
|
|
|
|
pub const Package = struct {
|
|
directory: Directory,
|
|
/// Relative to the package main directory
|
|
source_path: []const u8,
|
|
dependencies: StringHashMap(*Package) = .{},
|
|
|
|
fn addDependency(package: *Package, allocator: Allocator, package_name: []const u8, new_dependency: *Package) !void {
|
|
try package.dependencies.ensureUnusedCapacity(allocator, 1);
|
|
package.dependencies.putAssumeCapacityNoClobber(package_name, new_dependency);
|
|
}
|
|
};
|
|
|
|
pub const File = struct {
|
|
status: Status = .not_loaded,
|
|
source_code: []const u8 = &.{},
|
|
lexical_analyzer_result: lexical_analyzer.Result = undefined,
|
|
syntactic_analyzer_result: syntactic_analyzer.Result = undefined,
|
|
package_references: ArrayList(*Package) = .{},
|
|
file_references: ArrayList(*File) = .{},
|
|
relative_path: []const u8,
|
|
package: *Package,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
|
|
const Status = enum {
|
|
not_loaded,
|
|
loaded_into_memory,
|
|
lexed,
|
|
parsed,
|
|
};
|
|
|
|
fn addPackageReference(file: *File, allocator: Allocator, package: *Package) !void {
|
|
for (file.package_references.items) |other| {
|
|
if (other == package) return;
|
|
}
|
|
|
|
try file.package_references.insert(allocator, 0, package);
|
|
}
|
|
|
|
fn addFileReference(file: *File, allocator: Allocator, affected: *File) !void {
|
|
try file.file_references.append(allocator, affected);
|
|
}
|
|
|
|
fn lex(file: *File, allocator: Allocator) !void {
|
|
assert(file.status == .loaded_into_memory);
|
|
file.lexical_analyzer_result = try lexical_analyzer.analyze(allocator, file.source_code);
|
|
// if (!@import("builtin").is_test) {
|
|
// print("[LEXICAL ANALYSIS] {} ns\n", .{file.lexical_analyzer_result.time});
|
|
// }
|
|
file.status = .lexed;
|
|
}
|
|
|
|
fn parse(file: *File, allocator: Allocator) !void {
|
|
assert(file.status == .lexed);
|
|
file.syntactic_analyzer_result = try syntactic_analyzer.analyze(allocator, file.lexical_analyzer_result.tokens.items, file.source_code);
|
|
// if (!@import("builtin").is_test) {
|
|
// print("[SYNTACTIC ANALYSIS] {} ns\n", .{file.syntactic_analyzer_result.time});
|
|
// }
|
|
file.status = .parsed;
|
|
}
|
|
};
|