tcl_language_support #18
@@ -0,0 +1,234 @@
|
||||
from enum import Enum
|
||||
import ply.lex as lex
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Tok(str, Enum):
|
||||
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"
|
||||
|
||||
|
||||
STATE_BRACEDWORD = "bracedword"
|
||||
TOK_EOF = None
|
||||
|
||||
|
||||
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 = tuple(t.value for t in Tok)
|
||||
|
||||
states = ((STATE_BRACEDWORD, "exclusive"),)
|
||||
|
||||
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
|
||||
|
||||
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"\\\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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
def dump_tokens(code):
|
||||
lx = Lexer()
|
||||
lx.input(code)
|
||||
out = []
|
||||
while lx.type() is not TOK_EOF:
|
||||
out.append((lx.type(), lx.value(), lx.pos()))
|
||||
lx.next()
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
code = (
|
||||
"set a 1\nputs $a\nnamespace eval test {}\n proc myProc {arg1 {optArg 10}} {}"
|
||||
)
|
||||
for ttype, val, (ln, col) in dump_tokens(code):
|
||||
print(f"{ttype:<18} {val!r:<10} @ ({ln},{col})")
|
||||
@@ -0,0 +1,9 @@
|
||||
class _Word:
|
||||
def __init__(self):
|
||||
self.segements = []
|
||||
self.current_segment = ""
|
||||
self.current_start = None
|
||||
|
||||
def add_tok(self, tok):
|
||||
if self.current_start is None:
|
||||
self.current_start = tok.value[1]
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Classes for representing and interacting with Tcl syntax trees."""
|
||||
|
||||
|
||||
class Visitor:
|
||||
"""Abstract base class for Visitors that operate on syntax tree."""
|
||||
|
||||
def visit_script(self, script):
|
||||
pass
|
||||
|
||||
def visit_comment(self, comment):
|
||||
pass
|
||||
|
||||
def visit_command(self, command):
|
||||
pass
|
||||
|
||||
def visit_command_sub(self, command_sub):
|
||||
pass
|
||||
|
||||
def visit_bare_word(self, word):
|
||||
pass
|
||||
|
||||
def visit_braced_word(self, word):
|
||||
pass
|
||||
|
||||
def visit_quoted_word(self, word):
|
||||
pass
|
||||
|
||||
def visit_compound_bare_word(self, word):
|
||||
pass
|
||||
|
||||
def visit_var_sub(self, var_sub):
|
||||
pass
|
||||
|
||||
def visit_arg_expansion(self, arg_expansion):
|
||||
pass
|
||||
|
||||
def visit_list(self, list):
|
||||
pass
|
||||
|
||||
def visit_expression(self, expression):
|
||||
pass
|
||||
|
||||
def visit_braced_expression(self, expression):
|
||||
pass
|
||||
|
||||
def visit_paren_expression(self, expression):
|
||||
pass
|
||||
|
||||
def visit_unary_op(self, unary_op):
|
||||
pass
|
||||
|
||||
def visit_binary_op(self, binary_op):
|
||||
pass
|
||||
|
||||
def visit_ternary_op(self, ternary_op):
|
||||
pass
|
||||
|
||||
def visit_function(self, function):
|
||||
pass
|
||||
Reference in New Issue
Block a user