Files
ViperC/App/lib/lsp/LspDocuments.vp
T
TermiNexus 95a8da86e4 feat(lsp): Gate 9 语义高亮保真
define 宏函数与调用点使用 macro token;常量保持 variable+readonly;多行字符串按行拆分;修正符号/出现点合并顺序导致整行 token 丢失;大文档诊断 scratch 按源大小扩容。
2026-10-26 21:04:10 +08:00

225 lines
7.4 KiB
Plaintext

from stdint import *
import string
import memhub
import lib.lsp.LspPools as LspPools
import lib.lsp.LspPositions as LspPositions
define LSP_DOC_POOL_SIZE = 4194304
# Parse scratch arena shared by diagnostics: reset before each publish so ASTs and token lists do
# not accumulate across edits. It lives in the per-documents object, whose owner is the permanent
# pool, because the message pool is reset after every request.
# The arena grows with the largest published document: a 4 MiB arena is exhausted by a few hundred
# KiB of source, and allocation failure aborts the parse.
define LSP_SCRATCH_POOL_SIZE = 4194304
define LSP_SCRATCH_BYTES_PER_SOURCE = 128
define LSP_SCRATCH_POOL_MAX = 134217728
class LspDocument:
uri: str
text: str
version: int
index: LspPositions.LspLineIndex*
pool: LspPools.LspPool*
spare: LspPools.LspPool*
class LspDocuments:
pool: memhub.MemManager*
docs: list[LspDocument*]*
count: int
scratch: LspPools.LspPool*
def __new__(self, pool: memhub.MemManager*) -> rawptr:
p: LspDocuments* = (LspDocuments*)(pool.alloc(LspDocuments.__sizeof__()))
if p:
string.memset(p, 0, LspDocuments.__sizeof__())
return rawptr(p)
def __init__(self, pool: memhub.MemManager*):
self.pool = pool
self.docs = list[LspDocument*](pool, 8)
self.count = 0
self.scratch = LspPools.Create(pool, sizet(LSP_SCRATCH_POOL_SIZE))
def Scratch(self) -> LspPools.LspPool*:
return self.scratch
def ScratchFor(self, source_len: sizet) -> LspPools.LspPool*:
"""Return a scratch arena large enough to parse a document of source_len bytes."""
want: sizet = source_len * sizet(LSP_SCRATCH_BYTES_PER_SOURCE)
if want < sizet(LSP_SCRATCH_POOL_SIZE):
want = sizet(LSP_SCRATCH_POOL_SIZE)
if want > sizet(LSP_SCRATCH_POOL_MAX):
want = sizet(LSP_SCRATCH_POOL_MAX)
if self.scratch is not None and self.scratch.size >= want:
return self.scratch
if self.scratch is not None:
self.scratch.Destroy()
self.scratch = None
self.scratch = LspPools.Create(self.pool, want)
return self.scratch
def Find(self, uri: str) -> LspDocument*:
return self._Find(uri)
def _Find(self, uri: str) -> LspDocument*:
if uri is None:
return None
i: int = 0
while i < self.count:
doc: LspDocument* = self.docs[i]
if doc is not None and doc.uri is not None:
if string.strcmp(doc.uri, uri) == 0:
return doc
i += 1
return None
def _DestroyDoc(self, doc: LspDocument*) -> int:
if doc is None:
return -1
if doc.pool is not None:
doc.pool.Destroy()
doc.pool = None
if doc.spare is not None:
doc.spare.Destroy()
doc.spare = None
return 0
def Open(self, uri: str, text: str, version: int) -> LspDocument*:
if uri is None or text is None:
return None
existing: LspDocument* = self._Find(uri)
if existing is not None:
self.Close(uri)
doc: LspDocument* = (LspDocument*)(self.pool.alloc(LspDocument.__sizeof__()))
if doc is None:
return None
string.memset(doc, 0, LspDocument.__sizeof__())
doc.uri = LspPools.DupStr(self.pool, uri)
if doc.uri is None:
return None
p1: LspPools.LspPool* = LspPools.Create(self.pool, sizet(LSP_DOC_POOL_SIZE))
p2: LspPools.LspPool* = LspPools.Create(self.pool, sizet(LSP_DOC_POOL_SIZE))
if p1 is None or p2 is None:
if p1 is not None:
p1.Destroy()
if p2 is not None:
p2.Destroy()
return None
doc.pool = p1
doc.spare = p2
doc.text = LspPools.DupStr(p1.mb, text)
if doc.text is None:
self._DestroyDoc(doc)
return None
doc.index = LspPositions.LspLineIndex(p1.mb)
if doc.index is None or doc.index.Build(doc.text) != 0:
self._DestroyDoc(doc)
return None
doc.version = version
self.docs.append(doc)
self.count += 1
return doc
def ApplyChange(self, uri: str, text: str, has_range: int,
start_line: int, start_char: int,
end_line: int, end_char: int,
version: int) -> int:
doc: LspDocument* = self._Find(uri)
if doc is None or doc.index is None or doc.spare is None:
return -1
new_pool: LspPools.LspPool* = doc.spare
if new_pool.Reset() < 0:
return -1
old_len: sizet = doc.index.Length()
start: sizet = 0
end: sizet = old_len
if has_range != 0:
if doc.index.RangeToOffsets(start_line, start_char, end_line, end_char,
&start, &end) != 0:
return -1
if end > old_len:
return -1
add_len: sizet = 0
if text is not None:
add_len = string.strlen(text)
new_len: sizet = start + add_len + (old_len - end)
buf: bytes = new_pool.mb.alloc(new_len + 1)
if buf is None:
return -1
if start > 0:
string.memcpy(buf, doc.text, start)
if add_len > 0:
string.memcpy(buf + start, text, add_len)
if old_len > end:
string.memcpy(buf + start + add_len, doc.text + end, old_len - end)
buf[new_len] = 0
new_index: LspPositions.LspLineIndex* = LspPositions.LspLineIndex(new_pool.mb)
if new_index is None or new_index.Build(str(buf)) != 0:
return -1
old_pool: LspPools.LspPool* = doc.pool
doc.pool = new_pool
doc.spare = old_pool
doc.text = str(buf)
doc.index = new_index
doc.version = version
if old_pool is not None:
old_pool.Reset()
return 0
def Close(self, uri: str) -> int:
i: int = 0
while i < self.count:
doc: LspDocument* = self.docs[i]
if doc is not None and doc.uri is not None:
if string.strcmp(doc.uri, uri) == 0:
self._DestroyDoc(doc)
self.docs[i] = self.docs[self.count - 1]
self.docs.pop()
self.count -= 1
return 0
i += 1
return -1
def Count(self) -> int:
return self.count
def DocAt(self, index: int) -> LspDocument*:
if index < 0 or index >= self.count:
return None
return self.docs[index]
def UsedBytes(self) -> sizet:
total: sizet = 0
i: int = 0
while i < self.count:
doc: LspDocument* = self.docs[i]
if doc is not None:
if doc.pool is not None:
total += doc.pool.UsedBytes()
if doc.spare is not None:
total += doc.spare.UsedBytes()
i += 1
return total
def FreeBytes(self) -> sizet:
total: sizet = 0
i: int = 0
while i < self.count:
doc: LspDocument* = self.docs[i]
if doc is not None:
if doc.pool is not None:
total += doc.pool.FreeBytes()
if doc.spare is not None:
total += doc.spare.FreeBytes()
i += 1
return total