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

1from copy import deepcopy 

2import sys 

3import os.path 

4import pprint 

5 

6from skema.utils.misc import uuid 

7 

8from functools import singledispatchmethod 

9from datetime import datetime 

10from time import time 

11 

12from skema.program_analysis.CAST2FN.model.cast import StructureType 

13 

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) 

27 

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) 

41 

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 

48 

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) 

55 

56from skema.gromet.execution_engine.primitive_map import ( 

57 get_shorthand, 

58 get_inputs, 

59 get_outputs, 

60 is_primitive, 

61) 

62 

63from skema.gromet import GROMET_VERSION 

64 

65PYTHON_VERSION = "3.8" 

66 

67 

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" 

71 

72 

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 

83 

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 """ 

87 

88 if t == None: 

89 t = [] 

90 

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 

98 

99 t.append(obj) 

100 return t 

101 

102 

103def generate_provenance(): 

104 timestamp = str(datetime.fromtimestamp(time())) 

105 method_name = "skema_code2fn_program_analysis" 

106 return Provenance(method=method_name, timestamp=timestamp) 

107 

108 

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 ) 

115 

116 

117def retrieve_name_id_pair(node): 

118 """ 

119 Operand from an AnnCastOperator 

120 AnnCastName 

121 AnnCastCall 

122 AnnCastAttribute 

123 """ 

124 

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) 

138 

139 

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 

162 

163 n1_name, n1_id = retrieve_name_id_pair(n1) 

164 n2_name, n2_id = retrieve_name_id_pair(n2) 

165 

166 return n1_name == n2_name and n1_id == n2_id 

167 

168 

169def find_existing_opi(gromet_fn, opi_name): 

170 idx = 1 

171 if gromet_fn.opi == None: 

172 return False, idx 

173 

174 for opi in gromet_fn.opi: 

175 if opi_name == opi.name: 

176 return True, idx 

177 idx += 1 

178 return False, idx 

179 

180 

181def find_existing_pil(gromet_fn, opi_name): 

182 if gromet_fn.pil == None: 

183 return -1 

184 

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 

191 

192 

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" 

205 

206 

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) 

217 

218 

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}") 

229 

230 

231class ToGrometPass: 

232 def __init__(self, pipeline_state: PipelineState): 

233 self.pipeline_state = pipeline_state 

234 self.nodes = self.pipeline_state.nodes 

235 

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 = {} 

246 

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 ) 

258 

259 # build the built-in map 

260 build_map() 

261 

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 = {} 

265 

266 # When a record type is initiatied we keep track of its name and record type here 

267 self.initialized_records = {} 

268 

269 # Initialize the table of function arguments 

270 self.function_arguments = {} 

271 

272 # Maintain an fn_array index stack 

273 self.fn_stack = [] 

274 

275 # Table of labels 

276 self.labels = {} 

277 self.placeholder_gotos = {} 

278 

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) 

283 

284 pipeline_state.gromet_collection = self.gromet_module 

285 

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) 

290 

291 def peek_idx(self): 

292 if len(self.fn_stack) > 0: 

293 return self.fn_stack[-1] 

294 return 0 

295 

296 def pop_idx(self): 

297 if len(self.fn_stack) > 0: 

298 return self.fn_stack.pop() 

299 return 0 

300 

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 

305 

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 = {} 

310 

311 def symtab_variables(self): 

312 return self.symbol_table["variables"] 

313 

314 def symtab_functions(self): 

315 return self.symbol_table["functions"] 

316 

317 def symtab_records(self): 

318 return self.symbol_table["records"] 

319 

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 

332 

333 return 1 

334 

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 

339 

340 We also, for each function, create an initial entry containing its name and its 

341 index in the FN array 

342 

343 NOTE: functions within functions aren't currently supported 

344 

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)) 

352 

353 def wire_from_var_env(self, name, gromet_fn): 

354 var_environment = self.symtab_variables() 

355 

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 ) 

388 

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 

393 

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 

398 

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 ) 

412 

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) 

426 

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) 

434 

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 

442 

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) 

455 

456 func_name, qual_func_name = get_func_name(node) 

457 

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) 

468 

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 ) 

497 

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) 

506 

507 primitive_fn.opo = insert_gromet_object( 

508 primitive_fn.opo, GrometPort(box=len(primitive_fn.b)) 

509 ) 

510 

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 ) 

521 

522 

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 ) 

557 

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() 

564 

565 fn_array_idx = len(self.gromet_module.fn_array) 

566 

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 ) 

575 

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 

580 

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) 

585 

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() 

601 

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() 

615 

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 

633 

634 func_idx += 1 

635 

636 return func_idx + 1, found_func 

637 

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] 

655 

656 return -1 

657 

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] 

663 

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}") 

674 

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 

683 

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 ] 

694 

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)}") 

701 

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)) 

707 

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 ) 

738 

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 ) 

751 

752 parent_gromet_fn.bf = insert_gromet_object( 

753 parent_gromet_fn.bf, pack_bf 

754 ) 

755 

756 pack_index = len(parent_gromet_fn.bf) 

757 

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 ) 

763 

764 parent_gromet_fn.wff = insert_gromet_object( 

765 parent_gromet_fn.wff, 

766 GrometWire(src=len(parent_gromet_fn.pif), tgt=port), 

767 ) 

768 

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 ) 

775 

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 ) 

783 

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 ) 

794 

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 ) 

803 

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 ) 

808 

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 ) 

816 

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 ) 

879 

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 """ 

888 

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 ) 

951 

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 

964 

965 Return a tuple of 

966 (FunctionType, ImportType, ImportVersion, ImportSource, SourceLanguage, SourceLanguageVersion) 

967 """ 

968 func_name, _ = retrieve_name_id_pair(node) 

969 

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) 

973 

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 ) 

989 

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 ) 

1064 

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 ) 

1095 

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? 

1102 

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 

1108 

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 

1130 

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 

1146 

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 

1162 

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 

1200 

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 

1214 

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 

1223 

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 ) 

1272 

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 ) 

1287 

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 

1303 

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 ) 

1341 

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() 

1347 

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 ) 

1356 

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 ) 

1371 

1372 self.wire_from_var_env(node.right.name, parent_gromet_fn) 

1373 

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) 

1404 

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 # ) 

1411 

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 ) 

1425 

1426 self.visit(val, new_gromet, parent_cast_node) 

1427 

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 ) 

1432 

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 ) 

1440 

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() 

1447 

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 ) 

1458 

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 ) 

1469 

1470 var_pof = len(parent_gromet_fn.pof) 

1471 

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) 

1481 

1482 self.gromet_module.fn_array = insert_gromet_object( 

1483 self.gromet_module.fn_array, 

1484 new_gromet, 

1485 ) 

1486 self.set_index() 

1487 

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 ) 

1498 

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 ) 

1506 

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)) 

1528 

1529 tuple_indices.append(var_pof) 

1530 

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]) 

1545 

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) 

1567 

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 ) 

1574 

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) 

1577 

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 ) 

1588 

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() 

1595 

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 ) 

1613 

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 ) 

1631 

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 ) 

1640 

1641 elif isinstance(node.right, AnnCastAttribute): 

1642 ref = node.source_refs[0] 

1643 metadata = self.create_source_code_reference(ref) 

1644 

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 ) 

1651 

1652 self.visit(node.right, new_gromet, node) 

1653 

1654 self.gromet_module.fn_array = insert_gromet_object( 

1655 self.gromet_module.fn_array, new_gromet 

1656 ) 

1657 self.set_index() 

1658 

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 ) 

1674 

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) 

1682 

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 ) 

1689 

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 ) 

1698 

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() 

1732 

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 ) 

1745 

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) 

1757 

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 

1760 

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 ) 

1798 

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 ) 

1816 

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 ) 

1859 

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) 

1864 

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 ) 

1916 

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 

1932 

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 ) 

1945 

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 

1954 

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))) 

1970 

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) 

1985 

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 

2008 

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) 

2016 

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 ) 

2027 

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) 

2037 

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 ) 

2042 

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 ) 

2050 

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 ) 

2061 

2062 

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 ) 

2083 

2084 self.visit(node.value, new_gromet, node) 

2085 

2086 new_gromet.opo = insert_gromet_object( 

2087 new_gromet.opo, GrometPort(box=len(new_gromet.b)) 

2088 ) 

2089 

2090 new_gromet.wfopo = insert_gromet_object( 

2091 new_gromet.wfopo, 

2092 GrometWire(src=len(new_gromet.opo), tgt=len(new_gromet.pof)), 

2093 ) 

2094 

2095 self.gromet_module.fn_array = insert_gromet_object( 

2096 self.gromet_module.fn_array, new_gromet 

2097 ) 

2098 self.set_index() 

2099 

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 ) 

2110 

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 ) 

2120 

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 ) 

2131 

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) 

2136 

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 ) 

2147 

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 ) 

2167 

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 ) 

2175 

2176 parent_gromet_fn.pof = insert_gromet_object( 

2177 parent_gromet_fn.pof, 

2178 GrometPort(box=len(parent_gromet_fn.bf)), 

2179 ) 

2180 

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}"] 

2187 

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) 

2190 

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) 

2199 

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 ) 

2212 

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 ) 

2235 

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) 

2245 

2246 parent_gromet_fn.pif = insert_gromet_object( 

2247 parent_gromet_fn.pif, GrometPort(box=unop_idx) 

2248 ) 

2249 

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 

2258 

2259 if parent_gromet_fn.b[0].function_type != FunctionType.FUNCTION: 

2260 found_opi, opi_idx = find_existing_opi(parent_gromet_fn, name) 

2261 

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 

2285 

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 ) 

2292 

2293 parent_gromet_fn.pof = insert_gromet_object( 

2294 parent_gromet_fn.pof, GrometPort(box=unop_idx) 

2295 ) 

2296 

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) 

2304 

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) 

2316 

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) 

2321 

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) 

2333 

2334 ref = node.source_refs[0] 

2335 metadata = self.create_source_code_reference(ref) 

2336 

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 ) 

2348 

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 

2360 

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) 

2365 

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 ) 

2411 

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 ) 

2426 

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 ) 

2475 

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 ) 

2480 

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 

2494 

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) 

2499 

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 

2536 

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 

2542 

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' 

2557 

2558 return (False, "") 

2559 

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) 

2566 

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) 

2574 

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 

2583 

2584 if isinstance(node.func, AnnCastAttribute): 

2585 self.visit(node.func, parent_gromet_fn, parent_cast_node) 

2586 

2587 in_module = self.func_in_module(func_name) 

2588 func_info = self.determine_func_type(node) 

2589 

2590 

2591 

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() 

2615 

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 

2655 

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() 

2673 

2674 if func_name in self.record.keys(): 

2675 idx = self.record[func_name][f"new:{func_name}"] 

2676 

2677 body = idx 

2678 

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) 

2696 

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 

2711 

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 

2724 

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() 

2738 

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) 

2804 

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 ) 

2814 

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 ) 

2829 

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") 

2838 

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 ) 

2847 

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) 

2853 

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) 

2858 

2859 parent_gromet_fn.wff = insert_gromet_object( 

2860 parent_gromet_fn.wff, GrometWire(src=none_pif, tgt=none_pof) 

2861 ) 

2862 

2863 return call_bf_idx 

2864 

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 ) 

2894 

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]) 

2902 

2903 if isinstance(node, AnnCastLiteralValue): 

2904 ret_vals = list(node.value) 

2905 else: 

2906 ret_vals = list(node.values) 

2907 

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) 

2918 

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 ) 

2952 

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 ) 

2974 

2975 gromet_fn.pof = insert_gromet_object( 

2976 gromet_fn.pof, GrometPort(box=pack_bf_idx) 

2977 ) 

2978 

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 ) 

2985 

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 = {} 

2994 

2995 self.clear_labels() 

2996 

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 

3042 

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 """ 

3054 

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() 

3060 

3061 prev_local_env = {} 

3062 if isinstance(parent_cast_node, AnnCastFunctionDef): 

3063 prev_local_env = deepcopy(var_environment["local"]) 

3064 

3065 var_environment["local"] = {} 

3066 

3067 for n in func_body: 

3068 self.visit(n, new_gromet_fn, node) 

3069 

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 

3074 

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) 

3078 

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 ) 

3105 

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) 

3109 

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) 

3117 

3118 ref = node.source_refs[0] 

3119 

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] 

3136 

3137 self.push_idx(idx) 

3138 

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, []) 

3144 

3145 metadata = self.create_source_code_reference(ref) 

3146 

3147 new_gromet.b[0].metadata = self.insert_metadata(metadata) 

3148 var_environment = self.symtab_variables() 

3149 

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 

3153 

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"] 

3166 

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 

3178 

3179 for arg in node.func_args: 

3180 # Visit the arguments 

3181 self.visit(arg, new_gromet, node) 

3182 

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 

3186 

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 ) 

3249 

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 ) 

3258 

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 ) 

3278 

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 ) 

3284 

3285 self.pop_idx() 

3286 self.resolve_placeholder_gotos() 

3287 var_environment["args"] = deepcopy(prev_arg_env) 

3288 

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) 

3296 

3297 return labels 

3298 

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() 

3312 

3313 goto_fn_idx = len(self.gromet_module.fn_array) 

3314 

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) 

3325 

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) 

3332 

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 

3343 

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) 

3351 

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))) 

3356 

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) 

3362 

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)) 

3366 

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))) 

3370 

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) 

3373 

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))) 

3377 

3378 

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)) 

3384 

3385 # Associate this label with a particular index 

3386 fn_idx = self.peek_idx() 

3387 self.labels[node.label] = fn_idx 

3388 

3389 label_idx = len(parent_gromet_fn.bf) 

3390 vars = self.symtab_variables() 

3391 

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) 

3398 

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) 

3402 

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 ) 

3414 

3415 parent_gromet_fn.bf = insert_gromet_object( 

3416 parent_gromet_fn.bf, pack_bf 

3417 ) 

3418 

3419 pack_index = len(parent_gromet_fn.bf) 

3420 

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) 

3426 

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) 

3431 

3432 parent_gromet_fn.wff = insert_gromet_object( 

3433 parent_gromet_fn.wff, GrometWire(src=pif_idx, tgt=len(parent_gromet_fn.pof)) 

3434 ) 

3435 

3436 

3437 parent_gromet_fn.pof = insert_gromet_object( 

3438 parent_gromet_fn.pof, GrometPort(box=pack_index) 

3439 ) 

3440 

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 ) 

3454 

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 ) 

3465 

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 ) 

3479 

3480 # Perhaps we may need to return something in the future 

3481 # an idea: the index of where this exists 

3482 

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) 

3503 

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 ) 

3514 

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 ) 

3519 

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 ) 

3527 

3528 # Step 3 

3529 self.visit(node.expr, gromet_predicate_fn, node) # visit condition 

3530 

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 ) 

3543 

3544 return condition_array_idx 

3545 

3546 def loop_create_body(self, node, parent_gromet_fn, parent_cast_node): 

3547 """ 

3548 Creates a body FN for a loop 

3549 

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() 

3553 

3554 ref = node.body[0].source_refs[0] 

3555 metadata = self.insert_metadata(self.create_source_code_reference(ref)) 

3556 

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() 

3567 

3568 body_array_idx = len(self.gromet_module.fn_array) 

3569 var_environment = self.symtab_variables() 

3570 

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(): 

3573 

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"]) 

3580 

3581 var_environment["args"] = {} 

3582 

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 ) 

3600 

3601 self.handle_function_def( 

3602 AnnCastFunctionDef(None, None, None, None), 

3603 gromet_body_fn, 

3604 node.body, 

3605 ) 

3606 

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 

3612 

3613 return body_array_idx 

3614 

3615 def loop_create_post(self, node, parent_gromet_fn, parent_cast_node): 

3616 # TODO 

3617 pass 

3618 

3619 @_visit.register 

3620 def visit_loop(self, node: AnnCastLoop, parent_gromet_fn, parent_cast_node): 

3621 var_environment = self.symtab_variables() 

3622 

3623 # Create empty gromet box loop that gets filled out before 

3624 # being added to the parent gromet_fn 

3625 gromet_bl = GrometBoxLoop() 

3626 

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 ) 

3631 

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)) 

3636 

3637 parent_gromet_fn.pil = insert_gromet_object( 

3638 parent_gromet_fn.pil, 

3639 pil_port, 

3640 ) 

3641 

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 ) 

3669 

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() 

3677 

3678 pre_array_idx = len(self.gromet_module.fn_array) 

3679 

3680 gromet_pre_fn.b = insert_gromet_object( 

3681 gromet_pre_fn.b, 

3682 GrometBoxFunction(function_type=FunctionType.FUNCTION), 

3683 ) 

3684 

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"] = {} 

3691 

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 ) 

3696 

3697 var_environment["args"][val] = ( 

3698 val, 

3699 gromet_pre_fn.opi[-1], 

3700 len(gromet_pre_fn.opi), 

3701 ) 

3702 

3703 gromet_pre_fn.opo = insert_gromet_object( 

3704 gromet_pre_fn.opo, GrometPort(name=val, box=pre_array_idx) 

3705 ) 

3706 

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 ) 

3714 

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 

3722 

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 ) 

3735 

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 ) 

3746 

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 ) 

3757 

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 

3767 

3768 var_environment["args"] = var_args_copy 

3769 var_environment["local"] = var_local_copy 

3770 

3771 gromet_bl.pre = pre_array_idx 

3772 

3773 ######### Loop Condition 

3774 

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] 

3780 

3781 # NOTE: gromet_bl and gromet_bc store indicies into the fn_array directly now 

3782 gromet_bl.condition = condition_array_idx 

3783 

3784 ######### Loop Body 

3785 

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 

3793 

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() 

3801 

3802 post_array_idx = len(self.gromet_module.fn_array) 

3803 

3804 gromet_post_fn.b = insert_gromet_object( 

3805 gromet_post_fn.b, 

3806 GrometBoxFunction(function_type=FunctionType.FUNCTION), 

3807 ) 

3808 

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"] = {} 

3815 

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 ) 

3821 

3822 var_environment["args"][val] = ( 

3823 val, 

3824 gromet_post_fn.opi[-1], 

3825 len(gromet_post_fn.opi), 

3826 ) 

3827 

3828 gromet_post_fn.opo = insert_gromet_object( 

3829 gromet_post_fn.opo, 

3830 GrometPort(name=val, box=post_array_idx), 

3831 ) 

3832 

3833 for line in node.post: 

3834 self.visit( 

3835 line, 

3836 gromet_post_fn, 

3837 AnnCastFunctionDef(None, None, None, None), 

3838 ) 

3839 

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 

3842 

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 

3852 

3853 var_environment["args"] = var_args_copy 

3854 var_environment["local"] = var_local_copy 

3855 

3856 gromet_bl.post = post_array_idx 

3857 

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 ) 

3870 

3871 @_visit.register 

3872 def visit_model_break( 

3873 self, node: AnnCastModelBreak, parent_gromet_fn, parent_cast_node 

3874 ): 

3875 pass 

3876 

3877 @_visit.register 

3878 def visit_model_continue( 

3879 self, node: AnnCastModelContinue, parent_gromet_fn, parent_cast_node 

3880 ): 

3881 pass 

3882 

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() 

3892 

3893 condition_array_index = len(self.gromet_module.fn_array) 

3894 

3895 gromet_predicate_fn.b = insert_gromet_object( 

3896 gromet_predicate_fn.b, 

3897 GrometBoxFunction(function_type=FunctionType.PREDICATE), 

3898 ) 

3899 

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 ) 

3906 

3907 gromet_predicate_fn.opo = insert_gromet_object( 

3908 gromet_predicate_fn.opo, 

3909 GrometPort(name=val, box=len(gromet_predicate_fn.b)), 

3910 ) 

3911 

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 

3922 

3923 self.visit(node.expr, gromet_predicate_fn, node) 

3924 

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 ) 

3929 

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 ) 

3936 

3937 gromet_predicate_fn.poc = insert_gromet_object( 

3938 gromet_predicate_fn.poc, 

3939 GrometPort(box=len(gromet_predicate_fn.bc)), 

3940 ) 

3941 

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 ) 

3972 

3973 ref = node.expr.source_refs[0] 

3974 metadata = self.insert_metadata(self.create_source_code_reference(ref)) 

3975 gromet_predicate_fn.metadata = metadata 

3976 

3977 return condition_array_index 

3978 

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() 

3991 

3992 body_if_idx = len(self.gromet_module.fn_array) 

3993 

3994 ref = node.body[0].source_refs[0] 

3995 var_environment = self.symtab_variables() 

3996 

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() 

4009 

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 ) 

4018 

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"]) 

4023 

4024 var_environment["args"] = {} 

4025 

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 ) 

4039 

4040 body_if_fn.opo = insert_gromet_object( 

4041 body_if_fn.opo, GrometPort(name=val, box=len(body_if_fn.b)) 

4042 ) 

4043 

4044 self.handle_function_def( 

4045 AnnCastFunctionDef(None, None, None, None), body_if_fn, node.body 

4046 ) 

4047 

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 ) 

4060 

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 ) 

4081 

4082 # restore previous var environments 

4083 var_environment["args"] = previous_func_def_args 

4084 var_environment["local"] = previous_local_args 

4085 

4086 return body_if_idx 

4087 

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() 

4100 

4101 orelse_if_idx = len(self.gromet_module.fn_array) 

4102 ref = node.orelse[0].source_refs[0] 

4103 var_environment = self.symtab_variables() 

4104 

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() 

4116 

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 ) 

4125 

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"]) 

4130 

4131 var_environment["args"] = {} 

4132 

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 ) 

4146 

4147 orelse_if_fn.opo = insert_gromet_object( 

4148 orelse_if_fn.opo, GrometPort(name=val, box=len(orelse_if_fn.b)) 

4149 ) 

4150 

4151 self.handle_function_def( 

4152 AnnCastFunctionDef(None, None, None, None), 

4153 orelse_if_fn, 

4154 node.orelse, 

4155 ) 

4156 

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 ) 

4171 

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 ) 

4194 

4195 # restore previous var environments 

4196 var_environment["args"] = previous_func_def_args 

4197 var_environment["local"] = previous_local_args 

4198 

4199 return orelse_if_idx 

4200 

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) 

4208 

4209 parent_gromet_fn.bc = insert_gromet_object( 

4210 parent_gromet_fn.bc, gromet_bc 

4211 ) 

4212 

4213 bc_index = len(parent_gromet_fn.bc) 

4214 

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 ) 

4220 

4221 parent_gromet_fn.poc = insert_gromet_object( 

4222 parent_gromet_fn.poc, 

4223 GrometPort(name=val, box=len(parent_gromet_fn.bc)), 

4224 ) 

4225 

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 ) 

4272 

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 ) 

4292 

4293 gromet_bc.condition = self.if_create_condition( 

4294 node, parent_gromet_fn, parent_cast_node 

4295 ) 

4296 

4297 ########### If true generation 

4298 gromet_bc.body_if = self.if_create_body( 

4299 node, parent_gromet_fn, parent_cast_node 

4300 ) 

4301 

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 ) 

4309 

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 """ 

4320 

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 ) 

4327 

4328 bf_idx = len(parent_gromet_fn.bf) 

4329 

4330 parent_gromet_fn.pof = insert_gromet_object( 

4331 parent_gromet_fn.pof, GrometPort(name=symbol, box=bf_idx) 

4332 ) 

4333 

4334 pof_idx = len(parent_gromet_fn.pof) 

4335 

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 ) 

4343 

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 

4352 

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 ) 

4371 

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 ) 

4387 

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] 

4396 

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)))) 

4424 

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 

4430 

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) 

4435 

4436 file_name = node.source_refs[0].source_file_name 

4437 var_environment = self.symtab_variables() 

4438 var_environment["global"] = {} 

4439 

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() 

4444 

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 

4449 

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 ) 

4465 

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 ) 

4477 

4478 # Module level GroMEt FN sits in its own special field dicating the module node 

4479 self.gromet_module.fn = new_gromet 

4480 

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 ) 

4485 

4486 self.build_function_arguments_table(node.body) 

4487 

4488 self.visit_node_list(node.body, new_gromet, node) 

4489 

4490 var_environment["global"] = {} 

4491 

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 

4497 

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 ) 

4504 

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 

4512 

4513 self.symbol_table["records"][record_name] = record_name 

4514 var_environment = self.symtab_variables() 

4515 

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 

4523 

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() 

4529 

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"]) 

4535 

4536 var_environment["args"] = {} 

4537 

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 ) 

4557 

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 ) 

4570 

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 ) 

4582 

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) 

4589 

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 ) 

4598 

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 ) 

4643 

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 ) 

4655 

4656 # pof for "new_Record" 

4657 new_gromet.pof = insert_gromet_object( 

4658 new_gromet.pof, GrometPort(box=new_record_idx) 

4659 ) 

4660 

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 

4669 

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) 

4677 

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 ) 

4688 

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 ) 

4700 

4701 var_pof = len(new_gromet.pof) 

4702 

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 ) 

4714 

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 ) 

4733 

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 ) 

4742 

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) 

4748 

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 

4761 

4762 new_gromet.wfopi = insert_gromet_object( 

4763 new_gromet.wfopi, 

4764 GrometWire(src=set_third_arg, tgt=opi_i), 

4765 ) 

4766 

4767 else: 

4768 # The visitor sets a pof that we have to wire 

4769 self.visit(s.right, new_gromet, parent_cast_node) 

4770 

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 ) 

4777 

4778 # Output port for "set" 

4779 new_gromet.pof = insert_gromet_object( 

4780 new_gromet.pof, GrometPort(box=record_set_idx) 

4781 ) 

4782 

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 ) 

4788 

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 ) 

4795 

4796 var_environment["args"] = deepcopy(arg_env_copy) 

4797 var_environment["local"] = deepcopy(local_env_copy) 

4798 

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"] = {} 

4805 

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() 

4812 

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 ) 

4821 

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 ) 

4836 

4837 for s in f.body: 

4838 self.visit( 

4839 s, 

4840 new_gromet, 

4841 AnnCastFunctionDef(None, None, None, None), 

4842 ) 

4843 

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 ) 

4856 

4857 var_environment["args"] = deepcopy(arg_env_copy) 

4858 var_environment["local"] = deepcopy(local_env_copy) 

4859 

4860 self.record[node.name][f.name.name] = len( 

4861 self.gromet_module.fn_array 

4862 ) 

4863 

4864 record_metadata = ProgramAnalysisRecordBookkeeping( 

4865 provenance=generate_provenance(), 

4866 type_name=record_name, 

4867 field_declarations=record_fields, 

4868 method_declarations=record_methods, 

4869 ) 

4870 

4871 self.insert_record_info(record_metadata) 

4872 

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) 

4878 

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)