Files
spl/stage2/utils/source.spl
T

262 lines
7.3 KiB
Plaintext

// Expect retcode = 0
// 源文件与位置映射:fname + text 借用调用方存储;行起始表由 Source 拥有。
// 提供字节偏移 <-> (行,列)(行列均为 1-based、按字节计),以及取整行文本。
// Sources 是按 fname 索引的多文件集合。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView, .FindResult } = @import("byte_view.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .Map, .hash_kind_view } = @import("map.spl");
type .{ .hash_view, .eq_view } = @import("hash.spl");
type Position = struct {
line: usize, // 1-based
col: usize, // 1-based,按字节
}
type Source = @self;
fname: ByteView, // 借用
text: ByteView, // 借用
line_starts: Vec, // usize:每行起始字节偏移(含首行 0)
type SourceResult = enum {
Ok: Source,
Err: Error,
}
type AddResult = enum {
Ok: usize,
Err: Error,
}
fn blank(): Source {
ret Source {
.fname = ByteView.empty(),
.text = ByteView.empty(),
.line_starts = Vec.blank(@sizeof(usize), 8),
};
}
fn new(fname: ByteView, text: ByteView): SourceResult {
var starts = Vec.blank(@sizeof(usize), 8);
var zero: usize = 0;
match Vec.push(&starts, &zero as *u8) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var i: usize = 0;
while i < text.len {
if text.ptr[i] == '\n' {
var next = i + 1;
match Vec.push(&starts, &next as *u8) {
.Ok => {},
.Err(error) => { Vec.drop(&starts); ret .Err(error); },
}
}
i = i + 1;
}
ret .Ok(Source { .fname = fname, .text = text, .line_starts = starts });
}
fn line_count(self: Source): usize {
ret self.line_starts.len;
}
fn line_start_offset(self: Source, line: usize): usize {
if line == 0 || line > self.line_starts.len { ret ~(0 as usize); }
match Vec.get(&self.line_starts, line - 1) {
.Ok(ptr) => { ret (ptr as *usize).*; },
.Err(error) => { ret ~(0 as usize); },
}
ret ~(0 as usize);
}
// offset 所在行(0-based);超出范围夹到末行。
fn line_index_at(self: Source, offset: usize): usize {
var off = offset;
if off > self.text.len { off = self.text.len; }
var lo: usize = 0;
var hi: usize = self.line_starts.len;
while lo + 1 < hi {
var mid = (lo + hi) / 2;
var start = line_start_offset(self, mid + 1); // 1-based -> 内部
if start != ~(0 as usize) && start <= off { lo = mid; } else { hi = mid; }
}
ret lo;
}
fn line_col(self: Source, offset: usize): Position {
var off = offset;
if off > self.text.len { off = self.text.len; }
var line0 = line_index_at(self, off);
var start = line_start_offset(self, line0 + 1); // 1-based
if start == ~(0 as usize) { start = 0; }
ret Position { .line = line0 + 1, .col = off - start + 1 };
}
// line 为 1-based;返回整行(去掉行尾 '\n' 与 '\r')。
fn line_text(self: Source, line: usize): ByteView {
var n = self.line_starts.len;
if line == 0 || line > n { ret ByteView.empty(); }
var start = line_start_offset(self, line);
if start == ~(0 as usize) { ret ByteView.empty(); }
var end = self.text.len;
if line < n {
var next = line_start_offset(self, line + 1);
if next != ~(0 as usize) { end = next; }
}
while end > start && (self.text.ptr[end - 1] == '\n' || self.text.ptr[end - 1] == '\r') {
end = end - 1;
}
var r = ByteView.slice(self.text, start, end);
match r {
.Ok(v) => { ret v; },
.Err(error) => { ret ByteView.empty(); },
}
ret ByteView.empty();
}
fn drop(self: *Source) {
Vec.drop(&self.line_starts);
}
// ---- 多文件集合 ----
type Sources = struct {
items: Vec, // Source
by_name: Map, // ByteView(fname) -> usize
}
fn set_new(): Sources {
ret Sources {
.items = Vec.blank(@sizeof(Source), 8),
.by_name = Map.blank(@sizeof(ByteView), @alignof(ByteView), @sizeof(usize),
@alignof(usize), hash_kind_view()),
};
}
fn set_add(set: *Sources, fname: ByteView, text: ByteView): AddResult {
var src = new(fname, text);
var s = blank();
match src {
.Ok(v) => { s = v; },
.Err(error) => { ret .Err(error); },
}
var index = set.items.len;
match Vec.push(&set.items, &s as *u8) {
.Ok => {},
.Err(error) => { drop(&s); ret .Err(error); },
}
match Map.put(&set.by_name, &fname as *u8, &index as *u8) {
.Ok => {},
.Err(error) => {
// 回滚最后一项
set.items.len = index;
drop(&s);
ret .Err(error);
},
}
ret .Ok(index);
}
fn set_at(set: *Sources, index: usize): *Source {
match Vec.get(&set.items, index) {
.Ok(ptr) => { ret ptr as *Source; },
.Err(error) => { ret null; },
}
ret null;
}
fn set_find(set: *Sources, fname: ByteView): FindResult {
var out: usize = 0;
match Map.get(&set.by_name, &fname as *u8, &out as *u8) {
.Found => { ret .Found(out); },
.NotFound => {},
}
ret .NotFound;
}
fn set_drop(set: *Sources) {
var i: usize = 0;
while i < set.items.len {
var s = set_at(set, i);
if s != null { drop(s); }
i = i + 1;
}
Vec.drop(&set.items);
Map.drop(&set.by_name);
}
// ---- 测试辅助 ----
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn test_single() {
var text = "line1\nline2\n\nline4";
var r = new(view("a.spl"), view(text));
var src = blank();
match r {
.Ok(v) => { src = v; },
.Err(error) => { @assert(false, "source new"); },
}
@assert(line_count(src) == 4, "line count");
var p0 = line_col(src, 0);
@assert(p0.line == 1 && p0.col == 1, "start");
var p6 = line_col(src, 6);
@assert(p6.line == 2 && p6.col == 1, "line2 start");
var p8 = line_col(src, 8);
@assert(p8.line == 2 && p8.col == 3, "line2 col3");
var p12 = line_col(src, 12);
@assert(p12.line == 3 && p12.col == 1, "empty line3");
var p13 = line_col(src, 13);
@assert(p13.line == 4 && p13.col == 1, "line4 start");
var p18 = line_col(src, 18);
@assert(p18.line == 4 && p18.col == 6, "past end clamps");
var l1 = line_text(src, 1);
var l3 = line_text(src, 3);
var l4 = line_text(src, 4);
@assert(ByteView.eq(l1, view("line1")), "line1");
@assert(ByteView.eq(l3, view("")), "line3 empty");
@assert(ByteView.eq(l4, view("line4")), "line4");
drop(&src);
}
fn test_sources() {
var set = set_new();
var r1 = set_add(&set, view("a.spl"), view("x\ny"));
var r2 = set_add(&set, view("b.spl"), view("z"));
var a: usize = 0;
var b: usize = 0;
match r1 { .Ok(v) => { a = v; }, .Err(e) => { @assert(false, "add a"); } }
match r2 { .Ok(v) => { b = v; }, .Err(e) => { @assert(false, "add b"); } }
@assert(a == 0 && b == 1, "indices");
match set_find(&set, view("b.spl")) {
.Found(i) => { @assert(i == 1, "find b"); },
.NotFound => { @assert(false, "b missing"); },
}
match set_find(&set, view("c.spl")) {
.Found(i) => { @assert(false, "c unexpected"); },
.NotFound => {},
}
var sb = set_at(&set, b);
@assert(sb != null && line_count(sb.*) == 1, "at b");
set_drop(&set);
}
fn main() {
test_single();
test_sources();
}