Skip to content

Commit 6abc2de

Browse files
committed
added support for exceptions: try, except, raise
1 parent e1b1eef commit 6abc2de

File tree

3 files changed

+178
-2
lines changed

3 files changed

+178
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ Pyscript implements a Python interpreter using the ast parser output, in a fully
2929
allows several of the "magic" features to be implemented in a seamless Pythonic manner, such as
3030
binding of variables to states and functions to services. Pyscript supports imports, although the
3131
valid import list is restricted for security reasons. Pyscript does not (yet) support some language
32-
features like declaring new objects, `try/except`, generators and some syntax like `with` and
33-
`yield`. Pyscript provides a handful of additional built-in functions that connect to HASS
32+
features like declaring new objects, list comprehensions, generators and some syntax like `with`
33+
and `yield`. Pyscript provides a handful of additional built-in functions that connect to HASS
3434
features, like logging, accessing state variables as strings (if you need to compute their names
3535
dynamically), sleeping and waiting for triggers.
3636

custom_components/pyscript/eval.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,74 @@
6565
}
6666

6767

68+
BUILTIN_EXCEPTIONS = {
69+
"BaseException": BaseException,
70+
"SystemExit": SystemExit,
71+
"KeyboardInterrupt": KeyboardInterrupt,
72+
"GeneratorExit": GeneratorExit,
73+
"Exception": Exception,
74+
"StopIteration": StopIteration,
75+
"StopAsyncIteration": StopAsyncIteration,
76+
"ArithmeticError": ArithmeticError,
77+
"FloatingPointError": FloatingPointError,
78+
"OverflowError": OverflowError,
79+
"ZeroDivisionError": ZeroDivisionError,
80+
"AssertionError": AssertionError,
81+
"AttributeError": AttributeError,
82+
"BufferError": BufferError,
83+
"EOFError": EOFError,
84+
"ImportError": ImportError,
85+
"ModuleNotFoundError": ModuleNotFoundError,
86+
"LookupError": LookupError,
87+
"IndexError": IndexError,
88+
"KeyError": KeyError,
89+
"MemoryError": MemoryError,
90+
"NameError": NameError,
91+
"UnboundLocalError": UnboundLocalError,
92+
"OSError": OSError,
93+
"BlockingIOError": BlockingIOError,
94+
"ChildProcessError": ChildProcessError,
95+
"ConnectionError": ConnectionError,
96+
"BrokenPipeError": BrokenPipeError,
97+
"ConnectionAbortedError": ConnectionAbortedError,
98+
"ConnectionRefusedError": ConnectionRefusedError,
99+
"ConnectionResetError": ConnectionResetError,
100+
"FileExistsError": FileExistsError,
101+
"FileNotFoundError": FileNotFoundError,
102+
"InterruptedError": InterruptedError,
103+
"IsADirectoryError": IsADirectoryError,
104+
"NotADirectoryError": NotADirectoryError,
105+
"PermissionError": PermissionError,
106+
"ProcessLookupError": ProcessLookupError,
107+
"TimeoutError": TimeoutError,
108+
"ReferenceError": ReferenceError,
109+
"RuntimeError": RuntimeError,
110+
"NotImplementedError": NotImplementedError,
111+
"RecursionError": RecursionError,
112+
"SyntaxError": SyntaxError,
113+
"IndentationError": IndentationError,
114+
"TabError": TabError,
115+
"SystemError": SystemError,
116+
"TypeError": TypeError,
117+
"ValueError": ValueError,
118+
"UnicodeError": UnicodeError,
119+
"UnicodeDecodeError": UnicodeDecodeError,
120+
"UnicodeEncodeError": UnicodeEncodeError,
121+
"UnicodeTranslateError": UnicodeTranslateError,
122+
"Warning": Warning,
123+
"DeprecationWarning": DeprecationWarning,
124+
"PendingDeprecationWarning": PendingDeprecationWarning,
125+
"RuntimeWarning": RuntimeWarning,
126+
"SyntaxWarning": SyntaxWarning,
127+
"UserWarning": UserWarning,
128+
"FutureWarning": FutureWarning,
129+
"ImportWarning": ImportWarning,
130+
"UnicodeWarning": UnicodeWarning,
131+
"BytesWarning": BytesWarning,
132+
"ResourceWarning": ResourceWarning,
133+
}
134+
135+
68136
def ast_eval_exec_factory(ast_ctx, str_type):
69137
"""Generate a function that executes eval() or exec() with given ast_ctx."""
70138

@@ -491,6 +559,63 @@ async def ast_while(self, arg):
491559
return val
492560
return None
493561

562+
async def ast_try(self, arg):
563+
"""Execute try...except statement."""
564+
try:
565+
for arg1 in arg.body:
566+
val = await self.aeval(arg1)
567+
if isinstance(val, EvalStopFlow):
568+
return val
569+
print(f"exception_obj = {self.exception_obj}")
570+
if self.exception_obj is not None:
571+
raise self.exception_obj # pylint: disable=raising-bad-type
572+
except Exception as err: # pylint: disable=broad-except
573+
self.exception_obj = None
574+
self.exception = None
575+
self.exception_long = None
576+
for handler in arg.handlers:
577+
exc_list = await self.aeval(handler.type)
578+
if not isinstance(exc_list, tuple):
579+
exc_list = [exc_list]
580+
match = False
581+
for exc in exc_list:
582+
if isinstance(err, exc):
583+
match = True
584+
break
585+
if match:
586+
if handler.name is not None:
587+
self.sym_table[handler.name] = err
588+
for arg1 in handler.body:
589+
val = await self.aeval(arg1)
590+
if isinstance(val, EvalStopFlow):
591+
if handler.name is not None:
592+
del self.sym_table[handler.name]
593+
return val
594+
if self.exception_obj is not None:
595+
if handler.name is not None:
596+
del self.sym_table[handler.name]
597+
raise self.exception_obj # pylint: disable=raising-bad-type
598+
if handler.name is not None:
599+
del self.sym_table[handler.name]
600+
break
601+
else:
602+
raise err
603+
else:
604+
for arg1 in arg.orelse:
605+
val = await self.aeval(arg1)
606+
if isinstance(val, EvalStopFlow):
607+
return val
608+
finally:
609+
for arg1 in arg.finalbody:
610+
val = await self.aeval(arg1)
611+
if isinstance(val, EvalStopFlow):
612+
return val # pylint: disable=lost-exception
613+
return None
614+
615+
async def ast_raise(self, arg):
616+
"""Execute raise statement."""
617+
raise await self.aeval(arg.exc)
618+
494619
async def ast_pass(self, arg):
495620
"""Execute pass statement."""
496621

@@ -696,6 +821,8 @@ async def ast_name(self, arg):
696821
)
697822
if num_dots == 1 or (num_dots == 2 and self.state.exist(arg.id)):
698823
return self.state.get(arg.id)
824+
if arg.id in BUILTIN_EXCEPTIONS:
825+
return BUILTIN_EXCEPTIONS[arg.id]
699826
#
700827
# Couldn't find it, so return just the name wrapped in EvalName to
701828
# distinguish from a string variable value. This is to support

tests/custom_components/pyscript/test_unit_eval.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,55 @@ def foo(arg1=None, **kwargs):
422422
[None, {"arg2": 30}],
423423
],
424424
],
425+
[
426+
"""
427+
def func(exc):
428+
try:
429+
x = 1
430+
if exc:
431+
raise exc
432+
except NameError as err:
433+
x += 100
434+
except (NameError, OSError) as err:
435+
x += 200
436+
except Exception as err:
437+
x += 300
438+
return x
439+
else:
440+
x += 10
441+
finally:
442+
x += 2
443+
x += 1
444+
return x
445+
[func(None), func(NameError("x")), func(OSError("x")), func(ValueError("x"))]
446+
""",
447+
[14, 104, 204, 301],
448+
],
449+
[
450+
"""
451+
def func(exc):
452+
try:
453+
x = 1
454+
if exc:
455+
raise exc
456+
except NameError as err:
457+
x += 100
458+
except (NameError, OSError) as err:
459+
x += 200
460+
except Exception as err:
461+
x += 300
462+
return x
463+
else:
464+
return x + 10
465+
finally:
466+
x += 2
467+
return x
468+
x += 1
469+
return x
470+
[func(None), func(NameError("x")), func(OSError("x")), func(ValueError("x"))]
471+
""",
472+
[3, 103, 203, 303],
473+
],
425474
]
426475

427476

0 commit comments

Comments
 (0)