UNPKG

@gobstones/gobstones-parser

Version:
191 lines (174 loc) 5.88 kB
/* eslint-disable camelcase */ /* eslint-disable no-underscore-dangle */ import { N_DefFunction, N_DefInteractiveProgram, N_DefProcedure, N_DefProgram, N_ExprFunctionCall, N_StmtProcedureCall } from './ast'; import { SourceReader } from './reader'; import { Token } from './token'; export class RecursionChecker { /* * Each routine call (i.e. procedure or function call) in the source * code is of the form: * R(e1, ..., en) * where R is the identifier for the routine. * * The token R is called the 'location' of the call. * Observe that the location includes not only the name of the * routine but also its position in the source code. * * The call graph is a dictionary whose keys are strings * and whose values are again dictionaries. * The outer and the inner dictionaries are indexed by routine names * in such a way that: * * _callGraph[F][G] * * is the location of the first call to G inside the body of F. */ private _currentRoutine: any; private _callGraph: Record<string, Record<string, { value: string }>> = {}; /* * If there is a cycle in the call graph (using either procedure calls * or function calls), return a list: * [c1, ..., cn] * where ci is the i-th call involved in a cycle. * A call is of the form: * {caller: F , callee: G, location: L} * where F is the name (string) of the caller, * G is the name (string) of the callee, * and L is the location of the call. * * Otherwise return null. */ public callCycle(ast: { definitions: any[]; startPos: SourceReader; endPos: SourceReader; }): any { /* Build the call graph */ this._visitNode(ast); /* Find a cycle in the call graph */ return this._findCallCycle(); } /* Visitor -- build the call graph */ public _addEdge(caller: string, callee: { value: string }): void { if (!(caller in this._callGraph)) { this._callGraph[caller] = {}; } if (!(callee.value in this._callGraph[caller])) { this._callGraph[caller][callee.value] = callee; } } public _visitNode(node: { definitions: any[]; startPos: SourceReader; endPos: SourceReader; }): void { if (node === undefined || node instanceof Token) { /* Skip */ } else if (node instanceof Array) { this._visitNodes(node); } else { this._visitTaggedNode(node); } } public _visitNodes( nodes: { definitions: any[]; startPos: SourceReader; endPos: SourceReader; }[] ): void { for (const node of nodes) { this._visitNode(node); } } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public _visitTaggedNode(node: any): void { switch (node.tag) { case N_DefProgram: case N_DefInteractiveProgram: this._visitProgramDefinition(); break; case N_DefProcedure: case N_DefFunction: this._visitRoutineDefinition(node); break; case N_StmtProcedureCall: this._visitProcedureCall(node); break; case N_ExprFunctionCall: this._visitFunctionCall(node); break; default: break; } this._visitNodes(node.children); } public _visitProgramDefinition(): void { this._currentRoutine = 'program'; } public _visitRoutineDefinition(node: { name: { value: any } }): void { this._currentRoutine = node.name.value; } public _visitProcedureCall(node: { procedureName: { value: string } }): void { this._addEdge(this._currentRoutine, node.procedureName); } public _visitFunctionCall(node: { functionName: { value: string } }): void { this._addEdge(this._currentRoutine, node.functionName); } /* Find a cycle in the call graph */ public _findCallCycle(): | { caller: any; callee: string; location: { value: string } }[] | { caller: string }[] { const visited = {}; const parents = {}; for (const f in this._callGraph) { visited[f] = true; parents[f] = true; const cycle = this._findCallCycleFrom(visited, parents, [], f); if (cycle !== undefined) { return cycle; } delete parents[f]; } return undefined; } public _findCallCycleFrom( visited: { [x: string]: boolean }, parents: { [x: string]: any }, path: { caller: any; callee: string; location: { value: string } }[] | { caller: string }[], f: string ): { caller: any; callee: string; location: { value: string } }[] | { caller: string }[] { for (const g in this._callGraph[f]) { path.push({ caller: f, callee: g, location: this._callGraph[f][g] }); if (g in parents) { while (path[0].caller !== g) { path.shift(); } path.push(); return path; /* Cycle */ } if (!(g in visited)) { visited[g] = true; parents[g] = true; const cycle = this._findCallCycleFrom(visited, parents, path, g); if (cycle !== undefined) { return cycle; } delete parents[g]; } path.pop(); } return undefined; } }