add tcl parser

This commit is contained in:
2025-07-18 22:49:49 +02:00
parent e2ba07c8b4
commit e045e1533c
3 changed files with 63 additions and 59 deletions
+15 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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