import "@std/mem" import "@std/io" import "@std/fs" import "@std/arraylist" SourceError :: enum { source_too_large } Error :: alias fs.Error | SourceError | io.OpenError | io.CloseError | io.ReadError @hide:file copy proc(allocator mem.Allocator, text []u8) []mut u8 ! mem.AllocError { bytes :: try mem.alloc(u8, allocator, text.len) memcopy!(bytes, text) return bytes } #! returns an owned canonical package directory for either a file or directory input. directory proc(allocator mem.Allocator, path []u8) []mut u8 ! Error { resolved :: try fs.canonical(allocator, path) errdefer mem.free(allocator, resolved) if (try fs.kind(allocator, resolved) == .directory) return resolved end := resolved.len while (end > 1 and resolved[end - 1] != '/') end -= 1 parent_text :: if (end == 1) resolved[..1] else resolved[..end - 1] parent :: try copy(allocator, parent_text) mem.free(allocator, resolved) return parent } @hide:file join proc(allocator mem.Allocator, base, name []u8) []mut u8 ! mem.AllocError { separator :: if (base.len == 1 and base[0] == '/') usize(0) else usize(1) if (base.len > maxval!(usize) - separator or name.len > maxval!(usize) - base.len - separator) return .out_of_memory bytes :: try mem.alloc(u8, allocator, base.len + separator + name.len) memcopy!(bytes[..base.len], base) if (separator != 0) bytes[base.len] = '/' memcopy!(bytes[base.len + separator..], name) return bytes } @hide:file exists proc(allocator mem.Allocator, base, name []u8) bool ! Error { path :: try join(allocator, base, name) defer mem.free(allocator, path) return try fs.exists(allocator, path) } #! finds the nearest ancestor build.hon/build.bro, otherwise uses the supplied directory. project_root proc(allocator mem.Allocator, package_dir []u8) []mut u8 ! Error { end := package_dir.len while true { base :: package_dir[..end] if (try exists(allocator, base, "build.hon") or try exists(allocator, base, "build.bro")) return try copy(allocator, base) if (end == 1) break while (end > 1 and package_dir[end - 1] != '/') end -= 1 if (end > 1) end -= 1 } return try copy(allocator, package_dir) } #! relative imports start at the importing package; @ imports start at the project root. resolve proc(allocator mem.Allocator, root_dir, package_dir, import_path []u8) []mut u8 ! Error { if (import_path.len == 0 or import_path[0] == '/') return .invalid_path for (import_path) |byte| if (byte == 0) return .invalid_path base := package_dir path := import_path if (path[0] == '@') { base = root_dir path = path[1..] if (path.len == 0 or path[0] == '/') return .invalid_path } joined :: try join(allocator, base, path) defer mem.free(allocator, joined) return try fs.canonical(allocator, joined) } @hide:file ends_with proc(text, suffix []u8) bool { return text.len >= suffix.len and mem.eql(text[text.len - suffix.len..], suffix) } @hide:file before proc(left, right []u8) bool { count :: if (left.len < right.len) left.len else right.len for 0..count |index| { if (left[index] != right[index]) return left[index] < right[index] } return left.len < right.len } #! lists production sources in bytewise order; caller frees every path and the list. files proc(allocator mem.Allocator, package_dir []u8) arraylist.ArrayList([]u8) ! Error { handle := try fs.open_directory(allocator, package_dir) closing := false defer { if (!closing) fs.close_directory(&handle) catch |_| {} } result := arraylist.init([]u8, allocator) errdefer { for (result.items) |path| mem.free(allocator, path) arraylist.deinit(&result) } while (try fs.next(&handle)) |entry| { if ( entry.kind != .file and entry.kind != .link and entry.kind != .unknown ) continue name :: entry.name if ((!ends_with(name, ".hon") and !ends_with(name, ".bro")) or ends_with(name, ".test.hon") or ends_with(name, ".test.bro")) continue path :: try join(allocator, package_dir, name) errdefer mem.free(allocator, path) if (entry.kind != .file and try fs.kind(allocator, path) != .file) { mem.free(allocator, path) continue } try arraylist.append(&result, path) } closing = true try fs.close_directory(&handle) # in-place heapsort: deterministic O(n log n), no additional allocation. count := result.items.len start :: divtrunc!(count, 2) for (0..start) |i| sift(result.items, start - i - 1, count) while (count > 1) { count -= 1 temporary :: result.items[0] result.items[0] = result.items[count] result.items[count] = temporary sift(result.items, 0, count) } return result } @hide:file sift proc(items []mut []u8, start, count usize) void { root := start while (root < divtrunc!(count, 2)) { child := root * 2 + 1 if (child + 1 < count and before(items[child], items[child + 1])) child += 1 if (!before(items[root], items[child])) break temporary :: items[root] items[root] = items[child] items[child] = temporary root = child } } #! the compiler limits source size to its u32 span address space; storage remains caller-owned. read_source proc(allocator mem.Allocator, runtime io.Io, path []u8) arraylist.ArrayList(u8) ! Error { return fs.read_file(allocator, runtime, path, usize(maxval!(u32))) catch |err| { match err { .limit_exceeded: return .source_too_large else: return err } } }