@bscotch/gml-parser
Version:
A parser for GML (GameMaker Language) files for programmatic manipulation and analysis of GameMaker projects.
112 lines • 4.85 kB
JavaScript
// CST Visitor for creating an AST etc
import { withCtxKind } from './parser.js';
import { arrayLiteralFromRhs, functionFromRhs, rhsFrom, structLiteralFromRhs, } from './parser.utility.js';
import { normalizeType } from './types.checks.js';
export function assignVariable(visitor, variable, rawRhs, info) {
const rhs = rhsFrom(rawRhs);
//#region Collect useful info
// Figure out what we're assigning to so we can handle
// already-known types (from JSDocs).
const fullScope = visitor.PROCESSOR.fullScope;
// Are we in the definitiveSelf?
const inDefinitiveSelf = variable.container === fullScope.definitiveSelf;
//#endregion
// Find the existing variable
let signifier = variable.container.getMember(variable.name);
const isSelfOwned = !!signifier && !!variable.container.getMember(variable.name, true);
let ref;
// Add the variable if missing
let wasUndeclared = false;
if (!signifier) {
wasUndeclared = true;
if (variable.container !== fullScope.global) {
// Then we can add a new member
signifier = variable.container.addMember(variable.name);
if (signifier) {
signifier.definedAt(variable.range);
signifier.static = !!info.static;
signifier.instance = !!info.instance || !info.local;
signifier.local = !!info.local;
signifier.definitive = inDefinitiveSelf;
ref = signifier.addRef(variable.range, true);
}
else {
// Then this is an immutable type
visitor.PROCESSOR.addDiagnostic('INVALID_OPERATION', variable.range, `Cannot add variables to this type.`);
}
}
else {
visitor.PROCESSOR.addDiagnostic('UNDECLARED_GLOBAL_REFERENCE', variable.range, `${variable.name} is not declared anywhere but is assigned in global scope.`);
}
}
else {
// Add a reference to the item.
ref = signifier.addRef(variable.range);
// If this is the first time we've seen it, and it wouldn't have
// an unambiguous declaration, add its definition
if (!signifier.def) {
wasUndeclared = true;
signifier.definedAt(variable.range);
ref.isDef = true;
}
// If this variable comes from a non-definitive declaration,
// and *would* be definitive here, then we need to update it.
ensureDefinitive(variable.container, visitor.PROCESSOR.currentDefinitiveSelf, signifier, ref);
}
// Handle RHS
const assignedToFunction = functionFromRhs(rhs);
const assignedToStructLiteral = structLiteralFromRhs(rhs);
const assignedToArrayLiteral = arrayLiteralFromRhs(rhs);
const ctx = { ...info.ctx, docs: info.docs, signifier };
if (assignedToFunction || assignedToStructLiteral || assignedToArrayLiteral) {
if (assignedToFunction) {
ctx.self = variable.container;
visitor.functionExpression(assignedToFunction, ctx);
}
else if (assignedToStructLiteral) {
visitor.structLiteral(assignedToStructLiteral, ctx);
}
else if (assignedToArrayLiteral) {
visitor.arrayLiteral(assignedToArrayLiteral, ctx);
}
}
else if (rhs) {
const inferredType = normalizeType(visitor.assignmentRightHandSide(rhs, withCtxKind(ctx, 'assignment')), visitor.PROCESSOR.project.types);
const forceOverride = info.docs?.jsdoc.kind === 'type';
if (signifier && (!signifier.isTyped || wasUndeclared || forceOverride)) {
if (info.docs) {
signifier.describe(info.docs.jsdoc.description);
signifier.setType(info.docs.type.length ? info.docs.type : inferredType);
}
else if (inferredType) {
signifier.setType(inferredType);
}
}
}
if (signifier && ref) {
return {
item: signifier,
ref: ref,
};
}
return;
}
export function ensureDefinitive(self, currentDefinitiveSelf, member, ref) {
if (!currentDefinitiveSelf)
return;
// If this variable comes from a non-definitive declaration,
// and *would* be definitive here, then we need to update it.
const inDefinitiveSelf = currentDefinitiveSelf === self;
const isSelfOwned = self.getMember(member.name, true) === member;
if (isSelfOwned && !member.definitive && inDefinitiveSelf) {
member.definitive = true;
// Ensure the definition is HERE
member.unsetDef();
member.definedAt(ref);
for (const otherRef of member.refs) {
otherRef.isDef = false; // Unset all other definitions
}
ref.isDef = true;
}
}
//# sourceMappingURL=visitor.assign.js.map