mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-05 17:00:58 +01:00
first go at new resolution for additional syntax errors
This commit is contained in:
parent
c30000f9c9
commit
4ff14f5094
2 changed files with 84 additions and 0 deletions
|
@ -17,6 +17,7 @@ from xonsh.lexer import Lexer, LexToken
|
|||
from xonsh.platform import PYTHON_VERSION_INFO
|
||||
from xonsh.tokenize import SearchPath
|
||||
from xonsh.lazyasd import LazyObject
|
||||
from xonsh.parsers.context_check import check_contexts
|
||||
|
||||
RE_SEARCHPATH = LazyObject(lambda: re.compile(SearchPath), globals(),
|
||||
'RE_SEARCHPATH')
|
||||
|
@ -309,6 +310,8 @@ class BaseParser(object):
|
|||
while self.parser is None:
|
||||
time.sleep(0.01) # block until the parser is ready
|
||||
tree = self.parser.parse(input=s, lexer=self.lexer, debug=debug_level)
|
||||
if tree is not None:
|
||||
check_contexts(tree)
|
||||
# hack for getting modes right
|
||||
if mode == 'single':
|
||||
if isinstance(tree, ast.Expression):
|
||||
|
|
81
xonsh/parsers/context_check.py
Normal file
81
xonsh/parsers/context_check.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import ast
|
||||
import keyword
|
||||
import collections
|
||||
|
||||
_all_keywords = frozenset(keyword.kwlist)
|
||||
|
||||
def _not_assignable(x, augassign=False):
|
||||
"""
|
||||
If ``x`` represents a value that can be assigned to, return ``None``.
|
||||
Otherwise, return a string describing the object. For use in generating
|
||||
meaningful syntax errors.
|
||||
"""
|
||||
if augassign and isinstance(x, (ast.Tuple, ast.List)):
|
||||
return 'literal'
|
||||
elif isinstance(x, (ast.Tuple, ast.List)):
|
||||
if len(x.elts) == 0:
|
||||
return '()'
|
||||
for i in x.elts:
|
||||
res = _not_assignable(i)
|
||||
if res is not None:
|
||||
return res
|
||||
elif isinstance(x, (ast.Set, ast.Dict, ast.Num, ast.Str, ast.Bytes)):
|
||||
return 'literal'
|
||||
elif isinstance(x, ast.Call):
|
||||
return 'function call'
|
||||
elif isinstance(x, ast.Lambda):
|
||||
return 'lambda'
|
||||
elif isinstance(x, (ast.BoolOp, ast.BinOp, ast.UnaryOp)):
|
||||
return 'operator'
|
||||
elif isinstance(x, ast.IfExp):
|
||||
return 'conditional expression'
|
||||
elif isinstance(x, ast.ListComp):
|
||||
return 'list comprehension'
|
||||
elif isinstance(x, ast.DictComp):
|
||||
return 'dictionary comprehension'
|
||||
elif isinstance(x, ast.SetComp):
|
||||
return 'set comprehension'
|
||||
elif isinstance(x, ast.GeneratorExp):
|
||||
return 'generator expression'
|
||||
elif isinstance(x, ast.Compare):
|
||||
return 'comparison'
|
||||
elif isinstance(x, ast.Name) and x.id in _all_keywords:
|
||||
return 'keyword'
|
||||
elif isinstance(x, ast.NameConstant):
|
||||
return 'keyword'
|
||||
|
||||
_loc = collections.namedtuple('_loc', ['lineno', 'column'])
|
||||
|
||||
def check_contexts(tree):
|
||||
c = ContextCheckingVisitor()
|
||||
c.visit(tree)
|
||||
if c.error is not None:
|
||||
e = SyntaxError(c.error[0])
|
||||
e.loc = _loc(c.error[1], c.error[2])
|
||||
raise e
|
||||
|
||||
class ContextCheckingVisitor(ast.NodeVisitor):
|
||||
def __init__(self):
|
||||
self.error = None
|
||||
|
||||
def visit_Delete(self, node):
|
||||
for i in node.targets:
|
||||
err = _not_assignable(i)
|
||||
if err is not None:
|
||||
msg = "can't delete {}".format(err)
|
||||
self.error = msg, i.lineno, i.col_offset
|
||||
break
|
||||
|
||||
def visit_Assign(self, node):
|
||||
for i in node.targets:
|
||||
err = _not_assignable(i)
|
||||
if err is not None:
|
||||
msg = "can't assign to {}".format(err)
|
||||
self.error = msg, i.lineno, i.col_offset
|
||||
break
|
||||
|
||||
def visit_AugAssign(self, node):
|
||||
err = _not_assignable(node.target, True)
|
||||
if err is not None:
|
||||
msg = "illegal target for augmented assignment: {}".format(err)
|
||||
self.error = msg, node.target.lineno, node.target.col_offset
|
Loading…
Add table
Reference in a new issue