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/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 \n", io.stderr(runtime), {}) return 2 } if (!mem.eql(args.values[1], "run")) { try io.print("usage: honey run \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 { strpool.strings = strpool.init(allocator) defer strpool.deinit(&strpool.strings) scan_state := lexer.init(allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, diagnostic_store) tree := try parser.parse(allocator, scan_state.tokens.items, source, diagnostic_store) defer ast.deinit(&tree) resolution := try sema.resolve(allocator, &tree, 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)?, }) } } }