Files

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
}
}
}