Coverage for skema/program_analysis/CAST2FN/ann_cast/to_gromet_pass.py: 82%
1586 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
1from copy import deepcopy
2import sys
3import os.path
4import pprint
6from skema.utils.misc import uuid
8from functools import singledispatchmethod
9from datetime import datetime
10from time import time
12from skema.program_analysis.CAST2FN.model.cast import StructureType
14from skema.gromet.fn import (
15 FunctionType,
16 GrometBoxConditional,
17 GrometBoxFunction,
18 GrometBoxLoop,
19 GrometFNModule,
20 GrometFN,
21 GrometPort,
22 GrometWire,
23 ImportType,
24 LiteralValue as GLiteralValue,
25 TypedValue,
26)
28from skema.gromet.metadata import (
29 Provenance,
30 SourceCodeDataType,
31 SourceCodeReference,
32 SourceCodeCollection,
33 SourceCodePortDefaultVal,
34 SourceCodePortKeywordArg,
35 CodeFileReference,
36 GrometCreation,
37 ProgramAnalysisRecordBookkeeping,
38 SourceCodeBoolAnd,
39 SourceCodeBoolOr,
40)
42from skema.program_analysis.CAST.pythonAST.builtin_map import (
43 build_map,
44 dump_map,
45 check_builtin,
46)
47from skema.program_analysis.CAST2FN.model.cast.scalar_type import ScalarType
49from skema.program_analysis.CAST2FN.ann_cast.annotated_cast import *
50from skema.program_analysis.CAST.pythonAST.modules_list import (
51 BUILTINS,
52 find_func_in_module,
53 find_std_lib_module,
54)
56from skema.gromet.execution_engine.primitive_map import (
57 get_shorthand,
58 get_inputs,
59 get_outputs,
60 is_primitive,
61)
63from skema.gromet import GROMET_VERSION
65PYTHON_VERSION = "3.8"
68def is_inline(func_name):
69 # Tells us which functions should be inlined in GroMEt (i.e. don't make GroMEt FNs for these)
70 return func_name == "iter" or func_name == "next" or func_name == "range"
73def insert_gromet_object(t: list, obj):
74 """Inserts a GroMEt object obj into a GroMEt table t
75 Where obj can be
76 - A GroMEt Box
77 - A GroMEt Port
78 - A GroMEt Wire
79 And t can be
80 - A list of GroMEt Boxes
81 - A list of GroMEt ports
82 - A list of GroMEt wires
84 If the table we're trying to insert into doesn't already exist, then we
85 first create it, and then insert the value.
86 """
88 if t == None:
89 t = []
91 # Logic for generating port ids
92 if isinstance(obj, GrometPort):
93 obj.id = 1
94 for port in reversed(t):
95 if port.box == obj.box:
96 obj.id = port.id + 1
97 break
99 t.append(obj)
100 return t
103def generate_provenance():
104 timestamp = str(datetime.fromtimestamp(time()))
105 method_name = "skema_code2fn_program_analysis"
106 return Provenance(method=method_name, timestamp=timestamp)
109def is_tuple(node):
110 # Checks if an AnnCast Node is a Tuple LiteralValue
111 return (
112 isinstance(node, AnnCastLiteralValue)
113 and node.value_type == StructureType.TUPLE
114 )
117def retrieve_name_id_pair(node):
118 """
119 Operand from an AnnCastOperator
120 AnnCastName
121 AnnCastCall
122 AnnCastAttribute
123 """
125 if isinstance(node, AnnCastOperator):
126 return retrieve_name_id_pair(node.operands[0])
127 if isinstance(node, AnnCastName):
128 return (node.name, node.id)
129 if isinstance(node, AnnCastAttribute):
130 if isinstance(
131 node.value, (AnnCastAttribute, AnnCastName, AnnCastCall)
132 ):
133 return retrieve_name_id_pair(node.value)
134 return (node.attr.name, node.attr.id)
135 if isinstance(node, AnnCastCall):
136 return retrieve_name_id_pair(node.func)
137 return ("", -1)
140def comp_name_nodes(n1, n2):
141 """Given two AnnCast nodes we compare their name
142 and ids to see if they reference the same name
143 """
144 # If n1 or n2 is not a Name or an Operator node
145 if (
146 not isinstance(n1, AnnCastName)
147 and not isinstance(n1, AnnCastOperator)
148 and not isinstance(n1, AnnCastAttribute)
149 ):
150 return False
151 if (
152 not isinstance(n2, AnnCastName)
153 and not isinstance(n2, AnnCastOperator)
154 and not isinstance(n2, AnnCastAttribute)
155 ):
156 return False
157 # LiteralValues can't have 'names' compared
158 if isinstance(n1, AnnCastLiteralValue) or isinstance(
159 n2, AnnCastLiteralValue
160 ):
161 return False
163 n1_name, n1_id = retrieve_name_id_pair(n1)
164 n2_name, n2_id = retrieve_name_id_pair(n2)
166 return n1_name == n2_name and n1_id == n2_id
169def find_existing_opi(gromet_fn, opi_name):
170 idx = 1
171 if gromet_fn.opi == None:
172 return False, idx
174 for opi in gromet_fn.opi:
175 if opi_name == opi.name:
176 return True, idx
177 idx += 1
178 return False, idx
181def find_existing_pil(gromet_fn, opi_name):
182 if gromet_fn.pil == None:
183 return -1
185 idx = 1
186 for pil in gromet_fn.pil:
187 if opi_name == pil.name:
188 return idx
189 idx += 1
190 return -1
193def get_left_side_name(node):
194 if isinstance(node, AnnCastAttribute):
195 return node.attr.name
196 if isinstance(node, AnnCastName):
197 return node.name
198 if isinstance(node, AnnCastVar):
199 return get_left_side_name(node.val)
200 if isinstance(node, AnnCastCall):
201 if isinstance(node.func, AnnCastAttribute):
202 return get_left_side_name(node.func)
203 return node.func.name
204 return "NO LEFT SIDE NAME"
207def get_attribute_name(node):
208 """
209 Given an AnnCastAttribute node
210 """
211 if isinstance(node, AnnCastName):
212 return str(node.name)
213 if isinstance(node, AnnCastAttribute):
214 return get_attribute_name(node.value) + "." + str(node.attr)
215 if isinstance(node, AnnCastCall):
216 return get_attribute_name(node.func)
219def get_func_name(node: AnnCastCall):
220 if isinstance(node.func, AnnCastName):
221 return (node.func.name, f"{node.func.name}_id{node.func.id}")
222 if isinstance(node.func, AnnCastAttribute):
223 return (
224 node.func.attr.name,
225 f"{'.'.join(node.func.con_scope)}.{node.func.attr.name}_{node.invocation_index}",
226 )
227 if isinstance(node.func, str):
228 return (node.func, f"{node.func}_id{node.func.id}")
231class ToGrometPass:
232 def __init__(self, pipeline_state: PipelineState):
233 self.pipeline_state = pipeline_state
234 self.nodes = self.pipeline_state.nodes
236 self.var_environment = {"global": {}, "args": {}, "local": {}}
237 self.symbol_table = {
238 "functions": {},
239 "variables": {"global": {}, "args": {}, "local": {}},
240 "records": {},
241 }
242 # Attribute accesses check this collection
243 # to see if we're using an imported item
244 # Function calls to imported functions without their attributes will also check here
245 self.import_collection = {}
247 # creating a GroMEt FN object here or a collection of GroMEt FNs
248 # generally, programs are complex, so a collection of GroMEt FNs is usually created
249 # visiting nodes adds FNs
250 self.gromet_module = GrometFNModule(
251 schema="FN",
252 schema_version=GROMET_VERSION,
253 name="",
254 fn=None,
255 fn_array=[],
256 metadata_collection=[],
257 )
259 # build the built-in map
260 build_map()
262 # Everytime we see an AnnCastRecordDef we can store information for it
263 # for example the name of the class and indices to its functions
264 self.record = {}
266 # When a record type is initiatied we keep track of its name and record type here
267 self.initialized_records = {}
269 # Initialize the table of function arguments
270 self.function_arguments = {}
272 # Maintain an fn_array index stack
273 self.fn_stack = []
275 # Table of labels
276 self.labels = {}
277 self.placeholder_gotos = {}
279 # the fullid of a AnnCastName node is a string which includes its
280 # variable name, numerical id, version, and scope
281 for node in self.pipeline_state.nodes:
282 self.visit(node, parent_gromet_fn=None, parent_cast_node=None)
284 pipeline_state.gromet_collection = self.gromet_module
286 # Interface that allows us to put what FN index is currently on the stack
287 # Used for labels
288 def push_idx(self, idx):
289 self.fn_stack.append(idx)
291 def peek_idx(self):
292 if len(self.fn_stack) > 0:
293 return self.fn_stack[-1]
294 return 0
296 def pop_idx(self):
297 if len(self.fn_stack) > 0:
298 return self.fn_stack.pop()
299 return 0
301 # Adds a label to the labels table, associating a label
302 # with the index into the fn_array table that label is in
303 def add_label(self, label, fn_idx):
304 self.labels[label] = fn_idx
306 # Clears the labels table.
307 # NOTE: Do we need to do this everytime we leave a function definition?
308 def clear_labels(self):
309 self.labels = {}
311 def symtab_variables(self):
312 return self.symbol_table["variables"]
314 def symtab_functions(self):
315 return self.symbol_table["functions"]
317 def symtab_records(self):
318 return self.symbol_table["records"]
320 def offset_pif(self, var_name, func_name):
321 """Allows us to determine how much to offset a variable's pif.
322 This is done by acquiring its 'index location' in the function definition
323 that is foo(x,y,z) has an offset of 2 for variable y, and an offset of 3 for variable z.
324 Used primarily with keyword argument wiring.
325 """
326 args = self.symbol_table["functions"][func_name][2]
327 idx = 1
328 for arg,_ in args:
329 if arg == var_name:
330 return idx
331 idx += 1
333 return 1
335 def build_function_arguments_table(self, nodes):
336 """Iterates through all the function definitions at the module
337 level and creates a table that maps their function names to a map
338 of its arguments with position values
340 We also, for each function, create an initial entry containing its name and its
341 index in the FN array
343 NOTE: functions within functions aren't currently supported
345 """
346 for node in nodes:
347 if isinstance(node, AnnCastFunctionDef):
348 self.function_arguments[node.name.name] = {}
349 for i, arg in enumerate(node.func_args, 1):
350 self.function_arguments[node.name.name][arg.val.name] = i
351 self.symbol_table["functions"][node.name.name] = (node.name.name, -1, [-1] * len(node.func_args))
353 def wire_from_var_env(self, name, gromet_fn):
354 var_environment = self.symtab_variables()
356 if name in var_environment["local"]:
357 local_env = var_environment["local"]
358 entry = local_env[name]
359 if isinstance(entry[0], AnnCastLoop):
360 gromet_fn.wlf = insert_gromet_object(
361 gromet_fn.wlf,
362 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
363 )
364 if isinstance(entry[0], AnnCastModelIf):
365 gromet_fn.wfopi = insert_gromet_object(
366 gromet_fn.wfopi,
367 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
368 )
369 if isinstance(entry[0], AnnCastFunctionDef):
370 gromet_fn.wff = insert_gromet_object(
371 gromet_fn.wff,
372 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
373 )
374 elif name in var_environment["args"]:
375 args_env = var_environment["args"]
376 entry = args_env[name]
377 gromet_fn.wfopi = insert_gromet_object(
378 gromet_fn.wfopi,
379 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
380 )
381 elif name in var_environment["global"]:
382 global_env = var_environment["global"]
383 entry = global_env[name]
384 gromet_fn.wff = insert_gromet_object(
385 gromet_fn.wff,
386 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
387 )
389 def create_source_code_reference(self, ref_info):
390 # return None # comment this when we want metadata
391 if ref_info == None:
392 return None
394 line_begin = ref_info.row_start
395 line_end = ref_info.row_end
396 col_begin = ref_info.col_start
397 col_end = ref_info.col_end
399 # file_uid = str(self.gromet_module.metadata[-1].files[0].uid)
400 file_uid = str(
401 self.gromet_module.metadata_collection[1][0].files[0].uid
402 )
403 # file_uid = ""
404 return SourceCodeReference(
405 provenance=generate_provenance(),
406 code_file_reference_uid=file_uid,
407 line_begin=line_begin,
408 line_end=line_end,
409 col_begin=col_begin,
410 col_end=col_end,
411 )
413 def insert_metadata(self, *metadata):
414 """
415 insert_metadata inserts metadata into the self.gromet_module.metadata_collection list
416 Then, the index of where this metadata lives is returned
417 The idea is that all GroMEt objects that store metadata will store an index
418 into metadata_collection that points to the metadata they stored
419 """
420 # return None # Uncomment this line if we don't want metadata
421 to_insert = []
422 for md in metadata:
423 to_insert.append(md)
424 self.gromet_module.metadata_collection.append(to_insert)
425 return len(self.gromet_module.metadata_collection)
427 def insert_record_info(self, metadata: ProgramAnalysisRecordBookkeeping):
428 """
429 insert_record_info inserts a ProgramAnalysisRecordBookkeping metadata
430 into the metadata table
431 All metadata of this kind lives in the first index of the entire collection
432 """
433 self.gromet_module.metadata_collection[0].append(metadata)
435 def set_index(self):
436 """Called after a Gromet FN is added to the whole collection
437 Properly sets the index of the Gromet FN that was just added
438 """
439 return # comment this line if we need the indices
440 idx = len(self.gromet_module.fn_array)
441 self.gromet_module._fn_array[-1].index = idx
443 def handle_primitive_function(
444 self,
445 node: AnnCastCall,
446 parent_gromet_fn,
447 parent_cast_node,
448 from_assignment,
449 ):
450 """Creates an Expression GroMEt FN for the primitive function stored in node.
451 Then it gets wired up to its parent_gromet_fn appropriately
452 """
453 ref = node.source_refs[0]
454 metadata = self.create_source_code_reference(ref)
456 func_name, qual_func_name = get_func_name(node)
458 # primitives that come from something other than an assignment or functions designated to be inlined at all times have
459 # special semantics in that they're inlined as opposed to creating their own GroMEt FNs
460 if from_assignment or is_inline(func_name):
461 inline_func_bf = GrometBoxFunction(
462 name=func_name, function_type=FunctionType.LANGUAGE_PRIMITIVE
463 )
464 parent_gromet_fn.bf = insert_gromet_object(
465 parent_gromet_fn.bf, inline_func_bf
466 )
467 inline_bf_loc = len(parent_gromet_fn.bf)
469 for arg in node.arguments:
470 if (
471 isinstance(arg, AnnCastOperator)
472 or isinstance(arg, AnnCastLiteralValue)
473 or isinstance(arg, AnnCastCall)
474 ):
475 self.visit(arg, parent_gromet_fn, node)
476 parent_gromet_fn.pif = insert_gromet_object(
477 parent_gromet_fn.pif, GrometPort(box=inline_bf_loc)
478 )
479 parent_gromet_fn.wff = insert_gromet_object(
480 parent_gromet_fn.wff,
481 GrometWire(
482 src=len(parent_gromet_fn.pif),
483 tgt=len(parent_gromet_fn.pof),
484 ),
485 )
486 return inline_bf_loc
487 else:
488 # Create the Expression FN and its box function
489 primitive_fn = GrometFN()
490 primitive_fn.b = insert_gromet_object(
491 primitive_fn.b,
492 GrometBoxFunction(
493 function_type=FunctionType.EXPRESSION,
494 metadata=self.insert_metadata(metadata),
495 ),
496 )
498 # Create the primitive expression bf
499 primitive_func_bf = GrometBoxFunction(
500 name=func_name, function_type=FunctionType.LANGUAGE_PRIMITIVE
501 )
502 primitive_fn.bf = insert_gromet_object(
503 primitive_fn.bf, primitive_func_bf
504 )
505 primitive_bf_loc = len(primitive_fn.bf)
507 primitive_fn.opo = insert_gromet_object(
508 primitive_fn.opo, GrometPort(box=len(primitive_fn.b))
509 )
511 # Write its pof and wire it to its opo
512 primitive_fn.pof = insert_gromet_object(
513 primitive_fn.pof, GrometPort(box=len(primitive_fn.bf))
514 )
515 primitive_fn.wfopo = insert_gromet_object(
516 primitive_fn.wfopo,
517 GrometWire(
518 src=len(primitive_fn.opo), tgt=len(primitive_fn.pof)
519 ),
520 )
523 # Create FN's opi and and opo
524 for arg in node.arguments:
525 if (
526 isinstance(arg, AnnCastOperator)
527 or isinstance(arg, AnnCastLiteralValue)
528 or isinstance(arg, AnnCastCall)
529 ):
530 self.visit(arg, primitive_fn, parent_cast_node)
531 primitive_fn.pif = insert_gromet_object(
532 primitive_fn.pif, GrometPort(box=primitive_bf_loc)
533 )
534 primitive_fn.wff = insert_gromet_object(
535 primitive_fn.wff,
536 GrometWire(
537 src=len(primitive_fn.pif),
538 tgt=len(primitive_fn.pof),
539 ),
540 )
541 else:
542 var_env = self.symtab_variables()
543 primitive_fn.pif = insert_gromet_object(
544 primitive_fn.pif, GrometPort(box=primitive_bf_loc)
545 )
546 arg_name = get_left_side_name(arg)
547 primitive_fn.opi = insert_gromet_object(
548 primitive_fn.opi, GrometPort(box=len(primitive_fn.b))
549 )
550 primitive_fn.wfopi = insert_gromet_object(
551 primitive_fn.wfopi,
552 GrometWire(
553 src=len(primitive_fn.pif),
554 tgt=len(primitive_fn.opi),
555 ),
556 )
558 # Insert it into the overall Gromet FN collection
559 self.gromet_module.fn_array = insert_gromet_object(
560 self.gromet_module.fn_array,
561 primitive_fn,
562 )
563 self.set_index()
565 fn_array_idx = len(self.gromet_module.fn_array)
567 ref = node.source_refs[0]
568 # metadata = self.create_source_code_reference(ref)
569 # Creates the 'call' to this primitive expression which then gets inserted into the parent's Gromet FN
570 parent_primitive_call_bf = GrometBoxFunction(
571 function_type=FunctionType.EXPRESSION,
572 body=fn_array_idx,
573 metadata=self.insert_metadata(metadata),
574 )
576 # We create the arguments of the primitive expression call here and then
577 # We must wire the arguments of this primitive expression appropriately
578 # We have an extra check to see if the local came from a Loop, in which
579 # case we use a wlf wire to wire the pol to the pif
581 parent_gromet_fn.bf = insert_gromet_object(
582 parent_gromet_fn.bf, parent_primitive_call_bf
583 )
584 return len(parent_gromet_fn.bf)
586 def add_var_to_env(
587 self, var_name, var_cast, var_pof, var_pof_idx, parent_cast_node
588 ):
589 """Adds a variable with name var_name, CAST node var_cast, Gromet pof var_pof
590 and pof index var_pof_idx to the overall variable environment.
591 This addition to the environment happens in these conditions
592 - An assignment at the global (module) level
593 - An assignment at the local (function def) level
594 - When visiting a function argument (This is done at the function def visitor)
595 This environment is used when a reference to a variable and its pof is
596 needed in Gromet, this is mostly used when creating wires between outputs
597 and inputs
598 parent_cast_node allows us to determine if this variable exists within
599 """
600 var_environment = self.symtab_variables()
602 if isinstance(parent_cast_node, AnnCastModule):
603 global_env = var_environment["global"]
604 global_env[var_name] = (var_cast, var_pof, var_pof_idx)
605 elif (
606 isinstance(parent_cast_node, AnnCastFunctionDef)
607 or isinstance(parent_cast_node, AnnCastModelIf)
608 or isinstance(parent_cast_node, AnnCastLoop)
609 ):
610 local_env = var_environment["local"]
611 local_env[var_name] = (parent_cast_node, var_pof, var_pof_idx)
612 # else:
613 # print(f"error: add_var_to_env: we came from{type(parent_cast_node)}")
614 # sys.exit()
616 def find_gromet(self, func_name):
617 """Attempts to find func_name in self.gromet_module.fn_array
618 and will return the index of where it is if it finds it.
619 It checks if the attribute is a GroMEt FN.
620 It will also return a boolean stating whether or not it found it.
621 If it doesn't find it, the func_idx then represents the index at
622 the end of the self.gromet_module.fn_array collection.
623 """
624 func_idx = 0
625 found_func = False
626 for attribute in self.gromet_module.fn_array:
627 gromet_fn = attribute
628 if gromet_fn.b != None:
629 gromet_fn_b = gromet_fn.b[0]
630 if gromet_fn_b.name == func_name:
631 found_func = True
632 break
634 func_idx += 1
636 return func_idx + 1, found_func
638 def retrieve_var_port(self, var_name):
639 """Given a variable named var_name in the variable environment
640 This function attempts to look up the port in which it's located
641 """
642 var_environment = self.symtab_variables()
643 if var_name in var_environment["local"]:
644 local_env = var_environment["local"]
645 entry = local_env[var_name]
646 return entry[2]
647 elif var_name in var_environment["args"]:
648 args_env = var_environment["args"]
649 entry = args_env[var_name]
650 return entry[2]
651 elif var_name in var_environment["global"]:
652 global_env = var_environment["global"]
653 entry = global_env[var_name]
654 return entry[2]
656 return -1
658 def check_var_location(self, var_name, env):
659 # Given the name of a variable and the name of an environment,
660 # check if that variable is in that environment
661 var_environment = self.symtab_variables()
662 return var_name in var_environment[env]
664 def visit(self, node: AnnCastNode, parent_gromet_fn, parent_cast_node):
665 """
666 External visit that callsthe internal visit
667 Useful for debugging/development. For example,
668 printing the nodes that are visited
669 """
670 # print current node being visited.
671 # this can be useful for debugging
672 # class_name = node.__class__.__name__
673 # print(f"\nProcessing node type {class_name}")
675 # call internal visit
676 try:
677 return self._visit(node, parent_gromet_fn, parent_cast_node)
678 except Exception as e:
679 print(
680 f"Error in visitor for {type(node)} which has source ref information {node.source_refs}"
681 )
682 raise e
684 def visit_node_list(
685 self,
686 node_list: typing.List[AnnCastNode],
687 parent_gromet_fn,
688 parent_cast_node,
689 ):
690 return [
691 self.visit(node, parent_gromet_fn, parent_cast_node)
692 for node in node_list
693 ]
695 @singledispatchmethod
696 def _visit(self, node: AnnCastNode, parent_gromet_fn, parent_cast_node):
697 """
698 Internal visit
699 """
700 raise NameError(f"Unrecognized node type: {type(node)}")
702 # This creates 'expression' GroMEt FNs (i.e. new big standalone colored boxes in the diagram)
703 # - The expression on the right hand side of an assignment
704 # - This could be as simple as a LiteralValue (like the number 2)
705 # - It could be a binary expression (like 2 + 3)
706 # - It could be a function call (foo(2))
708 def unpack_create_collection_pofs(
709 self, tuple_values, parent_gromet_fn, parent_cast_node
710 ):
711 """When we encounter a case where a tuple has a tuple (or list) inside of it
712 we call this helper function to appropriately unpack it and create its pofs
713 """
714 for elem in tuple_values:
715 if isinstance(elem, AnnCastLiteralValue):
716 self.unpack_create_collection_pofs(
717 elem.value, parent_gromet_fn, parent_cast_node
718 )
719 else:
720 ref = elem.source_refs[0]
721 metadata = self.create_source_code_reference(ref)
722 parent_gromet_fn.pof = insert_gromet_object(
723 parent_gromet_fn.pof,
724 GrometPort(
725 name=elem.val.name,
726 box=len(parent_gromet_fn.bf),
727 metadata=self.insert_metadata(metadata),
728 ),
729 )
730 pof_idx = len(parent_gromet_fn.pof)
731 self.add_var_to_env(
732 elem.val.name,
733 elem,
734 parent_gromet_fn.pof[pof_idx - 1],
735 pof_idx,
736 parent_cast_node,
737 )
739 def create_pack(
740 self, var, tuple_values, parent_gromet_fn, parent_cast_node
741 ):
742 """Creates a 'pack' primitive whenever the left hand side
743 of a tuple assignment is a single variable, such as:
744 x = a,b,c...
745 """
746 # Make the "pack" literal and insert it in the GroMEt FN
747 # TODO: a better way to get the name of this 'pack'
748 pack_bf = GrometBoxFunction(
749 name="pack", function_type=FunctionType.ABSTRACT
750 )
752 parent_gromet_fn.bf = insert_gromet_object(
753 parent_gromet_fn.bf, pack_bf
754 )
756 pack_index = len(parent_gromet_fn.bf)
758 # Construct the pifs for the pack and wire them
759 for port in tuple_values:
760 parent_gromet_fn.pif = insert_gromet_object(
761 parent_gromet_fn.pif, GrometPort(box=pack_index)
762 )
764 parent_gromet_fn.wff = insert_gromet_object(
765 parent_gromet_fn.wff,
766 GrometWire(src=len(parent_gromet_fn.pif), tgt=port),
767 )
769 # Insert the return value of the pack
770 # Which is one variable
771 parent_gromet_fn.pof = insert_gromet_object(
772 parent_gromet_fn.pof,
773 GrometPort(name=get_left_side_name(var), box=pack_index),
774 )
776 self.add_var_to_env(
777 get_left_side_name(var),
778 var,
779 parent_gromet_fn.pof[-1],
780 len(parent_gromet_fn.pof),
781 parent_cast_node,
782 )
784 def create_unpack(self, tuple_values, parent_gromet_fn, parent_cast_node):
785 """Creates an 'unpack' primitive whenever the left hand side
786 of an assignment is a tuple. Example:
787 x,y,z = foo(...)
788 Then, an unpack with x,y,z as pofs is created and a pif connecting to the return value of
789 foo() is created
790 """
791 parent_gromet_fn.pof = insert_gromet_object(
792 parent_gromet_fn.pof, GrometPort(box=len(parent_gromet_fn.bf))
793 )
795 # Make the "unpack" literal here
796 # And wire it appropriately
797 unpack_bf = GrometBoxFunction(
798 name="unpack", function_type=FunctionType.ABSTRACT
799 ) # TODO: a better way to get the name of this 'unpack'
800 parent_gromet_fn.bf = insert_gromet_object(
801 parent_gromet_fn.bf, unpack_bf
802 )
804 # Make its pif so that it takes the return value of the function call
805 parent_gromet_fn.pif = insert_gromet_object(
806 parent_gromet_fn.pif, GrometPort(box=len(parent_gromet_fn.bf))
807 )
809 # Wire the pif to the function call's pof
810 parent_gromet_fn.wff = insert_gromet_object(
811 parent_gromet_fn.wff,
812 GrometWire(
813 src=len(parent_gromet_fn.pif), tgt=len(parent_gromet_fn.pof)
814 ),
815 )
817 for elem in tuple_values:
818 if isinstance(elem, AnnCastLiteralValue):
819 self.unpack_create_collection_pofs(
820 elem.value, parent_gromet_fn, parent_cast_node
821 )
822 elif isinstance(elem, AnnCastCall):
823 ref = elem.source_refs[0]
824 metadata = self.create_source_code_reference(ref)
825 parent_gromet_fn.pof = insert_gromet_object(
826 parent_gromet_fn.pof,
827 GrometPort(
828 name=elem.func.name,
829 box=len(parent_gromet_fn.bf),
830 metadata=self.insert_metadata(metadata),
831 ),
832 )
833 pof_idx = len(parent_gromet_fn.pof)
834 self.add_var_to_env(
835 elem.func.name,
836 elem,
837 parent_gromet_fn.pof[pof_idx - 1],
838 pof_idx,
839 parent_cast_node,
840 )
841 elif isinstance(elem, AnnCastAttribute):
842 ref = elem.source_refs[0]
843 metadata = self.create_source_code_reference(ref)
844 parent_gromet_fn.pof = insert_gromet_object(
845 parent_gromet_fn.pof,
846 GrometPort(
847 name=elem.attr.name,
848 box=len(parent_gromet_fn.bf),
849 metadata=self.insert_metadata(metadata),
850 ),
851 )
852 pof_idx = len(parent_gromet_fn.pof)
853 self.add_var_to_env(
854 elem.attr.name,
855 elem,
856 parent_gromet_fn.pof[pof_idx - 1],
857 pof_idx,
858 parent_cast_node,
859 )
860 else:
861 ref = elem.source_refs[0]
862 metadata = self.create_source_code_reference(ref)
863 parent_gromet_fn.pof = insert_gromet_object(
864 parent_gromet_fn.pof,
865 GrometPort(
866 name=elem.val.name,
867 box=len(parent_gromet_fn.bf),
868 metadata=self.insert_metadata(metadata),
869 ),
870 )
871 pof_idx = len(parent_gromet_fn.pof)
872 self.add_var_to_env(
873 elem.val.name,
874 elem,
875 parent_gromet_fn.pof[pof_idx - 1],
876 pof_idx,
877 parent_cast_node,
878 )
880 def create_implicit_unpack(
881 self, tuple_values, parent_gromet_fn, parent_cast_node
882 ):
883 """
884 In some cases, we need to unpack a tuple without using an 'unpack' primitive
885 In this case, we directly attach the pofs to the FN instead of going through
886 and 'unpack'
887 """
889 for elem in tuple_values:
890 if isinstance(elem, AnnCastLiteralValue):
891 self.unpack_create_collection_pofs(
892 elem.value, parent_gromet_fn, parent_cast_node
893 )
894 elif isinstance(elem, AnnCastCall):
895 ref = elem.source_refs[0]
896 metadata = self.create_source_code_reference(ref)
897 parent_gromet_fn.pof = insert_gromet_object(
898 parent_gromet_fn.pof,
899 GrometPort(
900 name=elem.func.name,
901 box=len(parent_gromet_fn.bf),
902 metadata=self.insert_metadata(metadata),
903 ),
904 )
905 pof_idx = len(parent_gromet_fn.pof)
906 self.add_var_to_env(
907 elem.func.name,
908 elem,
909 parent_gromet_fn.pof[pof_idx - 1],
910 pof_idx,
911 parent_cast_node,
912 )
913 elif isinstance(elem, AnnCastAttribute):
914 ref = elem.source_refs[0]
915 metadata = self.create_source_code_reference(ref)
916 parent_gromet_fn.pof = insert_gromet_object(
917 parent_gromet_fn.pof,
918 GrometPort(
919 name=elem.attr.name,
920 box=len(parent_gromet_fn.bf),
921 metadata=self.insert_metadata(metadata),
922 ),
923 )
924 pof_idx = len(parent_gromet_fn.pof)
925 self.add_var_to_env(
926 elem.attr.name,
927 elem,
928 parent_gromet_fn.pof[pof_idx - 1],
929 pof_idx,
930 parent_cast_node,
931 )
932 else:
933 ref = elem.source_refs[0]
934 metadata = self.create_source_code_reference(ref)
935 parent_gromet_fn.pof = insert_gromet_object(
936 parent_gromet_fn.pof,
937 GrometPort(
938 name=elem.val.name,
939 box=len(parent_gromet_fn.bf),
940 metadata=self.insert_metadata(metadata),
941 ),
942 )
943 pof_idx = len(parent_gromet_fn.pof)
944 self.add_var_to_env(
945 elem.val.name,
946 elem,
947 parent_gromet_fn.pof[pof_idx - 1],
948 pof_idx,
949 parent_cast_node,
950 )
952 def determine_func_type(self, node):
953 """
954 Determines what kind of function this Call or Attribute node is referring to
955 Potential options
956 - ABSTRACT
957 - LANGUAGE_PRIMITIVE
958 - IMPORTED
959 - GROMET_FN_MODULE
960 - NATIVE
961 - OTHER
962 - IMPORTED_METHOD
963 - UNKNOWN_METHOD
965 Return a tuple of
966 (FunctionType, ImportType, ImportVersion, ImportSource, SourceLanguage, SourceLanguageVersion)
967 """
968 func_name, _ = retrieve_name_id_pair(node)
970 if is_primitive(func_name, "Python"):
971 # print(f"{func_name} is a primitive GroMEt function")
972 return (FunctionType.ABSTRACT, None, None, None, None, None)
974 if isinstance(node, AnnCastCall):
975 if func_name in BUILTINS or check_builtin(func_name):
976 # print(f"{func_name} is a python builtin")
977 if isinstance(node.func, AnnCastAttribute):
978 attr_node = node.func
979 if func_name in self.import_collection:
980 # print(f"Module {func_name} has imported function {attr_node.attr.name}")
981 return (
982 FunctionType.IMPORTED,
983 ImportType.NATIVE,
984 None,
985 None,
986 "Python",
987 PYTHON_VERSION,
988 )
990 return (
991 FunctionType.LANGUAGE_PRIMITIVE,
992 None,
993 None,
994 None,
995 "Python",
996 PYTHON_VERSION,
997 )
998 if isinstance(node.func, AnnCastAttribute):
999 attr_node = node.func
1000 if func_name in self.import_collection:
1001 # print(f"Module {func_name} has imported function {attr_node.attr.name}")
1002 # Check if it's gromet_fn_module/native/other
1003 # TODO: import_version/import_source
1004 if isinstance(node.func.attr, AnnCastName):
1005 name = node.func.attr.name
1006 if name in self.import_collection[func_name][1]:
1007 return (
1008 FunctionType.IMPORTED_METHOD,
1009 ImportType.NATIVE,
1010 None,
1011 None,
1012 "Python",
1013 PYTHON_VERSION
1014 )
1015 else:
1016 return (
1017 FunctionType.IMPORTED,
1018 ImportType.OTHER,
1019 None,
1020 None,
1021 "Python",
1022 PYTHON_VERSION
1023 )
1024 else:
1025 return (
1026 FunctionType.IMPORTED,
1027 ImportType.OTHER,
1028 None,
1029 None,
1030 "Python",
1031 PYTHON_VERSION
1032 )
1033 else:
1034 return (
1035 FunctionType.IMPORTED_METHOD,
1036 ImportType.OTHER,
1037 None,
1038 None,
1039 "Python",
1040 PYTHON_VERSION
1041 )
1042 else:
1043 return (
1044 FunctionType.IMPORTED_METHOD,
1045 ImportType.OTHER,
1046 None,
1047 None,
1048 "Python",
1049 PYTHON_VERSION,
1050 )
1051 elif isinstance(node, AnnCastAttribute):
1052 if func_name in BUILTINS or check_builtin(func_name):
1053 # print(f"{func_name} is a python builtin")
1054 if func_name in self.import_collection:
1055 # print(f"Module {func_name} has imported function {node.attr.name}")
1056 return (
1057 FunctionType.IMPORTED,
1058 ImportType.NATIVE,
1059 None,
1060 None,
1061 "Python",
1062 PYTHON_VERSION,
1063 )
1065 return (
1066 FunctionType.LANGUAGE_PRIMITIVE,
1067 None,
1068 None,
1069 None,
1070 "Python",
1071 PYTHON_VERSION,
1072 )
1073 elif func_name in self.import_collection:
1074 # print(f"Module {func_name} has imported function {node.attr.name}")
1075 # Check if it's gromet_fn_module/native/other
1076 # TODO: import_version/import_source
1077 return (
1078 FunctionType.IMPORTED,
1079 ImportType.OTHER,
1080 None,
1081 None,
1082 "Python",
1083 PYTHON_VERSION,
1084 )
1085 # Attribute of a class we don't have access to
1086 else:
1087 return (
1088 FunctionType.IMPORTED_METHOD,
1089 ImportType.OTHER,
1090 None,
1091 None,
1092 "Python",
1093 PYTHON_VERSION,
1094 )
1096 @_visit.register
1097 def visit_assignment(
1098 self, node: AnnCastAssignment, parent_gromet_fn, parent_cast_node
1099 ):
1100 # How does this creation of a GrometBoxFunction object play into the overall construction?
1101 # Where does it go?
1103 # This first visit on the node.right should create a FN
1104 # where the outer box is a GExpression (GroMEt Expression)
1105 # The purple box on the right in examples (exp0.py)
1106 # Because we don't know exactly what node.right holds at this time
1107 # we create the Gromet FN for the GExpression here
1109 # A function call creates a GroMEt FN at the scope of the
1110 # outer GroMEt FN box. In other words it's incorrect
1111 # to scope it to this assignment's Gromet FN
1112 if isinstance(node.right, AnnCastCall):
1113 # Assignment for
1114 # x = foo(...)
1115 # x = a.foo(...)
1116 # x,y,z = foo(...)
1117 func_bf_idx = self.visit(node.right, parent_gromet_fn, node)
1118 # NOTE: x = foo(...) <- foo returns multiple values that get packed
1119 # Several conditions for this
1120 # - foo has multiple output ports for returning
1121 # - multiple output ports but assignment to a single variable, then we introduce a pack
1122 # the result of the pack is a single introduced variable that gets wired to the single
1123 # variable
1124 # - multiple output ports but assignment to multiple variables, then we wire one-to-one
1125 # in order, all the output ports of foo to each variable
1126 # - else, if we dont have a one to one matching then it's an error
1127 # - foo has a single output port to return a value
1128 # - in the case of a single target variable, then we wire directly one-to-one
1129 # - otherwise if multiple target variables for a single return output port, then it's an error
1131 # We've made the call box function, which made its argument box functions and wired them appropriately.
1132 # Now, we have to make the output(s) to this call's box function and have them be assigned appropriately.
1133 # We also add any variables that have been assigned in this AnnCastAssignment to the variable environment
1134 if not isinstance(node.right.func, AnnCastAttribute) and not is_inline(node.right.func.name):
1135 # if isinstance(node.right.func, AnnCastName) and not is_inline(node.right.func.name):
1136 # if isinstance(node.left, AnnCastTuple):
1137 if is_tuple(node.left):
1138 self.create_unpack(
1139 node.left.value, parent_gromet_fn, parent_cast_node
1140 )
1141 else:
1142 if node.right.func.name in self.record.keys():
1143 self.initialized_records[
1144 node.left.val.name
1145 ] = node.right.func.name
1147 ref = node.left.source_refs[0]
1148 metadata = self.create_source_code_reference(ref)
1149 if func_bf_idx == None:
1150 func_bf_idx = len(parent_gromet_fn.bf)
1151 if isinstance(node.left, AnnCastAttribute):
1152 self.add_var_to_env(
1153 node.left.attr.name,
1154 node.left,
1155 parent_gromet_fn.pof[-1],
1156 len(parent_gromet_fn.pof),
1157 parent_cast_node,
1158 )
1159 parent_gromet_fn.pof[
1160 len(parent_gromet_fn.pof) - 1
1161 ].name = node.left.attr.name
1163 elif isinstance(node.left.val, AnnCastAttribute):
1164 self.add_var_to_env(
1165 node.left.val.attr.name,
1166 node.left,
1167 parent_gromet_fn.pof[-1],
1168 len(parent_gromet_fn.pof),
1169 parent_cast_node,
1170 )
1171 parent_gromet_fn.pof[
1172 len(parent_gromet_fn.pof) - 1
1173 ].name = node.left.val.attr.name
1174 else:
1175 self.add_var_to_env(
1176 get_left_side_name(node.left),
1177 node.left,
1178 parent_gromet_fn.pof[-1],
1179 len(parent_gromet_fn.pof),
1180 parent_cast_node,
1181 )
1182 parent_gromet_fn.pof[
1183 len(parent_gromet_fn.pof) - 1
1184 ].name = get_left_side_name(node.left)
1185 else:
1186 if is_tuple(node.left):
1187 if (
1188 isinstance(node.right.func, AnnCastName)
1189 and node.right.func.name == "next"
1190 ):
1191 tuple_values = node.left.value
1192 i = 2
1193 pof_length = len(parent_gromet_fn.pof)
1194 for elem in tuple_values:
1195 if isinstance(elem, AnnCastVar):
1196 name = elem.val.name
1197 parent_gromet_fn.pof[
1198 pof_length - 1 - i
1199 ].name = name
1201 self.add_var_to_env(
1202 name,
1203 elem,
1204 parent_gromet_fn.pof[pof_length - 1 - i],
1205 pof_length - i,
1206 parent_cast_node,
1207 )
1208 i -= 1
1209 elif isinstance(elem, AnnCastLiteralValue):
1210 name = elem.value[0].val.name
1211 parent_gromet_fn.pof[
1212 pof_length - 1 - i
1213 ].name = name
1215 self.add_var_to_env(
1216 name,
1217 elem,
1218 parent_gromet_fn.pof[pof_length - 1 - i],
1219 pof_length - i,
1220 parent_cast_node,
1221 )
1222 i -= 1
1224 else:
1225 self.create_unpack(
1226 node.left.value, parent_gromet_fn, parent_cast_node
1227 )
1228 elif isinstance(node.right.func, AnnCastAttribute):
1229 if (
1230 parent_gromet_fn.pof == None
1231 ): # TODO: check this guard later
1232 # print(node.source_refs[0])
1233 if isinstance(node.left, AnnCastAttribute):
1234 name = node.left.attr
1235 else:
1236 name = node.left.val.name
1237 parent_gromet_fn.pof = insert_gromet_object(
1238 parent_gromet_fn.pof,
1239 GrometPort(name=name, box=-1),
1240 )
1241 else:
1242 if isinstance(node.left, AnnCastAttribute):
1243 parent_gromet_fn.pof = insert_gromet_object(
1244 parent_gromet_fn.pof,
1245 GrometPort(
1246 name=node.left.attr.name,
1247 box=len(parent_gromet_fn.pof),
1248 ),
1249 )
1250 self.add_var_to_env(
1251 node.left.attr.name,
1252 node.left,
1253 parent_gromet_fn.pof[-1],
1254 len(parent_gromet_fn.pof),
1255 parent_cast_node,
1256 )
1257 elif isinstance(node.left, AnnCastVar):
1258 parent_gromet_fn.pof = insert_gromet_object(
1259 parent_gromet_fn.pof,
1260 GrometPort(
1261 name=node.left.val.name,
1262 box=len(parent_gromet_fn.bf),
1263 ),
1264 )
1265 self.add_var_to_env(
1266 node.left.val.name,
1267 node.left,
1268 parent_gromet_fn.pof[-1],
1269 len(parent_gromet_fn.pof),
1270 parent_cast_node,
1271 )
1273 if parent_gromet_fn.pif != None:
1274 self.wire_from_var_env(
1275 node.left.val.name, parent_gromet_fn
1276 )
1277 else:
1278 # NOTE: This case needs to eventually removed as this handler gets fleshed out more
1279 parent_gromet_fn.pof[-1].name = node.left.val.name
1280 self.add_var_to_env(
1281 node.left.val.name,
1282 node.left,
1283 parent_gromet_fn.pof[-1],
1284 len(parent_gromet_fn.pof),
1285 parent_cast_node,
1286 )
1288 if parent_gromet_fn.pif != None:
1289 self.wire_from_var_env(
1290 node.left.val.name, parent_gromet_fn
1291 )
1292 else:
1293 self.add_var_to_env(
1294 node.left.val.name,
1295 node.left,
1296 parent_gromet_fn.pof[-1],
1297 len(parent_gromet_fn.pof),
1298 parent_cast_node,
1299 )
1300 parent_gromet_fn.pof[
1301 len(parent_gromet_fn.pof) - 1
1302 ].name = node.left.val.name
1304 elif isinstance(node.right, AnnCastName):
1305 # Assignment for
1306 # x = y
1307 # or some,set,of,values,... = y
1308 if isinstance(parent_cast_node, AnnCastCall):
1309 parent_gromet_fn.bf = insert_gromet_object(
1310 parent_gromet_fn.bf,
1311 GrometBoxFunction(
1312 function_type=FunctionType.LITERAL,
1313 value=GLiteralValue("string", node.right.name),
1314 ),
1315 )
1316 parent_gromet_fn.pof = insert_gromet_object(
1317 parent_gromet_fn.pof,
1318 GrometPort(
1319 box = len(parent_gromet_fn.bf),
1320 name=get_left_side_name(node.left),
1321 metadata=self.insert_metadata(SourceCodePortKeywordArg())
1322 )
1323 )
1324 else:
1325 # Create a passthrough GroMEt
1326 new_gromet = GrometFN()
1327 new_gromet.b = insert_gromet_object(
1328 new_gromet.b,
1329 GrometBoxFunction(function_type=FunctionType.EXPRESSION),
1330 )
1331 new_gromet.opi = insert_gromet_object(
1332 new_gromet.opi, GrometPort(box=len(new_gromet.b))
1333 )
1334 new_gromet.opo = insert_gromet_object(
1335 new_gromet.opo, GrometPort(box=len(new_gromet.b))
1336 )
1337 new_gromet.wopio = insert_gromet_object(
1338 new_gromet.wopio,
1339 GrometWire(src=len(new_gromet.opo), tgt=len(new_gromet.opi)),
1340 )
1342 # Add it to the GroMEt collection
1343 self.gromet_module.fn_array = insert_gromet_object(
1344 self.gromet_module.fn_array, new_gromet
1345 )
1346 self.set_index()
1348 # Make it's 'call' expression in the parent gromet
1349 parent_gromet_fn.bf = insert_gromet_object(
1350 parent_gromet_fn.bf,
1351 GrometBoxFunction(
1352 function_type=FunctionType.EXPRESSION,
1353 body=len(self.gromet_module.fn_array),
1354 ),
1355 )
1357 parent_gromet_fn.pif = insert_gromet_object(
1358 parent_gromet_fn.pif, GrometPort(box=len(parent_gromet_fn.bf))
1359 )
1360 if isinstance(parent_gromet_fn.b[0], GrometBoxFunction) and (
1361 parent_gromet_fn.b[0].function_type == FunctionType.EXPRESSION
1362 or parent_gromet_fn.b[0].function_type
1363 == FunctionType.PREDICATE
1364 ):
1365 parent_gromet_fn.opi = insert_gromet_object(
1366 parent_gromet_fn.opi,
1367 GrometPort(
1368 box=len(parent_gromet_fn.b), name=node.right.name
1369 ),
1370 )
1372 self.wire_from_var_env(node.right.name, parent_gromet_fn)
1374 # if isinstance(node.left, AnnCastTuple): TODO: double check that this addition is correct
1375 if is_tuple(node.left):
1376 self.create_unpack(
1377 node.left.value, parent_gromet_fn, parent_cast_node
1378 )
1379 else:
1380 parent_gromet_fn.pof = insert_gromet_object(
1381 parent_gromet_fn.pof,
1382 GrometPort(
1383 name=get_left_side_name(node.left),
1384 box=len(parent_gromet_fn.bf),
1385 ),
1386 )
1387 self.add_var_to_env(
1388 get_left_side_name(node.left),
1389 node.left,
1390 parent_gromet_fn.pof[-1],
1391 len(parent_gromet_fn.pof),
1392 parent_cast_node,
1393 )
1394 elif isinstance(node.right, AnnCastLiteralValue):
1395 # Assignment for
1396 # LiteralValue (i.e. 3), tuples
1397 if is_tuple(node.right):
1398 # Case for when right hand side is a tuple
1399 # For instances like
1400 # x = a,b,c,...
1401 # x,y,z,... = w,a,b,...
1402 ref = node.source_refs[0]
1403 metadata = self.create_source_code_reference(ref)
1405 # Make Expression GrometFN
1406 # new_gromet = GrometFN()
1407 # new_gromet.b = insert_gromet_object(
1408 # new_gromet.b,
1409 # GrometBoxFunction(function_type=FunctionType.EXPRESSION),
1410 # )
1412 # Visit each individual value in the tuple
1413 # and collect the resulting
1414 # pofs from each value
1415 tuple_indices = []
1416 for val in node.right.value:
1417 if isinstance(val, AnnCastLiteralValue):
1418 new_gromet = GrometFN()
1419 new_gromet.b = insert_gromet_object(
1420 new_gromet.b,
1421 GrometBoxFunction(
1422 function_type=FunctionType.EXPRESSION
1423 ),
1424 )
1426 self.visit(val, new_gromet, parent_cast_node)
1428 # Create the opo for the Gromet Expression holding the literal and then wire its opo to the literal's pof
1429 new_gromet.opo = insert_gromet_object(
1430 new_gromet.opo, GrometPort(box=len(new_gromet.b))
1431 )
1433 new_gromet.wfopo = insert_gromet_object(
1434 new_gromet.wfopo,
1435 GrometWire(
1436 src=len(new_gromet.opo),
1437 tgt=len(new_gromet.pof),
1438 ),
1439 )
1441 # Append this Gromet Expression holding the value to the overall gromet FN collection
1442 self.gromet_module.fn_array = insert_gromet_object(
1443 self.gromet_module.fn_array,
1444 new_gromet,
1445 )
1446 self.set_index()
1448 # Make the 'call' box function that connects the expression to the parent and creates its output port
1449 # print(node.source_refs)
1450 parent_gromet_fn.bf = insert_gromet_object(
1451 parent_gromet_fn.bf,
1452 GrometBoxFunction(
1453 function_type=FunctionType.EXPRESSION,
1454 body=len(self.gromet_module.fn_array),
1455 metadata=self.insert_metadata(metadata),
1456 ),
1457 )
1459 # Make the output port for this value in the tuple
1460 # If the left hand side is also a tuple this port will get named
1461 # further down
1462 parent_gromet_fn.pof = insert_gromet_object(
1463 parent_gromet_fn.pof,
1464 GrometPort(
1465 name=None,
1466 box=len(parent_gromet_fn.bf),
1467 ),
1468 )
1470 var_pof = len(parent_gromet_fn.pof)
1472 elif isinstance(val, AnnCastOperator):
1473 new_gromet = GrometFN()
1474 new_gromet.b = insert_gromet_object(
1475 new_gromet.b,
1476 GrometBoxFunction(
1477 function_type=FunctionType.EXPRESSION
1478 ),
1479 )
1480 self.visit(val, new_gromet, parent_cast_node)
1482 self.gromet_module.fn_array = insert_gromet_object(
1483 self.gromet_module.fn_array,
1484 new_gromet,
1485 )
1486 self.set_index()
1488 # Make the 'call' box function that connects the expression to the parent and creates its output port
1489 # print(node.source_refs)
1490 parent_gromet_fn.bf = insert_gromet_object(
1491 parent_gromet_fn.bf,
1492 GrometBoxFunction(
1493 function_type=FunctionType.EXPRESSION,
1494 body=len(self.gromet_module.fn_array),
1495 metadata=self.insert_metadata(metadata),
1496 ),
1497 )
1499 parent_gromet_fn.pof = insert_gromet_object(
1500 parent_gromet_fn.pof,
1501 GrometPort(
1502 name=None,
1503 box=len(parent_gromet_fn.bf),
1504 ),
1505 )
1507 var_pof = len(parent_gromet_fn.pof)
1508 elif isinstance(val, AnnCastName):
1509 var_pof = self.retrieve_var_port(val.name)
1510 if var_pof == -1 and val.name in self.symtab_functions():
1511 parent_gromet_fn.bf = insert_gromet_object(
1512 parent_gromet_fn.bf,
1513 GrometBoxFunction(
1514 function_type=FunctionType.LITERAL,
1515 value=GLiteralValue("string", val.name),
1516 ),
1517 )
1518 parent_gromet_fn.pof = insert_gromet_object(
1519 parent_gromet_fn.pof,
1520 GrometPort(
1521 box = len(parent_gromet_fn.bf)
1522 )
1523 )
1524 var_pof = len(parent_gromet_fn.pof)
1525 else:
1526 var_pof = -1
1527 # print(type(val))
1529 tuple_indices.append(var_pof)
1531 # Determine if the left hand side is
1532 # - A tuple of variables
1533 # - In this case we can directly wire each value from the
1534 # right hand side to values on the left hand side
1535 # - One variable
1536 # - We need to add a pack primitive if that's the case
1537 # NOTE: This is subject to change
1538 if is_tuple(node.left):
1539 # tuple_indices stores 1-index pofs, so we have to offset by one
1540 # to index with them
1541 for i, val in enumerate(node.left.value, 0):
1542 parent_gromet_fn.pof[
1543 tuple_indices[i] - 1
1544 ].name = get_left_side_name(node.left.value[i])
1546 self.add_var_to_env(
1547 get_left_side_name(node.left.value[i]),
1548 node.left.value[i],
1549 parent_gromet_fn.pof[tuple_indices[i] - 1],
1550 tuple_indices[i],
1551 parent_cast_node,
1552 )
1553 elif isinstance(node.left, AnnCastVar):
1554 self.create_pack(
1555 node.left,
1556 tuple_indices,
1557 parent_gromet_fn,
1558 parent_cast_node,
1559 )
1560 else:
1561 if node.source_refs == None:
1562 ref = []
1563 metadata = None
1564 else:
1565 ref = node.source_refs[0]
1566 metadata = self.create_source_code_reference(ref)
1568 # Make Expression GrometFN
1569 new_gromet = GrometFN()
1570 new_gromet.b = insert_gromet_object(
1571 new_gromet.b,
1572 GrometBoxFunction(function_type=FunctionType.EXPRESSION),
1573 )
1575 # Visit the literal value, which makes a bf for a literal and puts a pof to it
1576 self.visit(node.right, new_gromet, node)
1578 # Create the opo for the Gromet Expression holding the literal and then wire its opo to the literal's pof
1579 new_gromet.opo = insert_gromet_object(
1580 new_gromet.opo, GrometPort(box=len(new_gromet.b))
1581 )
1582 new_gromet.wfopo = insert_gromet_object(
1583 new_gromet.wfopo,
1584 GrometWire(
1585 src=len(new_gromet.opo), tgt=len(new_gromet.pof)
1586 ),
1587 )
1589 # Append this Gromet Expression holding the literal to the overall gromet FN collection
1590 self.gromet_module.fn_array = insert_gromet_object(
1591 self.gromet_module.fn_array,
1592 new_gromet,
1593 )
1594 self.set_index()
1596 # Make the 'call' box function that connects the expression to the parent and creates its output port
1597 # print(node.source_refs)
1598 parent_gromet_fn.bf = insert_gromet_object(
1599 parent_gromet_fn.bf,
1600 GrometBoxFunction(
1601 function_type=FunctionType.EXPRESSION,
1602 body=len(self.gromet_module.fn_array),
1603 metadata=self.insert_metadata(metadata),
1604 ),
1605 )
1606 parent_gromet_fn.pof = insert_gromet_object(
1607 parent_gromet_fn.pof,
1608 GrometPort(
1609 name=get_left_side_name(node.left),
1610 box=len(parent_gromet_fn.bf),
1611 ),
1612 )
1614 # TODO: expand on this later with loops
1615 if isinstance(parent_cast_node, AnnCastModelIf):
1616 parent_gromet_fn.opi = insert_gromet_object(
1617 parent_gromet_fn.opi,
1618 GrometPort(box=len(parent_gromet_fn.b)),
1619 )
1620 parent_gromet_fn.opo = insert_gromet_object(
1621 parent_gromet_fn.opo,
1622 GrometPort(box=len(parent_gromet_fn.b)),
1623 )
1624 parent_gromet_fn.wfopo = insert_gromet_object(
1625 parent_gromet_fn.wfopo,
1626 GrometWire(
1627 src=len(parent_gromet_fn.opo),
1628 tgt=len(parent_gromet_fn.pof),
1629 ),
1630 )
1632 # Store the new variable we created into the variable environment
1633 self.add_var_to_env(
1634 get_left_side_name(node.left),
1635 node.left,
1636 parent_gromet_fn.pof[-1],
1637 len(parent_gromet_fn.pof),
1638 parent_cast_node,
1639 )
1641 elif isinstance(node.right, AnnCastAttribute):
1642 ref = node.source_refs[0]
1643 metadata = self.create_source_code_reference(ref)
1645 # Create an expression FN
1646 new_gromet = GrometFN()
1647 new_gromet.b = insert_gromet_object(
1648 new_gromet.b,
1649 GrometBoxFunction(function_type=FunctionType.EXPRESSION),
1650 )
1652 self.visit(node.right, new_gromet, node)
1654 self.gromet_module.fn_array = insert_gromet_object(
1655 self.gromet_module.fn_array, new_gromet
1656 )
1657 self.set_index()
1659 parent_gromet_fn.bf = insert_gromet_object(
1660 parent_gromet_fn.bf,
1661 GrometBoxFunction(
1662 function_type=FunctionType.EXPRESSION,
1663 body=len(self.gromet_module.fn_array),
1664 metadata=self.insert_metadata(metadata),
1665 ),
1666 )
1667 expr_call_bf = len(parent_gromet_fn.bf)
1668 if is_tuple(node.left):
1669 self.create_unpack(node.left.value, parent_gromet_fn, parent_cast_node)
1670 else:
1671 parent_gromet_fn.pof = insert_gromet_object(
1672 parent_gromet_fn.pof, GrometPort(box=expr_call_bf, name=get_left_side_name(node.left))
1673 )
1675 else:
1676 # General Case
1677 # Assignment for
1678 # - Expression consisting of binary ops (x + y + ...), etc
1679 # - Other cases we haven't thought about
1680 ref = node.source_refs[0]
1681 metadata = self.create_source_code_reference(ref)
1683 # Create an expression FN
1684 new_gromet = GrometFN()
1685 new_gromet.b = insert_gromet_object(
1686 new_gromet.b,
1687 GrometBoxFunction(function_type=FunctionType.EXPRESSION),
1688 )
1690 self.visit(node.right, new_gromet, node)
1691 # At this point we identified the variable being assigned (i.e. for exp0.py: x)
1692 # we need to do some bookkeeping to associate the source CAST/GrFN variable with
1693 # the output port of the GroMEt expression call
1694 # NOTE: This may need to change from just indexing to something more
1695 new_gromet.opo = insert_gromet_object(
1696 new_gromet.opo, GrometPort(box=len(new_gromet.b))
1697 )
1699 # GroMEt wiring creation
1700 # The creation of the wire between the output port (pof) of the top-level node
1701 # of the tree rooted in node.right needs to be wired to the output port out (OPO)
1702 # of the GExpression of this AnnCastAssignment
1703 if (
1704 new_gromet.opo == None and new_gromet.pof == None
1705 ): # TODO: double check this guard to see if it's necessary
1706 # print(node.source_refs[0])
1707 new_gromet.wfopo = insert_gromet_object(
1708 new_gromet.wfopo, GrometWire(src=-1, tgt=-1)
1709 )
1710 elif new_gromet.pof == None:
1711 new_gromet.wfopo = insert_gromet_object(
1712 new_gromet.wfopo,
1713 GrometWire(src=len(new_gromet.opo), tgt=-1),
1714 )
1715 elif new_gromet.opo == None:
1716 # print(node.source_refs[0])
1717 new_gromet.wfopo = insert_gromet_object(
1718 new_gromet.wfopo,
1719 GrometWire(src=-1, tgt=len(new_gromet.pof)),
1720 )
1721 else:
1722 new_gromet.wfopo = insert_gromet_object(
1723 new_gromet.wfopo,
1724 GrometWire(
1725 src=len(new_gromet.opo), tgt=len(new_gromet.pof)
1726 ),
1727 )
1728 self.gromet_module.fn_array = insert_gromet_object(
1729 self.gromet_module.fn_array, new_gromet
1730 )
1731 self.set_index()
1733 # An assignment in a conditional or loop's body doesn't add bf, pif, or pof to the parent gromet FN
1734 # So we check if this assignment is not in either of those and add accordingly
1735 # NOTE: The above is no longer true because now Ifs/Loops create an additional 'Function' GroMEt FN for
1736 # their respective parts, so we do need to add this Expression GroMEt FN to the parent bf
1737 parent_gromet_fn.bf = insert_gromet_object(
1738 parent_gromet_fn.bf,
1739 GrometBoxFunction(
1740 function_type=FunctionType.EXPRESSION,
1741 body=len(self.gromet_module.fn_array),
1742 metadata=self.insert_metadata(metadata),
1743 ),
1744 )
1746 # There's no guarantee that our expression GroMEt used any inputs
1747 # Therefore we check if we have any inputs before checking them
1748 # For each opi the Expression GroMEt may have, we add a corresponding pif
1749 # to it, and then we see if we need to wire the pif to anything
1750 if new_gromet.opi != None:
1751 for opi in new_gromet.opi:
1752 parent_gromet_fn.pif = insert_gromet_object(
1753 parent_gromet_fn.pif,
1754 GrometPort(box=len(parent_gromet_fn.bf)),
1755 )
1756 self.wire_from_var_env(opi.name, parent_gromet_fn)
1758 # This is kind of a hack, so the opis are labeled by the GroMEt expression creation, but then we have to unlabel them
1759 opi.name = None
1761 # Put the final pof in the GroMEt expression call, and add its respective variable to the variable environment
1762 if isinstance(node.left, AnnCastAttribute):
1763 parent_gromet_fn.pof = insert_gromet_object(
1764 parent_gromet_fn.pof,
1765 GrometPort(
1766 name=node.left.attr.name, box=len(parent_gromet_fn.bf)
1767 ),
1768 )
1769 elif is_tuple(node.left):
1770 for i, elem in enumerate(node.left.value, 1):
1771 if (
1772 parent_gromet_fn.pof != None
1773 ): # TODO: come back and fix this guard later
1774 pof_idx = len(parent_gromet_fn.pof) - 1
1775 else:
1776 pof_idx = -1
1777 if (
1778 parent_gromet_fn.pof != None
1779 ): # TODO: come back and fix this guard later
1780 self.add_var_to_env(
1781 elem.val.name,
1782 elem,
1783 parent_gromet_fn.pof[pof_idx],
1784 pof_idx,
1785 parent_cast_node,
1786 )
1787 parent_gromet_fn.pof[pof_idx].name = elem.val.name
1788 else:
1789 name = ""
1790 if isinstance(node.left, AnnCastCall):
1791 name = node.left.func.name
1792 else:
1793 name = node.left.val.name
1794 parent_gromet_fn.pof = insert_gromet_object(
1795 parent_gromet_fn.pof,
1796 GrometPort(name=name, box=len(parent_gromet_fn.bf)),
1797 )
1799 # TODO: expand on this later
1800 if isinstance(parent_cast_node, AnnCastModelIf):
1801 parent_gromet_fn.opi = insert_gromet_object(
1802 parent_gromet_fn.opi,
1803 GrometPort(box=len(parent_gromet_fn.b)),
1804 )
1805 parent_gromet_fn.opo = insert_gromet_object(
1806 parent_gromet_fn.opo,
1807 GrometPort(box=len(parent_gromet_fn.b)),
1808 )
1809 parent_gromet_fn.wfopo = insert_gromet_object(
1810 parent_gromet_fn.wfopo,
1811 GrometWire(
1812 src=len(parent_gromet_fn.opo),
1813 tgt=len(parent_gromet_fn.pof),
1814 ),
1815 )
1817 if isinstance(node.left, AnnCastAttribute):
1818 self.add_var_to_env(
1819 node.left.attr.name,
1820 node.left,
1821 parent_gromet_fn.pof[-1],
1822 len(parent_gromet_fn.pof),
1823 parent_cast_node,
1824 )
1825 # elif isinstance(node.left, AnnCastTuple): # TODO: double check that this addition is correct
1826 elif is_tuple(node.left):
1827 for i, elem in enumerate(node.left.value, 1):
1828 if (
1829 parent_gromet_fn.pof != None
1830 ): # TODO: come back and fix this guard later
1831 pof_idx = len(parent_gromet_fn.pof) - i
1832 # pof_idx = len(parent_gromet_fn.pof) - 1
1833 else:
1834 pof_idx = -1
1835 if (
1836 parent_gromet_fn.pof != None
1837 ): # TODO: come back and fix this guard later
1838 self.add_var_to_env(
1839 elem.val.name,
1840 elem,
1841 parent_gromet_fn.pof[pof_idx],
1842 pof_idx,
1843 parent_cast_node,
1844 )
1845 parent_gromet_fn.pof[pof_idx].name = elem.val.name
1846 else:
1847 name = ""
1848 if isinstance(node.left, AnnCastCall):
1849 name = node.left.func.name
1850 else:
1851 name = node.left.val.name
1852 self.add_var_to_env(
1853 name,
1854 node.left,
1855 parent_gromet_fn.pof[-1],
1856 len(parent_gromet_fn.pof),
1857 parent_cast_node,
1858 )
1860 # One way or another we have a hold of the GEXpression object here.
1861 # Whatever's returned by the RHS of the assignment,
1862 # i.e. LiteralValue or primitive operator or function call.
1863 # Now we can look at its output port(s)
1865 @_visit.register
1866 def visit_attribute(
1867 self, node: AnnCastAttribute, parent_gromet_fn, parent_cast_node
1868 ):
1869 # Use self.import_collection to look up the attribute name
1870 # to see if it exists in there.
1871 # If the attribute exists, then we can create an import reference
1872 # node.value: left-side (i.e. module name or a class variable)
1873 # node.attr: right-side (i.e. name of a function or an attribute of a class)
1874 ref = node.source_refs[0]
1875 if isinstance(node.value, AnnCastName):
1876 name = node.value.name
1877 if name in self.import_collection:
1878 if isinstance(node.attr, AnnCastName):
1879 if not node.attr.name in self.import_collection[name][1]:
1880 self.import_collection[name][1].append(node.attr.name)
1881 self.add_import_symbol_to_env(
1882 node.attr.name, parent_gromet_fn, parent_cast_node
1883 )
1884 else:
1885 func_info = self.determine_func_type(node)
1886 metadata = self.create_source_code_reference(ref)
1887 parent_gromet_fn.bf = insert_gromet_object(
1888 parent_gromet_fn.bf,
1889 GrometBoxFunction(
1890 name=f"{name}.{node.attr.name}",
1891 function_type=func_info[0],
1892 import_type=func_info[1],
1893 import_version=func_info[2],
1894 import_source=func_info[3],
1895 source_language=func_info[4],
1896 source_language_version=func_info[5],
1897 body=None,
1898 metadata=self.insert_metadata(metadata)
1899 ),
1900 )
1901 elif isinstance(node.attr, AnnCastName):
1902 if node.value.name == "self":
1903 # Compose the case of "self.x" where x is an attribute
1904 # Create string literal for "get" second argument
1905 parent_gromet_fn.bf = insert_gromet_object(
1906 parent_gromet_fn.bf,
1907 GrometBoxFunction(
1908 function_type=FunctionType.LITERAL,
1909 value=GLiteralValue("string", node.attr.name),
1910 ),
1911 )
1912 parent_gromet_fn.pof = insert_gromet_object(
1913 parent_gromet_fn.pof,
1914 GrometPort(box=len(parent_gromet_fn.bf)),
1915 )
1917 # Create "get" function and first argument, then wire it to 'self' argument
1918 get_bf = GrometBoxFunction(
1919 name="get", function_type=FunctionType.ABSTRACT
1920 )
1921 parent_gromet_fn.bf = insert_gromet_object(
1922 parent_gromet_fn.bf, get_bf
1923 )
1924 parent_gromet_fn.pif = insert_gromet_object(
1925 parent_gromet_fn.pif,
1926 GrometPort(box=len(parent_gromet_fn.bf)),
1927 )
1928 parent_gromet_fn.wfopi = insert_gromet_object(
1929 parent_gromet_fn.wfopi,
1930 GrometWire(src=len(parent_gromet_fn.pif), tgt=1),
1931 ) # self is opi 1 everytime
1933 # Create "get" second argument and wire it to the string literal from earlier
1934 parent_gromet_fn.pif = insert_gromet_object(
1935 parent_gromet_fn.pif,
1936 GrometPort(box=len(parent_gromet_fn.bf)),
1937 )
1938 parent_gromet_fn.wff = insert_gromet_object(
1939 parent_gromet_fn.wff,
1940 GrometWire(
1941 src=len(parent_gromet_fn.pif),
1942 tgt=len(parent_gromet_fn.pof),
1943 ),
1944 )
1946 # Create "get" pof
1947 parent_gromet_fn.pof = insert_gromet_object(
1948 parent_gromet_fn.pof,
1949 GrometPort(box=len(parent_gromet_fn.bf)),
1950 )
1951 elif isinstance(parent_cast_node, AnnCastCall):
1952 # Case where a class is calling a method (i.e. mc is a class, and we do mc.get_c())
1953 func_name = node.attr.name
1955 if node.value.name in self.initialized_records:
1956 obj_name = self.initialized_records[node.value.name]
1957 if (
1958 func_name in self.record[obj_name].keys()
1959 ): # TODO: remove this guard later
1960 idx = self.record[obj_name][func_name]
1961 parent_gromet_fn.bf = insert_gromet_object(
1962 parent_gromet_fn.bf,
1963 GrometBoxFunction(
1964 name=func_name,
1965 function_type=FunctionType.FUNCTION,
1966 body=idx,
1967 ),
1968 )
1969 # parent_gromet_fn.bf = insert_gromet_object(parent_gromet_fn.bf, GrometBoxFunction(name=f"{obj_name}:{func_name}", function_type=FunctionType.FUNCTION, contents=idx, metadata=self.insert_metadata(metadata)))
1971 parent_gromet_fn.pif = insert_gromet_object(
1972 parent_gromet_fn.pif,
1973 GrometPort(
1974 name=node.value.name,
1975 box=len(parent_gromet_fn.bf),
1976 ),
1977 )
1978 parent_gromet_fn.pof = insert_gromet_object(
1979 parent_gromet_fn.pof,
1980 GrometPort(box=len(parent_gromet_fn.bf)),
1981 )
1982 else: # Attribute of a class that we don't have access to
1983 # NOTE: This will probably have to change later
1984 func_info = self.determine_func_type(node)
1986 parent_gromet_fn.bf = insert_gromet_object(
1987 parent_gromet_fn.bf,
1988 GrometBoxFunction(
1989 name=f"{node.value.name}.{func_name}",
1990 function_type=func_info[0],
1991 import_type=func_info[1],
1992 import_version=func_info[2],
1993 import_source=func_info[3],
1994 source_language=func_info[4],
1995 source_language_version=func_info[5],
1996 body=None,
1997 ),
1998 )
1999 parent_gromet_fn.pof = insert_gromet_object(
2000 parent_gromet_fn.pof,
2001 GrometPort(box=len(parent_gromet_fn.bf)),
2002 )
2003 else:
2004 # default case of accessing x.T where T is an attribute
2005 # using 'get' to access the attribute
2006 val_name = get_attribute_name(node.value) # left side of dot
2007 attr_name = get_attribute_name(node.attr) # right side of dot
2009 get_bf = GrometBoxFunction(
2010 name="get", function_type=FunctionType.ABSTRACT
2011 )
2012 parent_gromet_fn.bf = insert_gromet_object(
2013 parent_gromet_fn.bf, get_bf
2014 )
2015 get_bf_idx = len(parent_gromet_fn.bf)
2017 # Make the attribute value port and wire to the opi
2018 parent_gromet_fn.pif = insert_gromet_object(
2019 parent_gromet_fn.pif, GrometPort(box=get_bf_idx)
2020 )
2021 parent_gromet_fn.opi = insert_gromet_object(
2022 parent_gromet_fn.opi, GrometPort(box=len(parent_gromet_fn.b), name=val_name)
2023 )
2024 parent_gromet_fn.wfopi = insert_gromet_object(
2025 parent_gromet_fn.wfopi, GrometWire(src=len(parent_gromet_fn.opi) ,tgt=len(parent_gromet_fn.pif))
2026 )
2028 # Create the attribute attr literal and wire appropriately
2029 parent_gromet_fn.bf = insert_gromet_object(
2030 parent_gromet_fn.bf,
2031 GrometBoxFunction(
2032 function_type=FunctionType.LITERAL,
2033 value=GLiteralValue("string", attr_name),
2034 ),
2035 )
2036 attr_bf_idx = len(parent_gromet_fn.bf)
2038 # output of the gromet literal for attribute attr
2039 parent_gromet_fn.pof = insert_gromet_object(
2040 parent_gromet_fn.pof, GrometPort(box=attr_bf_idx)
2041 )
2043 # Second argument to "get"
2044 parent_gromet_fn.pif = insert_gromet_object(
2045 parent_gromet_fn.pif, GrometPort(box=get_bf_idx)
2046 )
2047 parent_gromet_fn.wff = insert_gromet_object(
2048 parent_gromet_fn.wff, GrometWire(src=len(parent_gromet_fn.pif), tgt=len(parent_gromet_fn.pof))
2049 )
2051 # Final output and wire
2052 parent_gromet_fn.pof = insert_gromet_object(
2053 parent_gromet_fn.pof, GrometPort(box=get_bf_idx)
2054 )
2055 parent_gromet_fn.opo = insert_gromet_object(
2056 parent_gromet_fn.opo, GrometPort(box=len(parent_gromet_fn.b))
2057 )
2058 parent_gromet_fn.wfopo = insert_gromet_object(
2059 parent_gromet_fn.wfopo, GrometWire(src=len(parent_gromet_fn.opo) ,tgt=len(parent_gromet_fn.pof))
2060 )
2063 elif isinstance(node.value, AnnCastCall):
2064 # NOTE: M7 placeholder
2065 parent_gromet_fn.bf = insert_gromet_object(
2066 parent_gromet_fn.bf,
2067 GrometBoxFunction(
2068 function_type=FunctionType.FUNCTION,
2069 body=None,
2070 metadata=self.insert_metadata(
2071 self.create_source_code_reference(ref)
2072 ),
2073 ),
2074 )
2075 elif isinstance(node.value, AnnCastOperator):
2076 # Added to support scenario 2 of Jul'23 hackathon
2077 # Create an expression FN
2078 new_gromet = GrometFN()
2079 new_gromet.b = insert_gromet_object(
2080 new_gromet.b,
2081 GrometBoxFunction(function_type=FunctionType.EXPRESSION),
2082 )
2084 self.visit(node.value, new_gromet, node)
2086 new_gromet.opo = insert_gromet_object(
2087 new_gromet.opo, GrometPort(box=len(new_gromet.b))
2088 )
2090 new_gromet.wfopo = insert_gromet_object(
2091 new_gromet.wfopo,
2092 GrometWire(src=len(new_gromet.opo), tgt=len(new_gromet.pof)),
2093 )
2095 self.gromet_module.fn_array = insert_gromet_object(
2096 self.gromet_module.fn_array, new_gromet
2097 )
2098 self.set_index()
2100 parent_gromet_fn.bf = insert_gromet_object(
2101 parent_gromet_fn.bf,
2102 GrometBoxFunction(
2103 function_type=FunctionType.FUNCTION,
2104 body=len(self.gromet_module.fn_array),
2105 metadata=self.insert_metadata(
2106 self.create_source_code_reference(ref)
2107 ),
2108 ),
2109 )
2111 operator_idx = len(parent_gromet_fn.bf)
2112 # The operation makes some opis, we attempt to
2113 # match the number of opis with pifs in the parent FN
2114 # and also wire these ports appropriately
2115 if new_gromet.opi != None:
2116 for opi in new_gromet.opi:
2117 parent_gromet_fn.pif = insert_gromet_object(
2118 parent_gromet_fn.pif, GrometPort(box=operator_idx)
2119 )
2121 # Attempt to find where the port is in the parent FN and wire it
2122 # NOTE: this will need to be updated with more handling, i.e. for loops cond etc
2123 var_loc = self.retrieve_var_port(opi.name)
2124 parent_gromet_fn.wff = insert_gromet_object(
2125 parent_gromet_fn.wff,
2126 GrometWire(
2127 src=len(parent_gromet_fn.pif),
2128 tgt=var_loc,
2129 ),
2130 )
2132 parent_gromet_fn.pof = insert_gromet_object(
2133 parent_gromet_fn.pof, GrometPort(box=operator_idx)
2134 )
2135 operator_pof_idx = len(parent_gromet_fn.pof)
2137 if isinstance(parent_cast_node, AnnCastCall):
2138 func_name = node.attr.name
2139 func_info = (
2140 FunctionType.IMPORTED,
2141 ImportType.NATIVE,
2142 None,
2143 None,
2144 "Python",
2145 PYTHON_VERSION,
2146 )
2148 parent_gromet_fn.bf = insert_gromet_object(
2149 parent_gromet_fn.bf,
2150 GrometBoxFunction(
2151 name=f"{func_name}",
2152 function_type=func_info[0],
2153 import_type=func_info[1],
2154 import_version=func_info[2],
2155 import_source=func_info[3],
2156 source_language=func_info[4],
2157 source_language_version=func_info[5],
2158 body=None,
2159 ),
2160 )
2161 # Add the input for this function, and then wire it
2162 # NOTE: This needs more development to support multiple arguments
2163 parent_gromet_fn.pif = insert_gromet_object(
2164 parent_gromet_fn.pif,
2165 GrometPort(box=len(parent_gromet_fn.bf)),
2166 )
2168 parent_gromet_fn.wff = insert_gromet_object(
2169 parent_gromet_fn.wff,
2170 GrometWire(
2171 src=len(parent_gromet_fn.pif),
2172 tgt=operator_pof_idx,
2173 ),
2174 )
2176 parent_gromet_fn.pof = insert_gromet_object(
2177 parent_gromet_fn.pof,
2178 GrometPort(box=len(parent_gromet_fn.bf)),
2179 )
2181 else:
2182 pass
2183 # if node.value.name not in self.record.keys():
2184 # pass
2185 # if func_name in self.record.keys():
2186 # idx = self.record[func_name][f"new:{func_name}"]
2188 # parent_gromet_fn.bf = insert_gromet_object(parent_gromet_fn.bf, GrometBoxFunction(name=func_name, function_type=FunctionType.FUNCTION, contents=idx, metadata=self.insert_metadata(metadata)))
2189 # func_call_idx = len(parent_gromet_fn.bf)
2191 def handle_unary_op(
2192 self, node: AnnCastOperator, parent_gromet_fn, parent_cast_node
2193 ):
2194 """
2195 Handles an AnnCastOperator node that consists of one operand
2196 """
2197 metadata = self.create_source_code_reference(node.source_refs[0])
2198 opd_ret_val = self.visit(node.operands[0], parent_gromet_fn, node)
2200 opd_pof = -1
2201 if parent_gromet_fn.pof != None:
2202 opd_pof = len(parent_gromet_fn.pof)
2203 if isinstance(node.operands[0], AnnCastName):
2204 opd_pof = -1
2205 elif isinstance(node.operands[0], AnnCastCall):
2206 opd_pof = len(parent_gromet_fn.pof)
2207 for arg in node.operands[0].arguments:
2208 if hasattr(arg, "name"):
2209 found_opi, opi_idx = find_existing_opi(
2210 parent_gromet_fn, arg.name
2211 )
2213 if found_opi:
2214 parent_gromet_fn.wfopi = insert_gromet_object(
2215 parent_gromet_fn.wfopi,
2216 GrometWire(
2217 src=len(parent_gromet_fn.pif),
2218 tgt=opi_idx,
2219 ),
2220 )
2221 else:
2222 parent_gromet_fn.opi = insert_gromet_object(
2223 parent_gromet_fn.opi,
2224 GrometPort(
2225 name=arg.name, box=len(parent_gromet_fn.b)
2226 ),
2227 )
2228 parent_gromet_fn.wfopi = insert_gromet_object(
2229 parent_gromet_fn.wfopi,
2230 GrometWire(
2231 src=len(parent_gromet_fn.pif),
2232 tgt=len(parent_gromet_fn.opi),
2233 ),
2234 )
2236 parent_gromet_fn.bf = insert_gromet_object(
2237 parent_gromet_fn.bf,
2238 GrometBoxFunction(
2239 name=node.op,
2240 function_type=FunctionType.LANGUAGE_PRIMITIVE,
2241 metadata=self.insert_metadata(metadata),
2242 ),
2243 )
2244 unop_idx = len(parent_gromet_fn.bf)
2246 parent_gromet_fn.pif = insert_gromet_object(
2247 parent_gromet_fn.pif, GrometPort(box=unop_idx)
2248 )
2250 if (
2251 isinstance(node.operands[0], (AnnCastName, AnnCastVar))
2252 and opd_pof == -1
2253 ):
2254 if isinstance(node.operands[0], AnnCastName):
2255 name = node.operands[0].name
2256 elif isinstance(node.operands[0], AnnCastVar):
2257 name = node.operands[0].val.name
2259 if parent_gromet_fn.b[0].function_type != FunctionType.FUNCTION:
2260 found_opi, opi_idx = find_existing_opi(parent_gromet_fn, name)
2262 if not found_opi:
2263 parent_gromet_fn.opi = insert_gromet_object(
2264 parent_gromet_fn.opi,
2265 GrometPort(name=name, box=len(parent_gromet_fn.b)),
2266 )
2267 parent_gromet_fn.wfopi = insert_gromet_object(
2268 parent_gromet_fn.wfopi,
2269 GrometWire(
2270 src=len(parent_gromet_fn.pif),
2271 tgt=len(parent_gromet_fn.opi),
2272 ),
2273 )
2274 else:
2275 parent_gromet_fn.wfopi = insert_gromet_object(
2276 parent_gromet_fn.wfopi,
2277 GrometWire(
2278 src=len(parent_gromet_fn.pif),
2279 tgt=opi_idx,
2280 ),
2281 )
2282 else:
2283 # If we are in a function def then we retrieve where the variable is
2284 # Whether it's in the local or the args environment
2286 self.wire_from_var_env(name, parent_gromet_fn)
2287 else:
2288 parent_gromet_fn.wff = insert_gromet_object(
2289 parent_gromet_fn.wff,
2290 GrometWire(src=len(parent_gromet_fn.pif), tgt=opd_pof),
2291 )
2293 parent_gromet_fn.pof = insert_gromet_object(
2294 parent_gromet_fn.pof, GrometPort(box=unop_idx)
2295 )
2297 def handle_binary_op(
2298 self, node: AnnCastOperator, parent_gromet_fn, parent_cast_node
2299 ):
2300 # visit LHS first, storing the return value and used if necessary
2301 # cases where it's used
2302 # - Function call: function call returns its index which can be used for pof generation
2303 opd_one_ret_val = self.visit(node.operands[0], parent_gromet_fn, node)
2305 # Collect where the location of the left pof is
2306 # If the left node is an AnnCastName then it
2307 # automatically doesn't have a pof
2308 # (This creates an opi later)
2309 opd_one_pof = -1
2310 if parent_gromet_fn.pof != None:
2311 opd_one_pof = len(parent_gromet_fn.pof)
2312 if isinstance(node.operands[0], AnnCastName):
2313 opd_one_pof = -1
2314 elif isinstance(node.operands[0], AnnCastCall):
2315 opd_one_pof = len(parent_gromet_fn.pof)
2317 # visit RHS second, storing the return value and used if necessary
2318 # cases where it's used
2319 # - Function call: function call returns its index which can be used for pof generation
2320 opd_two_ret_val = self.visit(node.operands[1], parent_gromet_fn, node)
2322 # Collect where the location of the right pof is
2323 # If the right node is an AnnCastName then it
2324 # automatically doesn't have a pof
2325 # (This create an opi later)
2326 opd_two_pof = -1
2327 if parent_gromet_fn.pof != None:
2328 opd_two_pof = len(parent_gromet_fn.pof)
2329 if isinstance(node.operands[1], AnnCastName):
2330 opd_two_pof = -1
2331 elif isinstance(node.operands[1], AnnCastCall):
2332 opd_two_pof = len(parent_gromet_fn.pof)
2334 ref = node.source_refs[0]
2335 metadata = self.create_source_code_reference(ref)
2337 # NOTE/TODO Maintain a table of primitive operators that when queried give you back
2338 # their signatures that can be used for generating
2339 # A global mapping is maintained but it isnt being used for their signatures yet
2340 parent_gromet_fn.bf = insert_gromet_object(
2341 parent_gromet_fn.bf,
2342 GrometBoxFunction(
2343 name=node.op,
2344 function_type=FunctionType.LANGUAGE_PRIMITIVE,
2345 metadata=self.insert_metadata(metadata),
2346 ),
2347 )
2349 # After we visit the left and right they (in all scenarios but one) append a POF
2350 # The one case where it doesnt happen is when the left or right are variables in the expression
2351 # In this case then they need an opi and the appropriate wiring for it
2352 parent_gromet_fn.pif = insert_gromet_object(
2353 parent_gromet_fn.pif, GrometPort(box=len(parent_gromet_fn.bf))
2354 )
2355 if (isinstance(node.operands[0], (AnnCastName, AnnCastVar))) and opd_one_pof == -1:
2356 if isinstance(node.operands[0], AnnCastName):
2357 name = node.operands[0].name
2358 elif isinstance(node.operands[0], AnnCastVar):
2359 name = node.operands[0].val.name
2361 if parent_gromet_fn.b[0].function_type != FunctionType.FUNCTION:
2362 # This check is used for when the binary operation is part of a Function and not an Expression
2363 # the FunctionDef handles creating opis, so we create any here as necessary
2364 found_opi, opi_idx = find_existing_opi(parent_gromet_fn, name)
2366 name_comp = comp_name_nodes(node.operands[0], node.operands[1])
2367 if not name_comp and not found_opi:
2368 parent_gromet_fn.opi = insert_gromet_object(
2369 parent_gromet_fn.opi,
2370 GrometPort(name=name, box=len(parent_gromet_fn.b)),
2371 )
2372 parent_gromet_fn.wfopi = insert_gromet_object(
2373 parent_gromet_fn.wfopi,
2374 GrometWire(
2375 src=len(parent_gromet_fn.pif),
2376 tgt=len(parent_gromet_fn.opi),
2377 ),
2378 )
2379 elif name_comp and not found_opi:
2380 # NOTE: Added for M7, handling operations like x * x
2381 parent_gromet_fn.opi = insert_gromet_object(
2382 parent_gromet_fn.opi,
2383 GrometPort(name=name, box=len(parent_gromet_fn.b)),
2384 )
2385 parent_gromet_fn.wfopi = insert_gromet_object(
2386 parent_gromet_fn.wfopi,
2387 GrometWire(
2388 src=len(parent_gromet_fn.pif),
2389 tgt=len(parent_gromet_fn.opi),
2390 ),
2391 )
2392 else:
2393 parent_gromet_fn.wfopi = insert_gromet_object(
2394 parent_gromet_fn.wfopi,
2395 GrometWire(
2396 src=len(parent_gromet_fn.pif),
2397 tgt=opi_idx if found_opi else -1,
2398 ),
2399 )
2400 else:
2401 # If we are in a function def then we retrieve where the variable is
2402 # Whether it's in the local or the args environment
2403 self.wire_from_var_env(name, parent_gromet_fn)
2404 else:
2405 # In this case, the left node gave us a pof, so we can wire it to the pif here
2406 # if left_pof == -1:
2407 parent_gromet_fn.wff = insert_gromet_object(
2408 parent_gromet_fn.wff,
2409 GrometWire(src=len(parent_gromet_fn.pif), tgt=opd_one_pof),
2410 )
2412 # Repeat the above but for the right node this time
2413 # NOTE: In the case that the left and the right node both refer to the same function argument we only
2414 # want one opi created and so we dont create one here
2415 parent_gromet_fn.pif = insert_gromet_object(
2416 parent_gromet_fn.pif,
2417 GrometPort(box=len(parent_gromet_fn.bf)),
2418 )
2419 if isinstance(node.operands[1], AnnCastName) and opd_two_pof == -1:
2420 # This check is used for when the binary operation is part of a Function and not an Expression
2421 # the FunctionDef handles creating opis, so we create any here as necessary
2422 if parent_gromet_fn.b[0].function_type != FunctionType.FUNCTION:
2423 found_opi, opi_idx = find_existing_opi(
2424 parent_gromet_fn, node.operands[1].name
2425 )
2427 name_comp = comp_name_nodes(node.operands[0], node.operands[1])
2428 if not name_comp and not found_opi:
2429 parent_gromet_fn.opi = insert_gromet_object(
2430 parent_gromet_fn.opi,
2431 GrometPort(
2432 name=node.operands[1].name,
2433 box=len(parent_gromet_fn.b),
2434 ),
2435 )
2436 parent_gromet_fn.wfopi = insert_gromet_object(
2437 parent_gromet_fn.wfopi,
2438 GrometWire(
2439 src=len(parent_gromet_fn.pif),
2440 tgt=len(parent_gromet_fn.opi),
2441 ),
2442 )
2443 elif (
2444 name_comp and not found_opi
2445 ): # NOTE: Added for M7, handling operations like x * x
2446 parent_gromet_fn.opi = insert_gromet_object(
2447 parent_gromet_fn.opi,
2448 GrometPort(name=name, box=len(parent_gromet_fn.b)),
2449 )
2450 parent_gromet_fn.wfopi = insert_gromet_object(
2451 parent_gromet_fn.wfopi,
2452 GrometWire(
2453 src=len(parent_gromet_fn.pif),
2454 tgt=len(parent_gromet_fn.opi),
2455 ),
2456 )
2457 else:
2458 parent_gromet_fn.wfopi = insert_gromet_object(
2459 parent_gromet_fn.wfopi,
2460 GrometWire(
2461 src=len(parent_gromet_fn.pif),
2462 tgt=opi_idx if found_opi else -1,
2463 ),
2464 )
2465 else:
2466 # If we are in a function def then we retrieve where the variable is
2467 # Whether it's in the local or the args environment
2468 self.wire_from_var_env(node.operands[1].name, parent_gromet_fn)
2469 else:
2470 # In this case, the right node gave us a pof, so we can wire it to the pif here
2471 parent_gromet_fn.wff = insert_gromet_object(
2472 parent_gromet_fn.wff,
2473 GrometWire(src=len(parent_gromet_fn.pif), tgt=opd_two_pof),
2474 )
2476 # Add the pof that serves as the output of this operation
2477 parent_gromet_fn.pof = insert_gromet_object(
2478 parent_gromet_fn.pof, GrometPort(box=len(parent_gromet_fn.bf))
2479 )
2481 @_visit.register
2482 def visit_operator(
2483 self, node: AnnCastOperator, parent_gromet_fn, parent_cast_node
2484 ):
2485 # What constitutes the two pieces of a BinaryOp?
2486 # Each piece can either be
2487 # - A literal value (i.e. 2)
2488 # - A function call that returns a value (i.e. foo())
2489 # - A BinaryOp itself
2490 # - A variable reference (i.e. x), this is the only one that doesnt plug a pof
2491 # - This generally causes us to create an opi and a wfopi to connect this to a pif
2492 # - Other
2493 # - A list access (i.e. x[2]) translates to a function call (_list_set), same for other sequential types
2495 if len(node.operands) == 1:
2496 self.handle_unary_op(node, parent_gromet_fn, parent_cast_node)
2497 elif len(node.operands) == 2:
2498 self.handle_binary_op(node, parent_gromet_fn, parent_cast_node)
2500 def wire_binary_op_args(self, node, parent_gromet_fn):
2501 if isinstance(node, AnnCastName):
2502 parent_gromet_fn.pif = insert_gromet_object(
2503 parent_gromet_fn.pif, GrometPort(box=len(parent_gromet_fn.bf))
2504 )
2505 var_environment = self.symtab_variables()
2506 if node.name in var_environment["local"]:
2507 local_env = var_environment["local"]
2508 entry = local_env[node.name]
2509 if isinstance(entry[0], AnnCastLoop):
2510 parent_gromet_fn.wlf = insert_gromet_object(
2511 parent_gromet_fn.wlf,
2512 GrometWire(
2513 src=len(parent_gromet_fn.pif), tgt=entry[2]
2514 ),
2515 )
2516 else:
2517 parent_gromet_fn.wff = insert_gromet_object(
2518 parent_gromet_fn.wff,
2519 GrometWire(
2520 src=len(parent_gromet_fn.pif), tgt=entry[2]
2521 ),
2522 )
2523 elif node.name in var_environment["args"]:
2524 args_env = var_environment["args"]
2525 entry = args_env[node.name]
2526 parent_gromet_fn.wfopi = insert_gromet_object(
2527 parent_gromet_fn.wfopi,
2528 GrometWire(src=len(parent_gromet_fn.pif), tgt=entry[2]),
2529 )
2530 return
2531 if isinstance(node, AnnCastOperator):
2532 self.wire_binary_op_args(node.operands[0], parent_gromet_fn)
2533 if len(node.operands) > 1:
2534 self.wire_binary_op_args(node.operands[1], parent_gromet_fn)
2535 return
2537 def func_in_module(self, func_name):
2538 """See if func_name is actually a function from
2539 an imported module
2540 A tuple of (Boolean, String) where the boolean value tells us
2541 if we found it or not and the string denotes the module if we did find it
2543 """
2544 for mname in self.import_collection.keys():
2545 curr_module = self.import_collection[mname]
2546 if curr_module[2] and find_func_in_module(
2547 mname, func_name
2548 ): # If curr module is of form 'from mname import *'
2549 return (True, mname)
2550 if (
2551 func_name in curr_module[1]
2552 ): # If the function has been imported individually and is in the symbols list
2553 return (
2554 True,
2555 mname,
2556 ) # With the form 'from mname import func_name'
2558 return (False, "")
2560 @_visit.register
2561 def visit_call(
2562 self, node: AnnCastCall, parent_gromet_fn, parent_cast_node
2563 ):
2564 ref = node.source_refs[0]
2565 metadata = self.create_source_code_reference(ref)
2567 # Used in special scenarios, when we might need
2568 # to do something slightly different
2569 from_assignment = False
2570 from_call = False
2571 from_operator = False
2572 from_loop = False
2573 func_name, qual_func_name = get_func_name(node)
2575 if isinstance(parent_cast_node, AnnCastAssignment):
2576 from_assignment = True
2577 elif isinstance(parent_cast_node, AnnCastCall):
2578 from_call = True
2579 elif isinstance(parent_cast_node, AnnCastOperator):
2580 from_operator = True
2581 elif isinstance(parent_cast_node, AnnCastLoop):
2582 from_loop = True
2584 if isinstance(node.func, AnnCastAttribute):
2585 self.visit(node.func, parent_gromet_fn, parent_cast_node)
2587 in_module = self.func_in_module(func_name)
2588 func_info = self.determine_func_type(node)
2592 # Have to find the index of the function we're trying to call
2593 # What if it's a primitive?
2594 # What if it doesn't exist for some reason?
2595 # What if it's from a module?
2596 if is_primitive(func_name, "CAST") and not in_module[0]:
2597 call_bf_idx = self.handle_primitive_function(
2598 node, parent_gromet_fn, parent_cast_node, from_assignment
2599 )
2600 # Argument handling for primitives is a little different here, because we only want to find the variables that we need, and not create
2601 # any additional FNs. The additional FNs are created in the primitive handler
2602 for arg in node.arguments:
2603 if isinstance(arg, AnnCastName):
2604 parent_gromet_fn.pif = insert_gromet_object(
2605 parent_gromet_fn.pif, GrometPort(box=call_bf_idx)
2606 )
2607 pif_idx = len(parent_gromet_fn.pif)
2608 # Have to wire from either
2609 # - a local variable
2610 # - an argument/global variable introduced as argument
2611 # NAME:
2612 # if it's local, attempt to find it in the local args
2613 # if it's argument, attempt to find its opi first
2614 var_env = self.symtab_variables()
2616 if arg.name in var_env["local"]:
2617 self.wire_from_var_env(arg.name, parent_gromet_fn)
2618 elif arg.name in var_env["args"]:
2619 # The reason we have to explicitly check if the call argument is in the variable environment as opposed
2620 # to just attempting to wire with 'wire_from_var_env' is that the expression can be its own FN without
2621 # Attempt to find the opi if it already exists and wire to it
2622 # otherwise add it
2623 found_opi, opi_idx = find_existing_opi(
2624 parent_gromet_fn, arg.name
2625 )
2626 if found_opi:
2627 parent_gromet_fn.wfopi = insert_gromet_object(
2628 parent_gromet_fn.wfopi,
2629 GrometWire(
2630 src=len(parent_gromet_fn.pif), tgt=opi_idx
2631 ),
2632 )
2633 else:
2634 parent_gromet_fn.opi = insert_gromet_object(
2635 parent_gromet_fn.opi,
2636 GrometPort(name=arg.name, box=len(parent_gromet_fn.b)), # was call_bf_index, why?
2637 )
2638 opi_idx = len(parent_gromet_fn.opi)
2639 parent_gromet_fn.wfopi = insert_gromet_object(
2640 parent_gromet_fn.wfopi,
2641 GrometWire(
2642 src=len(parent_gromet_fn.pif), tgt=opi_idx
2643 ),
2644 )
2645 elif arg.name in var_env["global"]:
2646 self.wire_from_var_env(arg.name, parent_gromet_fn)
2647 else:
2648 if in_module[0]:
2649 if isinstance(node.func, AnnCastAttribute):
2650 name = qual_func_name
2651 else:
2652 name = f"{in_module[1]}.{func_name}_id{node.func.id}"
2653 else:
2654 name = qual_func_name
2656 if check_builtin(func_name):
2657 body = None
2658 else:
2659 identified_func_name = qual_func_name
2660 idx, found = self.find_gromet(identified_func_name)
2661 if not found and func_name not in self.record.keys():
2662 temp_gromet_fn = GrometFN()
2663 temp_gromet_fn.b = insert_gromet_object(
2664 temp_gromet_fn.b,
2665 GrometBoxFunction(
2666 name=func_name, function_type=FunctionType.FUNCTION
2667 ),
2668 )
2669 self.gromet_module.fn_array = insert_gromet_object(
2670 self.gromet_module.fn_array, temp_gromet_fn
2671 )
2672 self.set_index()
2674 if func_name in self.record.keys():
2675 idx = self.record[func_name][f"new:{func_name}"]
2677 body = idx
2679 call_box_func = GrometBoxFunction(
2680 name=name,
2681 function_type=func_info[0] if func_info != None else None,
2682 import_type=func_info[1] if func_info != None else None,
2683 import_version=func_info[2] if func_info != None else None,
2684 import_source=func_info[3] if func_info != None else None,
2685 source_language=func_info[4] if func_info != None else None,
2686 source_language_version=func_info[5]
2687 if func_info != None
2688 else None,
2689 body=body,
2690 metadata=self.insert_metadata(metadata),
2691 )
2692 parent_gromet_fn.bf = insert_gromet_object(
2693 parent_gromet_fn.bf, call_box_func
2694 )
2695 call_bf_idx = len(parent_gromet_fn.bf)
2697 # Iterate through all the arguments first
2698 # In the case that we are looking at a primitive that
2699 # is not inlined or part of an assignment we don't visit the
2700 # arguments as that's already been handled by the primitive handler
2701 # if not is_primitive(func_name, "CAST") or (from_assignment or is_inline(func_name)):
2702 if func_name in self.symtab_functions().keys():
2703 print(self.symtab_functions()[func_name])
2704 keyword_arg_len = len(self.symtab_functions()[func_name][2])
2705 else:
2706 keyword_arg_len = 0
2707 visited_args = 0
2708 for arg in node.arguments:
2709 self.visit(arg, parent_gromet_fn, node)
2710 visited_args += 1
2712 parent_gromet_fn.pif = insert_gromet_object(
2713 parent_gromet_fn.pif, GrometPort(box=call_bf_idx)
2714 )
2715 pif_idx = len(parent_gromet_fn.pif)
2716 if is_tuple(arg):
2717 for v in arg.value:
2718 if hasattr(v, "name"):
2719 self.wire_from_var_env(v.name, parent_gromet_fn)
2720 elif isinstance(
2721 arg, (AnnCastLiteralValue, AnnCastCall, AnnCastOperator)
2722 ):
2723 # Can wff here due to all these ^^ giving us local pofs
2725 pof_idx = len(parent_gromet_fn.pof)
2726 parent_gromet_fn.wff = insert_gromet_object(
2727 parent_gromet_fn.wff,
2728 GrometWire(src=pif_idx, tgt=pof_idx),
2729 )
2730 elif isinstance(arg, AnnCastName):
2731 # Have to wire from either
2732 # - a local variable
2733 # - an argument/global variable introduced as argument
2734 # NAME:
2735 # if it's local, attempt to find it in the local args
2736 # if it's argument, attempt to find its opi first
2737 var_env = self.symtab_variables()
2739 if arg.name in var_env["local"]:
2740 self.wire_from_var_env(arg.name, parent_gromet_fn)
2741 elif arg.name in var_env["args"]:
2742 # The reason we have to explicitly check if the call argument is in the variable environment as opposed
2743 # to just attempting to wire with 'wire_from_var_env' is that the expression can be its own FN without
2744 # Attempt to find the opi if it already exists and wire to it
2745 # otherwise add it
2746 found_opi, opi_idx = find_existing_opi(
2747 parent_gromet_fn, arg.name
2748 )
2749 if found_opi:
2750 parent_gromet_fn.wfopi = insert_gromet_object(
2751 parent_gromet_fn.wfopi,
2752 GrometWire(
2753 src=len(parent_gromet_fn.pif), tgt=opi_idx
2754 ),
2755 )
2756 else:
2757 parent_gromet_fn.opi = insert_gromet_object(
2758 parent_gromet_fn.opi,
2759 GrometPort(name=arg.name, box=len(parent_gromet_fn.b)), # was call_bf_idx, why?
2760 )
2761 opi_idx = len(parent_gromet_fn.opi)
2762 parent_gromet_fn.wfopi = insert_gromet_object(
2763 parent_gromet_fn.wfopi,
2764 GrometWire(
2765 src=len(parent_gromet_fn.pif), tgt=opi_idx
2766 ),
2767 )
2768 elif arg.name in self.symtab_functions():
2769 parent_gromet_fn.bf = insert_gromet_object(
2770 parent_gromet_fn.bf,
2771 GrometBoxFunction(
2772 function_type=FunctionType.LITERAL,
2773 value=GLiteralValue("string", arg.name),
2774 ),
2775 )
2776 parent_gromet_fn.pof = insert_gromet_object(
2777 parent_gromet_fn.pof,
2778 GrometPort(
2779 box = len(parent_gromet_fn.bf)
2780 )
2781 )
2782 pof_idx = len(parent_gromet_fn.pof)
2783 parent_gromet_fn.wff = insert_gromet_object(
2784 parent_gromet_fn.wff,
2785 GrometWire(src=pif_idx, tgt=pof_idx),
2786 )
2787 elif isinstance(arg, AnnCastAssignment):
2788 # We do an offset computation here that allows us to appropriately
2789 # wire keyword arguments whenever they're assigned in a different
2790 # order than they show up
2791 # i.e. foo(y=1,x=2) when the definition is foo(x,y)
2792 pif_offset = self.offset_pif(arg.left.val.name, func_name)
2793 if keyword_arg_len > 0:
2794 parent_gromet_fn.wff = insert_gromet_object(
2795 parent_gromet_fn.wff,
2796 GrometWire(src=pif_idx - (visited_args - pif_offset), tgt=len(parent_gromet_fn.pof))
2797 )
2798 else:
2799 parent_gromet_fn.wff = insert_gromet_object(
2800 parent_gromet_fn.wff,
2801 GrometWire(src=pif_idx, tgt=len(parent_gromet_fn.pof))
2802 )
2803 # if isinstance(arg.right)
2805 # the number of visited args matches the number of keyword args
2806 # if we used them all
2807 # otherwise if more keyword args exist than visited args
2808 # we need to add some ports
2809 if keyword_arg_len > visited_args:
2810 for i in range(keyword_arg_len - visited_args):
2811 parent_gromet_fn.pif = insert_gromet_object(
2812 parent_gromet_fn.pif, GrometPort(box=call_bf_idx)
2813 )
2815 if from_call or from_operator or from_assignment or from_loop:
2816 # Operator and calls need a pof appended here because they dont
2817 # do it themselves
2818 # At some point we would like the call handler to always append a POF
2819 if from_assignment and is_tuple(parent_cast_node.left):
2820 # If an assignment is to a tuple, we create multiple pofs
2821 for _ in parent_cast_node.left.value:
2822 parent_gromet_fn.pof = insert_gromet_object(
2823 parent_gromet_fn.pof, GrometPort(box=call_bf_idx)
2824 )
2825 else:
2826 parent_gromet_fn.pof = insert_gromet_object(
2827 parent_gromet_fn.pof, GrometPort(box=call_bf_idx)
2828 )
2830 # If we're doing a call to a Record's "__init__" which is
2831 # determined by the function name matching the
2832 # record name, then we need to add one additional argument
2833 # to represent the parent class that this current record 'might'
2834 # inherit. Currently we support either no parent class or one parent class
2835 if func_name in self.record.keys():
2836 # Generate a "None" for no parent class
2837 val = GLiteralValue("None", "None")
2839 parent_gromet_fn.bf = insert_gromet_object(
2840 parent_gromet_fn.bf,
2841 GrometBoxFunction(
2842 function_type=FunctionType.LITERAL,
2843 value=val,
2844 metadata=None, # TODO: Insert metadata for generated none value
2845 ),
2846 )
2848 # The None LiteralValue needs a pof to wire to
2849 parent_gromet_fn.pof = insert_gromet_object(
2850 parent_gromet_fn.pof, GrometPort(box=len(parent_gromet_fn.bf))
2851 )
2852 none_pof = len(parent_gromet_fn.pof)
2854 parent_gromet_fn.pif = insert_gromet_object(
2855 parent_gromet_fn.pif, GrometPort(box=call_bf_idx)
2856 )
2857 none_pif = len(parent_gromet_fn.pif)
2859 parent_gromet_fn.wff = insert_gromet_object(
2860 parent_gromet_fn.wff, GrometWire(src=none_pif, tgt=none_pof)
2861 )
2863 return call_bf_idx
2865 def wire_return_name(self, name, gromet_fn, index=1):
2866 var_environment = self.symtab_variables()
2867 if name in var_environment["local"]:
2868 # If it's in the local env, then
2869 # either it comes from a loop (wlopo), a conditional (wcopo), or just another
2870 # function (wfopo), then we check where it comes from and wire appropriately
2871 local_env = var_environment["local"]
2872 entry = local_env[name]
2873 if isinstance(entry[0], AnnCastLoop):
2874 gromet_fn.wlopo = insert_gromet_object(
2875 gromet_fn.wlopo, GrometWire(src=index, tgt=entry[2])
2876 )
2877 elif isinstance(entry[0], AnnCastModelIf):
2878 gromet_fn.wcopo = insert_gromet_object(
2879 gromet_fn.wcopo, GrometWire(src=index, tgt=entry[2])
2880 )
2881 else:
2882 gromet_fn.wfopo = insert_gromet_object(
2883 gromet_fn.wfopo, GrometWire(src=index, tgt=entry[2])
2884 )
2885 elif name in var_environment["args"]:
2886 # If it comes from arguments, then that means the variable
2887 # Didn't get changed in the function at all and thus it's just
2888 # A pass through (wopio)
2889 args_env = var_environment["args"]
2890 entry = args_env[name]
2891 gromet_fn.wopio = insert_gromet_object(
2892 gromet_fn.wopio, GrometWire(src=index, tgt=entry[2])
2893 )
2895 def pack_return_tuple(self, node, gromet_fn):
2896 """Given a tuple node in a return statement
2897 This function creates the appropriate packing
2898 construct to pack the values of the tuple into one value
2899 that gets returned
2900 """
2901 metadata = self.create_source_code_reference(node.source_refs[0])
2903 if isinstance(node, AnnCastLiteralValue):
2904 ret_vals = list(node.value)
2905 else:
2906 ret_vals = list(node.values)
2908 # Create the pack primitive
2909 gromet_fn.bf = insert_gromet_object(
2910 gromet_fn.bf,
2911 GrometBoxFunction(
2912 function_type=FunctionType.ABSTRACT,
2913 name="pack",
2914 metadata=self.insert_metadata(metadata),
2915 ),
2916 )
2917 pack_bf_idx = len(gromet_fn.bf)
2919 for i, val in enumerate(ret_vals, 1):
2920 if isinstance(val, AnnCastName):
2921 # Need: The port number where it is from, and whether it's a local/function param/global
2922 name = val.name
2923 var_environment = self.symtab_variables()
2924 if name in var_environment["local"]:
2925 local_env = var_environment["local"]
2926 entry = local_env[name]
2927 gromet_fn.pif = insert_gromet_object(
2928 gromet_fn.pif, GrometPort(box=pack_bf_idx)
2929 )
2930 gromet_fn.wff = insert_gromet_object(
2931 gromet_fn.wff,
2932 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
2933 )
2934 elif name in var_environment["args"]:
2935 args_env = var_environment["args"]
2936 entry = args_env[name]
2937 gromet_fn.pif = insert_gromet_object(
2938 gromet_fn.pif, GrometPort(box=pack_bf_idx)
2939 )
2940 gromet_fn.wfopi = insert_gromet_object(
2941 gromet_fn.wfopi,
2942 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
2943 )
2944 elif name in var_environment["global"]:
2945 # TODO
2946 global_env = var_environment["global"]
2947 entry = global_env[name]
2948 gromet_fn.wff = insert_gromet_object(
2949 gromet_fn.wff,
2950 GrometWire(src=len(gromet_fn.pif), tgt=entry[2]),
2951 )
2953 # elif isinstance(val, AnnCastTuple): # or isinstance(val, AnnCastList):
2954 elif (
2955 isinstance(val, AnnCastLiteralValue)
2956 and val.value_type == StructureType.TUPLE
2957 ):
2958 # TODO: this wire an extra wfopo that we don't need, must fix
2959 self.pack_return_tuple(val, gromet_fn)
2960 # elif isinstance(val, AnnCastCall):
2961 # pass
2962 else: # isinstance(val, AnnCastBinaryOp) or isinstance(val, AnnCastCall):
2963 # A Binary Op will create an expression FN
2964 # Which leaves a pof
2965 self.visit(val, gromet_fn, node)
2966 last_pof = len(gromet_fn.pof)
2967 gromet_fn.pif = insert_gromet_object(
2968 gromet_fn.pif, GrometPort(box=pack_bf_idx)
2969 )
2970 gromet_fn.wff = insert_gromet_object(
2971 gromet_fn.wff,
2972 GrometWire(src=len(gromet_fn.pif), tgt=last_pof),
2973 )
2975 gromet_fn.pof = insert_gromet_object(
2976 gromet_fn.pof, GrometPort(box=pack_bf_idx)
2977 )
2979 # Add the opo for this gromet FN for the one return value that we're returning with the
2980 # pack
2981 gromet_fn.wfopo = insert_gromet_object(
2982 gromet_fn.wfopo,
2983 GrometWire(src=len(gromet_fn.opo), tgt=len(gromet_fn.pof)),
2984 )
2986 def resolve_placeholder_gotos(self):
2987 # When we generate GOTOs, often we will see GOTOs referring to labels
2988 # that we haven't seen in the pipeline yet. Here we resolve any
2989 # GOTOs for which the labels we haven't seen yet.
2990 for label in self.placeholder_gotos.keys():
2991 for label_bf in self.placeholder_gotos[label]:
2992 label_bf.value = self.labels[label]
2993 self.placeholder_gotos = {}
2995 self.clear_labels()
2997 def wire_return_node(self, node, gromet_fn):
2998 """Return statements have many ways in which they can be wired, and thus
2999 we use this recursive function to handle all the possible cases
3000 """
3001 # NOTE: Thinking of adding an index parameter that is set to 1 when originally called, and then
3002 # if we have a tuple of returns then we can change the index then
3003 if isinstance(node, AnnCastLiteralValue):
3004 if is_tuple(node):
3005 self.pack_return_tuple(node, gromet_fn)
3006 else:
3007 gromet_fn.opo = insert_gromet_object(gromet_fn.opo, GrometPort(box=len(gromet_fn.b)))
3008 gromet_fn.wfopo = insert_gromet_object(gromet_fn.wfopo, GrometWire(src=len(gromet_fn.opo),tgt=len(gromet_fn.pof)))
3009 return
3010 elif isinstance(node, AnnCastVar):
3011 var_name = node.val.name
3012 self.wire_return_name(var_name, gromet_fn)
3013 elif isinstance(node, AnnCastName):
3014 name = node.name
3015 self.wire_return_name(name, gromet_fn)
3016 elif (
3017 isinstance(node, AnnCastLiteralValue)
3018 and node.val.value_type == StructureType.LIST
3019 ):
3020 ret_vals = list(node.value)
3021 for i, val in enumerate(ret_vals, 1):
3022 if isinstance(val, AnnCastOperator):
3023 self.wire_return_node(val.operands[0], gromet_fn)
3024 if len(val.operands) > 1:
3025 self.wire_return_node(val.operands[1], gromet_fn)
3026 # elif isinstance(val, AnnCastTuple) or (
3027 elif is_tuple(val) or (
3028 isinstance(val, AnnCastLiteralValue)
3029 and val.value_type == StructureType.LIST
3030 ):
3031 self.wire_return_node(val, gromet_fn)
3032 else:
3033 self.wire_return_name(val.name, gromet_fn, i)
3034 elif isinstance(node, AnnCastOperator):
3035 # A BinaryOp currently implies that we have one single OPO to put return values into
3036 gromet_fn.wfopo = insert_gromet_object(
3037 gromet_fn.wfopo, GrometWire(src=1, tgt=len(gromet_fn.pof))
3038 )
3039 # self.wire_return_node(node.left, gromet_fn)
3040 # self.wire_return_node(node.right, gromet_fn)
3041 return
3043 def handle_function_def(
3044 self,
3045 node: AnnCastFunctionDef,
3046 new_gromet_fn,
3047 func_body,
3048 parent_cast_node=None,
3049 ):
3050 """Handles the logic of making a function, whether the function itself is a real
3051 function definition (that is, it comes from an AnnCastFunctionDef) or it's
3052 'artifically generated' (that is, a set of statements coming from a loop or an if statement)
3053 """
3055 # If this function definition is within another function definition
3056 # Then we need to do some merging of function argument environments
3057 # so that this inner function definition can see and use the arguments from the outer
3058 # function definition
3059 var_environment = self.symtab_variables()
3061 prev_local_env = {}
3062 if isinstance(parent_cast_node, AnnCastFunctionDef):
3063 prev_local_env = deepcopy(var_environment["local"])
3065 var_environment["local"] = {}
3067 for n in func_body:
3068 self.visit(n, new_gromet_fn, node)
3070 # Create wfopo/wlopo/wcopo to wire the final computations to the output port
3071 # TODO: What about the case where there's multiple return values
3072 # also TODO: We need some kind of logic check to determine when we make a wopio for the case that an argument just passes through without
3073 # being used
3075 # If the last node in the FunctionDef is a return node we must do some final wiring
3076 if isinstance(n, AnnCastModelReturn):
3077 self.wire_return_node(n.value, new_gromet_fn)
3079 elif (
3080 new_gromet_fn.opo != None
3081 ): # This is in the case of a loop or conditional adding opos
3082 for i, opo in enumerate(new_gromet_fn.opo, 1):
3083 if opo.name in var_environment["local"]:
3084 local_env = var_environment["local"]
3085 entry = local_env[opo.name]
3086 if isinstance(entry[0], AnnCastLoop):
3087 new_gromet_fn.wlopo = insert_gromet_object(
3088 new_gromet_fn.wlopo,
3089 GrometWire(src=i, tgt=entry[2]),
3090 )
3091 # elif isinstance(entry[0], AnnCastModelIf):
3092 # new_gromet_fn.wcopo = insert_gromet_object(new_gromet_fn.wcopo, GrometWire(src=i,tgt=entry[2]+1))
3093 else:
3094 new_gromet_fn.wfopo = insert_gromet_object(
3095 new_gromet_fn.wfopo,
3096 GrometWire(src=i, tgt=entry[2]),
3097 )
3098 elif opo.name in var_environment["args"]:
3099 args_env = var_environment["args"]
3100 entry = args_env[opo.name]
3101 new_gromet_fn.wopio = insert_gromet_object(
3102 new_gromet_fn.wopio,
3103 GrometWire(src=i, tgt=entry[2]),
3104 )
3106 # We're out of the function definition here, so we
3107 # can clear the local variable environment
3108 var_environment["local"] = deepcopy(prev_local_env)
3110 @_visit.register
3111 def visit_function_def(
3112 self, node: AnnCastFunctionDef, parent_gromet_fn, parent_cast_node
3113 ):
3114 func_name = f"{node.name.name}_id{node.name.id}"
3115 identified_func_name = ".".join(node.con_scope)
3116 idx, found = self.find_gromet(func_name)
3118 ref = node.source_refs[0]
3120 if not found:
3121 new_gromet = GrometFN()
3122 self.gromet_module.fn_array = insert_gromet_object(
3123 self.gromet_module.fn_array, new_gromet
3124 )
3125 self.set_index()
3126 new_gromet.b = insert_gromet_object(
3127 new_gromet.b,
3128 GrometBoxFunction(
3129 name=func_name,
3130 function_type=FunctionType.FUNCTION
3131 # name=func_name, function_type=FunctionType.FUNCTION
3132 ),
3133 )
3134 else:
3135 new_gromet = self.gromet_module.fn_array[idx - 1]
3137 self.push_idx(idx)
3139 # Update the functions symbol table with its index in the FN array
3140 # This is currently used for wiring function names as parameters to function calls
3141 # Also used to keep track of default values in a function's arguments
3142 functions = self.symtab_functions()
3143 functions[node.name.name] = (node.name.name, idx, [])
3145 metadata = self.create_source_code_reference(ref)
3147 new_gromet.b[0].metadata = self.insert_metadata(metadata)
3148 var_environment = self.symtab_variables()
3150 # metadata type for capturing the original identifier name (i.e. just foo) as it appeared in the code
3151 # as opposed to the PA derived name (i.e. module.foo_id0, etc..)
3152 # source_code_identifier_name
3154 # If this function definition is within another function definition
3155 # Then we need to do some merging of function argument environments
3156 # so that this inner function definition can see and use the arguments from the outer
3157 # function definition
3158 if isinstance(parent_cast_node, AnnCastFunctionDef):
3159 prev_arg_env = deepcopy(var_environment["args"])
3160 else:
3161 # Initialize the function argument variable environment and populate it as we
3162 # visit the function arguments
3163 prev_arg_env = {}
3164 var_environment["args"] = {}
3165 # arg_env = var_environment["args"]
3167 # Copy the previous local and argument environments
3168 # If we're a function within a function this effectively lets us
3169 # see all the local and arguments from the outer scope and use them
3170 # within here
3171 # If we have an argument or a local variable that share a name
3172 # With a variable or argument in the outer scope, then they get
3173 # overwritten (to simulate scope shadowing)
3174 # The use of {**var_env_args, **var_env_local} here creates new dictionaries,
3175 # so the old environments are left unchanged
3176 arg_env = {**var_environment["args"], **var_environment["local"]}
3177 var_environment["args"] = arg_env
3179 for arg in node.func_args:
3180 # Visit the arguments
3181 self.visit(arg, new_gromet, node)
3183 # for each argument we want to have a corresponding port (OPI) here
3184 arg_ref = arg.source_refs[0]
3185 arg_name = arg.val.name
3187 if arg.default_value != None:
3188 # if isinstance(arg.default_value, AnnCastTuple):
3189 if is_tuple(arg.default_value):
3190 new_gromet.opi = insert_gromet_object(
3191 new_gromet.opi,
3192 GrometPort(
3193 box=len(new_gromet.b),
3194 name=arg_name,
3195 default_value=arg.default_value.value,
3196 metadata=self.insert_metadata(
3197 self.create_source_code_reference(arg_ref)
3198 ),
3199 ),
3200 )
3201 elif isinstance(arg.default_value, AnnCastCall):
3202 new_gromet.opi = insert_gromet_object(
3203 new_gromet.opi,
3204 GrometPort(
3205 box=len(new_gromet.b),
3206 name=arg_name,
3207 default_value=None, # TODO: What's the actual default value?
3208 metadata=self.insert_metadata(
3209 self.create_source_code_reference(arg_ref)
3210 ),
3211 ),
3212 )
3213 elif isinstance(arg.default_value, AnnCastOperator):
3214 new_gromet.opi = insert_gromet_object(
3215 new_gromet.opi,
3216 GrometPort(
3217 box=len(new_gromet.b),
3218 name=arg_name,
3219 default_value=None, # TODO: M7 placeholder
3220 metadata=self.insert_metadata(
3221 self.create_source_code_reference(arg_ref)
3222 ),
3223 ),
3224 )
3225 else:
3226 functions[node.name.name][2].append((arg.val.name, arg.default_value.value))
3227 new_gromet.opi = insert_gromet_object(
3228 new_gromet.opi,
3229 GrometPort(
3230 box=len(new_gromet.b),
3231 name=arg_name,
3232 default_value=arg.default_value.value,
3233 metadata=self.insert_metadata(
3234 self.create_source_code_reference(arg_ref)
3235 ),
3236 ),
3237 )
3238 else:
3239 new_gromet.opi = insert_gromet_object(
3240 new_gromet.opi,
3241 GrometPort(
3242 box=len(new_gromet.b),
3243 name=arg_name,
3244 metadata=self.insert_metadata(
3245 self.create_source_code_reference(arg_ref)
3246 ),
3247 ),
3248 )
3250 # Store each argument, its opi, and where it is in the opi table
3251 # For use when creating wfopi wires
3252 # Have to add 1 to the third value if we want to use it as an index reference
3253 arg_env[arg_name] = (
3254 arg,
3255 new_gromet.opi[-1],
3256 len(new_gromet.opi),
3257 )
3259 for var in var_environment["args"]:
3260 if new_gromet.opi != None and not var in [
3261 opi.name for opi in new_gromet.opi
3262 ]:
3263 new_gromet.opi = insert_gromet_object(
3264 new_gromet.opi,
3265 GrometPort(
3266 box=len(new_gromet.b),
3267 name=var,
3268 metadata=self.insert_metadata(
3269 self.create_source_code_reference(arg_ref)
3270 ),
3271 ),
3272 )
3273 arg_env[var] = (
3274 var_environment["args"][var][0],
3275 new_gromet.opi[-1],
3276 len(new_gromet.opi),
3277 )
3279 # handle_function_def() will visit the body of the function and take care of
3280 # wiring any GroMEt FNs in its body
3281 self.handle_function_def(
3282 node, new_gromet, node.body, parent_cast_node=parent_cast_node
3283 )
3285 self.pop_idx()
3286 self.resolve_placeholder_gotos()
3287 var_environment["args"] = deepcopy(prev_arg_env)
3289 def retrieve_labels(self, node: AnnCastCall):
3290 # Retrieves all the labels in node
3291 # Assumes node is a Function Call to "_get" with its labels in the first argument
3292 labels = []
3293 arguments = node.arguments[0]
3294 for arg in arguments.value:
3295 labels.append(arg.value)
3297 return labels
3299 @_visit.register
3300 def visit_goto(
3301 self, node: AnnCastGoto, parent_gromet_fn, parent_cast_node
3302 ):
3303 # Make an expression FN for computing the label and index values of this goto
3304 # Insert it into the overall fn_array table
3305 goto_fn = GrometFN()
3306 goto_fn.b = insert_gromet_object(goto_fn.b, GrometBoxFunction(function_type=FunctionType.EXPRESSION))
3307 self.gromet_module.fn_array = insert_gromet_object(
3308 self.gromet_module.fn_array,
3309 goto_fn,
3310 )
3311 self.set_index()
3313 goto_fn_idx = len(self.gromet_module.fn_array)
3315 parent_gromet_fn.bf = insert_gromet_object(parent_gromet_fn.bf, GrometBoxFunction(function_type=FunctionType.GOTO, name=node.label, body=goto_fn_idx))
3316 goto_idx_call = len(parent_gromet_fn.bf)
3317 vars = self.symtab_variables()
3318 added_vars = []
3319 for var in vars:
3320 for v in vars[var]:
3321 if v not in added_vars:
3322 parent_gromet_fn.pif = insert_gromet_object(parent_gromet_fn.pif, GrometPort(box=goto_idx_call))
3323 self.wire_from_var_env(v, parent_gromet_fn)
3324 added_vars.append(v)
3326 added_vars = []
3327 for var in vars:
3328 for v in vars[var]:
3329 if v not in added_vars:
3330 goto_fn.opi = insert_gromet_object(goto_fn.opi, GrometPort(box=len(goto_fn.b), name=v))
3331 added_vars.append(v)
3333 # The goto expression FN has two potential expressions, to determine the label and the index
3334 # TODO: compute an expression for an index when expr is not None
3335 if node.expr == None:
3336 # When a GOTO references a label that we have not seen yet
3337 # We put a place holder that we can go fill out later
3338 label_index_bf = GrometBoxFunction(function_type=FunctionType.LITERAL)
3339 if node.label in self.labels:
3340 label_index = self.labels[node.label]
3341 else:
3342 label_index = 0
3344 # Multiple gotos could reference the same label that we haven't seen
3345 # So we maintain a list that we can update later
3346 if node.label not in self.placeholder_gotos.keys():
3347 self.placeholder_gotos[node.label] = []
3348 self.placeholder_gotos[node.label].append(label_index_bf)
3349 label_index_bf.value = label_index
3350 goto_fn.bf = insert_gromet_object(goto_fn.bf, label_index_bf)
3352 index_comp_bf = len(goto_fn.bf)
3353 goto_fn.pof = insert_gromet_object(goto_fn.pof, GrometPort(box=index_comp_bf))
3354 goto_fn.opo = insert_gromet_object(goto_fn.opo, GrometPort(box=len(goto_fn.b), name="fn_idx"))
3355 goto_fn.wfopo = insert_gromet_object(goto_fn.wfopo, GrometWire(src=len(goto_fn.opo), tgt=len(goto_fn.pof)))
3357 goto_fn.bf = insert_gromet_object(goto_fn.bf, GrometBoxFunction(function_type=FunctionType.LITERAL, value=node.label))
3358 label_bf = len(goto_fn.bf)
3359 else:
3360 self.visit(node.expr, goto_fn, node)
3361 index_comp_bf = len(goto_fn.bf)
3363 for idx,_ in enumerate(goto_fn.opi, 1):
3364 goto_fn.pif = insert_gromet_object(goto_fn.pif, GrometPort(box=1))
3365 goto_fn.wfopi = insert_gromet_object(goto_fn.wfopi, GrometWire(src=len(goto_fn.pif), tgt=idx))
3367 goto_fn.pof = insert_gromet_object(goto_fn.pof, GrometPort(box=index_comp_bf))
3368 goto_fn.opo = insert_gromet_object(goto_fn.opo, GrometPort(box=len(goto_fn.b), name="fn_idx"))
3369 goto_fn.wfopo = insert_gromet_object(goto_fn.wfopo, GrometWire(src=len(goto_fn.opo), tgt=len(goto_fn.pof)))
3371 goto_fn.bf = insert_gromet_object(goto_fn.bf, GrometBoxFunction(function_type=FunctionType.LITERAL, value=GLiteralValue("None","None")))
3372 label_bf = len(goto_fn.bf)
3374 goto_fn.pof = insert_gromet_object(goto_fn.pof, GrometPort(box=label_bf))
3375 goto_fn.opo = insert_gromet_object(goto_fn.opo, GrometPort(box=len(goto_fn.b), name="label"))
3376 goto_fn.wfopo = insert_gromet_object(goto_fn.wfopo, GrometWire(src=len(goto_fn.opo), tgt=len(goto_fn.pof)))
3379 @_visit.register
3380 def visit_label(
3381 self, node: AnnCastLabel, parent_gromet_fn, parent_cast_node
3382 ):
3383 parent_gromet_fn.bf = insert_gromet_object(parent_gromet_fn.bf, GrometBoxFunction(function_type=FunctionType.LABEL, name=node.label))
3385 # Associate this label with a particular index
3386 fn_idx = self.peek_idx()
3387 self.labels[node.label] = fn_idx
3389 label_idx = len(parent_gromet_fn.bf)
3390 vars = self.symtab_variables()
3392 added_vars = []
3393 for var in vars:
3394 for v in vars[var]:
3395 if v not in added_vars:
3396 parent_gromet_fn.pif = insert_gromet_object(parent_gromet_fn.pif, GrometPort(box=label_idx))
3397 self.wire_from_var_env(v, parent_gromet_fn)
3399 parent_gromet_fn.pof = insert_gromet_object(parent_gromet_fn.pof, GrometPort(box=label_idx, name=v))
3400 self.add_var_to_env(v, None, parent_gromet_fn.pof[-1], len(parent_gromet_fn.pof), parent_cast_node)
3401 added_vars.append(v)
3403 @_visit.register
3404 def visit_literal_value(
3405 self, node: AnnCastLiteralValue, parent_gromet_fn, parent_cast_node
3406 ):
3407 if node.value_type == StructureType.TUPLE:
3408 # We create a pack here to pack all the arguments into one single value
3409 # for a function call
3410 if isinstance(parent_cast_node, AnnCastCall):
3411 pack_bf = GrometBoxFunction(
3412 name="pack", function_type=FunctionType.ABSTRACT
3413 )
3415 parent_gromet_fn.bf = insert_gromet_object(
3416 parent_gromet_fn.bf, pack_bf
3417 )
3419 pack_index = len(parent_gromet_fn.bf)
3421 for val in node.value:
3422 parent_gromet_fn.pif = insert_gromet_object(
3423 parent_gromet_fn.pif, GrometPort(box=pack_index)
3424 )
3425 pif_idx = len(parent_gromet_fn.pif)
3427 if isinstance(val, AnnCastName):
3428 self.wire_from_var_env(val.name, parent_gromet_fn)
3429 else:
3430 self.visit(val, parent_gromet_fn, parent_cast_node)
3432 parent_gromet_fn.wff = insert_gromet_object(
3433 parent_gromet_fn.wff, GrometWire(src=pif_idx, tgt=len(parent_gromet_fn.pof))
3434 )
3437 parent_gromet_fn.pof = insert_gromet_object(
3438 parent_gromet_fn.pof, GrometPort(box=pack_index)
3439 )
3441 else:
3442 self.visit_node_list(
3443 node.value, parent_gromet_fn, parent_cast_node
3444 )
3445 else:
3446 # Create the GroMEt literal value (A type of Function box)
3447 # This will have a single outport (the little blank box)
3448 # What we dont determine here is the wiring to whatever variable this
3449 # literal value goes to (that's up to the parent context)
3450 ref = node.source_code_data_type
3451 source_code_metadata = self.create_source_code_reference(
3452 node.source_refs[0]
3453 )
3455 code_data_metadata = SourceCodeDataType(
3456 provenance=generate_provenance(),
3457 source_language=ref[0],
3458 source_language_version=ref[1],
3459 data_type=str(ref[2]),
3460 )
3461 val = GLiteralValue(
3462 node.value_type if node.value_type is not None else "None",
3463 node.value if node.value is not None else "None",
3464 )
3466 parent_gromet_fn.bf = insert_gromet_object(
3467 parent_gromet_fn.bf,
3468 GrometBoxFunction(
3469 function_type=FunctionType.LITERAL,
3470 value=val,
3471 metadata=self.insert_metadata(
3472 code_data_metadata, source_code_metadata
3473 ),
3474 ),
3475 )
3476 parent_gromet_fn.pof = insert_gromet_object(
3477 parent_gromet_fn.pof, GrometPort(box=len(parent_gromet_fn.bf))
3478 )
3480 # Perhaps we may need to return something in the future
3481 # an idea: the index of where this exists
3483 # node type: Loop or Condition
3484 def loop_create_condition(self, node, parent_gromet_fn, parent_cast_node):
3485 """
3486 Creates the condition field in a loop
3487 Steps:
3488 1. Create the predicate box
3489 2. Given all the vars, make opis and opos for them,
3490 and then wire them all together using wopio's
3491 3. Visit the node's conditional box and create everything as usual
3492 - (Add an extra check to the conditional visitor to make sure we don't double add)
3493 4. Add the extra exit condition port
3494 """
3495 # Step 1
3496 gromet_predicate_fn = GrometFN()
3497 self.gromet_module.fn_array = insert_gromet_object(
3498 self.gromet_module.fn_array,
3499 gromet_predicate_fn,
3500 )
3501 self.set_index()
3502 condition_array_idx = len(self.gromet_module.fn_array)
3504 # Step 2
3505 gromet_predicate_fn.b = insert_gromet_object(
3506 gromet_predicate_fn.b,
3507 GrometBoxFunction(function_type=FunctionType.PREDICATE),
3508 )
3509 for _, var_name in node.used_vars.items():
3510 gromet_predicate_fn.opi = insert_gromet_object(
3511 gromet_predicate_fn.opi,
3512 GrometPort(name=var_name, box=len(gromet_predicate_fn.b)),
3513 )
3515 gromet_predicate_fn.opo = insert_gromet_object(
3516 gromet_predicate_fn.opo,
3517 GrometPort(name=var_name, box=len(gromet_predicate_fn.b)),
3518 )
3520 gromet_predicate_fn.wopio = insert_gromet_object(
3521 gromet_predicate_fn.wopio,
3522 GrometWire(
3523 src=len(gromet_predicate_fn.opi),
3524 tgt=len(gromet_predicate_fn.opo),
3525 ),
3526 )
3528 # Step 3
3529 self.visit(node.expr, gromet_predicate_fn, node) # visit condition
3531 # Step 4
3532 # Create the predicate's opo and wire it appropriately
3533 gromet_predicate_fn.opo = insert_gromet_object(
3534 gromet_predicate_fn.opo, GrometPort(box=len(gromet_predicate_fn.b))
3535 )
3536 gromet_predicate_fn.wfopo = insert_gromet_object(
3537 gromet_predicate_fn.wfopo,
3538 GrometWire(
3539 src=len(gromet_predicate_fn.opo),
3540 tgt=len(gromet_predicate_fn.pof),
3541 ),
3542 )
3544 return condition_array_idx
3546 def loop_create_body(self, node, parent_gromet_fn, parent_cast_node):
3547 """
3548 Creates a body FN for a loop
3550 """
3551 # The body section of the loop is itself a Gromet FN, so we create one and add it to our global list of FNs for this overall module
3552 gromet_body_fn = GrometFN()
3554 ref = node.body[0].source_refs[0]
3555 metadata = self.insert_metadata(self.create_source_code_reference(ref))
3557 gromet_body_fn.b = insert_gromet_object(
3558 gromet_body_fn.b,
3559 GrometBoxFunction(
3560 function_type=FunctionType.FUNCTION, metadata=metadata
3561 ),
3562 )
3563 self.gromet_module.fn_array = insert_gromet_object(
3564 self.gromet_module.fn_array, gromet_body_fn
3565 )
3566 self.set_index()
3568 body_array_idx = len(self.gromet_module.fn_array)
3569 var_environment = self.symtab_variables()
3571 # The 'call' bf for the body FN needs to have its pifs and pofs generated here as well
3572 # for (_, val) in node.used_vars.items():
3574 # Because the code in a loop body is technically a function on its own, we have to create a new
3575 # Variable environment for the local variables and function arguments
3576 # While preserving the old one
3577 # After we're done with the body of the loop, we restore the old environment
3578 previous_func_def_args = deepcopy(var_environment["args"])
3579 previous_local_args = deepcopy(var_environment["local"])
3581 var_environment["args"] = {}
3583 # The Gromet FN for the loop body needs to have its opis and opos generated here, since it isn't an actual FunctionDef here to make it with
3584 # Any opis we create for this Gromet FN are also added to the variable environment
3585 for _, val in node.used_vars.items():
3586 gromet_body_fn.opi = insert_gromet_object(
3587 gromet_body_fn.opi,
3588 GrometPort(name=val, box=len(gromet_body_fn.b)),
3589 )
3590 arg_env = var_environment["args"]
3591 arg_env[val] = (
3592 AnnCastFunctionDef(None, None, None, None),
3593 gromet_body_fn.opi[-1],
3594 len(gromet_body_fn.opi),
3595 )
3596 gromet_body_fn.opo = insert_gromet_object(
3597 gromet_body_fn.opo,
3598 GrometPort(name=val, box=len(gromet_body_fn.b)),
3599 )
3601 self.handle_function_def(
3602 AnnCastFunctionDef(None, None, None, None),
3603 gromet_body_fn,
3604 node.body,
3605 )
3607 # If the opo's name doesn't appear as a pof
3608 # then it hasn't been changed, create a wopio for it
3609 # Restore the old variable environment
3610 var_environment["args"] = previous_func_def_args
3611 var_environment["local"] = previous_local_args
3613 return body_array_idx
3615 def loop_create_post(self, node, parent_gromet_fn, parent_cast_node):
3616 # TODO
3617 pass
3619 @_visit.register
3620 def visit_loop(self, node: AnnCastLoop, parent_gromet_fn, parent_cast_node):
3621 var_environment = self.symtab_variables()
3623 # Create empty gromet box loop that gets filled out before
3624 # being added to the parent gromet_fn
3625 gromet_bl = GrometBoxLoop()
3627 # Insert the gromet box loop into the parent gromet
3628 parent_gromet_fn.bl = insert_gromet_object(
3629 parent_gromet_fn.bl, gromet_bl
3630 )
3632 # Create the pil ports that the gromet box loop uses
3633 # Also, create any necessary wires that the pil uses
3634 for pil_idx, (_, val) in enumerate(node.used_vars.items(), 1):
3635 pil_port = GrometPort(name=val, box=len(parent_gromet_fn.bl))
3637 parent_gromet_fn.pil = insert_gromet_object(
3638 parent_gromet_fn.pil,
3639 pil_port,
3640 )
3642 port = self.retrieve_var_port(pil_port.name)
3643 if port != -1:
3644 if self.check_var_location(pil_port.name, "local"):
3645 # Local variables manifest themselves through pofs
3646 parent_gromet_fn.wfl = insert_gromet_object(
3647 parent_gromet_fn.wfl, GrometWire(src=pil_idx, tgt=port)
3648 )
3649 elif self.check_var_location(pil_port.name, "args"):
3650 # Function arguments manifest themselves through opis
3651 parent_gromet_fn.wlopi = insert_gromet_object(
3652 parent_gromet_fn.wlopi,
3653 GrometWire(src=pil_idx, tgt=port),
3654 )
3655 elif self.check_var_location(pil_port.name, "global"):
3656 # globals manifest themselves through opis or pofs depending
3657 # on whether we're at the global scope or function def scope
3658 # through an opi
3659 if isinstance(parent_cast_node, AnnCastModule):
3660 parent_gromet_fn.wfl = insert_gromet_object(
3661 parent_gromet_fn.wfl,
3662 GrometWire(src=pil_idx, tgt=port),
3663 )
3664 else:
3665 parent_gromet_fn.wlopi = insert_gromet_object(
3666 parent_gromet_fn.wlopi,
3667 GrometWire(src=pil_idx, tgt=port),
3668 )
3670 ######### Loop Pre (if one exists)
3671 if node.pre != None and len(node.pre) > 0:
3672 gromet_pre_fn = GrometFN()
3673 self.gromet_module.fn_array = insert_gromet_object(
3674 self.gromet_module.fn_array, gromet_pre_fn
3675 )
3676 self.set_index()
3678 pre_array_idx = len(self.gromet_module.fn_array)
3680 gromet_pre_fn.b = insert_gromet_object(
3681 gromet_pre_fn.b,
3682 GrometBoxFunction(function_type=FunctionType.FUNCTION),
3683 )
3685 # Copy the var environment, as we're in a 'function' of sorts
3686 # so we need a new var environment
3687 var_args_copy = deepcopy(var_environment["args"])
3688 var_local_copy = deepcopy(var_environment["local"])
3689 var_environment["args"] = {}
3690 var_environment["local"] = {}
3692 for _, val in node.used_vars.items():
3693 gromet_pre_fn.opi = insert_gromet_object(
3694 gromet_pre_fn.opi, GrometPort(name=val, box=pre_array_idx)
3695 )
3697 var_environment["args"][val] = (
3698 val,
3699 gromet_pre_fn.opi[-1],
3700 len(gromet_pre_fn.opi),
3701 )
3703 gromet_pre_fn.opo = insert_gromet_object(
3704 gromet_pre_fn.opo, GrometPort(name=val, box=pre_array_idx)
3705 )
3707 for line in node.pre:
3708 # self.visit(line, gromet_pre_fn, parent_cast_node)
3709 self.visit(
3710 line,
3711 gromet_pre_fn,
3712 AnnCastFunctionDef(None, None, None, None),
3713 )
3715 def find_opo_idx(gromet_fn, name):
3716 i = 1
3717 for opo in gromet_fn.opo:
3718 if opo.name == name:
3719 return i
3720 i += 1
3721 return -1 # Not found
3723 # The pre GroMEt FN always has three OPOs to match up with the return values of the '_next' call
3724 # Create and wire the pofs to the OPOs
3725 gromet_port_name = gromet_pre_fn.pof[
3726 len(gromet_pre_fn.pof) - 3
3727 ].name
3728 gromet_pre_fn.wfopo = insert_gromet_object(
3729 gromet_pre_fn.wfopo,
3730 GrometWire(
3731 src=find_opo_idx(gromet_pre_fn, gromet_port_name),
3732 tgt=len(gromet_pre_fn.pof) - 2,
3733 ),
3734 )
3736 gromet_port_name = gromet_pre_fn.pof[
3737 len(gromet_pre_fn.pof) - 2
3738 ].name
3739 gromet_pre_fn.wfopo = insert_gromet_object(
3740 gromet_pre_fn.wfopo,
3741 GrometWire(
3742 src=find_opo_idx(gromet_pre_fn, gromet_port_name),
3743 tgt=len(gromet_pre_fn.pof) - 1,
3744 ),
3745 )
3747 gromet_port_name = gromet_pre_fn.pof[
3748 len(gromet_pre_fn.pof) - 1
3749 ].name
3750 gromet_pre_fn.wfopo = insert_gromet_object(
3751 gromet_pre_fn.wfopo,
3752 GrometWire(
3753 src=find_opo_idx(gromet_pre_fn, gromet_port_name),
3754 tgt=len(gromet_pre_fn.pof),
3755 ),
3756 )
3758 # Create wopios
3759 local_env = var_environment["local"]
3760 i = 1
3761 for opi in gromet_pre_fn.opi:
3762 if not opi.name in local_env.keys():
3763 gromet_pre_fn.wopio = insert_gromet_object(
3764 gromet_pre_fn.wopio, GrometWire(src=i, tgt=i)
3765 )
3766 i += 1
3768 var_environment["args"] = var_args_copy
3769 var_environment["local"] = var_local_copy
3771 gromet_bl.pre = pre_array_idx
3773 ######### Loop Condition
3775 # This creates a predicate Gromet FN
3776 condition_array_idx = self.loop_create_condition(
3777 node, parent_gromet_fn, parent_cast_node
3778 )
3779 ref = node.expr.source_refs[0]
3781 # NOTE: gromet_bl and gromet_bc store indicies into the fn_array directly now
3782 gromet_bl.condition = condition_array_idx
3784 ######### Loop Body
3786 # The body section of the loop is itself a Gromet FN, so we create one and add it to our global list of FNs for this overall module
3787 gromet_bl.body = self.loop_create_body(
3788 node, parent_gromet_fn, parent_cast_node
3789 )
3790 # pols become 'locals' from this point on
3791 # That is, any code that is after the while loop should be looking at the pol ports to fetch data for
3792 # any variables that were used in the loop even if they weren't directly modified by it
3794 # post section of the loop, currently used in Fortran for loops
3795 if node.post != None and len(node.post) > 0:
3796 gromet_post_fn = GrometFN()
3797 self.gromet_module.fn_array = insert_gromet_object(
3798 self.gromet_module.fn_array, gromet_post_fn
3799 )
3800 self.set_index()
3802 post_array_idx = len(self.gromet_module.fn_array)
3804 gromet_post_fn.b = insert_gromet_object(
3805 gromet_post_fn.b,
3806 GrometBoxFunction(function_type=FunctionType.FUNCTION),
3807 )
3809 # Copy the var environment, as we're in a 'function' of sorts
3810 # so we need a new var environment
3811 var_args_copy = deepcopy(var_environment["args"])
3812 var_local_copy = deepcopy(var_environment["local"])
3813 var_environment["args"] = {}
3814 var_environment["local"] = {}
3816 for _, val in node.used_vars.items():
3817 gromet_post_fn.opi = insert_gromet_object(
3818 gromet_post_fn.opi,
3819 GrometPort(name=val, box=post_array_idx),
3820 )
3822 var_environment["args"][val] = (
3823 val,
3824 gromet_post_fn.opi[-1],
3825 len(gromet_post_fn.opi),
3826 )
3828 gromet_post_fn.opo = insert_gromet_object(
3829 gromet_post_fn.opo,
3830 GrometPort(name=val, box=post_array_idx),
3831 )
3833 for line in node.post:
3834 self.visit(
3835 line,
3836 gromet_post_fn,
3837 AnnCastFunctionDef(None, None, None, None),
3838 )
3840 # The pre GroMEt FN always has three OPOs to match up with the return values of the '_next' call
3841 # Create and wire the pofs to the OPOs
3843 # Create wopios
3844 local_env = var_environment["local"]
3845 i = 1
3846 for opi in gromet_post_fn.opi:
3847 if not opi.name in local_env.keys():
3848 gromet_post_fn.wopio = insert_gromet_object(
3849 gromet_post_fn.wopio, GrometWire(src=i, tgt=i)
3850 )
3851 i += 1
3853 var_environment["args"] = var_args_copy
3854 var_environment["local"] = var_local_copy
3856 gromet_bl.post = post_array_idx
3858 for _, val in node.used_vars.items():
3859 parent_gromet_fn.pol = insert_gromet_object(
3860 parent_gromet_fn.pol,
3861 GrometPort(name=val, box=len(parent_gromet_fn.bl)),
3862 )
3863 self.add_var_to_env(
3864 val,
3865 AnnCastLoop(None, None, None, None, None),
3866 parent_gromet_fn.pol[-1],
3867 len(parent_gromet_fn.pol),
3868 node,
3869 )
3871 @_visit.register
3872 def visit_model_break(
3873 self, node: AnnCastModelBreak, parent_gromet_fn, parent_cast_node
3874 ):
3875 pass
3877 @_visit.register
3878 def visit_model_continue(
3879 self, node: AnnCastModelContinue, parent_gromet_fn, parent_cast_node
3880 ):
3881 pass
3883 def if_create_condition(
3884 self, node: AnnCastModelIf, parent_gromet_fn, parent_cast_node
3885 ):
3886 # This creates a predicate Gromet FN
3887 gromet_predicate_fn = GrometFN()
3888 self.gromet_module.fn_array = insert_gromet_object(
3889 self.gromet_module.fn_array, gromet_predicate_fn
3890 )
3891 self.set_index()
3893 condition_array_index = len(self.gromet_module.fn_array)
3895 gromet_predicate_fn.b = insert_gromet_object(
3896 gromet_predicate_fn.b,
3897 GrometBoxFunction(function_type=FunctionType.PREDICATE),
3898 )
3900 # Create all opis and opos for conditionals
3901 for _, val in node.used_vars.items():
3902 gromet_predicate_fn.opi = insert_gromet_object(
3903 gromet_predicate_fn.opi,
3904 GrometPort(name=val, box=len(gromet_predicate_fn.b)),
3905 )
3907 gromet_predicate_fn.opo = insert_gromet_object(
3908 gromet_predicate_fn.opo,
3909 GrometPort(name=val, box=len(gromet_predicate_fn.b)),
3910 )
3912 # Create wopios
3913 if gromet_predicate_fn.opi != None and gromet_predicate_fn.opo != None:
3914 i = 1
3915 while i - 1 < len(gromet_predicate_fn.opi) and i - 1 < len(
3916 gromet_predicate_fn.opo
3917 ):
3918 gromet_predicate_fn.wopio = insert_gromet_object(
3919 gromet_predicate_fn.wopio, GrometWire(src=i, tgt=i)
3920 )
3921 i += 1
3923 self.visit(node.expr, gromet_predicate_fn, node)
3925 # Create the predicate's opo and wire it appropriately
3926 gromet_predicate_fn.opo = insert_gromet_object(
3927 gromet_predicate_fn.opo, GrometPort(box=len(gromet_predicate_fn.b))
3928 )
3930 # TODO: double check this guard to see if it's necessary
3931 if isinstance(node.expr, AnnCastModelIf):
3932 for i, _ in enumerate(gromet_predicate_fn.opi, 1):
3933 gromet_predicate_fn.wcopi = insert_gromet_object(
3934 gromet_predicate_fn.wcopi, GrometWire(src=i, tgt=i)
3935 )
3937 gromet_predicate_fn.poc = insert_gromet_object(
3938 gromet_predicate_fn.poc,
3939 GrometPort(box=len(gromet_predicate_fn.bc)),
3940 )
3942 for i, _ in enumerate(gromet_predicate_fn.opo, 1):
3943 gromet_predicate_fn.wcopo = insert_gromet_object(
3944 gromet_predicate_fn.wcopo, GrometWire(src=i, tgt=i)
3945 )
3946 else:
3947 if (
3948 gromet_predicate_fn.opo == None
3949 and gromet_predicate_fn.pof == None
3950 ):
3951 gromet_predicate_fn.wfopo = insert_gromet_object(
3952 gromet_predicate_fn.wfopo, GrometWire(src=-1, tgt=-11112)
3953 )
3954 elif gromet_predicate_fn.pof == None:
3955 gromet_predicate_fn.wfopo = insert_gromet_object(
3956 gromet_predicate_fn.wfopo,
3957 GrometWire(src=len(gromet_predicate_fn.opo), tgt=-1111),
3958 )
3959 elif gromet_predicate_fn.opo == None:
3960 gromet_predicate_fn.wfopo = insert_gromet_object(
3961 gromet_predicate_fn.wfopo,
3962 GrometWire(src=-11113, tgt=len(gromet_predicate_fn.pof)),
3963 )
3964 else:
3965 gromet_predicate_fn.wfopo = insert_gromet_object(
3966 gromet_predicate_fn.wfopo,
3967 GrometWire(
3968 src=len(gromet_predicate_fn.opo),
3969 tgt=len(gromet_predicate_fn.pof),
3970 ),
3971 )
3973 ref = node.expr.source_refs[0]
3974 metadata = self.insert_metadata(self.create_source_code_reference(ref))
3975 gromet_predicate_fn.metadata = metadata
3977 return condition_array_index
3979 def if_create_body(
3980 self, node: AnnCastModelIf, parent_gromet_fn, parent_cast_node
3981 ):
3982 body_if_fn = GrometFN()
3983 body_if_fn.b = insert_gromet_object(
3984 body_if_fn.b,
3985 GrometBoxFunction(function_type=FunctionType.FUNCTION),
3986 )
3987 self.gromet_module.fn_array = insert_gromet_object(
3988 self.gromet_module.fn_array, body_if_fn
3989 )
3990 self.set_index()
3992 body_if_idx = len(self.gromet_module.fn_array)
3994 ref = node.body[0].source_refs[0]
3995 var_environment = self.symtab_variables()
3997 # NOTE: might change this to an if/elif if it's proven that
3998 # both an "And" and an "Or" can't exist at the same time in both parts
3999 # of the if statement
4000 # Having a boolean literal value in the node body implies a True value which means we have an Or
4001 # Having a boolean literal value in the node orelse implies a False value which means we have an And
4002 and_or_metadata = None
4003 if (
4004 len(node.body) > 0
4005 and isinstance(node.body[0], AnnCastLiteralValue)
4006 and node.body[0].value_type == ScalarType.BOOLEAN
4007 ):
4008 and_or_metadata = SourceCodeBoolOr()
4010 if and_or_metadata != None:
4011 metadata = self.insert_metadata(
4012 self.create_source_code_reference(ref), and_or_metadata
4013 )
4014 else:
4015 metadata = self.insert_metadata(
4016 self.create_source_code_reference(ref)
4017 )
4019 body_if_fn.metadata = metadata
4020 # copy the old var environments over since we're going into a function
4021 previous_func_def_args = deepcopy(var_environment["args"])
4022 previous_local_args = deepcopy(var_environment["local"])
4024 var_environment["args"] = {}
4026 # TODO: determine a better for loop that only grabs
4027 # what appears in the body of the if_true
4028 # for (_, val) in node.expr_used_vars.items():
4029 for _, val in node.used_vars.items():
4030 body_if_fn.opi = insert_gromet_object(
4031 body_if_fn.opi, GrometPort(box=len(body_if_fn.b))
4032 )
4033 arg_env = var_environment["args"]
4034 arg_env[val] = (
4035 AnnCastFunctionDef(None, None, None, None),
4036 body_if_fn.opi[-1],
4037 len(body_if_fn.opi),
4038 )
4040 body_if_fn.opo = insert_gromet_object(
4041 body_if_fn.opo, GrometPort(name=val, box=len(body_if_fn.b))
4042 )
4044 self.handle_function_def(
4045 AnnCastFunctionDef(None, None, None, None), body_if_fn, node.body
4046 )
4048 if (
4049 len(node.body) > 0
4050 and isinstance(node.body[0], AnnCastLiteralValue)
4051 and node.body[0].value_type == ScalarType.BOOLEAN
4052 ):
4053 body_if_fn.opo = insert_gromet_object(
4054 body_if_fn.opo, GrometPort(box=len(body_if_fn.b))
4055 )
4056 body_if_fn.wfopo = insert_gromet_object(
4057 body_if_fn.wfopo,
4058 GrometWire(src=len(body_if_fn.opo), tgt=len(body_if_fn.pof)),
4059 )
4061 if (
4062 len(node.body) > 0
4063 and isinstance(node.body[0], AnnCastOperator)
4064 and node.body[0].op
4065 in (
4066 "ast.Eq",
4067 "ast.NotEq",
4068 "ast.Lt",
4069 "ast.LtE",
4070 "ast.Gt",
4071 "ast.GtE",
4072 )
4073 ):
4074 body_if_fn.opo = insert_gromet_object(
4075 body_if_fn.opo, GrometPort(box=len(body_if_fn.b))
4076 )
4077 body_if_fn.wfopo = insert_gromet_object(
4078 body_if_fn.wfopo,
4079 GrometWire(src=len(body_if_fn.opo), tgt=len(body_if_fn.pof)),
4080 )
4082 # restore previous var environments
4083 var_environment["args"] = previous_func_def_args
4084 var_environment["local"] = previous_local_args
4086 return body_if_idx
4088 def if_create_orelse(
4089 self, node: AnnCastModelIf, parent_gromet_fn, parent_cast_node
4090 ):
4091 orelse_if_fn = GrometFN()
4092 orelse_if_fn.b = insert_gromet_object(
4093 orelse_if_fn.b,
4094 GrometBoxFunction(function_type=FunctionType.FUNCTION),
4095 )
4096 self.gromet_module.fn_array = insert_gromet_object(
4097 self.gromet_module.fn_array, orelse_if_fn
4098 )
4099 self.set_index()
4101 orelse_if_idx = len(self.gromet_module.fn_array)
4102 ref = node.orelse[0].source_refs[0]
4103 var_environment = self.symtab_variables()
4105 # NOTE: might change this to an if/elif if it's proven that
4106 # both an "And" and an "Or" can't exist at the same time in both parts
4107 # of the if statement
4108 # Having a boolean literal value in the node orelse implies a False value which means we have an And
4109 and_or_metadata = None
4110 if (
4111 len(node.orelse) > 0
4112 and isinstance(node.orelse[0], AnnCastLiteralValue)
4113 and node.orelse[0].value_type == ScalarType.BOOLEAN
4114 ):
4115 and_or_metadata = SourceCodeBoolAnd()
4117 if and_or_metadata != None:
4118 metadata = self.insert_metadata(
4119 self.create_source_code_reference(ref), and_or_metadata
4120 )
4121 else:
4122 metadata = self.insert_metadata(
4123 self.create_source_code_reference(ref)
4124 )
4126 orelse_if_fn.metadata = metadata
4127 # copy the old var environments over since we're going into a function
4128 previous_func_def_args = deepcopy(var_environment["args"])
4129 previous_local_args = deepcopy(var_environment["local"])
4131 var_environment["args"] = {}
4133 # TODO: determine a better for loop that only grabs
4134 # what appears in the orelse of the if_true
4135 # for (_, val) in node.expr_used_vars.items():
4136 for _, val in node.used_vars.items():
4137 orelse_if_fn.opi = insert_gromet_object(
4138 orelse_if_fn.opi, GrometPort(box=len(orelse_if_fn.b))
4139 )
4140 arg_env = var_environment["args"]
4141 arg_env[val] = (
4142 AnnCastFunctionDef(None, None, None, None),
4143 orelse_if_fn.opi[-1],
4144 len(orelse_if_fn.opi),
4145 )
4147 orelse_if_fn.opo = insert_gromet_object(
4148 orelse_if_fn.opo, GrometPort(name=val, box=len(orelse_if_fn.b))
4149 )
4151 self.handle_function_def(
4152 AnnCastFunctionDef(None, None, None, None),
4153 orelse_if_fn,
4154 node.orelse,
4155 )
4157 if (
4158 len(node.orelse) > 0
4159 and isinstance(node.orelse[0], AnnCastLiteralValue)
4160 and node.orelse[0].value_type == ScalarType.BOOLEAN
4161 ):
4162 orelse_if_fn.opo = insert_gromet_object(
4163 orelse_if_fn.opo, GrometPort(box=len(orelse_if_fn.b))
4164 )
4165 orelse_if_fn.wfopo = insert_gromet_object(
4166 orelse_if_fn.wfopo,
4167 GrometWire(
4168 src=len(orelse_if_fn.opo), tgt=len(orelse_if_fn.pof)
4169 ),
4170 )
4172 if (
4173 len(node.orelse) > 0
4174 and isinstance(node.orelse[0], AnnCastOperator)
4175 and node.orelse[0].op
4176 in (
4177 "ast.Eq",
4178 "ast.NotEq",
4179 "ast.Lt",
4180 "ast.LtE",
4181 "ast.Gt",
4182 "ast.GtE",
4183 )
4184 ):
4185 orelse_if_fn.opo = insert_gromet_object(
4186 orelse_if_fn.opo, GrometPort(box=len(orelse_if_fn.b))
4187 )
4188 orelse_if_fn.wfopo = insert_gromet_object(
4189 orelse_if_fn.wfopo,
4190 GrometWire(
4191 src=len(orelse_if_fn.opo), tgt=len(orelse_if_fn.pof)
4192 ),
4193 )
4195 # restore previous var environments
4196 var_environment["args"] = previous_func_def_args
4197 var_environment["local"] = previous_local_args
4199 return orelse_if_idx
4201 @_visit.register
4202 def visit_model_if(
4203 self, node: AnnCastModelIf, parent_gromet_fn, parent_cast_node
4204 ):
4205 ref = node.source_refs[0]
4206 metadata = self.insert_metadata(self.create_source_code_reference(ref))
4207 gromet_bc = GrometBoxConditional(metadata=metadata)
4209 parent_gromet_fn.bc = insert_gromet_object(
4210 parent_gromet_fn.bc, gromet_bc
4211 )
4213 bc_index = len(parent_gromet_fn.bc)
4215 for _, val in node.used_vars.items():
4216 parent_gromet_fn.pic = insert_gromet_object(
4217 parent_gromet_fn.pic,
4218 GrometPort(name=val, box=len(parent_gromet_fn.bc)),
4219 )
4221 parent_gromet_fn.poc = insert_gromet_object(
4222 parent_gromet_fn.poc,
4223 GrometPort(name=val, box=len(parent_gromet_fn.bc)),
4224 )
4226 # TODO: We also need to put this around a loop
4227 # And in particular we only want to make wires to variables that are used in the conditional
4228 # Check type of parent_cast_node to determine which wire to create
4229 # TODO: Previously, we were always generating a wfc wire for variables coming into a conditional
4230 # However, we can also have variables coming in from other sources such as an opi.
4231 # This is a temporary fix for the specific case in the CHIME model, but will need to be revisited
4232 if isinstance(parent_cast_node, AnnCastFunctionDef):
4233 if (
4234 parent_gromet_fn.pic == None and parent_gromet_fn.opi == None
4235 ): # TODO: double check this guard to see if it's necessary
4236 # print(node.source_refs[0])
4237 parent_gromet_fn.wcopi = insert_gromet_object(
4238 parent_gromet_fn.wcopi, GrometWire(src=-1, tgt=-912)
4239 )
4240 elif parent_gromet_fn.opi == None and parent_gromet_fn.pof == None:
4241 # print(node.source_refs[0])
4242 parent_gromet_fn.wcopi = insert_gromet_object(
4243 parent_gromet_fn.wcopi,
4244 GrometWire(src=len(parent_gromet_fn.pic), tgt=-913),
4245 )
4246 elif parent_gromet_fn.pic == None:
4247 # print(node.source_refs[0])
4248 parent_gromet_fn.wcopi = insert_gromet_object(
4249 parent_gromet_fn.wcopi,
4250 GrometWire(src=-1, tgt=len(parent_gromet_fn.opi)),
4251 )
4252 else:
4253 for pic_idx, pic in enumerate(parent_gromet_fn.pic, 1):
4254 if pic.box == bc_index:
4255 port = self.retrieve_var_port(pic.name)
4256 if port != -1:
4257 if self.check_var_location(pic.name, "local"):
4258 parent_gromet_fn.wfc = insert_gromet_object(
4259 parent_gromet_fn.wfc,
4260 GrometWire(src=pic_idx, tgt=port),
4261 )
4262 elif self.check_var_location(pic.name, "args"):
4263 parent_gromet_fn.wcopi = insert_gromet_object(
4264 parent_gromet_fn.wcopi,
4265 GrometWire(src=pic_idx, tgt=port),
4266 )
4267 elif self.check_var_location(pic.name, "global"):
4268 parent_gromet_fn.wfc = insert_gromet_object(
4269 parent_gromet_fn.wfc,
4270 GrometWire(src=pic_idx, tgt=port),
4271 )
4273 if isinstance(parent_cast_node, AnnCastModule):
4274 for pic_idx, pic in enumerate(parent_gromet_fn.pic, 1):
4275 port = self.retrieve_var_port(pic.name)
4276 if port != -1:
4277 if self.check_var_location(pic.name, "local"):
4278 parent_gromet_fn.wfc = insert_gromet_object(
4279 parent_gromet_fn.wfc,
4280 GrometWire(src=pic_idx, tgt=port),
4281 )
4282 elif self.check_var_location(pic.name, "args"):
4283 parent_gromet_fn.wcopi = insert_gromet_object(
4284 parent_gromet_fn.wcopi,
4285 GrometWire(src=pic_idx, tgt=port),
4286 )
4287 elif self.check_var_location(pic.name, "global"):
4288 parent_gromet_fn.wfc = insert_gromet_object(
4289 parent_gromet_fn.wfc,
4290 GrometWire(src=pic_idx, tgt=port),
4291 )
4293 gromet_bc.condition = self.if_create_condition(
4294 node, parent_gromet_fn, parent_cast_node
4295 )
4297 ########### If true generation
4298 gromet_bc.body_if = self.if_create_body(
4299 node, parent_gromet_fn, parent_cast_node
4300 )
4302 ########### If false generation
4303 if (
4304 len(node.orelse) > 0
4305 ): # NOTE: guards against when there's no else to the if statement
4306 gromet_bc.body_else = self.if_create_orelse(
4307 node, parent_gromet_fn, parent_cast_node
4308 )
4310 def add_import_symbol_to_env(
4311 self, symbol, parent_gromet_fn, parent_cast_node
4312 ):
4313 """
4314 Adds symbol to the GroMEt FN as a 'variable'
4315 When we import something from another file with a symbol,
4316 we don't know the symbol is a function call or variable
4317 so we add in a 'dummy' variable of sorts so that it can
4318 be used in this file
4319 """
4321 parent_gromet_fn.bf = insert_gromet_object(
4322 parent_gromet_fn.bf,
4323 GrometBoxFunction(
4324 function_type=FunctionType.IMPORTED, name=symbol, body=None
4325 ),
4326 )
4328 bf_idx = len(parent_gromet_fn.bf)
4330 parent_gromet_fn.pof = insert_gromet_object(
4331 parent_gromet_fn.pof, GrometPort(name=symbol, box=bf_idx)
4332 )
4334 pof_idx = len(parent_gromet_fn.pof)
4336 self.add_var_to_env(
4337 symbol,
4338 None,
4339 parent_gromet_fn.pof[pof_idx - 1],
4340 pof_idx,
4341 parent_cast_node,
4342 )
4344 @_visit.register
4345 def visit_model_import(
4346 self, node: AnnCastModelImport, parent_gromet_fn, parent_cast_node
4347 ):
4348 name = node.name
4349 alias = node.alias
4350 symbol = node.symbol
4351 all = node.all
4353 # self.import collection maintains a dictionary of
4354 # name:(alias, [symbols], all boolean flag)
4355 # pairs that we can use to look up later
4356 if (
4357 name in self.import_collection
4358 ): # If this import already exists, then perhaps we add a new symbol to its list of symbols
4359 if symbol != None:
4360 if self.import_collection[name][1] == None:
4361 self.import_collection[name] = (
4362 self.import_collection[name][0],
4363 [],
4364 self.import_collection[name][2],
4365 )
4366 self.import_collection[name][1].append(symbol)
4367 # We also maintain the symbol as a 'variable' of sorts in the global environment
4368 self.add_import_symbol_to_env(
4369 symbol, parent_gromet_fn, parent_cast_node
4370 )
4372 self.import_collection[name] = (
4373 self.import_collection[name][0],
4374 self.import_collection[name][1],
4375 all,
4376 )
4377 # self.import_collection[name][2] = all # Update the all field if necessary
4378 else: # Otherwise we haven't seen this import yet and we add its fields and potential symbol accordingly
4379 if symbol == None:
4380 self.import_collection[name] = (alias, [], all)
4381 else:
4382 self.import_collection[name] = (alias, [symbol], all)
4383 # We also maintain the symbol as a 'variable' of sorts in the global environment
4384 self.add_import_symbol_to_env(
4385 symbol, parent_gromet_fn, parent_cast_node
4386 )
4388 @_visit.register
4389 def visit_model_return(
4390 self, node: AnnCastModelReturn, parent_gromet_fn, parent_cast_node
4391 ):
4392 # if not isinstance(node.value, AnnCastTuple):
4393 if not is_tuple(node.value):
4394 self.visit(node.value, parent_gromet_fn, node)
4395 ref = node.source_refs[0]
4397 # A binary op sticks a single return value in the opo
4398 # Where as a tuple can stick multiple opos, one for each thing being returned
4399 # NOTE: The above comment about tuples is outdated, as we now pack the tuple's values into a pack, and return one
4400 # value with that
4401 if isinstance(node.value, AnnCastOperator):
4402 parent_gromet_fn.opo = insert_gromet_object(
4403 parent_gromet_fn.opo,
4404 GrometPort(
4405 box=len(parent_gromet_fn.b),
4406 metadata=self.insert_metadata(
4407 self.create_source_code_reference(ref)
4408 ),
4409 ),
4410 )
4411 # elif isinstance(node.value, AnnCastTuple):
4412 elif is_tuple(node.value):
4413 parent_gromet_fn.opo = insert_gromet_object(
4414 parent_gromet_fn.opo,
4415 GrometPort(
4416 box=len(parent_gromet_fn.b),
4417 metadata=self.insert_metadata(
4418 self.create_source_code_reference(ref)
4419 ),
4420 ),
4421 )
4422 # for elem in node.value.values:
4423 # parent_gromet_fn.opo = insert_gromet_object(parent_gromet_fn.opo, GrometPort(box=len(parent_gromet_fn.b),metadata=self.insert_metadata(self.create_source_code_reference(ref))))
4425 @_visit.register
4426 def visit_module(
4427 self, node: AnnCastModule, parent_gromet_fn, parent_cast_node
4428 ):
4429 # We create a new GroMEt FN and add it to the GroMEt FN collection
4431 # Creating a new Function Network (FN) where the outer box is a module
4432 # i.e. a gray colored box in the drawings
4433 # It's like any FN but it doesn't have any outer ports, or inner/outer port boxes
4434 # on it (i.e. little squares on the gray box in a drawing)
4436 file_name = node.source_refs[0].source_file_name
4437 var_environment = self.symtab_variables()
4438 var_environment["global"] = {}
4440 # Have a FN constructor to build the GroMEt FN
4441 # and pass this FN to maintain a 'nesting' approach (boxes within boxes)
4442 # instead of passing a GrFNSubgraph through the visitors
4443 new_gromet = GrometFN()
4445 # Initialie the Gromet module's Record Bookkeeping metadata
4446 # Which lives in the very first element of the metadata array
4447 self.gromet_module.metadata_collection = [[]]
4448 self.gromet_module.metadata = 0
4450 # Initialize the Gromet module's SourceCodeCollection of CodeFileReferences
4451 code_file_references = [
4452 CodeFileReference(uid=str(uuid.uuid4()), name=file_name, path="")
4453 ]
4454 gromet_creation_metadata = GrometCreation(provenance=generate_provenance())
4455 gromet_creation_metadata.gromet_version = GROMET_VERSION
4456 self.gromet_module.metadata = self.insert_metadata(
4457 SourceCodeCollection(
4458 provenance=generate_provenance(),
4459 name="",
4460 global_reference_id="",
4461 files=code_file_references,
4462 ),
4463 gromet_creation_metadata,
4464 )
4466 # Outer module box only has name 'module' and its type 'Module'
4467 new_gromet.b = insert_gromet_object(
4468 new_gromet.b,
4469 GrometBoxFunction(
4470 name="module",
4471 function_type=FunctionType.MODULE,
4472 metadata=self.insert_metadata(
4473 self.create_source_code_reference(node.source_refs[0])
4474 ),
4475 ),
4476 )
4478 # Module level GroMEt FN sits in its own special field dicating the module node
4479 self.gromet_module.fn = new_gromet
4481 # Set the name of the outer Gromet module to be the source file name
4482 self.gromet_module.name = os.path.basename(file_name).replace(
4483 ".py", ""
4484 )
4486 self.build_function_arguments_table(node.body)
4488 self.visit_node_list(node.body, new_gromet, node)
4490 var_environment["global"] = {}
4492 @_visit.register
4493 def visit_name(
4494 self, node: AnnCastName, parent_gromet_fn, parent_cast_node
4495 ):
4496 # NOTE: Maybe make wfopi between the function input and where it's being used
4498 # If this name access comes from a return node then we make the opo for the GroMEt FN that this
4499 # return is in
4500 if isinstance(parent_cast_node, AnnCastModelReturn):
4501 parent_gromet_fn.opo = insert_gromet_object(
4502 parent_gromet_fn.opo, GrometPort(box=len(parent_gromet_fn.b))
4503 )
4505 @_visit.register
4506 def visit_record_def(
4507 self, node: AnnCastRecordDef, parent_gromet_fn, parent_cast_node
4508 ):
4509 record_name = node.name
4510 record_methods = [] # strings (method names)
4511 record_fields = {} # field:method_name
4513 self.symbol_table["records"][record_name] = record_name
4514 var_environment = self.symtab_variables()
4516 # Find 'init' and create a special new:Object function for it
4517 # Repeat with the getters I think?
4518 f = None
4519 for f in node.funcs:
4520 if isinstance(f, AnnCastFunctionDef) and f.name.name == "__init__":
4521 record_methods.append("__init__")
4522 break
4524 new_gromet = GrometFN()
4525 self.gromet_module.fn_array = insert_gromet_object(
4526 self.gromet_module.fn_array, new_gromet
4527 )
4528 self.set_index()
4530 # Because "new:Record" is a function definition itself we
4531 # need to maintain an argument environment for it
4532 # store copies of previous ones and create new ones
4533 arg_env_copy = deepcopy(var_environment["args"])
4534 local_env_copy = deepcopy(var_environment["local"])
4536 var_environment["args"] = {}
4538 # Generate the init new:ClassName FN
4539 new_gromet.b = insert_gromet_object(
4540 new_gromet.b,
4541 GrometBoxFunction(
4542 name=f"new:{node.name}", function_type=FunctionType.FUNCTION
4543 ),
4544 )
4545 if f != None:
4546 for arg in f.func_args:
4547 if arg.val.name != "self":
4548 new_gromet.opi = insert_gromet_object(
4549 new_gromet.opi,
4550 GrometPort(name=arg.val.name, box=len(new_gromet.b)),
4551 )
4552 var_environment["args"][arg.val.name] = (
4553 arg,
4554 new_gromet.opi[-1],
4555 len(new_gromet.opi),
4556 )
4558 # We maintain an additional 'obj' field that is used in the case that we inherit a parent class
4559 new_gromet.opi = insert_gromet_object(
4560 new_gromet.opi, GrometPort(name="obj", box=len(new_gromet.b))
4561 )
4562 var_environment["args"]["obj"] = (
4563 None,
4564 new_gromet.opi[-1],
4565 len(new_gromet.opi),
4566 )
4567 new_gromet.opo = insert_gromet_object(
4568 new_gromet.opo, GrometPort(box=len(new_gromet.b))
4569 )
4571 # The first value that goes into the "new_Record" primitive is the name of the class
4572 new_gromet.bf = insert_gromet_object(
4573 new_gromet.bf,
4574 GrometBoxFunction(
4575 function_type=FunctionType.LITERAL,
4576 value=GLiteralValue("string", node.name),
4577 ),
4578 )
4579 new_gromet.pof = insert_gromet_object(
4580 new_gromet.pof, GrometPort(box=len(new_gromet.bf))
4581 )
4583 # Create the initial constructor function and wire it accordingly
4584 inline_new_record = GrometBoxFunction(
4585 name="new_Record", function_type=FunctionType.ABSTRACT
4586 )
4587 new_gromet.bf = insert_gromet_object(new_gromet.bf, inline_new_record)
4588 new_record_idx = len(new_gromet.bf)
4590 # Create the first port for "new_Record" and wire the first value created earlier
4591 new_gromet.pif = insert_gromet_object(
4592 new_gromet.pif, GrometPort(box=new_record_idx)
4593 )
4594 new_gromet.wff = insert_gromet_object(
4595 new_gromet.wff,
4596 GrometWire(src=len(new_gromet.pif), tgt=len(new_gromet.pof)),
4597 )
4599 # The second value that goes into the "new_Record" primitive is either the name of the superclass or None
4600 # Checking if we have a superclass (parent class) or not
4601 if len(node.bases) == 0:
4602 new_gromet.bf = insert_gromet_object(
4603 new_gromet.bf,
4604 GrometBoxFunction(
4605 function_type=FunctionType.LITERAL,
4606 value=GLiteralValue("None", "None"),
4607 ),
4608 )
4609 new_gromet.pof = insert_gromet_object(
4610 new_gromet.pof, GrometPort(box=len(new_gromet.bf))
4611 )
4612 new_gromet.pif = insert_gromet_object(
4613 new_gromet.pif, GrometPort(box=new_record_idx)
4614 )
4615 new_gromet.wff = insert_gromet_object(
4616 new_gromet.wff,
4617 GrometWire(src=len(new_gromet.pif), tgt=len(new_gromet.pof)),
4618 )
4619 else:
4620 base = node.bases[0]
4621 name = ""
4622 if isinstance(base, AnnCastAttribute):
4623 name = base.attr
4624 else:
4625 name = base.name
4626 new_gromet.bf = insert_gromet_object(
4627 new_gromet.bf,
4628 GrometBoxFunction(
4629 function_type=FunctionType.LITERAL,
4630 value=GLiteralValue("string", name),
4631 ),
4632 )
4633 new_gromet.pof = insert_gromet_object(
4634 new_gromet.pof, GrometPort(box=len(new_gromet.bf))
4635 )
4636 new_gromet.pif = insert_gromet_object(
4637 new_gromet.pif, GrometPort(box=new_record_idx)
4638 )
4639 new_gromet.wff = insert_gromet_object(
4640 new_gromet.wff,
4641 GrometWire(src=len(new_gromet.pif), tgt=len(new_gromet.pof)),
4642 )
4644 # Add the third argument to new_Record, which is the obj argument
4645 new_gromet.pif = insert_gromet_object(
4646 new_gromet.pif, GrometPort(box=new_record_idx)
4647 )
4648 new_gromet.wfopi = insert_gromet_object(
4649 new_gromet.wfopi,
4650 GrometWire(
4651 src=len(new_gromet.pif),
4652 tgt=var_environment["args"]["obj"][2],
4653 ),
4654 )
4656 # pof for "new_Record"
4657 new_gromet.pof = insert_gromet_object(
4658 new_gromet.pof, GrometPort(box=new_record_idx)
4659 )
4661 if f != None:
4662 for s in f.body:
4663 if (
4664 isinstance(s, AnnCastAssignment)
4665 and isinstance(s.left, AnnCastAttribute)
4666 and s.left.value.name == "self"
4667 ):
4668 record_fields[s.left.attr.name] = record_name
4670 inline_new_record = GrometBoxFunction(
4671 name="new_Field", function_type=FunctionType.ABSTRACT
4672 )
4673 new_gromet.bf = insert_gromet_object(
4674 new_gromet.bf, inline_new_record
4675 )
4676 new_field_idx = len(new_gromet.bf)
4678 # Wire first pif of "new_field" which relies on the previous pof of "new_record" or a previous "set" call
4679 new_gromet.pif = insert_gromet_object(
4680 new_gromet.pif, GrometPort(box=new_field_idx)
4681 )
4682 new_gromet.wff = insert_gromet_object(
4683 new_gromet.wff,
4684 GrometWire(
4685 src=len(new_gromet.pif), tgt=len(new_gromet.pof)
4686 ),
4687 )
4689 # Second pif of "new_field"/"set" involves this variable and its pof
4690 new_gromet.bf = insert_gromet_object(
4691 new_gromet.bf,
4692 GrometBoxFunction(
4693 function_type=FunctionType.LITERAL,
4694 value=GLiteralValue("string", s.left.attr.name),
4695 ),
4696 )
4697 new_gromet.pof = insert_gromet_object(
4698 new_gromet.pof, GrometPort(box=len(new_gromet.bf))
4699 )
4701 var_pof = len(new_gromet.pof)
4703 # Second argument to "new_Field"
4704 new_gromet.pif = insert_gromet_object(
4705 new_gromet.pif, GrometPort(box=new_field_idx)
4706 )
4707 new_gromet.wff = insert_gromet_object(
4708 new_gromet.wff,
4709 GrometWire(src=len(new_gromet.pif), tgt=var_pof),
4710 )
4711 new_gromet.pof = insert_gromet_object(
4712 new_gromet.pof, GrometPort(box=new_field_idx)
4713 )
4715 # Create set
4716 record_set = GrometBoxFunction(
4717 name="set", function_type=FunctionType.ABSTRACT
4718 )
4719 # Wires first arg for "set"
4720 new_gromet.bf = insert_gromet_object(
4721 new_gromet.bf, record_set
4722 )
4723 record_set_idx = len(new_gromet.bf)
4724 new_gromet.pif = insert_gromet_object(
4725 new_gromet.pif, GrometPort(box=record_set_idx)
4726 )
4727 new_gromet.wff = insert_gromet_object(
4728 new_gromet.wff,
4729 GrometWire(
4730 src=len(new_gromet.pif), tgt=len(new_gromet.pof)
4731 ),
4732 )
4734 # Wires second arg for "set"
4735 new_gromet.pif = insert_gromet_object(
4736 new_gromet.pif, GrometPort(box=record_set_idx)
4737 )
4738 new_gromet.wff = insert_gromet_object(
4739 new_gromet.wff,
4740 GrometWire(src=len(new_gromet.pif), tgt=var_pof),
4741 )
4743 # Create third argument for "set"
4744 new_gromet.pif = insert_gromet_object(
4745 new_gromet.pif, GrometPort(box=record_set_idx)
4746 )
4747 set_third_arg = len(new_gromet.pif)
4749 # Wire the last argument for "set" depending on what it is
4750 if isinstance(s.right, AnnCastName):
4751 # Find argument opi for "set" third argument
4752 if (
4753 new_gromet.opi != None
4754 ): # TODO: Fix it so opis aren't ever None
4755 for opi_i, opi in enumerate(new_gromet.opi, 1):
4756 if (
4757 isinstance(s.right, AnnCastName)
4758 and opi.name == s.right.name
4759 ):
4760 break
4762 new_gromet.wfopi = insert_gromet_object(
4763 new_gromet.wfopi,
4764 GrometWire(src=set_third_arg, tgt=opi_i),
4765 )
4767 else:
4768 # The visitor sets a pof that we have to wire
4769 self.visit(s.right, new_gromet, parent_cast_node)
4771 new_gromet.wff = insert_gromet_object(
4772 new_gromet.wff,
4773 GrometWire(
4774 src=set_third_arg, tgt=len(new_gromet.pof)
4775 ),
4776 )
4778 # Output port for "set"
4779 new_gromet.pof = insert_gromet_object(
4780 new_gromet.pof, GrometPort(box=record_set_idx)
4781 )
4783 # Wire output wire for "new:Record"
4784 new_gromet.wfopo = insert_gromet_object(
4785 new_gromet.wfopo,
4786 GrometWire(src=len(new_gromet.opo), tgt=len(new_gromet.pof)),
4787 )
4789 # Need to store the index of where "new:Record" is in the GroMEt table
4790 # in the record table
4791 self.record[node.name] = {}
4792 self.record[node.name][f"new:{node.name}"] = len(
4793 self.gromet_module.fn_array
4794 )
4796 var_environment["args"] = deepcopy(arg_env_copy)
4797 var_environment["local"] = deepcopy(local_env_copy)
4799 # Generate and store the rest of the functions associated with this record
4800 for f in node.funcs:
4801 if isinstance(f, AnnCastFunctionDef) and f.name.name != "__init__":
4802 arg_env_copy = deepcopy(var_environment["args"])
4803 local_env_copy = deepcopy(var_environment["local"])
4804 var_environment["args"] = {}
4806 # This is a new function, so create a GroMEt FN
4807 new_gromet = GrometFN()
4808 self.gromet_module.fn_array = insert_gromet_object(
4809 self.gromet_module.fn_array, new_gromet
4810 )
4811 self.set_index()
4813 # Create its name and its arguments
4814 new_gromet.b = insert_gromet_object(
4815 new_gromet.b,
4816 GrometBoxFunction(
4817 name=f"{node.name}:{f.name.name}",
4818 function_type=FunctionType.FUNCTION,
4819 ),
4820 )
4822 record_methods.append(f.name.name)
4823 for arg in f.func_args:
4824 new_gromet.opi = insert_gromet_object(
4825 new_gromet.opi,
4826 GrometPort(name=arg.val.name, box=len(new_gromet.b)),
4827 )
4828 var_environment["args"][arg.val.name] = (
4829 arg,
4830 new_gromet.opi[-1],
4831 len(new_gromet.opi),
4832 )
4833 new_gromet.opo = insert_gromet_object(
4834 new_gromet.opo, GrometPort(box=len(new_gromet.b))
4835 )
4837 for s in f.body:
4838 self.visit(
4839 s,
4840 new_gromet,
4841 AnnCastFunctionDef(None, None, None, None),
4842 )
4844 if new_gromet.pof != None:
4845 new_gromet.wfopo = insert_gromet_object(
4846 new_gromet.wfopo,
4847 GrometWire(
4848 src=len(new_gromet.opo), tgt=len(new_gromet.pof)
4849 ),
4850 )
4851 else:
4852 new_gromet.wfopo = insert_gromet_object(
4853 new_gromet.wfopo,
4854 GrometWire(src=len(new_gromet.opo), tgt=-1),
4855 )
4857 var_environment["args"] = deepcopy(arg_env_copy)
4858 var_environment["local"] = deepcopy(local_env_copy)
4860 self.record[node.name][f.name.name] = len(
4861 self.gromet_module.fn_array
4862 )
4864 record_metadata = ProgramAnalysisRecordBookkeeping(
4865 provenance=generate_provenance(),
4866 type_name=record_name,
4867 field_declarations=record_fields,
4868 method_declarations=record_methods,
4869 )
4871 self.insert_record_info(record_metadata)
4873 @_visit.register
4874 def visit_tuple(
4875 self, node: AnnCastTuple, parent_gromet_fn, parent_cast_node
4876 ):
4877 self.visit_node_list(node.values, parent_gromet_fn, parent_cast_node)
4879 @_visit.register
4880 def visit_var(self, node: AnnCastVar, parent_gromet_fn, parent_cast_node):
4881 self.visit(node.val, parent_gromet_fn, parent_cast_node)