From 82b165dd2119d073468e5ba56417ec2555617c8f Mon Sep 17 00:00:00 2001 From: AcerecA Date: Sun, 16 Nov 2025 14:59:02 +0100 Subject: [PATCH] redo --- skillls/builtins/__init__.py | 0 skillls/builtins/common.py | 145 ---------------- skillls/builtins/functions.defs | 6 - skillls/builtins/functions.py | 21 --- skillls/cache.py | 37 ---- skillls/grammar.peg | 34 ---- skillls/main.py | 123 ------------- skillls/parsing/__init__.py | 0 skillls/parsing/context.py | 32 ---- skillls/parsing/iterative.py | 296 -------------------------------- skillls/parsing/location.py | 42 ----- skillls/parsing/tokenize.py | 1 - 12 files changed, 737 deletions(-) delete mode 100644 skillls/builtins/__init__.py delete mode 100644 skillls/builtins/common.py delete mode 100644 skillls/builtins/functions.defs delete mode 100644 skillls/builtins/functions.py delete mode 100644 skillls/cache.py delete mode 100644 skillls/grammar.peg delete mode 100644 skillls/parsing/__init__.py delete mode 100644 skillls/parsing/context.py delete mode 100644 skillls/parsing/iterative.py delete mode 100644 skillls/parsing/location.py delete mode 100644 skillls/parsing/tokenize.py diff --git a/skillls/builtins/__init__.py b/skillls/builtins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/skillls/builtins/common.py b/skillls/builtins/common.py deleted file mode 100644 index b328624..0000000 --- a/skillls/builtins/common.py +++ /dev/null @@ -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, - ) diff --git a/skillls/builtins/functions.defs b/skillls/builtins/functions.defs deleted file mode 100644 index b399802..0000000 --- a/skillls/builtins/functions.defs +++ /dev/null @@ -1,6 +0,0 @@ -;; append -append( - l_list1 - l_list2 -) => l_result - diff --git a/skillls/builtins/functions.py b/skillls/builtins/functions.py deleted file mode 100644 index 13c33a7..0000000 --- a/skillls/builtins/functions.py +++ /dev/null @@ -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"] diff --git a/skillls/cache.py b/skillls/cache.py deleted file mode 100644 index 8417f7b..0000000 --- a/skillls/cache.py +++ /dev/null @@ -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) - - - - diff --git a/skillls/grammar.peg b/skillls/grammar.peg deleted file mode 100644 index c07ada0..0000000 --- a/skillls/grammar.peg +++ /dev/null @@ -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" diff --git a/skillls/main.py b/skillls/main.py index 1347206..cf682a1 100644 --- a/skillls/main.py +++ b/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): - 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(): diff --git a/skillls/parsing/__init__.py b/skillls/parsing/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/skillls/parsing/context.py b/skillls/parsing/context.py deleted file mode 100644 index 4114a94..0000000 --- a/skillls/parsing/context.py +++ /dev/null @@ -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 diff --git a/skillls/parsing/iterative.py b/skillls/parsing/iterative.py deleted file mode 100644 index 899f2e4..0000000 --- a/skillls/parsing/iterative.py +++ /dev/null @@ -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) diff --git a/skillls/parsing/location.py b/skillls/parsing/location.py deleted file mode 100644 index 9e506bc..0000000 --- a/skillls/parsing/location.py +++ /dev/null @@ -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) diff --git a/skillls/parsing/tokenize.py b/skillls/parsing/tokenize.py deleted file mode 100644 index 8b13789..0000000 --- a/skillls/parsing/tokenize.py +++ /dev/null @@ -1 +0,0 @@ -