260 lines
7.3 KiB
Plaintext
260 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();
|
|
}
|