diff options
Diffstat (limited to 'server/src/parser/lines.lua')
-rw-r--r-- | server/src/parser/lines.lua | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/server/src/parser/lines.lua b/server/src/parser/lines.lua new file mode 100644 index 00000000..ea41e927 --- /dev/null +++ b/server/src/parser/lines.lua @@ -0,0 +1,122 @@ +local m = require 'lpeglabel' + +local function utf8_len(buf, start, finish) + local len, pos = utf8.len(buf, start, finish) + if len then + return len + end + return 1 + utf8_len(buf, start, pos-1) + utf8_len(buf, pos+1, finish) +end + +local function Line(pos, str, ...) + local line = {...} + local sp = 0 + local tab = 0 + for i = 1, #line do + if line[i] == ' ' then + sp = sp + 1 + else + tab = tab + 1 + end + line[i] = nil + end + line[1] = pos + line[2] = sp + line[3] = tab + return line +end + +local parser = m.P{ +'Lines', +Lines = m.Ct(m.V'Line'^0 * m.V'LastLine'), +Line = m.Cp() * m.C(m.V'Indent' * (1 - m.V'Nl')^0 * m.V'Nl') / Line, +LastLine= m.Cp() * m.C(m.V'Indent' * (1 - m.V'Nl')^0) / Line, +Nl = m.P'\r\n' + m.S'\r\n', +Indent = m.C(m.S' \t')^0, +} + +local mt = {} +mt.__index = mt + +function mt:position(row, col, code) + if row < 1 then + return 1 + end + if row > #self then + if code == 'utf8' then + return utf8_len(self.buf) + 1 + else + return #self.buf + 1 + end + end + local line = self[row] + local next_line = self[row+1] + local start = line[1] + local finish + if next_line then + finish = next_line[1] - 1 + else + finish = #self.buf + 1 + end + local pos + if code == 'utf8' then + pos = utf8.offset(self.buf, col, start) or finish + else + pos = start + col - 1 + end + if pos < start then + pos = start + elseif pos > finish then + pos = finish + end + return pos +end + +function mt:rowcol(pos, code) + if pos < 1 then + return 1, 1 + end + if pos > #self.buf + 1 then + local start = self[#self][1] + if code == 'utf8' then + return #self, utf8_len(self.buf, start) + 1 + else + return #self, #self.buf - start + 2 + end + end + local min = 1 + local max = #self + for _ = 1, 100 do + local row = (max - min) // 2 + min + local start = self[row][1] + if pos < start then + max = row + elseif pos > start then + local next_start = self[row + 1][1] + if pos < next_start then + if code == 'utf8' then + return row, utf8_len(self.buf, start, pos) + else + return row, pos - start + 1 + end + elseif pos > next_start then + min = row + else + return row + 1, 1 + end + else + return row, 1 + end + end + error('rowcol failed!') +end + +return function (self, buf) + local lines, err = parser:match(buf) + if not lines then + return nil, err + end + lines.buf = buf + + return setmetatable(lines, mt) +end |