Compare commits

..
23 Commits
Author SHA1 Message Date
Christoph 7c834176a3 Merge pull request 'fix wired namspace call in poco' (#22) from tcl_language_support into main
/ build_and_publish (push) Successful in 32s
Reviewed-on: #22
2025-08-04 12:51:43 +00:00
Christoph Brandau 72be83c282 fix wired namspace call in poco 2025-08-04 14:51:07 +02:00
Christoph Brandau fa5ea00384 add test cases 2025-08-04 14:11:55 +02:00
Christoph a8b4fd9d1e Update .gitea/workflows/build_and_puplish.yml 2025-08-04 11:42:53 +00:00
Christoph 35d0f20fba Update version to 0.4.13 2025-08-04 11:33:48 +00:00
Christoph 440ebc4df5 Update package.json
/ build_and_publish (push) Successful in 26s
2025-08-04 11:31:39 +00:00
Christoph 1b48cc1983 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 23s
2025-08-04 11:30:13 +00:00
Christoph 6cde05b786 Update version to 0.4.13 2025-08-04 10:59:08 +00:00
Christoph cde58db3d7 Merge pull request 'tcl_language_support' (#21) from tcl_language_support into main
/ build_and_publish (push) Successful in 29s
Reviewed-on: #21
2025-08-04 10:58:10 +00:00
Christoph Brandau 3613391c23 fix semantics with var in array 2025-08-04 12:57:01 +02:00
Christoph Brandau 894a5b8b52 add more test cases 2025-08-04 12:48:21 +02:00
Christoph Brandau c644ac9bd3 add changes from tclint 2025-08-04 12:48:02 +02:00
Christoph Brandau fd5b1f2e71 add poco replace plugin 2025-08-04 12:47:49 +02:00
Christoph Brandau 57c65221d3 add LIB_GE_command_buffer_edit_replace to plugins 2025-08-04 09:17:30 +02:00
Christoph 528c7a6b9e add more ignore files 2025-08-03 21:54:45 +02:00
Christoph 2d034838b4 Update package.json 2025-08-03 19:45:39 +00:00
Christoph f0ed8ac708 Update version to 0.5.0 2025-08-03 19:44:23 +00:00
Christoph 6a30c1ab53 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Successful in 37s
2025-08-03 19:43:20 +00:00
Christoph 6d404f26f2 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 27s
2025-08-03 19:39:13 +00:00
Christoph afc675f47f Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 22s
2025-08-03 19:37:43 +00:00
Christoph d2936ec5f6 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 37s
2025-08-03 19:36:28 +00:00
Christoph 8b44b41d68 Update .gitea/workflows/build_and_puplish.yml 2025-08-03 19:35:31 +00:00
Christoph 5124c12bc6 Update version to 0.4.12 2025-08-03 19:30:13 +00:00
7 changed files with 111 additions and 336 deletions
+4 -2
View File
@@ -1,7 +1,7 @@
on: on:
push: push:
tags: tags:
- "*" - "*"
jobs: jobs:
build_and_publish: build_and_publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -39,10 +39,12 @@ jobs:
run: vsce publish run: vsce publish
env: env:
VSCE_PAT: ${{ secrets.VSCODE_MARKETPALCE }} VSCE_PAT: ${{ secrets.VSCODE_MARKETPALCE }}
- name: Pack Extension for Release
run: vsce pack
- name: Upload VSIX to Release - name: Upload VSIX to Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
files: "*.vsix" files: "nx-post-support-*.vsix"
# generate_release_notes: true # Automatische Release Notes # generate_release_notes: true # Automatische Release Notes
# draft: false # N # draft: false # N
# prerelease: false # prerelease: false
+4 -1
View File
@@ -11,8 +11,11 @@ esbuild.js
.gitea .gitea
.venv/** .venv/**
.nox/** .nox/**
server/.venv/**
server/.nox/**
**/__pycache__/** **/__pycache__/**
**/requirements.txt **/requirements.txt
**/requirements.in **/requirements.in
**/server/src/_debug_server.py **/server/src/_debug_server.py
noxfile.py server/noxfile.py
client/**
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "nx-post-support", "name": "nx-post-support",
"displayName": "NX Postprocessor Support", "displayName": "NX Postprocessor Support",
"description": "", "description": "",
"version": "0.4.11", "version": "0.4.13",
"publisher": "Christoph", "publisher": "Christoph",
"serverInfo": { "serverInfo": {
"name": "NX Postprocessor Support", "name": "NX Postprocessor Support",
+7
View File
@@ -36,6 +36,12 @@ def lib_ge_command_buffer_edit_prepend(args, parser):
) )
def lib_ge_command_buffer_edit_replace(args, parser):
_lib_ge_command_buffer_edit(
args, parser, "LIB_GE_command_buffer_edit_replace", 3, 5
)
def lib_ge_command_buffer_edit_insert(args, parser): def lib_ge_command_buffer_edit_insert(args, parser):
_lib_ge_command_buffer_edit(args, parser, "LIB_GE_command_buffer_edit_insert", 2, 6) _lib_ge_command_buffer_edit(args, parser, "LIB_GE_command_buffer_edit_insert", 2, 6)
@@ -44,5 +50,6 @@ commands = [
{"LIB_GE_command_buffer_edit_append": lib_ge_command_buffer_edit_append}, {"LIB_GE_command_buffer_edit_append": lib_ge_command_buffer_edit_append},
{"LIB_GE_command_buffer_edit_prepend": lib_ge_command_buffer_edit_prepend}, {"LIB_GE_command_buffer_edit_prepend": lib_ge_command_buffer_edit_prepend},
{"LIB_GE_command_buffer_edit_insert": lib_ge_command_buffer_edit_insert}, {"LIB_GE_command_buffer_edit_insert": lib_ge_command_buffer_edit_insert},
{"LIB_GE_command_buffer_edit_replace": lib_ge_command_buffer_edit_replace},
{"LIB_GE_command_buffer": _lib_ge_command_buffer}, {"LIB_GE_command_buffer": _lib_ge_command_buffer},
] ]
+45 -319
View File
@@ -1,3 +1,5 @@
import io
from typing import Optional, Tuple
from tclint.parser import Parser from tclint.parser import Parser
from tclint.commands import CommandArgError from tclint.commands import CommandArgError
from tclint.syntax_tree import ( from tclint.syntax_tree import (
@@ -8,254 +10,17 @@ from tclint.syntax_tree import (
QuotedWord, QuotedWord,
Expression, Expression,
) )
from tclint.lexer import TclSyntaxError, Lexer, TOK_EOF
import ply.lex as lex
from typing import Tuple
TOK_BACKSLASH_NEWLINE = "BACKSLASH_NEWLINE"
TOK_BACKSLASH_SUB = "BACKSLASH_SUB"
TOK_NEWLINE = "NEWLINE"
TOK_SEMI = "SEMI"
TOK_WS = "WS"
TOK_QUOTE = "QUOTE"
TOK_ARG_EXPANSION = "ARG_EXPANSION"
TOK_LBRACE = "LBRACE"
TOK_RBRACE = "RBRACE"
TOK_STAR = "STAR"
TOK_LBRACKET = "LBRACKET"
TOK_RBRACKET = "RBRACKET"
TOK_DOLLAR = "DOLLAR"
TOK_LPAREN = "LPAREN"
TOK_RPAREN = "RPAREN"
TOK_HASH = "HASH"
TOK_ALPHA_CHARS = "ALPHA_CHARS"
TOK_NUM_CHARS = "NUM_CHARS"
TOK_NAMESPACE_SEP = "NAMESPACE_SEP"
TOK_CHAR = "CHAR"
TOK_CONTENTS = "CONTENTS"
TOK_EOF = None
STATE_BRACEDWORD = "bracedword"
class TclSyntaxError(Exception):
def __init__(self, message, start: Tuple[int, int], end: Tuple[int, int]):
super().__init__(message)
self.start = start
self.end = end
class _LexTable:
tokens = (
TOK_BACKSLASH_NEWLINE,
TOK_BACKSLASH_SUB,
TOK_NEWLINE,
TOK_SEMI,
TOK_WS,
TOK_QUOTE,
TOK_ARG_EXPANSION,
TOK_LBRACE,
TOK_RBRACE,
TOK_STAR,
TOK_LBRACKET,
TOK_RBRACKET,
TOK_DOLLAR,
TOK_LPAREN,
TOK_RPAREN,
TOK_HASH,
TOK_ALPHA_CHARS,
TOK_NUM_CHARS,
TOK_NAMESPACE_SEP,
TOK_CHAR,
TOK_CONTENTS,
)
# This defines a conditional lexing state for parsing braced words. This is a
# performance optimization; since there are few special characters in this context,
# we can use a smaller set of tokens to parse them faster. This has a large impact
# since most Tcl programs have a large number of braced words. Any token with
# `bracedword` in its name is included in this state. Tokens that are included in
# this state and the default state also include `INITIAL` in their name.
states = ((STATE_BRACEDWORD, "exclusive"),)
def _tok(self, t):
pos = (t.lexer.lineno, t.lexer.colno)
t.lexer.lineno += t.value.count("\n")
index = t.value.rfind("\n")
if index == -1:
t.lexer.colno += len(t.value)
else:
remaining = t.value[index + 1 :]
t.lexer.colno = len(remaining) + 1
t.value = (t.value, pos)
return t
# Priority important
def t_bracedword_INITIAL_BACKSLASH_NEWLINE(self, t):
r"\\\r?\n"
return self._tok(t)
# Priority important
def t_bracedword_INITIAL_BACKSLASH_SUB(self, t):
r"\\."
return self._tok(t)
def t_NEWLINE(self, t):
r"\n"
return self._tok(t)
def t_SEMI(self, t):
r";"
return self._tok(t)
# TODO: should use \s?
def t_WS(self, t):
r"[\t\v\f\r ]+"
return self._tok(t)
def t_QUOTE(self, t):
r'"'
return self._tok(t)
# Must be higher priority than LBRACE
def t_ARG_EXPANSION(self, t):
r"\{\*\}"
return self._tok(t)
def t_bracedword_INITIAL_LBRACE(self, t):
r"\{"
return self._tok(t)
def t_bracedword_INITIAL_RBRACE(self, t):
r"\}"
return self._tok(t)
def t_STAR(self, t):
r"\*"
return self._tok(t)
def t_LBRACKET(self, t):
r"\["
return self._tok(t)
def t_RBRACKET(self, t):
r"\]"
return self._tok(t)
def t_DOLLAR(self, t):
r"\$"
return self._tok(t)
def t_LPAREN(self, t):
r"\("
return self._tok(t)
def t_RPAREN(self, t):
r"\)"
return self._tok(t)
def t_HASH(self, t):
r"\#"
return self._tok(t)
# Valid non-numeric chars in variable names
def t_ALPHA_CHARS(self, t):
r"[A-Za-z_]+"
return self._tok(t)
# Valid numeric chars in variable names
# This is split up from the above to facilitate expression parsing, since
# e.g. 1eq1 can't be a single token.
def t_NUM_CHARS(self, t):
r"[0-9]+"
return self._tok(t)
def t_NAMESPACE_SEP(self, t):
r"::+"
return self._tok(t)
def t_bracedword_CONTENTS(self, t):
r"[^{}\\]+"
return self._tok(t)
# Catch-all. TODO: inefficient, should probably munch multiple chars
def t_CHAR(self, t):
r"."
return self._tok(t)
# Error handling rule
# TODO: do we need this? since we have a catch-all...
# there is a warning
def t_bracedword_INITIAL_error(self, t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
def __init__(self):
self.lexer = lex.lex(object=self)
self.lexer.lineno = 1
self.lexer.colno = 1
def new_lexer(self, pos=None):
lexer = self.lexer.clone()
lexer.lineno = 1
lexer.colno = 1
if pos is not None:
line, col = pos
lexer.lineno = line
lexer.colno = col
return lexer
# Calling `lex.lex()` performs an expensive reflection process to generate the lexer.
# This singleton class holds a preinitialized lexer that can then be cloned to create
# individual instances.
LexTable = _LexTable()
class Lexer:
def __init__(self, pos=None):
self.lexer = LexTable.new_lexer(pos)
self.current = None
def input(self, text):
self.lexer.input(text)
self.current = self.lexer.token()
def type(self):
if self.current is None:
return TOK_EOF
return self.current.type
def value(self):
if self.current is None:
return None
return self.current.value[0]
def pos(self):
if self.current is None:
return (self.lexer.lineno, self.lexer.colno)
return self.current.value[1]
def next(self):
self.current = self.lexer.token()
def expect(self, *tokens, message, pos):
if self.type() not in tokens:
self.next() # munch another token to update position
raise TclSyntaxError(message, pos, self.pos())
self.next()
def assert_(self, *tokens):
assert self.current.type in tokens
self.next()
class CustomParser(Parser): class CustomParser(Parser):
def parse(self, script, pos=None): def __init__(self, debug=False, command_plugins=None):
super().__init__(debug, command_plugins)
# Used to normalize newlines consistently with open()'s universal newlines mode.
self._decoder = io.IncrementalNewlineDecoder(None, True)
def parse(self, script: str, pos: Optional[Tuple[int, int]] = None):
script = self._decoder.decode(script, True)
lexer = Lexer(pos=pos) lexer = Lexer(pos=pos)
lexer.input(script) lexer.input(script)
tree = self._parse_script(lexer, in_command_sub=False) tree = self._parse_script(lexer, in_command_sub=False)
@@ -265,83 +30,44 @@ class CustomParser(Parser):
return tree return tree
def parse_list(self, node): def _parse_operator(self, ts):
"""Parse contents of node as Tcl list. This is a distinct entry point pos = ts.pos()
that doesn't get used when generating the main syntax tree, but is used
in command-specific argument parsing.
"""
if isinstance(node, List):
return node
if node.contents is None: # hacky logic to handle parsing legal operators
raise CommandArgError(
"expected braced word or word without substitutions in argument"
" interpreted as list"
)
ts = Lexer(pos=node.contents_pos) if ts.value() in {"*", "&", "|"}:
ts.input(node.contents) # one or two of these characters are legal operators
operator = ts.value()
DELIMITERS = {TOK_WS, TOK_BACKSLASH_NEWLINE, TOK_NEWLINE} ts.next()
if ts.value() == operator:
list_node = List(pos=node.pos, end_pos=node.end_pos) operator += ts.value()
while ts.type() is not TOK_EOF:
while ts.type() in DELIMITERS:
ts.next() ts.next()
elif ts.value() in {"<", ">"}:
if ts.type() is TOK_EOF: operator = ts.value()
break ts.next()
if ts.value() in {operator, "="}:
if ts.type() == TOK_LBRACE: operator += ts.value()
# we can reuse parse_braced_word, since it doesn't use ts.next()
# substitutions in any case elif ts.value() in {"=", "!"}:
list_node.add(self.parse_braced_word(ts)) operator = ts.value()
elif ts.type() == TOK_QUOTE: ts.next()
quote_word_pos = ts.pos() if ts.value() != "=":
raise TclSyntaxError(
ts.assert_(TOK_QUOTE) f"invalid operator in expression: {operator}", pos, ts.pos()
)
bare_word_pos = ts.pos() operator += ts.value()
contents = "" ts.next()
while ts.type() not in {TOK_QUOTE, TOK_EOF}: elif ts.value() in {"*", "/", "%", "+", "-", "^", "eq", "ne", "in", "ni"}:
contents += ts.value() operator = ts.value()
ts.next() ts.next()
word = BareWord(contents, pos=bare_word_pos, end_pos=ts.pos()) else:
message = "invalid operator in expression: "
ts.expect( if ts.value() == "\\ ":
TOK_QUOTE, message += (
message="reached EOF without finding match for quote", "\\ (check for trailing whitespace if it's the end of the line)"
pos=quote_word_pos,
) )
list_node.add(QuotedWord(word, pos=quote_word_pos, end_pos=ts.pos()))
else: else:
pos = ts.pos() message += ts.value()
contents = "" raise TclSyntaxError(message, pos, ts.pos())
while ts.type() not in {*DELIMITERS, TOK_EOF}:
contents += ts.value()
ts.next()
list_node.add(BareWord(contents, pos=pos, end_pos=ts.pos()))
return list_node return BareWord(operator, pos=pos, end_pos=ts.pos())
def parse_expression(self, node):
if node.contents is None:
raise CommandArgError(
"expected braced word or word without substitutions in argument"
" interpreted as expr"
)
ts = Lexer(pos=node.contents_pos)
ts.input(node.contents)
contents = self._parse_expression(ts)
ts.expect(
TOK_EOF,
message=f"expected end of expression, got {ts.value()}",
pos=ts.pos(),
)
if isinstance(node, BracedWord):
return BracedExpression(contents, pos=node.pos, end_pos=node.end_pos)
return Expression(contents, pos=node.pos, end_pos=node.end_pos)
+37 -11
View File
@@ -45,6 +45,37 @@ class _Highlighter(Visitor):
self._tokens = [] self._tokens = []
self.log_to_output = log_to_output self.log_to_output = log_to_output
def _get_token_info(self, node):
"""Hilfsmethode um Token-Informationen aus verschiedenen Node-Typen zu extrahieren."""
if not hasattr(node, "pos"):
return None
# Einfacher Fall: Node hat direkten value
if hasattr(node, "value") and node.value is not None:
line, col = node.pos
return (line - 1, col - 1), len(node.value)
# CompoundBareWord: versuche erstes Segment
if hasattr(node, "children") and node.children:
first_segment = node.children[0]
if (
hasattr(first_segment, "value")
and first_segment.value is not None
and hasattr(first_segment, "pos")
):
line, col = first_segment.pos
return (line - 1, col - 1), len(first_segment.value)
# Fallback: Gesamtlänge aus Positionen berechnen
if hasattr(node, "end_pos"):
start_line, start_col = node.pos
end_line, end_col = node.end_pos
if start_line == end_line:
length = end_col - start_col
return (start_line - 1, start_col - 1), length
return None
def visit_quoted_word(self, word: QuotedWord): def visit_quoted_word(self, word: QuotedWord):
if not word.contents: if not word.contents:
return return
@@ -76,17 +107,16 @@ class _Highlighter(Visitor):
) )
) )
) )
pass
if routine.contents == "set" and command.args: if routine.contents == "set" and command.args:
first_arg = command.args[0] first_arg = command.args[0]
if hasattr(first_arg, "pos") and hasattr(first_arg, "value"): token_info = self._get_token_info(first_arg)
line, col = first_arg.pos if token_info:
(line, col), length = token_info
self._tokens.append( self._tokens.append(
( (
( (
(line - 1, col - 1), (line, col),
len(first_arg.value), length,
"variable", "variable",
[TokenModifier.declaration], [TokenModifier.declaration],
) )
@@ -140,18 +170,14 @@ class _Highlighter(Visitor):
[TokenModifier.declaration], [TokenModifier.declaration],
) )
) )
if routine.contents == "namespace" and command.args: if routine.contents == "namespace" and command.args:
first_arg = command.args[1] first_arg = command.args[1]
if hasattr(first_arg, "pos") and hasattr(first_arg, "value"): if hasattr(first_arg, "pos") and first_arg.value is not None:
line, col = first_arg.pos line, col = first_arg.pos
self._tokens.append( self._tokens.append(
(((line - 1, col - 1), len(first_arg.value), "class", [])) (((line - 1, col - 1), len(first_arg.value), "class", []))
) )
def visit_var_sub(self, var_sub):
pass
def tokens(self) -> list[Token]: def tokens(self) -> list[Token]:
"""Encode tokens as described in """Encode tokens as described in
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens.
+13 -2
View File
@@ -1,4 +1,10 @@
set main 1 set ::custom_flag(from_move,$::mom_path_name) 1
# set ::custom_flag(from_move,$::mom_path_name) 1
set te875st 11111
#set ::custom_flag(from_move,$::mom_path_name) 1
if {$main == 1 && 1 == 1} { if {$main == 1 && 1 == 1} {
puts "main" puts "main"
} }
@@ -93,7 +99,7 @@ proc SERVICE_output_handling {handler} {
#_________________________________________________________________________________________________ #_________________________________________________________________________________________________
proc SERVICE_get_tool_data {} { proc SERVICE_get_tool_data {} {
global mom_tool_data global mom_tool_data
global mom_operation_info global mom_operation_info
set mom_tool_data(toollist) "" set mom_tool_data(toollist) ""
set operations $::mom_operation_name_list set operations $::mom_operation_name_list
@@ -103,3 +109,8 @@ global mom_operation_info
} }
} }
} }
LIB_GE_command_buffer_edit_replace MOM_end_of_program_LIB END_OF_PROGRAM @END_OF_PROG {
MOM_do_template "end_of_program_rewind"
} EndOfProgramRewind