initial commit
This commit is contained in:
commit
c05fc0e1ba
|
@ -0,0 +1,5 @@
|
||||||
|
.venv/*
|
||||||
|
.idea/*
|
||||||
|
*.egg-info/*
|
||||||
|
**/__pycache__/*
|
||||||
|
*.log
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "/home/patrick/git/skill-ls/.venv/bin/skillls",
|
||||||
|
"python": "/home/patrick/git/skill-ls/.venv/bin/python"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
examlpe = nil
|
||||||
|
|
||||||
|
(procedure function(param1 (param2 t))
|
||||||
|
|
||||||
|
param1 = 1 + 3
|
||||||
|
|
||||||
|
|
||||||
|
(call_to_other_function "arg1" t)
|
||||||
|
|
||||||
|
c_stype_call("arg")
|
||||||
|
|
||||||
|
)
|
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "skillls"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"parsimonious~=0.10.0",
|
||||||
|
"pygls",
|
||||||
|
"rich"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"black",
|
||||||
|
"mypy",
|
||||||
|
"ruff",
|
||||||
|
"pytest",
|
||||||
|
"types-parsimonious",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
build-backend = 'setuptools.build_meta'
|
||||||
|
requires = [
|
||||||
|
'setuptools',
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
skillls = "skillls.main:main"
|
||||||
|
|
||||||
|
|
||||||
|
[tools.black]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
include = "skillls"
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
skill = inline_expr+
|
||||||
|
expr = (inline_expr / nl)
|
||||||
|
|
||||||
|
inline_expr = (listraw / listc / listskill / inline_get / inline_op / inline_assign / ws)
|
||||||
|
|
||||||
|
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"
|
|
@ -0,0 +1,83 @@
|
||||||
|
from logging import INFO, basicConfig, getLogger
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import unquote
|
||||||
|
from lsprotocol.types import (
|
||||||
|
TEXT_DOCUMENT_DOCUMENT_SYMBOL,
|
||||||
|
DocumentSymbol,
|
||||||
|
DocumentSymbolParams,
|
||||||
|
Position,
|
||||||
|
Range,
|
||||||
|
SymbolKind,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pygls.server import LanguageServer
|
||||||
|
from parsimonious import Grammar
|
||||||
|
from pygls.uris import urlparse
|
||||||
|
|
||||||
|
# from skillls.parsing.location import Range
|
||||||
|
|
||||||
|
from .parsing.tokenize import Locator, SkillVisitor
|
||||||
|
|
||||||
|
|
||||||
|
example = """
|
||||||
|
(skillist siomfpwqmqwepfomkjnbkjb
|
||||||
|
'(rawlist token)
|
||||||
|
clist(qwerfwf)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def parse(path: Path):
|
||||||
|
# path = Path(__file__).parent / "grammar.peg"
|
||||||
|
grammar = Grammar(path.read_text())
|
||||||
|
|
||||||
|
locator = Locator(example)
|
||||||
|
tree = grammar.parse(example)
|
||||||
|
|
||||||
|
iv = SkillVisitor(locator)
|
||||||
|
output = iv.visit(tree)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def parse_and_cache(uri: str) -> list[DocumentSymbol]:
|
||||||
|
path = Path(unquote(urlparse(uri).path))
|
||||||
|
if not path.exists():
|
||||||
|
logger.error("could not find %s", path)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if not cache.get(path):
|
||||||
|
logger.info("%s not yet cached, parsing...")
|
||||||
|
out = parse(path)
|
||||||
|
logger.info("%s", out)
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
basicConfig(filename="skillls.log", level=INFO)
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
server = LanguageServer("skillls", "v0.1")
|
||||||
|
|
||||||
|
|
||||||
|
@server.feature(TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
||||||
|
def document_symbols(params: DocumentSymbolParams) -> list[DocumentSymbol]:
|
||||||
|
logger.info("requested document symbols for %s", params.text_document.uri)
|
||||||
|
doc = server.workspace.documents[params.text_document.uri]
|
||||||
|
return [
|
||||||
|
DocumentSymbol(
|
||||||
|
"~global_scope",
|
||||||
|
kind=SymbolKind.Namespace,
|
||||||
|
range=Range(
|
||||||
|
start=Position(0, 0),
|
||||||
|
end=Position(len(doc.lines) - 1, len(doc.lines[-1])),
|
||||||
|
),
|
||||||
|
selection_range=Range(Position(0, 0), Position(0, 0)),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
server.start_io()
|
|
@ -0,0 +1,32 @@
|
||||||
|
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
|
|
@ -0,0 +1,71 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import cached_property
|
||||||
|
from typing import overload
|
||||||
|
from lsprotocol.types import Position, Range
|
||||||
|
|
||||||
|
from parsimonious.nodes import Node
|
||||||
|
|
||||||
|
|
||||||
|
# @total_ordering
|
||||||
|
# class Position(NamedTuple):
|
||||||
|
# line: int
|
||||||
|
# char: int
|
||||||
|
#
|
||||||
|
# def __lt__(self, other: Self) -> bool:
|
||||||
|
# return (self.line < other.line) or (
|
||||||
|
# (self.line == other.line) and (self.char < other.char)
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# def __eq__(self, other: Self) -> bool:
|
||||||
|
# return (self.line == other.line) and (self.char == other.char)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Range(NamedTuple):
|
||||||
|
# start: Position
|
||||||
|
# end: Position
|
||||||
|
#
|
||||||
|
# def __add__(self, other: Self) -> Self:
|
||||||
|
# start = min(self.start, other.start)
|
||||||
|
# end = max(self.end, other.end)
|
||||||
|
# return Range(start, end)
|
||||||
|
#
|
||||||
|
# def contained_by(self, possibly_contained_by: Self) -> bool:
|
||||||
|
# return (self.start >= possibly_contained_by.start) and (
|
||||||
|
# self.end <= possibly_contained_by.end
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# def contains(self, possibly_contains: Self) -> bool:
|
||||||
|
# return (self.start <= possibly_contains.start) and (
|
||||||
|
# self.end >= possibly_contains.end
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Locator:
|
||||||
|
raw: str
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def newlines(self) -> tuple[int, ...]:
|
||||||
|
t = tuple(i for i, char in enumerate(self.raw) if char == "\n")
|
||||||
|
return t
|
||||||
|
|
||||||
|
def _locate_pos(self, index: int) -> Position:
|
||||||
|
line = next(i for i, char in enumerate(self.newlines) if char >= index)
|
||||||
|
return Position(line - 1, index - (self.newlines[line - 1] if line > 0 else 0))
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
start = self._locate_pos(index.start)
|
||||||
|
end = self._locate_pos(index.end)
|
||||||
|
|
||||||
|
return Range(start, end)
|
|
@ -0,0 +1,111 @@
|
||||||
|
from typing import Any, Sequence
|
||||||
|
from lsprotocol.types import Range
|
||||||
|
from parsimonious import ParseError
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from parsimonious.nodes import Node, NodeVisitor
|
||||||
|
|
||||||
|
from .location import Locator
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BaseToken:
|
||||||
|
range: Range
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Literal(BaseToken):
|
||||||
|
value: str | float | bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Token(BaseToken):
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class List(BaseToken):
|
||||||
|
value: list[BaseToken]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SkillVisitor(NodeVisitor):
|
||||||
|
locator: Locator
|
||||||
|
|
||||||
|
def visit_skill(self, _: Node, visited_children: Sequence[Any]) -> list[BaseToken]:
|
||||||
|
children = []
|
||||||
|
for childlist in visited_children:
|
||||||
|
for child in childlist:
|
||||||
|
if isinstance(child, BaseToken):
|
||||||
|
children.append(child)
|
||||||
|
|
||||||
|
return children
|
||||||
|
|
||||||
|
def visit_TOKEN(self, node: Node, _: Any) -> Token:
|
||||||
|
return Token(self.locator.locate(node), node.text)
|
||||||
|
|
||||||
|
def visit_LITERAL(self, node: Node, visited_children: list[None | Node]) -> Literal:
|
||||||
|
value, *_ = visited_children
|
||||||
|
if value:
|
||||||
|
match value.expr_name:
|
||||||
|
case "L_t":
|
||||||
|
return Literal(self.locator.locate(node), True)
|
||||||
|
case "L_nil":
|
||||||
|
return Literal(self.locator.locate(node), False)
|
||||||
|
case "L_num":
|
||||||
|
return Literal(self.locator.locate(node), float(value.text))
|
||||||
|
case "L_string":
|
||||||
|
return Literal(self.locator.locate(node), value.text)
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise ParseError("something went wrong during literal parsing")
|
||||||
|
|
||||||
|
def visit_listraw(
|
||||||
|
self, node: Node, visited_children: list[list[list[Any]]]
|
||||||
|
) -> List:
|
||||||
|
rest = visited_children[2]
|
||||||
|
|
||||||
|
children = []
|
||||||
|
|
||||||
|
for child in rest:
|
||||||
|
for part in child:
|
||||||
|
if isinstance(part, BaseToken):
|
||||||
|
children.append(part)
|
||||||
|
|
||||||
|
return List(self.locator.locate(node), children)
|
||||||
|
|
||||||
|
def visit_listc(self, node: Node, visited_children: list[list[list[Any]]]) -> List:
|
||||||
|
rest = ([[visited_children[0]]], visited_children[2])
|
||||||
|
|
||||||
|
children = []
|
||||||
|
|
||||||
|
for child_list in rest:
|
||||||
|
for child in child_list:
|
||||||
|
for part in child:
|
||||||
|
if isinstance(part, BaseToken):
|
||||||
|
children.append(part)
|
||||||
|
|
||||||
|
return List(self.locator.locate(node), children)
|
||||||
|
|
||||||
|
def visit_listskill(
|
||||||
|
self, node: Node, visited_children: list[list[list[Any]]]
|
||||||
|
) -> List:
|
||||||
|
rest = visited_children[1]
|
||||||
|
|
||||||
|
children = []
|
||||||
|
|
||||||
|
for child in rest:
|
||||||
|
for part in child:
|
||||||
|
if isinstance(part, BaseToken):
|
||||||
|
children.append(part)
|
||||||
|
|
||||||
|
return List(self.locator.locate(node), children)
|
||||||
|
|
||||||
|
def visit_inline_assign(self, node: Node, visited_children: Sequence[Any]):
|
||||||
|
print(node)
|
||||||
|
|
||||||
|
def generic_visit(
|
||||||
|
self, node: Node, visited_children: Sequence[Any]
|
||||||
|
) -> Node | Sequence[None | Node]:
|
||||||
|
return visited_children or node
|
Loading…
Reference in New Issue