Files
honey/source/main.hon
T

296 lines
11 KiB
Plaintext

import "@ffi/c"
import "@std/mem"
import "@std/arraylist"
import "@std/enummap"
import "@std/io"
import "@std/process"
import "@source/diagnostics"
import "@source/compilation"
import "@source/ir"
import "@source/interpreter"
import "@source/llvm"
test import "@std/mem"
test import "@std/enummap"
test import "@std/arraylist"
test import "@std/hashmap"
test import "@std/strmap"
test import "@std/meta"
test import "@std/io"
test import "@std/process"
test import "@std/fs"
test import "@std/strconv"
test import "@source/diagnostics"
test import "@source/strpool"
test import "@source/lexer"
test import "@source/ast"
test import "@source/parser"
test import "@source/sema"
test import "@source/sema/check"
test import "@source/ir"
test import "@source/lower"
test import "@source/interpreter"
test import "@source/compilation"
test import "@source/loader"
@hide:file
DriverError :: alias (
compilation.Error
| io.OpenError
| io.CloseError
| io.ReadError
| io.WriteError
| process.RunError
| NativeBuildError
)
@hide:file
NativeBuildError :: enum { temporary_file_failed, compiler_failed }
main proc(init process.Init) i32 {
return command(mem.c_allocator, init.io, init.args) catch |err| {
if (init.args.values.len > 2) {
io.print("{s}: infrastructure error: {s}\n", io.stderr(init.io), {
init.args.values[2], enummap.get(&infrastructure_messages, err)?,
}) catch |_| {}
} else {
io.print("honey: infrastructure error: {s}\n", io.stderr(init.io), {
enummap.get(&infrastructure_messages, err)?,
}) catch |_| {}
}
return 2
}
}
@hide:file
infrastructure_messages :: $enummap.init(DriverError, []u8, {
out_of_memory = "out of memory",
diagnostic_limit_reached = "diagnostic limit reached",
storage_limit_reached = "aggregate storage limit reached",
source_too_large = "source file is too large",
out_of_space = "identifier storage is full",
open_failed = "cannot open source file",
close_failed = "cannot close source file",
not_open_for_reading = "source file is not readable",
read_failed = "cannot read source file",
not_open_for_writing = "output is not writable",
write_failed = "cannot write output",
no_progress = "output writer made no progress",
spawn_failed = "cannot start native compiler",
wait_failed = "cannot wait for native compiler",
invalid_arguments = "invalid native compiler arguments",
temporary_file_failed = "cannot create temporary LLVM file",
compiler_failed = "native compiler failed",
empty_package = "package contains no production source files",
invalid_path = "invalid filesystem path",
path_failed = "cannot resolve filesystem path",
directory_failed = "cannot read package directory",
})
@hide:file
command proc(allocator mem.Allocator, runtime io.Io, args process.Args) i32 ! DriverError {
if (args.values.len == 3) {
if (mem.eql(args.values[1], "run")) return try run_file(allocator, runtime, args.values[2])
if (mem.eql(args.values[1], "emit-llvm")) return try emit_file(allocator, runtime, args.values[2])
}
if (args.values.len == 5 or args.values.len == 6) {
if (mem.eql(args.values[1], "build") and mem.eql(args.values[3], "-o") and args.values[4].len != 0) {
optimization [;0]u8 := "-O0"
if (args.values.len == 6) {
optimization = args.values[5]
if (!mem.eql(optimization, "-O0") and !mem.eql(optimization, "-O2")) {
try usage(io.stderr(runtime))
return 2
}
}
return try build_file(allocator, runtime, args.values[2], args.values[4], optimization)
}
}
try usage(io.stderr(runtime))
return 2
}
@hide:file
usage proc(output io.Writer) void ! io.WriteError {
try io.write_all(output, "usage: honey run <file-or-directory>\n")
try io.write_all(output, " honey emit-llvm <file-or-directory>\n")
try io.write_all(output, " honey build <file-or-directory> -o <executable> [-O0|-O2]\n")
}
@hide:file
emit_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverError {
session := compilation.init(allocator)
defer compilation.deinit(&session)
diagnostic_store := diagnostics.init(allocator)
defer diagnostics.deinit(&diagnostic_store)
root :: try compilation.load(&session, runtime, path, &diagnostic_store)
module := try compilation.compile(&session, root, &diagnostic_store)
defer ir.deinit(&module)
sources :: compilation.source_files(&session)
primary :: session.packages.items[usize(root)].sources.items[0]
try llvm.emit(allocator, io.stdout(runtime), &module, &diagnostic_store, sources, primary)
try report_diagnostics(allocator, io.stderr(runtime), sources, &diagnostic_store, null)
return if (diagnostics.count(&diagnostic_store) == 0) 0 else 1
}
@hide:file
build_file proc(
allocator mem.Allocator,
runtime io.Io,
path [;0]u8,
output [;0]u8,
optimization [;0]u8,
) i32 ! DriverError {
session := compilation.init(allocator)
defer compilation.deinit(&session)
diagnostic_store := diagnostics.init(allocator)
defer diagnostics.deinit(&diagnostic_store)
root :: try compilation.load(&session, runtime, path, &diagnostic_store)
module := try compilation.compile(&session, root, &diagnostic_store)
defer ir.deinit(&module)
template [18]mut u8 := undefined
memcopy!(template[..], "/tmp/honey-XXXXXX\0")
descriptor :: c.mkstemp(ptrcast!(template[..].ptr))
if (descriptor < 0) return .temporary_file_failed
defer _ = c.unlink(ptrcast!(template[..].ptr))
file :: io.File{ io = runtime, handle = io.Handle{ file_desc = descriptor } }
closing := false
defer {
if (!closing) io.close(file) catch |_| {}
}
sources :: compilation.source_files(&session)
primary :: session.packages.items[usize(root)].sources.items[0]
try llvm.emit(allocator, io.writer(file), &module, &diagnostic_store, sources, primary)
closing = true
try io.close(file)
temporary [;0]u8 :: template[..].ptr[..17;0]
arguments [9][;0]u8 :: [
"clang", "-target", "arm64-apple-macosx11.0.0",
"-x", "ir", optimization, temporary, "-o", output,
]
status :: try process.run(allocator, arguments[..])
if (status != 0) return .compiler_failed
try report_diagnostics(allocator, io.stderr(runtime), sources, &diagnostic_store, null)
return if (diagnostics.count(&diagnostic_store) == 0) 0 else 1
}
@hide:file
run_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverError {
session := compilation.init(allocator)
defer compilation.deinit(&session)
diagnostic_store := diagnostics.init(allocator)
defer diagnostics.deinit(&diagnostic_store)
root :: try compilation.load(&session, runtime, path, &diagnostic_store)
module := try compilation.compile(&session, root, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
value := outcome.value
failed := false
if (outcome.trap == null) {
if (module.entry) |entry| {
if (module.functions.items[usize(entry)].result_fallible) {
failed = ir.union_active(&module, value, 1)
if (!failed) value = ir.aggregate_item(&module, value, 0)
}
}
if (!failed) try print_result(io.stdout(runtime), &module, value)
}
try report_diagnostics(allocator, io.stderr(runtime), compilation.source_files(&session), &diagnostic_store, outcome.trap)
if (failed or outcome.trap != null or diagnostics.count(&diagnostic_store) != 0) return 1
return 0
}
@hide:file
print_result proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
if (value.type == .void) return
try print_value(output, module, value)
try io.print("\n", output, {})
}
@hide:file
print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
match value.type {
.int, .i8, .i16, .i32, .isize: try io.print("{}", output, {value.data.integer})
.usize, .u8, .u16, .u32, .u64: try io.print("{}", output, {value.data.unsigned})
.float: try io.print("{d}", output, {value.data.real})
.bool: try io.print("{}", output, {value.data.boolean})
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
.pointer: try io.write_all(output, "<pointer>")
.array, .record: {
shape :: module.arrays.items[usize(value.array_type)]
count :: if (value.type == .array) shape.len else shape.fields.len
opening :: if (value.type == .array) "[" else "{"
closing :: if (value.type == .array) "]" else "}"
try io.write_all(output, opening)
if (shape.is_union) {
for (shape.fields) |field, index| {
if (!ir.union_active(module, value, index)) continue
if (field.type != .void) try print_value(output, module, ir.aggregate_item(module, value, index))
break
}
try io.write_all(output, closing)
return
}
for (0..count) |index| {
if (index != 0) try io.write_all(output, ", ")
try print_value(output, module, ir.aggregate_item(module, value, index))
}
try io.write_all(output, closing)
}
else: unreachable
}
}
@hide:file
report_diagnostics proc(
allocator mem.Allocator,
output io.Writer,
sources []diagnostics.Source,
diagnostic_store @diagnostics.Store,
trap ?diagnostics.DiagnosticId,
) void ! (mem.AllocError | io.WriteError) {
if (diagnostics.count(diagnostic_store) == 0) return
line_starts := arraylist.init(arraylist.ArrayList(usize), allocator)
defer {
for (line_starts.items) |@starts| arraylist.deinit(starts)
arraylist.deinit(&line_starts)
}
for (sources) |source| {
starts := arraylist.init(usize, allocator)
errdefer arraylist.deinit(&starts)
try arraylist.append(&starts, usize(0))
for (source.text) |byte, index| {
if (byte == '\n') try arraylist.append(&starts, index + 1)
}
try arraylist.append(&line_starts, starts)
}
for (0..diagnostics.count(diagnostic_store)) |index| {
if (diagnostics.get(diagnostic_store, diagnostics.DiagnosticId(index))) |diagnostic| {
source :: sources[usize(diagnostic.span.source)]
starts :: line_starts.items[usize(diagnostic.span.source)].items
offset :: usize(diagnostic.span.start)
line := usize(0)
end := starts.len
while (line + 1 < end) {
middle :: line + divtrunc!(end - line, 2)
if (starts[middle] <= offset) line = middle
else end = middle
}
label []u8 := "error"
if (trap) |id| {
if (id == diagnostics.DiagnosticId(index)) label = "runtime trap"
}
try io.print("{s}:{}:{}: {s}: {s}\n", output, {
source.path,
line + 1,
offset - starts[line] + 1,
label,
enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?,
})
}
}
}