1414from 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-
3017class _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 (
0 commit comments