redo
This commit is contained in:
parent
51984e297b
commit
82b165dd21
|
|
@ -1,145 +0,0 @@
|
||||||
from abc import ABC
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from enum import Enum
|
|
||||||
from typing import ClassVar
|
|
||||||
|
|
||||||
from lsprotocol.types import SymbolKind
|
|
||||||
|
|
||||||
|
|
||||||
class SkillDataType(Enum):
|
|
||||||
array = "a"
|
|
||||||
"""array"""
|
|
||||||
|
|
||||||
ddUserType = "b"
|
|
||||||
"""Boolean"""
|
|
||||||
|
|
||||||
opfcontext = "C"
|
|
||||||
"""OPF Context"""
|
|
||||||
|
|
||||||
dbobject = "d"
|
|
||||||
"""Cadence database object (CDBA)"""
|
|
||||||
|
|
||||||
envobj = "e"
|
|
||||||
"""environment"""
|
|
||||||
|
|
||||||
flonum = "f"
|
|
||||||
"""floating-point number"""
|
|
||||||
|
|
||||||
opffile = "F"
|
|
||||||
"""OPF file ID"""
|
|
||||||
|
|
||||||
general = "g"
|
|
||||||
"""any data type"""
|
|
||||||
|
|
||||||
nil = "g"
|
|
||||||
""""""
|
|
||||||
|
|
||||||
dgbSpecIlUserType = "G"
|
|
||||||
"""gdm spec"""
|
|
||||||
|
|
||||||
hdbobject = "h"
|
|
||||||
"""hierarchical database configuration object"""
|
|
||||||
|
|
||||||
list = "l"
|
|
||||||
"""linked list"""
|
|
||||||
|
|
||||||
nmpIlUserType = "m"
|
|
||||||
"""nmpll user type"""
|
|
||||||
|
|
||||||
cdsEvalObject = "M"
|
|
||||||
"""Cadence evaluation object"""
|
|
||||||
|
|
||||||
number = "n"
|
|
||||||
"""integere of floating point number"""
|
|
||||||
|
|
||||||
userType = "o"
|
|
||||||
"""user defined type (other)"""
|
|
||||||
|
|
||||||
port = "p"
|
|
||||||
"""I/O port"""
|
|
||||||
|
|
||||||
gdmspecListIlUSerType = "q"
|
|
||||||
""" gdm spec list"""
|
|
||||||
|
|
||||||
defstruct = "r"
|
|
||||||
"""defstruct"""
|
|
||||||
|
|
||||||
rodObj = "R"
|
|
||||||
"""relative object design (ROD) object"""
|
|
||||||
|
|
||||||
symbol = "s"
|
|
||||||
"""symbol"""
|
|
||||||
|
|
||||||
stringSymbol = "S"
|
|
||||||
"""symbol or character string"""
|
|
||||||
|
|
||||||
string = "t"
|
|
||||||
"""character string (text)"""
|
|
||||||
|
|
||||||
function = "u"
|
|
||||||
"""function object, either the name if a function (symbol) or a lambda function body (list)"""
|
|
||||||
|
|
||||||
funobj = "U"
|
|
||||||
"""function object"""
|
|
||||||
|
|
||||||
hdbpath = "v"
|
|
||||||
""""""
|
|
||||||
|
|
||||||
wtype = "w"
|
|
||||||
"""window type"""
|
|
||||||
|
|
||||||
integer = "x"
|
|
||||||
"""integer type"""
|
|
||||||
|
|
||||||
binary = "y"
|
|
||||||
"""binary function"""
|
|
||||||
|
|
||||||
pointer = "&"
|
|
||||||
"""pointer type"""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Builtin(ABC):
|
|
||||||
token: str
|
|
||||||
kind: ClassVar[SymbolKind]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Variable(Builtin):
|
|
||||||
kind: ClassVar[SymbolKind] = SymbolKind.Variable
|
|
||||||
typ: SkillDataType
|
|
||||||
default: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AnonymousVariable(Builtin):
|
|
||||||
kind: ClassVar[SymbolKind] = SymbolKind.Variable
|
|
||||||
typ: SkillDataType
|
|
||||||
default: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Procedure(Builtin):
|
|
||||||
kind: ClassVar[SymbolKind] = SymbolKind.Function
|
|
||||||
args: Mapping[str, Variable] = field(default_factory=dict)
|
|
||||||
rest: Variable | None = None
|
|
||||||
kwargs: Mapping[str, Variable] | Mapping[str, AnonymousVariable] = field(
|
|
||||||
default_factory=dict
|
|
||||||
)
|
|
||||||
"""list of ``Variable` if ``@key`` was used, and ``AnonymousVariable`` if ``@option`` was used"""
|
|
||||||
ret: SkillDataType = SkillDataType.nil
|
|
||||||
|
|
||||||
@property
|
|
||||||
def has_options(self) -> bool:
|
|
||||||
return bool(self.kwargs) and isinstance(
|
|
||||||
next(iter(self.kwargs.values())),
|
|
||||||
AnonymousVariable,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def has_keys(self) -> bool:
|
|
||||||
return bool(self.kwargs) and isinstance(
|
|
||||||
next(iter(self.kwargs.values())),
|
|
||||||
Variable,
|
|
||||||
)
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
;; append
|
|
||||||
append(
|
|
||||||
l_list1
|
|
||||||
l_list2
|
|
||||||
) => l_result
|
|
||||||
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
|
|
||||||
from .common import Procedure, SkillDataType, Variable
|
|
||||||
|
|
||||||
NUM = SkillDataType.number
|
|
||||||
ANY = SkillDataType.general
|
|
||||||
|
|
||||||
|
|
||||||
FUNCTIONS: Mapping[str, Procedure] = {
|
|
||||||
"plus": Procedure(
|
|
||||||
"plus",
|
|
||||||
ret=NUM,
|
|
||||||
args={
|
|
||||||
"op1": Variable("op1", NUM),
|
|
||||||
"op2": Variable("op2", NUM),
|
|
||||||
},
|
|
||||||
rest=Variable("op3", NUM),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
FUNCTIONS["plus"]
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Any, Generic, TypeVar, TypeVarTuple, Union, Unpack
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
L = TypeVarTuple("L")
|
|
||||||
ID = int
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Cache(Generic[*L, T]):
|
|
||||||
cached: list[T] = field(default_factory=list)
|
|
||||||
lookups: dict[type[Union[*L]], dict[Union[*L], ID]] = field(default_factory=dict)
|
|
||||||
|
|
||||||
def __getitem__(self, key: Union[*L]) -> T:
|
|
||||||
id = self.lookups[type(key)][key]
|
|
||||||
|
|
||||||
return self.cached[id]
|
|
||||||
|
|
||||||
def __setitem__(self, keys: tuple[Unpack[L]], value: T) -> None:
|
|
||||||
print(type(keys), keys)
|
|
||||||
id = len(self.cached)
|
|
||||||
self.cached.append(value)
|
|
||||||
|
|
||||||
for key in keys:
|
|
||||||
self.lookups.setdefault(type(key), {})
|
|
||||||
self.lookups[type(key)][key] = id
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
c = Cache[int, str, str]()
|
|
||||||
|
|
||||||
print(c)
|
|
||||||
c[0, None] = "a"
|
|
||||||
print(c)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
skill = inline_expr+
|
|
||||||
expr = (inline_expr / nl)
|
|
||||||
|
|
||||||
inline_expr = (listraw / listc / listskill / inline_get / inline_op / inline_assign / ws / nl)
|
|
||||||
|
|
||||||
inline_assign = TOKEN ws* "=" ws* (inline_expr / LITERAL / TOKEN)
|
|
||||||
|
|
||||||
inline_op = TOKEN ws* inline_op_symbol ws* (inline_expr / TOKEN / LITERAL)
|
|
||||||
inline_op_symbol = ~"[*-+/]"
|
|
||||||
|
|
||||||
inline_get = TOKEN inline_get_symbol (inline_expr / TOKEN / LITERAL)
|
|
||||||
inline_get_symbol = ~"(~>|->)"
|
|
||||||
|
|
||||||
|
|
||||||
listraw = "'" list_start expr* list_end
|
|
||||||
listc = TOKEN list_start expr* list_end
|
|
||||||
listskill = list_start expr* list_end
|
|
||||||
|
|
||||||
list_start = "("
|
|
||||||
list_end = ")"
|
|
||||||
|
|
||||||
TOKEN = ~"[a-zA-Z_][_a-zA-Z0-9]+"
|
|
||||||
LITERAL = L_num / L_t / L_nil / L_str
|
|
||||||
|
|
||||||
L_num = ~"[0-9]+(\.[0-9]+)?"
|
|
||||||
L_t = "t"
|
|
||||||
L_nil = "nil"
|
|
||||||
|
|
||||||
L_str = delim_str any_str delim_str
|
|
||||||
delim_str = "\""
|
|
||||||
any_str = ~"[^\"]*"
|
|
||||||
|
|
||||||
ws = ~"\\h"
|
|
||||||
nl = ~"\\n"
|
|
||||||
123
skillls/main.py
123
skillls/main.py
|
|
@ -123,26 +123,7 @@ def offset_range(range: Range, lines: int, cols: int = 0) -> Range:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# @dataclass(frozen=True)
|
|
||||||
# class ProcEnvironment(Environment):
|
|
||||||
# name: str
|
|
||||||
# args: tuple[DocumentSymbol, ...]
|
|
||||||
# kwargs: tuple[DocumentSymbol, ...]
|
|
||||||
# rest: DocumentSymbol | None = None
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def locals(self) -> tuple[DocumentSymbol, ...]:
|
|
||||||
# ret = [*self.args, *self.kwargs]
|
|
||||||
# if self.rest:
|
|
||||||
# ret.append(self.rest)
|
|
||||||
#
|
|
||||||
# return tuple(ret)
|
|
||||||
|
|
||||||
|
|
||||||
class SkillLanguageServer(LanguageServer):
|
class SkillLanguageServer(LanguageServer):
|
||||||
contents: dict[str, TextDocument]
|
|
||||||
trees: dict[str, Tree]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -165,111 +146,7 @@ class SkillLanguageServer(LanguageServer):
|
||||||
notebook_document_sync,
|
notebook_document_sync,
|
||||||
max_workers,
|
max_workers,
|
||||||
)
|
)
|
||||||
self.trees = {}
|
|
||||||
self.contents = {}
|
|
||||||
|
|
||||||
def parse(self, doc: TextDocument) -> None:
|
|
||||||
parsed = SKILL_PARSER.parse(doc.source.encode("utf8"), encoding="utf8")
|
|
||||||
self.trees[doc.uri] = parsed
|
|
||||||
self.contents[doc.uri] = doc
|
|
||||||
|
|
||||||
def update(self, uri: str, changes: list[TextDocumentContentChangeEvent]) -> None:
|
|
||||||
for change in changes:
|
|
||||||
if isinstance(change, TextDocumentContentChangeEvent_Type1):
|
|
||||||
logger.debug(f"updating {change.range}")
|
|
||||||
change_fixed = TextDocumentContentChangeEvent_Type1(
|
|
||||||
offset_range(change.range, -1),
|
|
||||||
change.text,
|
|
||||||
change.range_length,
|
|
||||||
)
|
|
||||||
old = self.contents[uri].lines
|
|
||||||
self.contents[uri].apply_change(change)
|
|
||||||
d = Differ()
|
|
||||||
logger.debug("".join(d.compare(old, self.contents[uri].lines)))
|
|
||||||
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.trees[uri] = SKILL_PARSER.parse(
|
|
||||||
self.contents[uri].source.encode("utf8"),
|
|
||||||
old_tree=self.trees[uri],
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_leaves(self, node: Node) -> list[Node]:
|
|
||||||
if node.children:
|
|
||||||
return [l for child in node.children for l in self._get_leaves(child)]
|
|
||||||
|
|
||||||
return [node]
|
|
||||||
|
|
||||||
def _diagnose_errors(self, uri: str) -> list[Diagnostic]:
|
|
||||||
diags: list[Diagnostic] = []
|
|
||||||
q = SKILL_LANG.query("(ERROR) @error")
|
|
||||||
nodes = (
|
|
||||||
q.captures(self.trees[uri].root_node)["error"]
|
|
||||||
if self.trees.get(uri)
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
|
|
||||||
for node in nodes:
|
|
||||||
if node.type == "ERROR":
|
|
||||||
logger.error(node)
|
|
||||||
logger.error(node.range)
|
|
||||||
content = node.text.decode("utf8") if node.text else ""
|
|
||||||
range = Range(
|
|
||||||
Position(*node.range.start_point), Position(*node.range.end_point)
|
|
||||||
)
|
|
||||||
|
|
||||||
if "UNEXPECTED" in str(node):
|
|
||||||
msg = f"unexpected '{content}'"
|
|
||||||
else:
|
|
||||||
msg = str()
|
|
||||||
|
|
||||||
diags.append(
|
|
||||||
Diagnostic(
|
|
||||||
range,
|
|
||||||
msg,
|
|
||||||
severity=DiagnosticSeverity.Error,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return diags
|
|
||||||
|
|
||||||
def diagnose(self, uri: str) -> list[Diagnostic]:
|
|
||||||
diags: list[Diagnostic] = []
|
|
||||||
|
|
||||||
diags.extend(self._diagnose_errors(uri))
|
|
||||||
|
|
||||||
return diags
|
|
||||||
|
|
||||||
|
|
||||||
server = SkillLanguageServer("skillls", "v0.3")
|
|
||||||
|
|
||||||
|
|
||||||
# @server.feature(TEXT_DOCUMENT_DID_SAVE)
|
|
||||||
@server.feature(TEXT_DOCUMENT_DID_OPEN)
|
|
||||||
def on_open(ls: SkillLanguageServer, params: DidSaveTextDocumentParams) -> None:
|
|
||||||
doc = server.workspace.get_text_document(params.text_document.uri)
|
|
||||||
ls.parse(doc)
|
|
||||||
|
|
||||||
diags = ls.diagnose(doc.uri)
|
|
||||||
ls.publish_diagnostics(doc.uri, diags)
|
|
||||||
|
|
||||||
|
|
||||||
@server.feature(TEXT_DOCUMENT_DID_CHANGE)
|
|
||||||
def on_change(ls: SkillLanguageServer, params: DidChangeTextDocumentParams) -> None:
|
|
||||||
ls.update(params.text_document.uri, changes=params.content_changes)
|
|
||||||
|
|
||||||
diags = ls.diagnose(params.text_document.uri)
|
|
||||||
ls.publish_diagnostics(params.text_document.uri, diags)
|
|
||||||
|
|
||||||
|
|
||||||
@server.feature(TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
|
||||||
def doc_symbols(
|
|
||||||
ls: SkillLanguageServer,
|
|
||||||
params: DocumentSymbolParams,
|
|
||||||
) -> list[DocumentSymbol]:
|
|
||||||
# return ls.procs + ls.lets + ls.defs + ls.globals
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum, auto
|
|
||||||
from typing import ClassVar, DefaultDict
|
|
||||||
|
|
||||||
from .location import Range
|
|
||||||
from .tokenize import BaseToken
|
|
||||||
|
|
||||||
|
|
||||||
class ContextType(Enum):
|
|
||||||
Use = auto()
|
|
||||||
Assign = auto()
|
|
||||||
Declare = auto()
|
|
||||||
Unbind = auto()
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Context:
|
|
||||||
lookup: ClassVar[dict[ContextType, list["Context"]]] = {}
|
|
||||||
|
|
||||||
typ: ContextType
|
|
||||||
token: list[BaseToken]
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
type(self).lookup.setdefault(self.typ, [])
|
|
||||||
type(self).lookup[self.typ].append(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def range(self) -> Range:
|
|
||||||
new_range = self.token[0].range
|
|
||||||
for token in self.token[1:]:
|
|
||||||
new_range += token.range
|
|
||||||
return new_range
|
|
||||||
|
|
@ -1,296 +0,0 @@
|
||||||
from abc import ABC
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from enum import Enum
|
|
||||||
from logging import getLogger
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import NamedTuple, Self
|
|
||||||
|
|
||||||
from lsprotocol.types import (
|
|
||||||
Diagnostic,
|
|
||||||
DiagnosticSeverity,
|
|
||||||
DocumentSymbol,
|
|
||||||
Position,
|
|
||||||
Range,
|
|
||||||
SymbolKind,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Pair(NamedTuple):
|
|
||||||
start: str
|
|
||||||
end: str
|
|
||||||
|
|
||||||
|
|
||||||
class SyntaxPair(Enum):
|
|
||||||
Paren = Pair("(", ")")
|
|
||||||
Square = Pair("[", "]")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def by_start_elem(cls, start: str) -> Self:
|
|
||||||
for option in cls:
|
|
||||||
if option.value[0] == start:
|
|
||||||
return option
|
|
||||||
|
|
||||||
raise ValueError(f"`{start}` not a valid start character")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def by_end_elem(cls, end: str) -> Self:
|
|
||||||
for option in cls:
|
|
||||||
if option.value[1] == end:
|
|
||||||
return option
|
|
||||||
|
|
||||||
raise ValueError(f"`{end}` not a valid end character")
|
|
||||||
|
|
||||||
|
|
||||||
def char_range(line: int, char: int) -> Range:
|
|
||||||
return Range(Position(line, char), Position(line, char + 1))
|
|
||||||
|
|
||||||
|
|
||||||
def pair_mismatch(line: int, char: int, msg: str) -> Diagnostic:
|
|
||||||
return Diagnostic(
|
|
||||||
char_range(line, char),
|
|
||||||
msg,
|
|
||||||
severity=DiagnosticSeverity.Error,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class StackElement(NamedTuple):
|
|
||||||
range: Range
|
|
||||||
elem: SyntaxPair
|
|
||||||
|
|
||||||
|
|
||||||
WHITESPACE_OR_PAREN = re.compile(r"(\s|\(|\)|\[|\]|\'\()+")
|
|
||||||
TOKEN_REGEX = re.compile(r"\w[a-zA-Z0-9_]*")
|
|
||||||
NUMBER_REGEX = re.compile(r"\d+(\.\d+)?")
|
|
||||||
OPERATORS = re.compile(r"(->|~>|\+|\-|\*|\/|\=|\|\||\&\&)")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TreeToken(ABC):
|
|
||||||
content: str
|
|
||||||
range: Range
|
|
||||||
|
|
||||||
|
|
||||||
def String(content: str, range: Range) -> DocumentSymbol:
|
|
||||||
return DocumentSymbol(
|
|
||||||
name=content,
|
|
||||||
range=range,
|
|
||||||
kind=SymbolKind.String,
|
|
||||||
selection_range=range,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def Operator(content: str, range: Range) -> DocumentSymbol:
|
|
||||||
return DocumentSymbol(
|
|
||||||
name=content,
|
|
||||||
range=range,
|
|
||||||
kind=SymbolKind.Operator,
|
|
||||||
selection_range=range,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def Number(content: str, range: Range) -> DocumentSymbol:
|
|
||||||
return DocumentSymbol(
|
|
||||||
name=content,
|
|
||||||
range=range,
|
|
||||||
kind=SymbolKind.Number,
|
|
||||||
selection_range=range,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def Token(content: str, range: Range) -> DocumentSymbol:
|
|
||||||
return DocumentSymbol(
|
|
||||||
name=content,
|
|
||||||
range=range,
|
|
||||||
kind=SymbolKind.Variable,
|
|
||||||
selection_range=range,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
RawIndex = int
|
|
||||||
ColIndex = int
|
|
||||||
LineIndex = int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TokenParser:
|
|
||||||
_in_string: bool = False
|
|
||||||
_in_comment: bool = False
|
|
||||||
_token_tree: list[DocumentSymbol] = field(default_factory=list)
|
|
||||||
_current: str = ""
|
|
||||||
_line_indices: list[RawIndex] = field(default_factory=list)
|
|
||||||
|
|
||||||
def _get_line(self, index: RawIndex) -> tuple[LineIndex, RawIndex]:
|
|
||||||
for line, newline_pos in enumerate(self._line_indices):
|
|
||||||
if index < newline_pos:
|
|
||||||
return line, self._line_indices[line - 1] if line > 0 else 0
|
|
||||||
|
|
||||||
return len(self._line_indices), self._line_indices[-1]
|
|
||||||
|
|
||||||
def _get_range(self, start: RawIndex, end: RawIndex) -> Range:
|
|
||||||
start_line, start_line_index = self._get_line(start)
|
|
||||||
start_col = start - start_line_index - 1
|
|
||||||
|
|
||||||
end_line, end_line_index = self._get_line(end)
|
|
||||||
end_col = end - end_line_index - 1
|
|
||||||
|
|
||||||
return Range(Position(start_line, start_col), Position(end_line, end_col))
|
|
||||||
|
|
||||||
def _parse_string(self, raw: str, index: int) -> int:
|
|
||||||
stop = raw.index('"', index + 1)
|
|
||||||
self._token_tree.append(
|
|
||||||
String(raw[index : stop + 1], self._get_range(index, stop))
|
|
||||||
)
|
|
||||||
return stop + 1
|
|
||||||
|
|
||||||
def _parse_comment(self, raw: str, index: int) -> int:
|
|
||||||
stop = raw.index("\n", index)
|
|
||||||
# self._token_tree.append(Comment(raw[index:stop], self._get_range(index, stop)))
|
|
||||||
return stop + 1
|
|
||||||
|
|
||||||
def _parse_whitespace(self, raw: str, index: int) -> int:
|
|
||||||
if m := WHITESPACE_OR_PAREN.search(raw, index):
|
|
||||||
stop = m.end()
|
|
||||||
else:
|
|
||||||
stop = index + 1
|
|
||||||
|
|
||||||
# self._token_tree.append(Whitespace(raw[index:stop]))
|
|
||||||
return stop
|
|
||||||
|
|
||||||
def _parse_operator(self, raw: str, index: int) -> int:
|
|
||||||
if m := OPERATORS.search(raw, index):
|
|
||||||
stop = m.end()
|
|
||||||
else:
|
|
||||||
stop = index + 1
|
|
||||||
|
|
||||||
self._token_tree.append(
|
|
||||||
Operator(raw[index:stop], self._get_range(index, stop - 1))
|
|
||||||
)
|
|
||||||
return stop + 1
|
|
||||||
|
|
||||||
def _parse_token(self, raw: str, index: int) -> int:
|
|
||||||
if m := TOKEN_REGEX.search(raw, index):
|
|
||||||
stop = m.end()
|
|
||||||
else:
|
|
||||||
stop = index + 1
|
|
||||||
|
|
||||||
self._token_tree.append(
|
|
||||||
Token(raw[index:stop], self._get_range(index, stop - 1))
|
|
||||||
)
|
|
||||||
return stop
|
|
||||||
|
|
||||||
def _parse_number(self, raw: str, index: int) -> int:
|
|
||||||
if m := NUMBER_REGEX.search(raw, index):
|
|
||||||
stop = m.end()
|
|
||||||
else:
|
|
||||||
stop = index + 1
|
|
||||||
|
|
||||||
self._token_tree.append(
|
|
||||||
Number(raw[index:stop], self._get_range(index, stop - 1))
|
|
||||||
)
|
|
||||||
return stop
|
|
||||||
|
|
||||||
def prepare_content(self, raw: str) -> None:
|
|
||||||
self._line_indices = [i for i, char in enumerate(raw) if char == "\n"]
|
|
||||||
max_index = len(raw)
|
|
||||||
index = 0
|
|
||||||
while index < max_index:
|
|
||||||
if raw[index] == '"':
|
|
||||||
index = self._parse_string(raw, index)
|
|
||||||
elif raw[index] == ";":
|
|
||||||
index = self._parse_comment(raw, index)
|
|
||||||
elif WHITESPACE_OR_PAREN.match(raw[index : index + 2]):
|
|
||||||
index = self._parse_whitespace(raw, index)
|
|
||||||
elif OPERATORS.match(raw[index]):
|
|
||||||
index = self._parse_operator(raw, index)
|
|
||||||
elif NUMBER_REGEX.match(raw[index]):
|
|
||||||
index = self._parse_number(raw, index)
|
|
||||||
else:
|
|
||||||
index = self._parse_token(raw, index)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class IterativeParser:
|
|
||||||
_stack: list[StackElement] = field(default_factory=list)
|
|
||||||
|
|
||||||
def peek(self) -> StackElement:
|
|
||||||
return self._stack[-1]
|
|
||||||
|
|
||||||
def pop(self) -> StackElement:
|
|
||||||
return self._stack.pop()
|
|
||||||
|
|
||||||
def push(self, pair: StackElement) -> None:
|
|
||||||
return self._stack.append(pair)
|
|
||||||
|
|
||||||
def __call__(self, raw: list[str]) -> list[Diagnostic]:
|
|
||||||
in_string = False
|
|
||||||
errs = []
|
|
||||||
for line, raw_line in enumerate(raw):
|
|
||||||
for char, raw_char in enumerate(raw_line):
|
|
||||||
match raw_char:
|
|
||||||
case ";":
|
|
||||||
if not in_string:
|
|
||||||
break
|
|
||||||
case '"':
|
|
||||||
in_string = not in_string
|
|
||||||
case "(" | "[":
|
|
||||||
if not in_string:
|
|
||||||
self.push(
|
|
||||||
StackElement(
|
|
||||||
char_range(line, char),
|
|
||||||
SyntaxPair.by_start_elem(raw_char),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case "]" | ")":
|
|
||||||
if not in_string:
|
|
||||||
if not self._stack:
|
|
||||||
errs.append(
|
|
||||||
pair_mismatch(
|
|
||||||
line, char, f"one {raw_char} too much"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
expected = SyntaxPair.by_end_elem(raw_char)
|
|
||||||
elem = self._stack.pop()
|
|
||||||
if elem.elem == expected:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self._stack and self._stack[-1].elem == expected:
|
|
||||||
errs.append(
|
|
||||||
pair_mismatch(
|
|
||||||
line, char, f"unclosed {elem.elem.value.start}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._stack.pop()
|
|
||||||
self._stack.append(elem)
|
|
||||||
else:
|
|
||||||
errs.append(
|
|
||||||
pair_mismatch(
|
|
||||||
line, char, f"one {raw_char} too much"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._stack.append(elem)
|
|
||||||
|
|
||||||
for rest in self._stack:
|
|
||||||
errs.append(
|
|
||||||
Diagnostic(
|
|
||||||
rest.range,
|
|
||||||
f"unclosed {rest.elem.value.start}",
|
|
||||||
severity=DiagnosticSeverity.Error,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._stack = []
|
|
||||||
|
|
||||||
return errs
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
example = Path(__file__).parent.parent.parent / "examples" / "example.il"
|
|
||||||
|
|
||||||
t = TokenParser()
|
|
||||||
t.prepare_content(example.read_text())
|
|
||||||
print(t._token_tree)
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
from functools import cached_property
|
|
||||||
from typing import overload
|
|
||||||
from lsprotocol.types import Position, Range
|
|
||||||
|
|
||||||
from parsimonious.nodes import Node
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Locator:
|
|
||||||
raw: list[str]
|
|
||||||
|
|
||||||
def _locate_pos(self, index: int) -> Position:
|
|
||||||
counter = 0
|
|
||||||
line = 0
|
|
||||||
for ix, raw_line in enumerate(self.raw):
|
|
||||||
if counter + len(raw_line) + 1 > index:
|
|
||||||
line = ix
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
counter += len(raw_line) + 1
|
|
||||||
|
|
||||||
print(counter, line)
|
|
||||||
return Position(line + 1, index - counter + 1)
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def locate(self, index: int) -> Position:
|
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def locate(self, index: Node) -> Range:
|
|
||||||
...
|
|
||||||
|
|
||||||
def locate(self, index: int | Node) -> Position | Range:
|
|
||||||
if isinstance(index, int):
|
|
||||||
return self._locate_pos(index)
|
|
||||||
|
|
||||||
print(index.start, index.end)
|
|
||||||
start = self._locate_pos(index.start)
|
|
||||||
end = self._locate_pos(index.end)
|
|
||||||
|
|
||||||
return Range(start, end)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
Loading…
Reference in New Issue