Skip to content

Commit

Permalink
Various Syntax Checker fixes
Browse files Browse the repository at this point in the history
It now works on Linux machines:
    #11

Fixed a few bugs causing "VCC is currently running..."
state stuck until the editor restarted.

Temporary files used with "As a Snippet" build now removed sooner.
An exception could cause files to get stuck in the temp folder.
Temporary filename pattern: tmpxxxxxx

Several more VCC errors are displayed.

Improved Snippet error detection of bad "@" symbol usages.

See "Optional: Configure VCC Path" and "Syntax Check"
sections of the readme for the relevant info:

    https://github.com/teared/VEX#optional-configure-vcc-path
    https://github.com/teared/VEX#syntax-check
  • Loading branch information
teared committed Feb 11, 2021
1 parent 2b3604c commit 7de6034
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 107 deletions.
225 changes: 118 additions & 107 deletions commands/vex_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@


def which(executable):
'''Simulates 'which' program (probably) performing executable search.'''
"""Simulates 'which' program (probably) performing executable search."""

def is_exe(path):
return op.isfile(path) and os.access(path, os.X_OK)
def is_exe(filepath):
return op.isfile(filepath) and os.access(filepath, os.X_OK)

head, _ = op.split(executable)
if head and is_exe(executable):
Expand All @@ -39,12 +39,12 @@ def is_exe(path):


def snippet_to_vex(source):
'''
"""
Wrap code into a function.
This allow to compile small VEX snippets without need to manually wrap
them into functions and convert all attributes into argument bindings.
'''
"""
bound = set() # Keeping track of bound attribute names.
args = []

Expand Down Expand Up @@ -78,6 +78,8 @@ def snippet_to_vex(source):
for j, fragment in enumerate(piece):
if '@' in fragment:
type_, name = fragment.split('@')
if not name.isidentifier():
raise ValueError('Bad attrubute name: ' + str(name))
bindings.append((type_, name))
piece[j] = '@' + name

Expand Down Expand Up @@ -131,7 +133,7 @@ def snippet_to_vex(source):


class VexBuildCommand(sublime_plugin.WindowCommand):
'''
"""
Compile VEX code in different ways.
Currently, this build command used only for syntax checking, with no
Expand All @@ -149,10 +151,12 @@ class VexBuildCommand(sublime_plugin.WindowCommand):
of code, this provides convenient way to fix typos and compile
errors without need to go back and forth between Houdini and
Sublime.
'''
"""

# Track if VCC not finished or the command is broken. Mostly if broken.
running = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.output = None # Output panel instance.
self.started = None # Timestamp to track if VCC has not finished. Also used to display compile time.

def run(self,
executable='vcc',
Expand All @@ -162,7 +166,7 @@ def run(self,
vex_output='stdout',
snippet=False):

if self.running:
if self.started is not None:
sublime.status_message('VCC is currently running...')
return

Expand All @@ -174,96 +178,98 @@ def run(self,
sublime.status_message('Please, save the file before building')
return

self.executable = executable
self.context = context
self.compile_all = compile_all
self.include_dirs = include_dirs
self.vex_output = vex_output
self.snippet = snippet

# Setup main variables.
window = self.window
view = window.active_view()
self.src = view.substr(sublime.Region(0, view.size()))
self.vars = window.extract_variables()
code = view.substr(sublime.Region(0, view.size()))
vars = window.extract_variables()

# Create and show output panel.
self.output = window.create_output_panel('exec')
settings = self.output.settings()
settings.set(
'result_file_regex',
r'^File "(.+)", line (\d+), columns? (\d+(?:-\d+)?): (.*)$'
r'^File "(.+)"(?:, line (\d+), columns? (\d+(?:-\d+)?)): (.*)$'
)
settings.set('line_numbers', False)
settings.set('gutter', False)
settings.set('scroll_past_end', False)
settings.set('result_base_dir', self.vars['file_path'])
settings.set('result_base_dir', vars['file_path'])
self.output.assign_syntax('Packages/VEX/syntax/VEX Build.sublime-syntax')

# Respect generic user preference about build window.
if sublime.load_settings('Preferences.sublime-settings').get('show_panel_on_build', True):
window.run_command('show_panel', {'panel': 'output.exec'})

# Run VCC in other thread.
threading.Thread(target=self.worker).start()

def expand(self, value):
return sublime.expand_variables(value, self.vars)
args = (executable, context, compile_all, include_dirs, vex_output, snippet, code, vars)
threading.Thread(target=self.worker, args=args).start()

def worker(self):
self.running = True # Track running VCC instances.
self.started = time.time()
def worker(self, executable, context, compile_all, include_dirs, vex_output, snippet, code, vars):
self.started = time.time() # Track running VCC instances.
sublime.status_message('Compiling VEX...')

# In snippet mode, wrap source and save it to a temporary file.
if self.snippet:
self.generated_code = snippet_to_vex(self.src)
with tempfile.NamedTemporaryFile('w', encoding='utf-8', delete=False) as f:
f.write(self.generated_code)
self.tempfile = f.name

# Call VCC and check output.
cmd = [self.expand(self.executable)]
cmd = [sublime.expand_variables(op.normpath(executable), vars)]

if self.compile_all:
if compile_all:
cmd.append('--compile-all')

if self.context:
cmd.extend(['--context', self.context])
if context:
cmd.extend(['--context', context])

if self.include_dirs:
for d in self.include_dirs:
cmd.extend(['--include-dir', self.expand(d)])
if include_dirs:
for include_dir in include_dirs:
cmd.extend(['--include-dir', op.normpath(sublime.expand_variables(include_dir, vars))])

if self.vex_output:
cmd.extend(['--vex-output', self.expand(self.vex_output)])
if vex_output and vex_output != 'stdout':
cmd.extend(['--vex-output', op.normpath(sublime.expand_variables(vex_output, vars))])
else:
cmd.extend(['--vex-output', 'stdout'])

# Specify input file.
if self.snippet:
cmd.append(self.tempfile)
if snippet:
# In snippet mode, wrap source and save it to a temporary file.
with tempfile.NamedTemporaryFile('w', encoding='utf-8', delete=False) as f:
try:
generated_code = snippet_to_vex(code)
except (ValueError, KeyError) as e:
sublime.status_message('Cannot wrap code into a snippet function: ("%s") ' % e +
'Usually due to erroneous @ usage.')
self.started = None
return
file_path = f.name
f.write(generated_code)
else:
cmd.append(self.vars['file'])

proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, universal_newlines=True)
code, generated_code = '', ''
file_path = vars['file']
cmd.append(file_path)

# Avoid console window flashing on Windows.
startupinfo = None
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
proc = subprocess.Popen(cmd, startupinfo=startupinfo, stderr=subprocess.PIPE, universal_newlines=True)
proc.wait()
vcc = proc.stderr.read()
vcc_output = proc.stderr.read()

# Delete generated code file.
if snippet:
os.remove(file_path)

self.output.run_command('append', {
'characters': self.format_output(vcc),
'characters': self.format_output(vcc_output, file_path, snippet, code, generated_code),
'force': True,
'scroll_to_end': True
})

# Delete generated code file.
if self.snippet:
os.remove(self.tempfile)

sublime.status_message('Finished compiling VEX')
self.running = False
self.started = None

@staticmethod
def match_columns(cols, genline, srcline):
'''
"""
Error lines and columns generated by wrapped code make no sense with
actual, non-wrapped code.
Expand All @@ -274,8 +280,8 @@ def match_columns(cols, genline, srcline):
@|foo = 4.2; | i[]@|bar = {4, 2};
Then two simple counters will be used to match column numbers.
'''
parts = zip(re.split(r'((?:\b[\w\d](?:\[\])?)?@)(?=[\w\d_]+)', srcline),
"""
parts = zip(re.split(r'((?:\b[\w\d](?:\[])?)?@)(?=[\w\d_]+)', srcline),
re.split(r'(\b_bound_)', genline))
c1 = 0
c2 = cols[0]
Expand All @@ -290,69 +296,74 @@ def match_columns(cols, genline, srcline):
else:
return cols

def format_output(self, output):
'''
def format_output(self, vcc_output, file_path='', snippet=False, code='', generated_code=''):
"""
Beautify output and fix issues caused by code wrapping.
TODO: fix splitting the whole file inside for loop.
'''

output = output.strip()
if not output:
TODO: fix splitting the whole path inside for loop.
"""
vcc_output = vcc_output.strip()
if not vcc_output:
return 'Successfully compiled in %.02fs' % (time.time() - self.started)

parsed = []
for line in output.split('\n'):
for line in vcc_output.split('\n'):
match = re.match(r'^(.+?):(?:(\d+):(\d+(?:-\d+)?):)?\s+(.+?):\s+(.+)$', line)
file, row, cols, err, msg = match.groups()
file = op.normpath(file)
path, row, cols, err, msg = match.groups()
path = op.normpath(path)

# Sometimes VCC don't output lines and columns.
row = int(row) if row else 1
row = int(row) if row else None
cols = [int(n) for n in cols.split('-')] if cols else [0]
parsed.append([file, row, cols, err, msg])
parsed.append([path, row, cols, err, msg])

messages = []

if self.snippet:
srclines = self.src.split('\n')
genlines = self.generated_code.split('\n')

for file, row, cols, err, msg in parsed:
# Format messy function suggestions.
if 'Candidates are:' in msg:
msg, funs = msg.split(': ', 1)
funs = '\n '.join(funs.split(', '))
msg = '%s:\n %s\n' % (msg, funs)

if self.snippet and file == self.tempfile:
# Fix row and columns numbers.
row = row - 2
srcline = srclines[min(row-1, len(srclines)-1)] # Temp fix for Index errors.
cols = self.match_columns(cols, genlines[row+1], srcline)
file = self.vars['file']
if snippet:
srclines = code.split('\n')
genlines = generated_code.split('\n')
else:
genlines = None
srclines = None

for path, row, cols, err, msg in parsed:
if row:
# Format messy function suggestions.
if 'Candidates are:' in msg:
msg, funs = msg.split(': ', 1)
funs = '\n '.join(funs.split(', '))
msg = '%s:\n %s\n' % (msg, funs)

if snippet and path == file_path:
# Fix row and columns numbers.
row = row - 2
srcline = srclines[min(row - 1, len(srclines) - 1)] # Temp fix for Index errors.
cols = self.match_columns(cols, genlines[row + 1], srcline)
else:
with open(path, encoding='utf-8') as f:
srcline = f.readlines()[row - 1].rstrip()

# Add arrows under source code line pointing an error.
arrows = ' ' * (cols[0] - 1) + '^' * (cols[-1] - (cols[0] - 1))

# Format columns representation.
col_or_cols = 'column' if len(cols) == 1 else 'columns'
cols = '-'.join(str(c) for c in cols)

message = '''\
File "{path}", line {row}, {col_or_cols} {cols}: {err}: {msg}
{srcline}
{arrows}
'''.format(**locals())
else:
with open(file, encoding='utf-8') as f:
srcline = f.readlines()[row-1].rstrip()

# Add arrows under source code line pointing an error.
arrows = ' ' * (cols[0]-1) + '^' * (cols[-1] - (cols[0]-1))

# Format columns representation.
col_or_cols = 'column' if len(cols) == 1 else 'columns'
cols = '-'.join(str(c) for c in cols)

message = '''\
File "{file}", line {row}, {col_or_cols} {cols}: {err}: {msg}
{srcline}
{arrows}
'''.format(**locals())
message = 'File "{path}": {err}: {msg}'.format(**locals())

messages.append(textwrap.dedent(message))

output = '\n'.join(messages)
vcc_output = '\n'.join(messages)

# if self.snippet:
# output = '\n'.join([output, 'Generated code:', self.generated_code])
# # Debug generated code.
# if snippet:
# vcc_output = '\n'.join([vcc_output, 'Generated code:', generated_code])

return output
return vcc_output
28 changes: 28 additions & 0 deletions messages/7.1.4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Houdini add-on for Sublime Text:
https://github.com/teared/VEX


Release 7.1.4


1. Various Syntax Checker fixes:

It now works on Linux machines:
https://github.com/teared/VEX/issues/11

Fixed a few bugs causing "VCC is currently running..."
state stuck until the editor restarted.

Temporary files used with "As a Snippet" build now removed sooner.
An exception could cause files to get stuck in the temp folder.
Temporary filename pattern: tmpxxxxxx

Several more VCC errors are displayed.

Improved Snippet error detection of bad "@" symbol usages.

See "Optional: Configure VCC Path" and "Syntax Check"
sections of the readme for the relevant info:

https://github.com/teared/VEX#optional-configure-vcc-path
https://github.com/teared/VEX#syntax-check

0 comments on commit 7de6034

Please sign in to comment.