import { Node, ProcedureDeclaration, Program, ProcedureCall, ClassCall, IdentifierExpression, AssignDestination, VariableDeclarator, ProcedureParameter } from "@pseuco/lang"; import { TypeCheckCoordinator } from "./typeCheck/TypeCheckCoordinator"; import { PseucoError } from "./PseucoError"; import { Type } from "./typeCheck/types/Type"; import { TypeEnvironment } from "./typeCheck/environments/TypeEnvironment"; import { PseucoWarning } from "./PseucoWarning"; export class StaticSemantics { private typeCheckCoordinator: TypeCheckCoordinator; private typeCheckResult: true | ReadonlyArray | undefined = undefined; /** * Get a mapping from all AST nodes to their inferred types. */ get allNodesAndTypes(): ReadonlyArray<[Node, Type]> { if(this.checkTypes() !== true) { throw Error("allNodesAndTypes can be queried only for well-typed programs."); } return this.typeCheckCoordinator.allNodesAndTypes; } /** * * @param program The root AST node of the program for which the static semantics analysis shall be performed. */ constructor(readonly program: Program) { this.typeCheckCoordinator = new TypeCheckCoordinator(program); } /** * Get a list of all type errors that occurred during type-checking. Returns `undefined` if the type analysis was not started yet (use `checkTypes()` in this case). */ get typeErrors(): ReadonlyArray | undefined { if (this.typeCheckResult === undefined) { return undefined; } else { return this.typeCheckCoordinator.typeErrors; } } /** * Get a list of all type warning that occurred during type-checking. Returns `undefined` if the type analysis was not started yet (use `checkTypes()` in this case). */ get typeWarnings(): ReadonlyArray | undefined { if (this.typeCheckResult === undefined) { return undefined; } else { return this.typeCheckCoordinator.typeWarnings; } } /** * Checks if the program is well-typed. The result is cached, i.e., successive calls do not perform a full static analysis. * @returns true, if the program is well-typed, otherwise false */ public checkTypes(): boolean { if (this.typeCheckResult != undefined) { return this.typeCheckResult === true; } try { this.typeCheckCoordinator.typeForNode(this.program); this.typeCheckResult = true; return true; } catch (error: any) { if (this.typeCheckCoordinator.typeErrors.length == 0) { console.error("Caught error is not in the list of type errors!"); console.dir(error); throw error; } this.typeCheckResult = this.typeCheckCoordinator.typeErrors; return false; } } /** * Access the types inferred for AST nodes during the type-checking process. * @param node The AST node for which the type information is requested. * @returns The type inferred for this node, if the program is well-typed. */ public typeForNode(node: Node): Type { if(this.checkTypes() !== true) { throw Error("Types can be queried only for well-typed programs."); } return this.typeCheckCoordinator.typeForNode(node); } /** * Access the type environment in which a node's types were checked. * @param node The AST node for which the type environment is requested. * @returns The type environment associated with the node. */ public environmentForNode(node: Node): TypeEnvironment { if(this.checkTypes() !== true) { throw Error("Environments can be queried only for well-typed programs."); } return this.typeCheckCoordinator.environmentForNode(node); } // Auxiliary methods /** * Get the procedure declaration of a called procedure. * @param node A procedure call in the AST. * @returns The procedure declaration of the called procedure. */ public procedureDeclarationForProcedureCall(node: ProcedureCall): ProcedureDeclaration { const env = this.environmentForNode(node); const decl = env.getDeclarationForProcedureIdentifier(node.procedureName); if (decl == undefined) { throw Error("Unknown procedure name."); } return decl; } /** * Get the AST node declaring a variable that gets assigned a new value. * @param node The AST node representing the left-hand side of an assignment. * @returns The declaration of the corresponding variable or procedure parameter declaration that defined the variable. */ public variableDeclarationForAssignDestination(node: AssignDestination): VariableDeclarator | ProcedureParameter { const env = this.environmentForNode(node); const decl = env.getDeclarationForVariableIdentifier(node.identifier); if (decl == undefined) { throw Error("Unknown variable identifier."); } return decl; } /** * Get the AST node declaring a variable represented by an identifier expression. * @param node The AST node using the variable. * @returns The declaration of the corresponding variable or procedure parameter declaration that defined the variable. */ public variableDeclarationForIdentifierExpression(node: IdentifierExpression): VariableDeclarator | ProcedureParameter { const env = this.environmentForNode(node); const decl = env.getDeclarationForVariableIdentifier(node.identifier); if (decl == undefined) { throw Error("Unknown variable identifier."); } return decl; } }