UNPKG

@bscotch/gml-parser

Version:

A parser for GML (GameMaker Language) files for programmatic manipulation and analysis of GameMaker projects.

143 lines 4.5 kB
// Handle GameMaker JSDoc typestrings // GameMaker allows for some variations on the allowed syntax. // Examples: // - `String` // - `String|Real` // - `String,Real` // - `String OR Real` // - `Array` // - `Array<String>` // - `Array[String|Real]` import { ok } from './util.js'; /** * Given a parsed feather type, create a flat array of flat types. * Useful for e.g. getting the offsets of all types in a union. * @param flattened The array collecting the flattened types */ export function flattenFeatherTypes(type, flattened = []) { if (typeof type === 'string') { return flattenFeatherTypes(parseFeatherTypeString(type), flattened); } if (type.kind === 'type') { flattened.push(type); if (type.of) { flattenFeatherTypes(type.of, flattened); } } else { type.types.forEach((t) => flattenFeatherTypes(t, flattened)); } return flattened; } export function parseFeatherTypeString(typeString) { // Patterns const whitespace = /\s+/y; const leftBracket = /[<[]/y; const rightBracket = /[>\]]/y; const or = /(\bOR\b|\bor\b|\||,)/y; const identifier = /[a-zA-Z_][a-zA-Z0-9_.]*/y; let offset = 0; // Handle case where a type is incorrectly wrapped in brackets // (e.g. Array<String|Undefined> is fine, but just <String|Undefined> is not) if (typeString.match(/^\s*[<[]/)) { const parts = typeString.match(/^(\s*[<[])(.*)([>\]]\s*)$/); if (parts) { typeString = ' '.repeat(parts[1].length) + parts[2] + ' '.repeat(parts[3].length); } } const lex = (pattern) => { pattern.lastIndex = offset; const match = pattern.exec(typeString); if (!match) { return; } offset = pattern.lastIndex; return match; }; const rootUnion = { kind: 'union', types: [] }; const typeUnionStack = [rootUnion]; 0; const currentUnion = () => typeUnionStack.at(-1); let currentType; if (!typeString?.trim()) { rootUnion.types.push({ kind: 'type', name: { content: 'Any', inferred: true, offset }, }); return rootUnion; } // Lex the string while (offset < typeString.length) { let match; match = lex(leftBracket); if (match) { ok(currentType, 'Unexpected left bracket'); // Create a new union const union = { kind: 'union', types: [] }; // Add it to the current type currentType.of = union; // Push it onto the stack typeUnionStack.push(union); continue; } match = lex(rightBracket); if (match) { // Pop the current union off the stack typeUnionStack.pop(); // Can only be followed by an OR, so unset the type currentType = undefined; continue; } match = lex(or); if (match) { continue; } match = lex(identifier); if (match) { // Create a new type const type = { kind: 'type', name: { content: match[0], offset: match.index }, }; // Add it to the current union currentUnion().types.push(type); currentType = type; continue; } match = lex(whitespace); if (match) { continue; } break; } return rootUnion; } export function typeToFeatherString(type) { // Functions, Structs, and Enums are the only types that can have names if (type.isGeneric && type.name) { return type.name; } if (type.signifier?.enumMember) { const parent = type.signifier.parent; return `Enum.${parent.name}.${type.name}`; } if (type.signifier?.enum) { return `Enum.${type.name}`; } if (type.name) { if (['Real', 'String', 'Boolean'].includes(type.kind)) { // Then this is a constant, represented in Feather as `Constant.<Class>` return `Constant.${type.name}`; } return `${type.kind}.${type.name}`; } // Arrays etc can contain items of a type) { if (type.items?.type.length) { return `${type.kind}<${type.items.type .map((t) => t.toFeatherString()) .join('|')}>`; } return type.kind; } //# sourceMappingURL=jsdoc.feather.js.map