UNPKG

@bscotch/gml-parser

Version:

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

387 lines 18.5 kB
import { Pathy } from '@bscotch/pathy'; import { type StitchConfig } from '@bscotch/stitch-config'; import { type Yyp, type YypFolder, type YypResource } from '@bscotch/yy'; import { EventEmitter } from 'events'; import { ImportModuleOptions } from './modules.types.js'; import { Asset } from './project.asset.js'; import { Code } from './project.code.js'; import { Diagnostic } from './project.diagnostics.js'; import { Native } from './project.native.js'; import { Signifier } from './signifiers.js'; import { StructType, Type } from './types.js'; export { setLogger, type Logger } from './logger.js'; export interface SymbolInfo { native: boolean; symbol: Signifier | Type; } export interface DiagnosticsEventPayload { filePath: string; code?: Code; diagnostics: Diagnostic[]; } export type OnDiagnostics = (diagnostics: DiagnosticsEventPayload) => void; export interface ProjectOptions { /** * Register a callback to run when diagnostics are emitted. * If not provided, a callback can be registered after * initialization, but will not receive any diagnostics * from the initial parse. */ onDiagnostics?: OnDiagnostics; /** * If registered, this callback will be used to report * when progress has been made towards loading the project. */ onLoadProgress?: (increment: number, message?: string) => void; settings?: { /** * Symbols starting with these prefixes that are not declared * in the coded will be treated as global symbols. This is * useful for ensuring globals created by extensions will not * show up as being undeclared. */ autoDeclareGlobalsPrefixes?: string[]; }; } export declare class Project { readonly yypPath: Pathy<any>; readonly options?: ProjectOptions | undefined; yyp: Yyp; /** Until this resolves, assume that this.yyp is not yet read */ yypWaiter?: Promise<any>; config: StitchConfig; readonly assets: Map<string, Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">>; /** * Store the "native" functions, constants, and enums on * a per-project basis, but separately from the project-specific * symbols. The native symbols and types are loaded from the spec, * so they can vary between projects. */ native: Native; helpLinks: { [method: string]: string; }; /** * When resolved, the GML spec has been loaded and the * `native` property has been populated. */ nativeWaiter?: Promise<void>; /** * The type of the 'global' struct, which contains all globalvars * and globally defined functions. */ self: StructType; /** * The `global` symbol, which has type `self`. */ symbol: Signifier; /** * Non-native global types, which can be referenced in JSDocs * and in a symbol's types. */ readonly types: Map<string, Type<"Real" | "Undefined" | "Enum" | "Function" | "Array" | "Bool" | "Pointer" | "String" | "Struct" | "ArgumentIdentity" | "Any" | "Asset.GMAnimCurve" | "Asset.GMAudioGroup" | "Asset.GMFont" | "Asset.GMObject" | "Asset.GMParticleSystem" | "Asset.GMPath" | "Asset.GMRoom" | "Asset.GMScript" | "Asset.GMSequence" | "Asset.GMShader" | "Asset.GMSound" | "Asset.GMSprite" | "Asset.GMTileSet" | "Asset.GMTimeline" | "Asset.Script" | "Id.AudioEmitter" | "Id.AudioListener" | "Id.AudioSyncGroup" | "Id.BackgroundElement" | "Id.BinaryFile" | "Id.Buffer" | "Id.Camera" | "Id.DsGrid" | "Id.DsList" | "Id.DsMap" | "Id.DsPriority" | "Id.DsQueue" | "Id.DsStack" | "Id.ExternalCall" | "Id.Gif" | "Id.Instance" | "Id.Layer" | "Id.MpGrid" | "Id.ParticleEmitter" | "Id.ParticleSystem" | "Id.ParticleType" | "Id.PhysicsIndex" | "Id.PhysicsParticleGroup" | "Id.Sampler" | "Id.SequenceElement" | "Id.Socket" | "Id.Sound" | "Id.SpriteElement" | "Id.Surface" | "Id.TextFile" | "Id.Texture" | "Id.TileElementId" | "Id.TileMapElement" | "Id.TimeSource" | "Id.Uniform" | "Id.VertexBuffer" | "Id.VertexFormat" | "Mixed" | "EnumMember" | "Unknown" | "Never" | "InstanceType" | "ObjectType" | "StaticType">>; protected emitter: EventEmitter<[never]>; /** Code that needs to be reprocessed, for one reason or another. */ protected dirtyFiles: Set<Code>; protected constructor(yypPath: Pathy<any>, options?: ProjectOptions | undefined); /** * @internal For tracking changed code files that will need to be re-parsed. */ queueDirtyFileUpdate(code: Code): void; /** * @internal Drain the queue of dirty files, updating their diagnostics */ drainDirtyFileUpdateQueue(): void; /** * Update the YYP file to list a specific GameMaker IDE version. * Note that the GameMaker IDE will overwrite this with whatever * its own version is -- this feature is useful for external tools * like Stitch that can manage multiple GameMaker IDE versions. */ setIdeVersion(version: string): Promise<void>; /** * The current version of the GameMaker IDE listed in * this project's YYP file. This is the GameMaker version that * the project was last opened with. */ get ideVersion(): string; /** * The directory in which the project lives. */ get dir(): Pathy; /** * Get the Stitch config for this project, which defines * various settings that may impact rule around adding * assets, parsing logs, etc. */ get stitchConfig(): Pathy<{ [x: string]: unknown; $schema: "https://raw.githubusercontent.com/bscotch/stitch/develop/packages/config/schemas/stitch.config.schema.json"; textureGroupAssignments?: Record<string, string> | undefined; audioGroupAssignments?: Record<string, string> | undefined; runtimeVersion?: string | undefined; newSpriteRules?: { [x: string]: unknown; allowedNames?: string[] | undefined; } | undefined; newSoundRules?: { [x: string]: unknown; allowedNames?: string[] | undefined; defaults?: Record<string, { mono?: boolean | undefined; }> | undefined; } | undefined; gameConsoleStyle?: { [x: string]: unknown; base?: string | undefined; lines?: { [x: string]: unknown; pattern: string; base?: string | undefined; description?: string | undefined; caseSensitive?: boolean | undefined; styles?: { [x: string]: string; } | undefined; }[] | undefined; } | undefined; }>; /** List the names of the GameMaker configs defined by this project. */ get configs(): string[]; /** List the project's "datafiles" (a.k.a. "Included Files"), in the same format as they appear in the YYP file. */ get datafiles(): { name: string; CopyToMask: bigint; filePath: string; resourceType: "GMIncludedFile"; resourceVersion: string; ConfigValues?: Record<string, { CopyToMask: string; }> | undefined; }[]; /** List the Folders in this project, normalized to regular POSIX paths * @example ['my/folder', 'my/other/folder'] */ get folders(): string[]; /** * Run a callback when diagnostics are emitted. Returns an unsubscribe function. */ onDiagnostics(callback: OnDiagnostics): () => void; /** @internal Method that can be called after some code has been parsed to report diagnostics to listeners. */ emitDiagnostics(code: Code | string, diagnostics: Diagnostic[]): void; /** * Since GameMaker assets are global they must have unique names independent of their type. Find an asset give it's name. Note that this is case-insensitive! * @param name The name of the asset to find, case-insensitive. */ getAssetByName<Assert extends boolean>(name: string | undefined, options?: { assertExists: Assert; }): Assert extends true ? Asset : Asset | undefined; /** * @param name The name of the asset to find and remove, case-insensitive. */ removeAssetByName(name: string | undefined): Promise<void>; getAsset(path: Pathy<any>): Asset | undefined; getGmlFile(path: Pathy<any>): Code | undefined; /** Normalize path information for a datafile ("Included File") */ parseIncludedFilePath(filePath: string, name?: string): { filePath: string; name: string; }; findIncludedFile(filePath: string, name?: string): { name: string; CopyToMask: bigint; filePath: string; resourceType: "GMIncludedFile"; resourceVersion: string; ConfigValues?: Record<string, { CopyToMask: string; }> | undefined; } | undefined; /** * Ensure that the included files listed in the YYP exactly match * the files in the `datafiles` directory. */ syncIncludedFiles(): Promise<void>; /** @internal Load an Asset instance into the project's data model. For use by methods that load the project, add assets, etc. */ registerAsset(resource: Asset): void; /** * @param from The name of the asset to rename, case-insensitive. * @param to The new name for the asset, which must be a valid identifier that doesn't already have an associated asset. The name will be set in the provided casing, but must be unique case-insensitively. */ renameAsset(from: string, to: string): Promise<void>; renameSignifier(signifier: Signifier, newName: string): Promise<void>; import(fromProject: Project | string, options?: ImportModuleOptions): Promise<{ created: string[]; updated: string[]; errors: string[]; skipped: string[]; }>; duplicateAsset(sourceName: string, newPath: string): Promise<Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">>; /** * Create a new sound asset. Will not do anything if the asset by this name already exists (but will log an error). * @param newSoundPath The POSIX-style path within the asset tree where you want this sound to be created, where the last component is the name of the sound asset. * @param fromFile The path to the source sound file to copy into the new asset's directory. * @example project.createSound('folder/of/sounds/snd_my_new_sound', 'path/to/sound.mp3'); */ createSound(newSoundPath: string, fromFile: string | Pathy): Promise<Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines"> | undefined>; /** * Create a new room asset. Will not do anything if the asset by this name already exists (but will log an error). * @param newRoomPath The POSIX-style path within the asset tree where you want this asset to be created, where the last component is the name of the asset. * @example project.createRoom('folder/of/rooms/rm_my_room'); */ createRoom(newRoomPath: string): Promise<Asset<'rooms'> | undefined>; /** * Create a new sprite asset. Will not do anything if the asset by this name already exists (but will log an error). * @param newSpritePath The POSIX-style path within the asset tree where you want this asset to be created, where the last component is the name of the asset. * @param fromImageFile Path to a source PNG image to use as the first frame of the sprite. * @example project.createSprite('folder/of/sprites/sp_my_sprite', 'path/to/sprite.png'); */ createSprite(newSpritePath: string, fromImageFile: string | Pathy): Promise<Asset<'sprites'> | undefined>; /** * Add an object to the yyp file. The string can include separators, * in which case folders will be ensured up to the final component. * @param newObjectName The POSIX-style path within the asset tree where you want this asset to be created, where the last component is the name of the asset. */ createObject(newObjectName: string): Promise<Asset<'objects'> | undefined>; createShader(path: string): Promise<Asset<'shaders'> | undefined>; /** * Add a script to the yyp file. The path string can include separators, * in which case folders will be ensured up to the final component. */ createScript(path: string): Promise<Asset<'scripts'> | undefined>; protected parseNewAssetPath(path: string): Promise<{ folder: { name: string; folderPath: string; resourceType: "GMFolder"; resourceVersion: string; tags?: string[] | undefined; order?: number | undefined; }; name: string; } | undefined>; /** * Given the path to a yy file for an asset, ensure * it has an entry in the yyp file. */ addAssetToYyp(yyPath: string, options?: { skipSave?: boolean; }): Promise<YypResource>; protected parseFolderPath(path: string | string[]): { parts: string[]; full: string; prefix: string; }; listAssetsInFolder(path: string | string[], options?: { recursive: boolean; }): Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">[]; /** * Delete a folder recursively. Only allowed if there are no assets * in this or any subfolder. */ deleteFolder(path: string | string[]): Promise<void>; /** * Rename an existing folder. Allows for renaming any part of * the path (useful both "moving" and "renaming" a folder). * Array inputs are interpreted as pre-split paths. If the new * name matches an existing folder, it will in effect be "merged" * with that existing folder. * * Returns the list of folders and assets that are now in a new * location. */ renameFolder(oldPath: string | string[], newPath: string | string[]): Promise<{ movedFolders: [from: { name: string; folderPath: string; resourceType: "GMFolder"; resourceVersion: string; tags?: string[] | undefined; order?: number | undefined; }, to: { name: string; folderPath: string; resourceType: "GMFolder"; resourceVersion: string; tags?: string[] | undefined; order?: number | undefined; } | undefined][]; movedAssets: Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">[]; } | undefined>; /** * Add a folder to the yyp file. The string can include separators, * in which case nested folders will be created. If an array is provided, * it is interpreted as a pre-split path. */ createFolder(path: string | string[], options?: { skipSave?: boolean; }): Promise<YypFolder | undefined>; saveYyp(): Promise<void>; /** * The name of a resource, *in lower case*, from * a path. This is used as the key for looking up resources. * * The path can be to the asset's folder, or to any file within * that folder. */ assetNameFromPath(path: Pathy<any>): string; /** * When first creating an instance, we need to get all project file * content into memory for fast access. In particular, we need all * yyp, yy, and gml files for scripts and objects. For other asset types * we just need their names and yyp filepaths. * * Can be called at any time -- will only operate on new assets. * * Returns the list of added assets. Assets are instanced and registered but their * code is not parsed! */ protected loadAssets(options?: ProjectOptions): Promise<Asset[]>; protected loadHelpLinks(): Promise<void>; reloadConfig(): Promise<{ [x: string]: unknown; $schema: "https://raw.githubusercontent.com/bscotch/stitch/develop/packages/config/schemas/stitch.config.schema.json"; textureGroupAssignments?: Record<string, string> | undefined; audioGroupAssignments?: Record<string, string> | undefined; runtimeVersion?: string | undefined; newSpriteRules?: { [x: string]: unknown; allowedNames?: string[] | undefined; } | undefined; newSoundRules?: { [x: string]: unknown; allowedNames?: string[] | undefined; defaults?: Record<string, { mono?: boolean | undefined; }> | undefined; } | undefined; gameConsoleStyle?: { [x: string]: unknown; base?: string | undefined; lines?: { [x: string]: unknown; pattern: string; base?: string | undefined; description?: string | undefined; caseSensitive?: boolean | undefined; styles?: { [x: string]: string; } | undefined; }[] | undefined; } | undefined; }>; getWindowsName(): Promise<string | undefined>; /** * Load the GML spec for the project's runtime version, falling * back on the included spec if necessary. */ protected loadGmlSpec(): Promise<void>; /** * Call to reload the project's yyp file (e.g. because it has changed * on disk) and add/remove any resources. */ reloadYyp(): Promise<void>; /** * @internal * Initialize a collection of new assets by parsing their GML */ initiallyParseAssetCode(assets: Asset[]): void; protected initialize(options?: ProjectOptions): Promise<void>; /** * Create a new project instance and initialize it. */ static initialize(yypPath: string, options?: ProjectOptions): Promise<Project>; static readonly fallbackGmlSpecPath: Pathy<unknown>; } //# sourceMappingURL=project.d.ts.map