@gobstones/gobstones-parser
Version:
Gobstones parser
191 lines (174 loc) • 5.88 kB
text/typescript
/* 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;
}
}