Skip to content

Commit 6e30e55

Browse files
committed
Make dict creation static
Signed-off-by: Aadya Chinubhai <aadyachinubhai@gmail.com>
1 parent d07091a commit 6e30e55

2 files changed

Lines changed: 97 additions & 47 deletions

File tree

csp/impl/wiring/node_parser.py

Lines changed: 95 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@
1414
from csp.impl.wiring.base_parser import BaseParser, CspParseError, _pythonic_depr_warning
1515

1616

17-
def _csp_output_kwargs(outputs_by_name, values):
18-
try:
19-
items = values.items()
20-
except AttributeError:
21-
raise TypeError(f"csp.output(**{values!r}) requires a dict-like value") from None
22-
for k, v in items:
23-
try:
24-
proxy = outputs_by_name[k]
25-
except KeyError:
26-
raise KeyError(f"unrecognized output '{k}'") from None
27-
proxy + v
28-
29-
3017
class _SingleProxyFuncArgResolver(object):
3118
class INVALID_VALUE:
3219
pass
@@ -86,7 +73,6 @@ class NodeParser(BaseParser):
8673
_CSP_ENGINE_START_TIME_FUNC = "_engine_start_time"
8774
_CSP_ENGINE_END_TIME_FUNC = "_engine_end_time"
8875
_CSP_ENGINE_STATS_FUNC = "_csp_engine_stats"
89-
_CSP_OUTPUT_KWARGS_FUNC = "_csp_output_kwargs"
9076

9177
_CSP_STOP_ENGINE_FUNC = "_csp_stop_engine"
9278
_CSP_IN_REALTIME_FUNC = "_csp_in_realtime"
@@ -97,7 +83,6 @@ class NodeParser(BaseParser):
9783
_CSP_STOP_ENGINE_FUNC: _cspimpl._csp_stop_engine,
9884
_CSP_ENGINE_STATS_FUNC: _cspimpl._csp_engine_stats,
9985
_CSP_IN_REALTIME_FUNC: _cspimpl._csp_in_realtime,
100-
_CSP_OUTPUT_KWARGS_FUNC: _csp_output_kwargs,
10186
}
10287

10388
_SPECIAL_BLOCKS_METH = {"alarms", "state", "start", "stop", "outputs"}
@@ -125,6 +110,8 @@ def __init__(self, name, raw_func, func_frame, debug_print=False):
125110
self._func_globals_modified.update(self._LOCAL_METHODS)
126111
self._gen = None
127112

113+
self._uses_outmap = False
114+
128115
# To catch returning from within a for or while loop, which wouldnt work as it seems
129116
self._inner_loop_count = 0
130117
self._returned_outputs = set()
@@ -417,6 +404,8 @@ def _parse_output_or_return(self, node, is_return):
417404
node.lineno,
418405
)
419406
nodes = []
407+
stmts = []
408+
420409
for node_arg in node.args:
421410
if isinstance(node_arg, ast.Starred):
422411
raise CspParseError(f"{func_name} does not support * unpacking", node.lineno)
@@ -486,16 +475,78 @@ def _parse_output_or_return(self, node, is_return):
486475
if arg.arg is None:
487476
# A **expr unpack, resolved at runtime, can't statically verify
488477
# which outputs it covers, so assume it may cover all of them.
489-
self._returned_outputs.update(o.name for o in self._signature._outputs if o.name is not None)
490-
nodes.append(
491-
ast.Call(
492-
func=ast.Name(id=self._CSP_OUTPUT_KWARGS_FUNC, ctx=ast.Load()),
493-
args=[self._build_outputs_by_name_dict(node), arg.value],
494-
keywords=[],
495-
lineno=node.lineno,
496-
end_lineno=node.end_lineno,
478+
self._uses_outmap = True
479+
self._returned_outputs.update(
480+
o.name for o in self._signature._outputs if o.name is not None
481+
)
482+
# We'll be generating raw statements to build the dict only once per node.
483+
stmts.append(
484+
# #csp_vals = <expr>
485+
ast.Assign(
486+
targets=[ast.Name(id="#csp_vals", ctx=ast.Store())],
487+
value=arg.value,
488+
)
489+
)
490+
stmts.append(
491+
# if not isinstance(#csp_vals, dict): raise TypeError(...)
492+
ast.If(
493+
test=ast.UnaryOp(
494+
op=ast.Not(),
495+
operand=ast.Call(
496+
func=ast.Name(id="isinstance", ctx=ast.Load()),
497+
args=[
498+
ast.Name(id="#csp_vals", ctx=ast.Load()),
499+
ast.Name(id="dict", ctx=ast.Load()),
500+
],
501+
keywords=[],
502+
),
503+
),
504+
body=[
505+
ast.Raise(
506+
exc=ast.Call(
507+
func=ast.Name(id="TypeError", ctx=ast.Load()),
508+
args=[ast.Constant(f"{func_name} argument after ** must be a dict")],
509+
keywords=[],
510+
),
511+
cause=None,
512+
)
513+
],
514+
orelse=[],
497515
)
498516
)
517+
stmts.append(
518+
ast.For(
519+
# for #csp_k, #csp_v in #csp_vals.items():
520+
target=ast.Tuple(
521+
elts=[
522+
ast.Name(id="#csp_k", ctx=ast.Store()),
523+
ast.Name(id="#csp_v", ctx=ast.Store())
524+
],
525+
ctx=ast.Store(),
526+
),
527+
iter=ast.Call(
528+
func=ast.Attribute(
529+
value=ast.Name(id="#csp_vals", ctx=ast.Load()),
530+
attr="items",
531+
ctx=ast.Load()
532+
),
533+
args=[],
534+
keywords=[],
535+
),
536+
# loop body
537+
body=[ast.Expr(
538+
ast.BinOp(
539+
left=ast.Subscript(
540+
value=ast.Name(id="#csp_outmap", ctx=ast.Load()),
541+
slice=ast.Name(id="#csp_k", ctx=ast.Load()),
542+
ctx=ast.Load(),
543+
),
544+
op=ast.Add(),
545+
right=ast.Name(id="#csp_v", ctx=ast.Load()),
546+
)
547+
)],
548+
orelse=[],
549+
))
499550
continue
500551
if self._signature.output(arg.arg, True) is None:
501552
raise CspParseError(f"unrecognized output '{arg.arg}'", node.lineno)
@@ -517,41 +568,35 @@ def _parse_output_or_return(self, node, is_return):
517568
nodes.append(ast.BinOp(left=self._ts_outproxy_expr(arg.arg), op=ast.Add(), right=arg.value))
518569
self._returned_outputs.add(arg.arg)
519570

520-
if len(nodes) == 0:
571+
result = []
572+
if len(nodes) == 0 and len(stmts) == 0:
521573
if not is_return:
522574
raise CspParseError("Empty output is not allowed", node.lineno)
523575
res = ast.Pass(lineno=node.lineno, end_lineno=node.end_lineno)
524576
else:
525-
res = (
526-
ast.BoolOp(op=ast.Or(), values=nodes, lineno=node.lineno, end_lineno=node.end_lineno)
527-
if len(nodes) > 1
528-
else nodes[0]
529-
)
577+
if nodes:
578+
res = (
579+
ast.BoolOp(op=ast.Or(), values=nodes, lineno=node.lineno, end_lineno=node.end_lineno)
580+
if len(nodes) > 1
581+
else nodes[0]
582+
)
583+
result.append(ast.Expr(res, lineno=node.lineno, end_lineno=node.end_lineno))
584+
result.extend(stmts)
530585
if is_return:
531-
if isinstance(res, ast.Pass):
532-
return [res, ast.Continue(lineno=node.lineno, end_lineno=node.end_lineno)]
533-
else:
534-
return [
535-
ast.Expr(res, lineno=node.lineno, end_lineno=node.end_lineno),
536-
ast.Continue(lineno=node.lineno, end_lineno=node.end_lineno),
537-
]
538-
586+
return result + [ast.Continue(lineno=node.lineno, end_lineno=node.end_lineno)]
587+
if stmts:
588+
return result
539589
return res
540590

541-
def _build_outputs_by_name_dict(self, node):
591+
def _build_outputs_by_name_dict(self):
542592
keys = []
543593
values = []
544594
for output in self._signature._outputs:
545595
if output.name is None:
546596
continue
547597
keys.append(ast.Constant(value=output.name))
548598
values.append(self._ts_outproxy_expr(output.name))
549-
return ast.Dict(
550-
keys=keys,
551-
values=values,
552-
lineno=node.lineno,
553-
end_lineno=node.end_lineno,
554-
)
599+
return ast.Dict(keys=keys, values=values)
555600

556601
def _parse_output(self, node):
557602
return self._parse_output_or_return(node=node, is_return=False)
@@ -902,7 +947,12 @@ def _parse_impl(self):
902947
# Yield before start block so we can setup stack frame before executing
903948
# However, this initial yield shouldn't be within the try-finally block, since if a node does not start, it's stop() logic should not be invoked
904949
# This avoids an issue where one node raises an exception upon start(), and then other nodes execute their stop() without having ever started
905-
start_and_body = [ast.Expr(value=ast.Yield(value=None))] + del_vars + start_and_body
950+
outmap_assign = (
951+
[ast.Assign(targets=[ast.Name(id="#csp_outmap", ctx=ast.Store())], value=self._build_outputs_by_name_dict())]
952+
if self._uses_outmap
953+
else []
954+
)
955+
start_and_body = [ast.Expr(value=ast.Yield(value=None))] + del_vars + outmap_assign + start_and_body
906956
newbody = init_block + start_and_body
907957

908958
newfuncdef = ast.FunctionDef(

csp/tests/test_engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def foo(x: ts[bool]) -> csp.Outputs(a=ts[int]):
369369
if csp.ticked(x):
370370
csp.output(**{"bogus": 1})
371371

372-
with self.assertRaisesRegex(KeyError, "unrecognized output 'bogus'"):
372+
with self.assertRaisesRegex(KeyError, "'bogus'"):
373373
csp.run(foo, csp.const(True), starttime=datetime(2020, 1, 1), endtime=timedelta(seconds=1))
374374

375375
def test_csp_output_dict_unpack_non_dict(self):
@@ -379,7 +379,7 @@ def foo(x: ts[bool]) -> csp.Outputs(a=ts[int], b=ts[int]):
379379
values = [1, 2] # not dict-like
380380
csp.output(**values)
381381

382-
with self.assertRaisesRegex(TypeError, "requires a dict-like value"):
382+
with self.assertRaisesRegex(TypeError, "argument after \\*\\* must be a dict"):
383383
csp.run(foo, csp.const(True), starttime=datetime(2020, 1, 1), endtime=timedelta(seconds=1))
384384

385385
def test_csp_output_dict_unpack_basket(self):

0 commit comments

Comments
 (0)