Files
honey/source/main.hon
T

229 lines
8.0 KiB
Plaintext

import "@std/mem"
import "@std/arraylist"
import "@std/enummap"
import "@std/io"
import "@std/process"
import "@source/diagnostics"
import "@source/strpool"
import "@source/lexer"
import "@source/ast"
import "@source/parser"
import "@source/sema"
import "@source/ir"
import "@source/lower"
import "@source/interpreter"
typecheck :: import "@source/sema/check"
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/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"
@hide:file
DriverError :: alias (
mem.AllocError
| diagnostics.CapacityError
| lexer.SourceError
| strpool.InternError
| io.OpenError
| io.CloseError
| io.ReadError
| io.WriteError
)
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",
})
@hide:file
command proc(allocator mem.Allocator, runtime io.Io, args process.Args) i32 ! DriverError {
if (args.values.len != 3) {
try io.print("usage: honey run <file>\n", io.stderr(runtime), {})
return 2
}
if (!mem.eql(args.values[1], "run")) {
try io.print("usage: honey run <file>\n", io.stderr(runtime), {})
return 2
}
return try run_file(allocator, runtime, args.values[2])
}
@hide:file
read_source proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) arraylist.ArrayList(u8) ! DriverError {
file :: try io.open(runtime, path, .read_only)
closing := false
defer {
if (!closing) io.close(file) catch |_| {}
}
bytes := io.read_all(allocator, io.reader(file), usize(maxval!(u32))) catch |err| {
match err {
.limit_exceeded: return .source_too_large
else: return err
}
}
errdefer arraylist.deinit(&bytes)
closing = true
try io.close(file)
return bytes
}
@hide:file
compile proc(allocator mem.Allocator, source []u8, diagnostic_store @mut diagnostics.Store) ir.Module ! DriverError {
strings := strpool.init(allocator)
defer strpool.deinit(&strings)
scan_state := lexer.init(allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, diagnostic_store)
tree := try parser.parse(allocator, scan_state.tokens.items, source, diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(allocator, &tree, &strings, diagnostic_store)
defer sema.deinit(&resolution)
checked := try typecheck.check(allocator, &tree, &resolution, diagnostic_store)
defer typecheck.deinit(&checked)
return try lower.lower(allocator, &tree, &resolution, &checked, diagnostic_store)
}
@hide:file
run_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverError {
source := try read_source(allocator, runtime, path)
defer arraylist.deinit(&source)
diagnostic_store := diagnostics.init(allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile(allocator, source.items, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
if (outcome.trap == null) try print_result(io.stdout(runtime), &module, outcome.value)
try report_diagnostics(allocator, io.stderr(runtime), path, source.items, &diagnostic_store, outcome.trap)
if (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: try io.print("{}", output, {value.data.integer})
.usize: 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)})
.array: {
shape :: module.arrays.items[usize(value.array_type)]
stride usize :: if (shape.element == .array)
module.arrays.items[usize(shape.element_array)].cells
else
1
try io.print("[", output, {})
for 0..(shape.len) |index| {
if (index != 0) try io.print(", ", output, {})
element := ir.Value{}
if shape.element == .array {
start :: index * stride
element = ir.Value{
type = .array,
array_type = shape.element_array,
data = ir.ValueData{ elements = value.data.elements[start..(start + stride)] },
}
} else {
element = value.data.elements[index]
}
try print_value(output, module, element)
}
try io.print("]", output, {})
}
else: unreachable
}
}
@hide:file
report_diagnostics proc(
allocator mem.Allocator,
output io.Writer,
path []u8,
source []u8,
diagnostic_store @diagnostics.Store,
trap ?diagnostics.DiagnosticId,
) void ! (mem.AllocError | io.WriteError) {
if (diagnostics.count(diagnostic_store) == 0) return
starts := arraylist.init(usize, allocator)
defer arraylist.deinit(&starts)
try arraylist.append(&starts, usize(0))
for (source) |byte, index| {
if (byte == '\n') try arraylist.append(&starts, index + 1)
}
for (0..diagnostics.count(diagnostic_store)) |index| {
if (diagnostics.get(diagnostic_store, diagnostics.DiagnosticId(index))) |diagnostic| {
offset :: usize(diagnostic.span.start)
line := usize(0)
end := starts.items.len
while (line + 1 < end) {
middle :: line + divtrunc!(end - line, 2)
if (starts.items[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, {
path,
line + 1,
offset - starts.items[line] + 1,
label,
enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?,
})
}
}
}