Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
import AssignmentNode from "../ast/AssignmentNode";
import ASTNode from "../ast/ASTNode";
import {
AnonymousFunctionExpr,
FunctionCallExpr,
LcForCExpr,
LcForExpr,
LcLetExpr,
LetExpr,
LookupExpr,
} from "../ast/expressions";
import ScadFile from "../ast/ScadFile";
import {
BlockStmt,
FunctionDeclarationStmt,
ModuleDeclarationStmt,
ModuleInstantiationStmt,
} from "../ast/statements";
import ASTMutator from "../ASTMutator";
import ErrorCollector from "../ErrorCollector";
import NodeWithScope from "./NodeWithScope";
import {
ResolvedLookupExpr,
ResolvedModuleInstantiationStmt,
} from "./resolvedNodes";
import Scope from "./Scope";
import {
UnresolvedFunctionError,
UnresolvedModuleError,
UnresolvedVariableError,
} from "./unresolvedSymbolErrors";
export default class SymbolResolver extends ASTMutator {
constructor(
private errorCollector: ErrorCollector,
/**
* Represents the scope where the resolver has descended.
* It initially is null, but the resolver should encounter a NodeWithScope
* and set this to the scope of that node.
*/
public currentScope: Scope | null = null,
public isInCallee: boolean = false
) {
super();
}
visitLookupExpr(n: LookupExpr): ASTNode {
if(! this.currentScope) {
throw new Error("currentScope cannot be null when resolving lookup");
}
const resolved = new ResolvedLookupExpr(n.name, n.tokens);
resolved.resolvedDeclaration = this.currentScope.lookupVariable(n.name);
if(this.isInCallee && !resolved.resolvedDeclaration) {
resolved.resolvedDeclaration = this.currentScope.lookupFunction(n.name);
}
if (!resolved.resolvedDeclaration) {
this.errorCollector.reportError(
new UnresolvedVariableError(n.span.start, n.name)
);
return n;
}
return resolved;
}
visitModuleInstantiationStmt(n: ModuleInstantiationStmt): ASTNode {
if(! this.currentScope) {
throw new Error("currentScope cannot be null when resolving module");
}
const resolved = new ResolvedModuleInstantiationStmt(
n.name,
n.args.map((a) => a.accept(this)) as AssignmentNode[],
n.child ? n.child.accept(this) : null,
n.tokens
);
resolved.resolvedDeclaration = this.currentScope.lookupModule(n.name);
if (!resolved.resolvedDeclaration) {
this.errorCollector.reportError(new UnresolvedModuleError(n.span.start, n.name));
return n;
}
return resolved;
}
/**
* visitFunctionCallExpr switches the SymbolResolver into a special mode where
* it falls back to resolving named functions when processing lookup expressions.
* This behaviour tries to mimic the behaviour of OpenSCAD's function call resolution,
* it is not perfect, since you can abuse this to do things like assign a named function
* to a variable which is not allowed in OpenSCAD. So this covers all but the most
* pathological cases.
* @param n
* @returns
*/
visitFunctionCallExpr(n: FunctionCallExpr): ASTNode {
return super.visitFunctionCallExpr.call(
this.copyWithIsInCallee(),
n
);
}
// scope handling
private copyWithNextScope(s: Scope) {
if (!s) {
throw new Error("Scope cannot be falsy");
}
return new SymbolResolver(this.errorCollector, s, this.isInCallee);
}
private copyWithIsInCallee() {
return new SymbolResolver(this.errorCollector, this.currentScope, true);
}
visitBlockStmt(n: BlockStmt): ASTNode {
return super.visitBlockStmt.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitLetExpr(n: LetExpr): ASTNode {
return super.visitLetExpr.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitScadFile(n: ScadFile): ASTNode {
return super.visitScadFile.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitFunctionDeclarationStmt(n: FunctionDeclarationStmt): ASTNode {
return super.visitFunctionDeclarationStmt.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitModuleDeclarationStmt(n: ModuleDeclarationStmt): ASTNode {
return super.visitModuleDeclarationStmt.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitLcLetExpr(n: LcLetExpr): ASTNode {
return super.visitLcLetExpr.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitLcForExpr(n: LcForExpr): ASTNode {
return super.visitLcForExpr.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitLcForCExpr(n: LcForCExpr): ASTNode {
return super.visitLcForCExpr.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
visitAnonymousFunctionExpr(n: AnonymousFunctionExpr): ASTNode {
return super.visitAnonymousFunctionExpr.call(
this.copyWithNextScope((n as unknown as NodeWithScope).scope),
n
);
}
}
2d56448eaThis file is pinned to the selected release. Download the original to open it in your design tool.