184 lines
6.5 KiB
Plaintext
184 lines
6.5 KiB
Plaintext
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
|
|
)
|
|
|
|
#! returns caller-owned storage; release it with mem.free using the same allocator.
|
|
@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.
|
|
#! free the returned slice with mem.free using the same allocator; temporary storage is released before returning.
|
|
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
|
|
}
|
|
|
|
#! returns a caller-owned joined path; release it with mem.free using the same allocator.
|
|
@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.
|
|
#! free the returned slice with mem.free using the same allocator; temporary search storage is released before returning.
|
|
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.
|
|
#! free the returned slice with mem.free using the same allocator; temporary path storage is released before returning.
|
|
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; free each path with mem.free using the same allocator.
|
|
#! release the list with arraylist.deinit, which uses its retained allocator; temporary directory storage is released before returning.
|
|
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.
|
|
#! release the returned list with arraylist.deinit, which uses its retained allocator.
|
|
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
|
|
}
|
|
}
|
|
}
|