This commit is contained in:
AcerecA 2025-11-16 14:59:02 +01:00
parent 51984e297b
commit 82b165dd21
12 changed files with 0 additions and 737 deletions

View File

@ -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,
)

View File

@ -1,6 +0,0 @@
;; append
append(
l_list1
l_list2
) => l_result

View File

@ -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"]

View File

@ -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)

View File

@ -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"

View File

@ -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):
contents: dict[str, TextDocument]
trees: dict[str, Tree]
def __init__(
self,
@ -165,111 +146,7 @@ class SkillLanguageServer(LanguageServer):
notebook_document_sync,
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():

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -1 +0,0 @@