add tcl parser
This commit is contained in:
+15
-12
@@ -13,6 +13,7 @@ import sys
|
||||
import sysconfig
|
||||
import traceback
|
||||
from typing import Any, Optional, Sequence
|
||||
from parser.parser import parse_tcl, format_tree
|
||||
|
||||
|
||||
# **********************************************************
|
||||
@@ -179,18 +180,20 @@ def _get_severity(*_codes: list[str]) -> lsp.DiagnosticSeverity:
|
||||
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_FORMATTING)
|
||||
def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | None:
|
||||
"""LSP handler for textDocument/formatting request."""
|
||||
# If your tool is a formatter you can use this handler to provide
|
||||
# formatting support on save. You have to return an array of lsp.TextEdit
|
||||
# objects, to provide your formatted results.
|
||||
|
||||
document = LSP_SERVER.workspace.get_document(params.text_document.uri)
|
||||
edits = _formatting_helper(document)
|
||||
if edits:
|
||||
return edits
|
||||
|
||||
# NOTE: If you provide [] array, VS Code will clear the file of all contents.
|
||||
# To indicate no changes to file return None.
|
||||
return None
|
||||
text = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||
tree, _ = parse_tcl(text)
|
||||
if not tree:
|
||||
return []
|
||||
new_text = format_tree(tree)
|
||||
return [
|
||||
{
|
||||
"range": {
|
||||
"start": {"line": 0, "character": 0},
|
||||
"end": {"line": len(text.splitlines()), "character": 0},
|
||||
},
|
||||
"newText": new_text,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _formatting_helper(document: workspace.Document) -> list[lsp.TextEdit] | None:
|
||||
|
||||
+32
-25
@@ -1,32 +1,39 @@
|
||||
from lark import Lark, Transformer, Token, v_args
|
||||
from pathlib import Path
|
||||
from lark import Lark, Tree, Token, UnexpectedInput
|
||||
|
||||
GRAMMAR_FILE = Path(__file__).parent.joinpath("tcl.lark")
|
||||
TCL_GRAMMAR = GRAMMAR_FILE.read_text(encoding="utf-8")
|
||||
tcl_parser = Lark.open(
|
||||
Path(__file__).parent.joinpath("tcl.lark"), parser="lalr", propagate_positions=True
|
||||
)
|
||||
|
||||
|
||||
@v_args(inline=True)
|
||||
class TCLTransformer(Transformer):
|
||||
def start(self, *stmts):
|
||||
return list(stmts)
|
||||
def parse_tcl(source: str):
|
||||
try:
|
||||
tree = tcl_parser.parse(source)
|
||||
return tree, []
|
||||
except UnexpectedInput as e:
|
||||
# e.line, e.column enthalten Position
|
||||
diagnostic = {
|
||||
"range": {
|
||||
"start": {"line": e.line - 1, "character": e.column - 1},
|
||||
"end": {"line": e.line - 1, "character": e.column},
|
||||
},
|
||||
"message": f"Syntaxfehler: {e}",
|
||||
"severity": 1, # Error
|
||||
}
|
||||
return None, [diagnostic]
|
||||
|
||||
def command(self, *elements):
|
||||
return list(elements)
|
||||
|
||||
def braced(self, *content):
|
||||
return "{" + "".join(c for c in content) + "}"
|
||||
def format_tree(tree):
|
||||
def fmt(node, indent=0):
|
||||
if isinstance(node, Token):
|
||||
return node.value
|
||||
elif isinstance(node, Tree):
|
||||
if node.data == "cmd":
|
||||
parts = []
|
||||
for child in node.children:
|
||||
parts.append(fmt(child, indent))
|
||||
return " ".join(parts) + ";\n"
|
||||
# weitere Node-Typen …
|
||||
return ""
|
||||
|
||||
def quoted(self, *content):
|
||||
return '"' + "".join(c for c in content) + '"'
|
||||
|
||||
def variable(self, token):
|
||||
return token.value
|
||||
|
||||
def cmdsubst(self, *inner):
|
||||
return "[" + " ".join(inner) + "]"
|
||||
|
||||
def bare(self, token):
|
||||
return token.value
|
||||
|
||||
def COMMENT(self, token):
|
||||
return token.value
|
||||
return "".join(fmt(tree))
|
||||
|
||||
+16
-22
@@ -1,27 +1,21 @@
|
||||
start: statement*
|
||||
// tcl.lark
|
||||
// sehr vereinfachtes Beispiel – für volle Abdeckung musst du die offizielle TCL-Spec implementieren
|
||||
|
||||
?statement: command | COMMENT
|
||||
?start: script
|
||||
|
||||
COMMENT: /#[^\n]*/
|
||||
script: stmt*
|
||||
stmt: command ";"? -> cmd
|
||||
| newline -> empty
|
||||
|
||||
command: element+
|
||||
command: WORD arg*
|
||||
arg: WORD -> bareword
|
||||
| STRING -> string
|
||||
| BRACED -> braced
|
||||
|
||||
?element: braced
|
||||
| quoted
|
||||
| variable
|
||||
| cmdsubst
|
||||
| bare
|
||||
WORD: /[^\s\{\}";]+/
|
||||
STRING: "\"" /([^"\\]|\\.)*/ "\"" // "…"
|
||||
BRACED: "{" /([^{}\\]|\\.)*/ "}" // {…}
|
||||
|
||||
braced: "{" inner_braced* "}"
|
||||
inner_braced: /[^{}]+/ | braced
|
||||
|
||||
quoted: '"' inner_quoted* '"'
|
||||
inner_quoted: /[^"{}]+/ | braced
|
||||
|
||||
variable: "$" /[A-Za-z_][A-Za-z0-9_]*/
|
||||
cmdsubst: "[" start "]"
|
||||
|
||||
bare: /[^\s{}$"]+/
|
||||
|
||||
%import common.WS_INLINE
|
||||
%ignore WS_INLINE
|
||||
%import common.NEWLINE -> newline
|
||||
%import common.WS
|
||||
%ignore WS
|
||||
|
||||
Reference in New Issue
Block a user