Coverage for skema/program_analysis/CAST2FN/model/cast_to_air_model.py: 49%

354 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-30 17:15 +0000

1from skema.program_analysis.CAST2FN.model.cast import source_ref 

2from skema.program_analysis.CAST2FN.model.cast.source_ref import SourceRef 

3from typing import List, Dict, NoReturn, Set 

4from enum import Enum 

5from dataclasses import dataclass 

6from datetime import datetime 

7 

8from skema.program_analysis.CAST2FN.model.cast import AstNode, var 

9from skema.model_assembly.metadata import ( 

10 BaseMetadata, 

11 MetadataType, 

12 TypedMetadata, 

13 VariableFromSource, 

14) 

15 

16 

17class C2ATypeError(TypeError): 

18 """ 

19 Used to create exceptions during the CAST to AIR execution 

20 

21 Args: 

22 Exception: An exception that occured during CAST to AIR execution 

23 """ 

24 

25 pass 

26 

27 

28class C2ARuntimeError(Exception): 

29 """ 

30 Used for any runtime errors that occur during CAST --> AIR processing 

31 

32 Args: 

33 Exception: An exception that occured during CAST to AIR execution 

34 """ 

35 

36 pass 

37 

38 

39class C2ANameError(NameError): 

40 """ 

41 Used when name errors occur (such as a missing member variable for some 

42 object) during CAST 

43 

44 Args: 

45 Exception: An exception that occured during CAST to AIR execution 

46 """ 

47 

48 pass 

49 

50 

51class C2AValueError(Exception): 

52 """ 

53 Used when an operation cannot be performed for a given value during CAST 

54 

55 Args: 

56 Exception: An exception that occured during CAST to AIR execution 

57 """ 

58 

59 pass 

60 

61 

62class C2AException(Exception): 

63 """ 

64 Used to create exceptions during the CAST to AIR execution 

65 

66 Args: 

67 Exception: An exception that occured during CAST to AIR execution 

68 """ 

69 

70 pass 

71 

72 

73class C2AIdentifierType(str, Enum): 

74 UNKNOWN = "unknown" 

75 VARIABLE = "variable" 

76 CONTAINER = "container" 

77 LAMBDA = "lambda" 

78 DECISION = "decision" 

79 PACK = "pack" 

80 EXTRACT = "extract" 

81 

82 

83@dataclass(repr=True, frozen=True) 

84class C2AIdentifierInformation(object): 

85 

86 name: str 

87 scope: List[str] 

88 module: str 

89 identifier_type: C2AIdentifierType 

90 

91 def build_identifier(self): 

92 return f'@{self.identifier_type}::{self.module}::{".".join(self.scope)}::{self.name}' 

93 

94 

95@dataclass(repr=True, frozen=True) 

96class C2ASourceRef(object): 

97 """ 

98 Represents a reference point of the containing object in the original source 

99 code. If a field of the line/col reference information is missing, it will 

100 hold the value -1. 

101 """ 

102 

103 file: str 

104 line_begin: int 

105 col_start: int 

106 line_end: int 

107 col_end: int 

108 

109 def to_AIR(self): 

110 return self.__dict__ 

111 

112 

113class C2AVariable(object): 

114 

115 identifier_information: C2AIdentifierInformation 

116 version: int 

117 type_name: str 

118 source_ref: C2ASourceRef 

119 metadata: list 

120 

121 def __init__( 

122 self, 

123 identifier_information: C2AIdentifierInformation, 

124 version: int, 

125 type_name: str, 

126 source_ref: C2ASourceRef, 

127 ): 

128 self.identifier_information = identifier_information 

129 self.version = version 

130 self.type_name = type_name 

131 self.source_ref = source_ref 

132 self.metadata = list() 

133 

134 def get_name(self): 

135 return self.identifier_information.name 

136 

137 def build_identifier(self): 

138 """ 

139 Builds the variable identifier which uses the identifier from identifier 

140 information plus the variable version 

141 

142 Returns: 

143 str: Unique variable identifier 

144 """ 

145 return f"{self.identifier_information.build_identifier()}::{str(self.version)}" 

146 

147 def add_metadata(self, data: BaseMetadata): 

148 self.metadata.append(data) 

149 

150 def to_AIR(self): 

151 # TODO 

152 domain = { 

153 "type": "type", # TODO what is this field? 

154 "mutable": False, # TODO probably only mutable if object/list/dict type 

155 } 

156 if self.type_name == "Number": 

157 domain["name"] = "integer" 

158 elif self.type_name.startswith("object$"): 

159 name = self.type_name.split("object$")[-1] 

160 domain.update( 

161 { 

162 "name": "object", 

163 "object_name": name, 

164 } 

165 ) 

166 else: 

167 domain["name"] = self.type_name 

168 

169 has_from_source_metadata = False 

170 for m in self.metadata: 

171 if m.type == MetadataType.FROM_SOURCE: 

172 has_from_source_metadata = True 

173 break 

174 # If from_source does not exist already, then it wasnt handled by a special 

175 # case where we added a variable during processing, so add True from_source 

176 # metadata 

177 if not has_from_source_metadata: 

178 self.add_metadata( 

179 TypedMetadata.from_data( 

180 { 

181 "type": "FROM_SOURCE", 

182 "provenance": { 

183 "method": "PROGRAM_ANALYSIS_PIPELINE", 

184 "timestamp": datetime.now(), 

185 }, 

186 "from_source": True, 

187 "creation_reason": "UNKNOWN", 

188 } 

189 ) 

190 ) 

191 

192 return { 

193 "name": self.build_identifier(), 

194 "source_refs": [self.source_ref.to_AIR()], 

195 "domain": domain, 

196 "domain_constraint": "(and (> v -infty) (< v infty))", # TODO 

197 "metadata": [m.to_dict() for m in self.metadata], 

198 } 

199 

200 

201class C2ALambdaType(str, Enum): 

202 UNKNOWN = "unknown" 

203 ASSIGN = "assign" 

204 CONDITION = "condition" 

205 DECISION = "decision" 

206 EXIT = "exit" 

207 RETURN = "return" 

208 CONTAINER = "container" 

209 OPERATOR = "operator" 

210 EXTRACT = "extract" 

211 PACK = "pack" 

212 

213 

214def build_unique_list_with_order(l, predicate): 

215 new_list = [] 

216 for i in l: 

217 res = predicate(i) 

218 if res not in new_list: 

219 new_list.append(res) 

220 return new_list 

221 

222 

223@dataclass(repr=True, frozen=False) 

224class C2ALambda(object): 

225 """ 

226 Represents an executable container/ function to transition between states in AIR 

227 

228 lambda, container, if-block, function 

229 """ 

230 

231 # Identifying information for lambda function 

232 identifier_information: C2AIdentifierInformation 

233 # Represents the variables coming into a lambda or container 

234 input_variables: List[C2AVariable] 

235 # Represents the new versions of variables that are created and output 

236 output_variables: List[C2AVariable] 

237 # Represents variables that were updated (typically list/dict/object with fields changed) 

238 updated_variables: List[C2AVariable] 

239 # The type of the container. 

240 container_type: C2ALambdaType 

241 # The reference to the source code that this lambda was derived from 

242 source_ref: C2ASourceRef 

243 

244 def build_name(self): 

245 var = None 

246 # TODO how should we build the name if there is multiple updated/output vars? 

247 # Will this situation be possible? 

248 if len(self.output_variables) > 0: 

249 var = self.output_variables[0] 

250 elif len(self.updated_variables) > 0: 

251 var = self.updated_variables[0] 

252 else: 

253 raise C2AException(f"No variables output or updated by lambda") 

254 

255 return ( 

256 f"{self.identifier_information.module}" 

257 f"__{'.'.join(self.identifier_information.scope)}" 

258 f"__{self.container_type}" 

259 f"__{var.identifier_information.name}" 

260 f"__{var.version}" 

261 ) 

262 

263 def to_AIR(self): 

264 return self 

265 

266 

267@dataclass(repr=True, frozen=False) 

268class C2AExpressionLambda(C2ALambda): 

269 """ 

270 A type of function within AIR that represents an executable lambda expression that transitions 

271 between states of the data flow of the program 

272 """ 

273 

274 lambda_expr: str 

275 cast: AstNode 

276 

277 def to_AIR(self): 

278 return { 

279 "function": { 

280 "name": self.build_name(), 

281 "type": "lambda", 

282 "code": self.lambda_expr, 

283 }, 

284 "input": build_unique_list_with_order( 

285 self.input_variables, lambda v: v.build_identifier() 

286 ), 

287 "output": build_unique_list_with_order( 

288 self.output_variables, lambda v: v.build_identifier() 

289 ), 

290 "updated": build_unique_list_with_order( 

291 self.updated_variables, lambda v: v.build_identifier() 

292 ), 

293 "source_ref": self.source_ref.to_AIR(), 

294 "metadata": [], 

295 } 

296 

297 

298@dataclass(repr=True, frozen=False) 

299class C2AContainerCallLambda(C2ALambda): 

300 """ 

301 Represents the call/passing to another container found in the body of a container definition 

302 """ 

303 

304 def build_name(self): 

305 return self.identifier_information.build_identifier() 

306 

307 def to_AIR(self): 

308 return { 

309 "function": { 

310 "name": self.identifier_information.build_identifier(), 

311 "type": self.container_type.value, 

312 }, 

313 # Note: Do not build a unique list because the same var could be 

314 # passed in multiple times 

315 "input": [v.build_identifier() for v in self.input_variables], 

316 "output": build_unique_list_with_order( 

317 self.output_variables, lambda v: v.build_identifier() 

318 ), 

319 "updated": build_unique_list_with_order( 

320 self.updated_variables, lambda v: v.build_identifier() 

321 ), 

322 "source_ref": self.source_ref.to_AIR(), 

323 "metadata": [], 

324 } 

325 

326 

327@dataclass(repr=True, frozen=False) 

328class C2AContainerDef(object): 

329 """ 

330 Represents a top level AIR container def. Has its arguments, outputs/ updates, and a body 

331 

332 lambda, container, if-block, function 

333 """ 

334 

335 # Name of the containrt 

336 identifier_information: C2AIdentifierInformation 

337 # Represents the variables coming into a lambda or container 

338 arguments: List[C2AVariable] 

339 # Represents the new versions of variables that are created and output 

340 output_variables: List[C2AVariable] 

341 # Represents variables that were updated (typically list/dict/object with fields changed) 

342 updated_variables: List[C2AVariable] 

343 # Represents the executable body statements 

344 body: List[C2ALambda] 

345 # Defines the span of code for this container body in the original source code 

346 body_source_ref: C2ASourceRef 

347 # Tracks what variables were added as arguments to container from previous scope 

348 vars_from_previous_scope: List[C2AVariable] 

349 

350 def build_identifier(self): 

351 return self.identifier_information.build_identifier() 

352 

353 def to_AIR(self): 

354 return self 

355 

356 def add_arguments(self, arguments_to_add: List[C2AVariable]): 

357 for v in arguments_to_add: 

358 if v not in set(self.arguments): 

359 self.arguments.append(v) 

360 

361 def add_outputs(self, output_variables_to_add: List[C2AVariable]): 

362 # self.output_variables.update(set(output_variables_to_add)) 

363 for v in output_variables_to_add: 

364 if v not in set(self.output_variables): 

365 self.output_variables.append(v) 

366 

367 def add_updated(self, updated_variables_to_add: List[C2AVariable]): 

368 # self.updated_variables.update(set(updated_variables_to_add)) 

369 for v in updated_variables_to_add: 

370 if v not in set(self.updated_variables): 

371 self.updated_variables.append(v) 

372 

373 def add_body_lambdas(self, body_to_add: List[C2ALambda]): 

374 self.body.extend(body_to_add) 

375 

376 def add_body_source_ref(self, body_source_ref: SourceRef): 

377 self.body_source_ref = body_source_ref 

378 

379 def add_var_used_from_previous_scope(self, var): 

380 self.vars_from_previous_scope.append(var) 

381 

382 

383@dataclass(repr=True, frozen=False) 

384class C2AFunctionDefContainer(C2AContainerDef): 

385 """ 

386 Represents a top level container definition. Input variables will represent the arguments to the funciton in the AIR. Also contains a body. 

387 """ 

388 

389 return_type_name: str 

390 

391 def to_AIR(self): 

392 return { 

393 # TODO 

394 "name": self.identifier_information.build_identifier(), 

395 "source_refs": [], 

396 "type": "function", 

397 "arguments": build_unique_list_with_order( 

398 self.arguments, lambda v: v.build_identifier() 

399 ), 

400 "updated": build_unique_list_with_order( 

401 self.updated_variables, lambda v: v.build_identifier() 

402 ), 

403 "return_value": build_unique_list_with_order( 

404 self.output_variables, lambda v: v.build_identifier() 

405 ), 

406 "body": [i.to_AIR() for i in self.body], 

407 "body_source_ref": self.body_source_ref.to_AIR(), 

408 "metadata": [], 

409 } 

410 

411 

412@dataclass(repr=True, frozen=False) 

413class C2ALoopContainer(C2AContainerDef): 

414 """ 

415 Represents a top level container definition. Input variables will represent 

416 the arguments that go through the loop interface. Also contains a body. 

417 """ 

418 

419 # Represents the reference to the source code where the conditional for this loop is 

420 condition_source_ref: C2ASourceRef 

421 

422 def to_AIR(self): 

423 return { 

424 # TODO 

425 "name": self.identifier_information.build_identifier(), 

426 "source_refs": [], 

427 "type": "loop", 

428 "arguments": build_unique_list_with_order( 

429 self.arguments, lambda v: v.build_identifier() 

430 ), 

431 "updated": build_unique_list_with_order( 

432 self.updated_variables, lambda v: v.build_identifier() 

433 ), 

434 "return_value": build_unique_list_with_order( 

435 self.output_variables, lambda v: v.build_identifier() 

436 ), 

437 "body": [i.to_AIR() for i in self.body], 

438 "body_source_ref": self.body_source_ref.to_AIR(), 

439 "condition_source_ref": self.condition_source_ref.to_AIR(), 

440 "metadata": [], 

441 } 

442 

443 

444@dataclass(repr=True, frozen=False) 

445class C2AIfContainer(C2AContainerDef): 

446 """ 

447 Represents a top level container definition. Input variables will represent 

448 the arguments that go through the if interface. Also contains a body. 

449 """ 

450 

451 # Represents the reference to the source code where the conditional for this if is 

452 condition_source_ref: C2ASourceRef 

453 # Output vars per each condition in the if block. Represent else condition 

454 # as condition number -1. 

455 output_per_condition: Dict[int, List[C2AVariable]] 

456 

457 def add_condition_outputs(self, condition_num, outputs): 

458 if condition_num not in self.output_per_condition: 

459 self.output_per_condition[condition_num] = [] 

460 self.output_per_condition[condition_num].extend(outputs) 

461 

462 def to_AIR(self): 

463 

464 return { 

465 # TODO 

466 "name": self.identifier_information.build_identifier(), 

467 "source_refs": [], 

468 "type": "if-block", 

469 "arguments": build_unique_list_with_order( 

470 self.arguments, lambda v: v.build_identifier() 

471 ), 

472 "updated": build_unique_list_with_order( 

473 self.updated_variables, lambda v: v.build_identifier() 

474 ), 

475 "return_value": build_unique_list_with_order( 

476 self.output_variables, lambda v: v.build_identifier() 

477 ), 

478 "body": [i.to_AIR() for i in self.body], 

479 "body_source_ref": self.body_source_ref.to_AIR(), 

480 "condition_source_ref": self.condition_source_ref.to_AIR(), 

481 "metadata": [], 

482 } 

483 

484 

485@dataclass(repr=True, frozen=True) 

486class C2ATypeDef(object): 

487 class C2AType(str, Enum): 

488 INTEGER = "integer" 

489 FLOAT = "float" 

490 STRING = "string" 

491 LIST = "list" 

492 DICT = "dict" 

493 SET = "set" 

494 OBJECT = "object" 

495 

496 name: str 

497 given_type: C2AType 

498 fields: Dict[str, C2AVariable] 

499 function_identifiers: List[str] 

500 source_ref: C2ASourceRef 

501 

502 def to_AIR(self): 

503 air = self.__dict__ 

504 air.update({"metadata": [], "metatype": "composite"}) 

505 return air 

506 

507 

508class C2AAttributeAccessState(object): 

509 

510 var_to_current_extract_node: Dict[str, C2ALambda] 

511 var_to_current_pack_node: Dict[str, C2ALambda] 

512 

513 def __init__(self): 

514 self.var_to_current_extract_node = {} 

515 self.var_to_current_pack_node = {} 

516 

517 def need_attribute_extract(self, var, attr_var): 

518 # Check if the attr_var name appears in either the extract node for the 

519 # var or the current pack var. If it exists in either, we should not 

520 # add the same attribute for extract. 

521 vars_to_check = ( 

522 self.var_to_current_extract_node[var].output_variables 

523 if var in self.var_to_current_extract_node 

524 else [] 

525 ) + ( 

526 self.var_to_current_pack_node[var].input_variables 

527 if var in self.var_to_current_pack_node 

528 else [] 

529 ) 

530 

531 return not any( 

532 [ 

533 v.identifier_information.name 

534 == attr_var.identifier_information.name 

535 for v in vars_to_check 

536 ] 

537 ) 

538 

539 def build_extract_lambda(self, extract_var, output_variables): 

540 obj_var_name = extract_var.identifier_information.name 

541 lambda_dict_keys = [ 

542 v.identifier_information.name.split("_", 1)[1] 

543 for v in output_variables 

544 ] 

545 lambda_dict_accesses = ",".join( 

546 [ 

547 f'{obj_var_name}["{v}"]' 

548 for v in lambda_dict_keys 

549 if obj_var_name != v 

550 ] 

551 ) 

552 lambda_expr = f"lambda {obj_var_name}: ({lambda_dict_accesses})" 

553 return lambda_expr 

554 

555 def add_attribute_access(self, var, attr_var): 

556 extract_lambda = self.var_to_current_extract_node.get(var, None) 

557 if extract_lambda is None: 

558 id = var.identifier_information 

559 extract_lambda = C2AExpressionLambda( 

560 C2AIdentifierInformation( 

561 "EXTRACT", id.scope, id.module, C2AIdentifierType.CONTAINER 

562 ), 

563 [var], 

564 [attr_var], 

565 [], 

566 C2ALambdaType.EXTRACT, 

567 C2ASourceRef("", None, None, None, None), 

568 self.build_extract_lambda(var, [attr_var]), 

569 None, 

570 ) 

571 self.var_to_current_extract_node[var] = extract_lambda 

572 return extract_lambda 

573 

574 extract_lambda.output_variables.append(attr_var) 

575 extract_lambda.lambda_expr = self.build_extract_lambda( 

576 var, extract_lambda.output_variables 

577 ) 

578 

579 def add_attribute_to_pack(self, var, attr_var): 

580 pack_lambda = self.var_to_current_pack_node.get(var, None) 

581 if pack_lambda is None: 

582 id = var.identifier_information 

583 pack_lambda = C2AExpressionLambda( 

584 C2AIdentifierInformation( 

585 "PACK", id.scope, id.module, C2AIdentifierType.CONTAINER 

586 ), 

587 [var], 

588 [], 

589 [], 

590 C2ALambdaType.PACK, 

591 C2ASourceRef("", None, None, None, None), 

592 "", # will be filled out when "get_outstandin_pack_node" is called 

593 None, 

594 ) 

595 

596 for v in pack_lambda.input_variables: 

597 if ( 

598 v.identifier_information.name 

599 == attr_var.identifier_information.name 

600 ): 

601 pack_lambda.input_variables.remove(v) 

602 pack_lambda.input_variables.append(attr_var) 

603 

604 self.var_to_current_pack_node[var] = pack_lambda 

605 

606 def has_outstanding_pack_nodes(self): 

607 return bool(self.var_to_current_pack_node) 

608 

609 def get_outstanding_pack_node(self, var): 

610 pack_lambda = self.var_to_current_pack_node.get(var) 

611 # Add the updated version of the var after packing 

612 new_var = C2AVariable( 

613 var.identifier_information, 

614 var.version + 1, 

615 var.type_name, 

616 var.source_ref, 

617 ) 

618 pack_lambda.output_variables.append(new_var) 

619 

620 # Add the correct lambda now that we have all vars to pack 

621 obj_var_name = var.identifier_information.name 

622 lambda_inputs = [ 

623 v.identifier_information.name for v in pack_lambda.input_variables 

624 ] 

625 lambda_body_dict = ",".join( 

626 [ 

627 f'"{v.split(f"{obj_var_name}_")[-1]}": ' + v 

628 for v in lambda_inputs 

629 if obj_var_name != v 

630 ] 

631 ) 

632 lambda_expr = ( 

633 f"lambda {','.join(lambda_inputs)}:" 

634 f"{{ **{obj_var_name}, **{{ {lambda_body_dict} }} }}" 

635 ) 

636 pack_lambda.lambda_expr = lambda_expr 

637 

638 # Delete from state map upon retrieval 

639 del self.var_to_current_pack_node[var] 

640 if var in self.var_to_current_extract_node: 

641 del self.var_to_current_extract_node[var] 

642 

643 return pack_lambda 

644 

645 def get_outstanding_pack_nodes(self): 

646 return [ 

647 self.get_outstanding_pack_node(k) 

648 for k in self.var_to_current_pack_node.copy().keys() 

649 ] 

650 

651 

652class C2AVariableContext(Enum): 

653 LOAD = 0 

654 STORE = 1 

655 ATTR_VALUE = 2 

656 UNKNOWN = 3 

657 

658 

659class C2AState(object): 

660 containers: List[C2AContainerDef] 

661 variables: List[C2AVariable] 

662 types: List[C2ATypeDef] 

663 scope_stack: List[str] 

664 current_module: str 

665 current_function: C2AFunctionDefContainer 

666 current_conditional: int 

667 attribute_access_state: C2AAttributeAccessState 

668 current_context: C2AVariableContext 

669 

670 def __init__(self): 

671 self.containers = list() 

672 self.variables = list() 

673 self.types = list() 

674 self.scope_stack = [] 

675 self.current_module = "initial" 

676 self.current_function = None 

677 self.current_conditional = 0 

678 self.current_context = C2AVariableContext.UNKNOWN 

679 self.attribute_access_state = C2AAttributeAccessState() 

680 

681 def add_container(self, con: C2AContainerDef): 

682 self.containers.append(con) 

683 

684 def add_variable(self, var: C2AVariable): 

685 self.variables.append(var) 

686 

687 def add_type(self, type: C2ATypeDef): 

688 self.types.append(type) 

689 

690 def get_scope_stack(self): 

691 """ 

692 Returns the current scope of the CAST to AIR state 

693 """ 

694 return self.scope_stack.copy() 

695 

696 def push_scope(self, scope): 

697 """ 

698 Places the name scope level name onto the scope stack 

699 """ 

700 self.scope_stack.append(scope) 

701 

702 def pop_scope(self): 

703 """ 

704 Removes the last scope name from the stack and returns it 

705 """ 

706 top = self.scope_stack[-1] 

707 self.scope_stack = self.scope_stack[:-1] 

708 return top 

709 

710 def is_var_identifier_in_variables(self, identifier): 

711 for v in self.variables: 

712 if v.build_identifier() == identifier: 

713 return True 

714 return False 

715 

716 def find_highest_version_var_in_scope(self, var_name, scope): 

717 """ 

718 Given a variable name, finds the highest version defined 

719 for that variable given a scope 

720 """ 

721 # Check that the global/function_name are the same 

722 # TODO define what needs to be checked here better 

723 def share_scope(scope1, scope2): 

724 return scope1 == scope2 

725 

726 instances = [ 

727 v 

728 for v in self.variables 

729 if v.identifier_information.name == var_name 

730 and share_scope(scope, v.identifier_information.scope) 

731 ] 

732 return max(instances, key=lambda v: v.version, default=None) 

733 

734 def find_highest_version_var_in_previous_scopes(self, var_name): 

735 """ 

736 Given a variable name, finds the highest version defined 

737 for that variable along our current scope path 

738 """ 

739 # Subtract one so we look at all scopes except "global" 

740 i = len(self.scope_stack) 

741 while i >= 0: 

742 res = self.find_highest_version_var_in_scope( 

743 var_name, self.scope_stack[:i] 

744 ) 

745 if res is not None: 

746 return res 

747 i -= 1 

748 

749 return None 

750 

751 def find_highest_version_var_in_current_scope(self, var_name): 

752 """ 

753 Given a variable name, finds the highest version defined 

754 for that variable given the current scope 

755 """ 

756 return self.find_highest_version_var_in_scope( 

757 var_name, self.scope_stack 

758 ) 

759 

760 def find_next_var_version(self, var_name): 

761 """ 

762 Determines the next version of a variable given its name and 

763 variables in the current scope. 

764 """ 

765 current_highest_ver = self.find_highest_version_var_in_current_scope( 

766 var_name 

767 ) 

768 return ( 

769 current_highest_ver.version + 1 

770 if current_highest_ver is not None 

771 else -1 

772 ) 

773 

774 def find_container(self, scope): 

775 matching = [ 

776 c 

777 for c in self.containers 

778 if c.identifier_information.scope + [c.identifier_information.name] 

779 == scope 

780 ] 

781 

782 return matching[0] if matching else None 

783 

784 def find_root_level_containers(self): 

785 called_containers = [ 

786 s.identifier_information.build_identifier() 

787 for c in self.containers 

788 for s in c.body 

789 if isinstance(s, C2AContainerCallLambda) 

790 ] 

791 root_containers = [ 

792 c.identifier_information.name 

793 for c in self.containers 

794 if c.build_identifier() not in called_containers 

795 ] 

796 return root_containers 

797 

798 def get_next_conditional(self): 

799 cur_cond = self.current_conditional 

800 self.current_conditional += 1 

801 return cur_cond 

802 

803 def reset_conditional_count(self): 

804 self.current_conditional = 0 

805 

806 def reset_current_function(self): 

807 self.current_function = None 

808 

809 def set_variable_context(self, context): 

810 self.current_context = context 

811 

812 def to_AIR(self): 

813 """ 

814 Translates the model used to translate CAST to AIR into the 

815 final AIR structure. 

816 """ 

817 container_air = [c.to_AIR() for c in self.containers] 

818 var_air = [v.to_AIR() for v in self.variables] 

819 types_air = [t.to_AIR() for t in self.types] 

820 

821 # Trim variables that are just defined and hanging. This seems like a 

822 # bug BUT it is actually a remnant of how GCC gives variable definitions. 

823 all_input_vars = { 

824 v for c in container_air for l in c["body"] for v in l["input"] 

825 } 

826 all_return_vars = {v for c in container_air for v in c["return_value"]} 

827 all_arg_vars = {v for c in container_air for v in c["arguments"]} 

828 # all_vars_into_lambdbas = {*all_input_vars, *all_return_vars, *all_arg_vars} 

829 

830 all_vars_passed_through_lambdas = { 

831 v_name 

832 for c in container_air 

833 for l in c["body"] 

834 for v_name in l["input"] + l["output"] 

835 if len(l["input"]) > 0 

836 } 

837 hanging_vars = [ 

838 v["name"] 

839 for v in var_air 

840 if v["name"] not in all_vars_passed_through_lambdas 

841 ] 

842 

843 def is_hanging_lambda(l, c): 

844 hanging_lambda = len(l["input"]) == 0 and all( 

845 [ 

846 v not in {*all_input_vars, *all_return_vars, *all_arg_vars} 

847 for v in l["output"] 

848 ] 

849 ) 

850 return hanging_lambda 

851 

852 for con in container_air: 

853 hanging_lambda_vars = [ 

854 v 

855 for l in con["body"] 

856 if is_hanging_lambda(l, con) 

857 for v in l["output"] 

858 ] 

859 # Trim variables 

860 var_air = [ 

861 v for v in var_air if v["name"] not in hanging_lambda_vars 

862 ] 

863 if "return_value" in con: 

864 hanging_ret_vars = { 

865 v for v in con["return_value"] if v in hanging_vars 

866 } 

867 lambdas_calling = [ 

868 l 

869 for c in container_air 

870 for l in c["body"] 

871 if l["function"]["type"] == "container" 

872 and l["function"]["name"] == con["name"] 

873 ] 

874 

875 if len(lambdas_calling) == 0: 

876 con["return_value"] = [ 

877 v 

878 for v in con["return_value"] 

879 if v not in hanging_ret_vars 

880 ] 

881 all_return_vars.difference_update(hanging_ret_vars) 

882 var_air = [ 

883 v for v in var_air if v["name"] not in hanging_ret_vars 

884 ] 

885 

886 if "arguments" in con: 

887 hanging_arg_vars = { 

888 v for v in con["arguments"] if v in hanging_vars 

889 } 

890 con["arguments"] = [ 

891 v for v in con["arguments"] if v not in hanging_arg_vars 

892 ] 

893 all_arg_vars.difference_update(hanging_arg_vars) 

894 var_air = [ 

895 v for v in var_air if v["name"] not in hanging_arg_vars 

896 ] 

897 

898 con["body"] = [ 

899 l for l in con["body"] if not is_hanging_lambda(l, con) 

900 ] 

901 

902 return { 

903 "containers": container_air, 

904 "variables": var_air, 

905 "types": types_air, 

906 }