UNPKG

@bscotch/gml-parser

Version:

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

571 lines 21.5 kB
import { CstParser } from 'chevrotain'; import { GmlLexer } from './lexer.js'; import { c, categories, t, tokens } from './tokens.js'; export class GmlParser extends CstParser { /** Parse GML Code, e.g. from a file. */ parse(code) { const lexed = this.lexer.tokenize(code); this.input = lexed.tokens; const cst = this.file(); return { lexed, cst, errors: this.errors, }; } lexer = GmlLexer; file = this.RULE('file', () => { this.SUBRULE(this.statements); }); optionallyConsumeSemicolon() { this.OPTION9(() => this.CONSUME(t.Semicolon)); } statements = this.RULE('statements', () => { this.MANY(() => this.SUBRULE(this.statement)); }); statement = this.RULE('statement', () => { this.OR([ { ALT: () => this.SUBRULE(this.functionStatement) }, { ALT: () => this.SUBRULE(this.localVarDeclarationsStatement) }, { ALT: () => this.SUBRULE(this.globalVarDeclarationsStatement) }, { ALT: () => this.SUBRULE(this.staticVarDeclarationStatement) }, { ALT: () => this.SUBRULE(this.variableAssignmentStatement) }, { ALT: () => this.SUBRULE(this.ifStatement) }, { ALT: () => this.SUBRULE(this.tryStatement) }, { ALT: () => this.SUBRULE(this.whileStatement) }, { ALT: () => this.SUBRULE(this.forStatement) }, { ALT: () => this.SUBRULE(this.doUntilStatement) }, { ALT: () => this.SUBRULE(this.switchStatement) }, { ALT: () => this.SUBRULE(this.breakStatement) }, { ALT: () => this.SUBRULE(this.continueStatement) }, { ALT: () => this.SUBRULE(this.returnStatement) }, { ALT: () => this.SUBRULE(this.exitStatement) }, { ALT: () => this.SUBRULE(this.withStatement) }, { ALT: () => this.SUBRULE(this.enumStatement) }, { ALT: () => this.SUBRULE(this.macroStatement) }, { ALT: () => this.SUBRULE(this.emptyStatement) }, { ALT: () => this.SUBRULE(this.repeatStatement) }, { ALT: () => this.SUBRULE(this.expressionStatement) }, { ALT: () => this.SUBRULE(this.jsdoc) }, ]); }); jsdoc = this.RULE('jsdoc', () => { this.OR2([ { ALT: () => this.SUBRULE(this.jsdocGml) }, { ALT: () => this.SUBRULE(this.jsdocJs) }, ]); }); jsdocGml = this.RULE('jsdocGml', () => { this.AT_LEAST_ONE(() => { this.CONSUME(t.JsdocGmlLine); }); }); jsdocJs = this.RULE('jsdocJs', () => { this.CONSUME(t.JsdocJs); }); stringLiteral = this.RULE('stringLiteral', () => { this.CONSUME(t.StringStart); this.MANY(() => { this.CONSUME(c.Substring); }); this.CONSUME(t.StringEnd); }); multilineDoubleStringLiteral = this.RULE('multilineDoubleStringLiteral', () => { this.CONSUME(t.MultilineDoubleStringStart); this.MANY(() => { this.CONSUME(c.Substring); }); this.CONSUME(t.MultilineDoubleStringEnd); }); multilineSingleStringLiteral = this.RULE('multilineSingleStringLiteral', () => { this.CONSUME(t.MultilineSingleStringStart); this.MANY(() => { this.CONSUME(c.Substring); }); this.CONSUME(t.MultilineSingleStringEnd); }); templateLiteral = this.RULE('templateLiteral', () => { this.CONSUME(t.TemplateStart); this.MANY(() => { this.OR([ { ALT: () => this.CONSUME(c.Substring) }, { ALT: () => { this.CONSUME(t.TemplateInterpStart); this.SUBRULE(this.expression); this.CONSUME(t.EndBrace); }, }, ]); }); this.CONSUME(t.TemplateStringEnd); }); repeatStatement = this.RULE('repeatStatement', () => { this.CONSUME(t.Repeat); this.SUBRULE(this.expression); this.SUBRULE(this.blockableStatement); }); returnStatement = this.RULE('returnStatement', () => { this.CONSUME(t.Return); this.OPTION1(() => this.SUBRULE(this.assignmentRightHandSide)); this.OPTION2(() => this.CONSUME(t.Semicolon)); }); ifStatement = this.RULE('ifStatement', () => { this.CONSUME(t.If); this.SUBRULE(this.expression); this.OPTION2(() => this.CONSUME(t.Then)); this.SUBRULE(this.blockableStatement); this.MANY(() => this.SUBRULE2(this.elseIfStatement)); this.OPTION(() => { this.SUBRULE(this.elseStatement); }); }); elseIfStatement = this.RULE('elseIfStatement', () => { this.CONSUME(t.Else); this.CONSUME(t.If); this.SUBRULE(this.expression); this.SUBRULE(this.blockableStatement); }); elseStatement = this.RULE('elseStatement', () => { this.CONSUME(t.Else); this.SUBRULE2(this.blockableStatement); }); blockableStatement = this.RULE('blockableStatement', () => { this.OR([ { ALT: () => this.SUBRULE(this.statement) }, { ALT: () => this.SUBRULE(this.blockStatement) }, ]); }); blockableStatements = this.RULE('blockableStatements', () => { this.OR([ { ALT: () => this.SUBRULE(this.statements) }, { ALT: () => this.SUBRULE(this.blockStatement) }, ]); }); blockStatement = this.RULE('blockStatement', () => { this.CONSUME(t.StartBrace); this.MANY(() => this.SUBRULE(this.statement)); this.CONSUME(t.EndBrace); }); expressionStatement = this.RULE('expressionStatement', () => { this.SUBRULE(this.expression); this.optionallyConsumeSemicolon(); }); expression = this.RULE('expression', () => { this.SUBRULE(this.primaryExpression); this.OPTION(() => { this.OR([ { ALT: () => this.SUBRULE(this.assignment) }, { ALT: () => this.AT_LEAST_ONE(() => this.SUBRULE2(this.binaryExpression)), }, { ALT: () => this.SUBRULE(this.ternaryExpression) }, ]); }); }); binaryExpression = this.RULE('binaryExpression', () => { this.CONSUME(c.BinaryOperator); this.SUBRULE(this.assignmentRightHandSide); }); ternaryExpression = this.RULE('ternaryExpression', () => { this.CONSUME(t.QuestionMark); this.SUBRULE(this.assignmentRightHandSide); this.CONSUME(t.Colon); this.SUBRULE2(this.assignmentRightHandSide); }); primaryExpression = this.RULE('primaryExpression', () => { this.OPTION1(() => this.CONSUME(c.UnaryPrefixOperator)); this.OR1([ { ALT: () => this.CONSUME(c.BooleanLiteral) }, { ALT: () => this.CONSUME(c.NumericLiteral) }, { ALT: () => this.CONSUME(c.PointerLiteral) }, { ALT: () => this.CONSUME(t.Undefined) }, { ALT: () => this.CONSUME(t.NaN) }, { ALT: () => this.SUBRULE(this.stringLiteral) }, { ALT: () => this.SUBRULE(this.multilineDoubleStringLiteral) }, { ALT: () => this.SUBRULE(this.multilineSingleStringLiteral) }, { ALT: () => this.SUBRULE(this.templateLiteral) }, { ALT: () => this.SUBRULE(this.identifierAccessor) }, { ALT: () => this.SUBRULE(this.parenthesizedExpression) }, { ALT: () => this.SUBRULE(this.arrayLiteral) }, ]); this.OPTION2(() => this.CONSUME(c.UnarySuffixOperator)); }); identifier = this.RULE('identifier', () => { this.OR([ { ALT: () => this.CONSUME(t.Identifier) }, { ALT: () => this.CONSUME(t.Self) }, { ALT: () => this.CONSUME(t.Other) }, { ALT: () => this.CONSUME(t.Global) }, { ALT: () => this.CONSUME(t.Noone) }, { ALT: () => this.CONSUME(t.All) }, ]); }); identifierAccessor = this.RULE('identifierAccessor', () => { this.OPTION(() => { this.CONSUME(t.New); }); this.SUBRULE(this.identifier); this.MANY(() => { this.SUBRULE(this.accessorSuffixes); }); this.OPTION2(() => { this.SUBRULE(this.assignment); }); }); parenthesizedExpression = this.RULE('parenthesizedExpression', () => { this.CONSUME(t.StartParen); this.SUBRULE(this.expression); this.CONSUME(t.EndParen); }); accessorSuffixes = this.RULE('accessorSuffixes', () => { this.OR([ { ALT: () => this.SUBRULE(this.gridAccessorSuffix) }, { ALT: () => this.SUBRULE(this.structAccessorSuffix) }, { ALT: () => this.SUBRULE(this.listAccessorSuffix) }, { ALT: () => this.SUBRULE(this.mapAccessorSuffix) }, { ALT: () => this.SUBRULE(this.arrayMutationAccessorSuffix) }, { ALT: () => this.SUBRULE(this.arrayAccessSuffix) }, { ALT: () => this.SUBRULE(this.dotAccessSuffix) }, { ALT: () => this.SUBRULE(this.functionArguments) }, ]); }); dotAccessSuffix = this.RULE('dotAccessSuffix', () => { this.CONSUME(t.Dot); this.SUBRULE(this.identifier); }); arrayAccessSuffix = this.RULE('arrayAccessSuffix', () => { this.CONSUME(t.StartBracket); this.SUBRULE(this.expression); // Support for legacy 2D array access this.OPTION(() => { this.CONSUME(t.Comma); this.SUBRULE2(this.expression); }); this.CONSUME(t.EndBracket); }); structAccessorSuffix = this.RULE('structAccessSuffix', () => { this.CONSUME(t.StructAccessorStart); this.SUBRULE(this.expression); this.CONSUME(t.EndBracket); }); listAccessorSuffix = this.RULE('listAccessSuffix', () => { this.CONSUME(t.DsListAccessorStart); this.SUBRULE(this.expression); this.CONSUME(t.EndBracket); }); mapAccessorSuffix = this.RULE('mapAccessSuffix', () => { this.CONSUME(t.DsMapAccessorStart); this.SUBRULE(this.expression); this.CONSUME(t.EndBracket); }); gridAccessorSuffix = this.RULE('gridAccessSuffix', () => { this.CONSUME(t.DsGridAccessorStart); this.SUBRULE(this.expression); this.CONSUME(t.Comma); this.SUBRULE2(this.expression); this.CONSUME(t.EndBracket); }); arrayMutationAccessorSuffix = this.RULE('arrayMutationAccessorSuffix', () => { this.CONSUME(t.ArrayMutateAccessorStart); this.SUBRULE(this.expression); this.CONSUME(t.EndBracket); }); functionArguments = this.RULE('functionArguments', () => { this.CONSUME(t.StartParen); this.OPTION1(() => { this.SUBRULE(this.functionArgument); }); this.MANY(() => { this.CONSUME(t.Comma); this.OPTION2(() => this.SUBRULE2(this.functionArgument)); }); this.CONSUME(t.EndParen); }); functionArgument = this.RULE('functionArgument', () => { this.OPTION1(() => { this.SUBRULE(this.jsdoc); }); this.SUBRULE(this.assignmentRightHandSide); }); emptyStatement = this.RULE('emptyStatement', () => { this.CONSUME(t.Semicolon); }); enumStatement = this.RULE('enumStatement', () => { this.CONSUME(t.Enum); this.CONSUME1(t.Identifier); this.CONSUME(t.StartBrace); this.SUBRULE(this.enumMember); this.MANY(() => { this.CONSUME2(t.Comma); this.SUBRULE2(this.enumMember); }); this.OPTION(() => this.CONSUME3(t.Comma)); this.CONSUME(t.EndBrace); }); enumMember = this.RULE('enumMember', () => { this.CONSUME(t.Identifier); this.OPTION(() => { this.CONSUME(t.Assign); this.OPTION2(() => this.CONSUME(t.Minus)); this.CONSUME(c.NumericLiteral); }); }); constructorSuffix = this.RULE('constructorSuffix', () => { this.OPTION(() => { this.CONSUME(t.Colon); this.CONSUME(t.Identifier); this.SUBRULE(this.functionArguments); }); this.CONSUME(t.Constructor); }); functionExpression = this.RULE('functionExpression', () => { this.CONSUME(t.Function); this.OPTION1(() => this.CONSUME1(t.Identifier)); this.SUBRULE1(this.functionParameters); this.OPTION2(() => { this.SUBRULE2(this.constructorSuffix); }); this.SUBRULE(this.blockStatement); }); functionStatement = this.RULE('functionStatement', () => { this.SUBRULE(this.functionExpression); this.OPTION(() => this.CONSUME(t.Semicolon)); }); functionParameters = this.RULE('functionParameters', () => { this.CONSUME(t.StartParen); this.MANY_SEP({ SEP: t.Comma, DEF: () => this.SUBRULE(this.functionParameter), }); this.CONSUME(t.EndParen); }); functionParameter = this.RULE('functionParameter', () => { this.CONSUME(t.Identifier); this.OPTION(() => { this.CONSUME(t.Assign); this.SUBRULE(this.assignmentRightHandSide); }); }); macroStatement = this.RULE('macroStatement', () => { this.CONSUME(t.Macro); this.CONSUME(t.Identifier); this.SUBRULE(this.expressionStatement); }); forStatement = this.RULE('forStatement', () => { this.CONSUME(t.For); this.CONSUME(t.StartParen); this.OPTION1(() => this.OR([ { ALT: () => this.SUBRULE(this.localVarDeclarations) }, { ALT: () => this.SUBRULE(this.expression) }, ])); this.CONSUME2(t.Semicolon); this.OPTION2(() => this.SUBRULE2(this.expression)); this.CONSUME3(t.Semicolon); this.OPTION3(() => this.SUBRULE3(this.expression)); this.CONSUME(t.EndParen); this.SUBRULE(this.blockableStatement); }); globalVarDeclarationsStatement = this.RULE('globalVarDeclarationsStatement', () => { this.SUBRULE(this.globalVarDeclarations); this.optionallyConsumeSemicolon(); }); globalVarDeclarations = this.RULE('globalVarDeclarations', () => { this.CONSUME(t.GlobalVar); this.AT_LEAST_ONE_SEP({ SEP: t.Comma, DEF: () => { this.SUBRULE(this.globalVarDeclaration); }, }); }); globalVarDeclaration = this.RULE('globalVarDeclaration', () => { this.CONSUME(t.Identifier); }); localVarDeclarationsStatement = this.RULE('localVarDeclarationsStatement', () => { this.SUBRULE(this.localVarDeclarations); this.optionallyConsumeSemicolon(); }); localVarDeclarations = this.RULE('localVarDeclarations', () => { this.CONSUME(t.Var); this.AT_LEAST_ONE_SEP({ SEP: t.Comma, DEF: () => { this.SUBRULE(this.localVarDeclaration); }, }); }); localVarDeclaration = this.RULE('localVarDeclaration', () => { this.CONSUME(t.Identifier); this.OPTION(() => { this.CONSUME(c.AssignmentOperator); this.SUBRULE(this.assignmentRightHandSide); }); }); staticVarDeclarationStatement = this.RULE('staticVarDeclarationStatement', () => { this.SUBRULE(this.staticVarDeclaration); this.optionallyConsumeSemicolon(); }); staticVarDeclaration = this.RULE('staticVarDeclarations', () => { this.CONSUME(t.Static); this.CONSUME(t.Identifier); this.CONSUME(c.AssignmentOperator); this.SUBRULE(this.assignmentRightHandSide); }); // For simple variable assignments (no accessors on the LHS) variableAssignmentStatement = this.RULE('variableAssignmentStatement', () => { this.SUBRULE(this.variableAssignment); this.optionallyConsumeSemicolon(); }); variableAssignment = this.RULE('variableAssignment', () => { this.CONSUME(t.Identifier); this.CONSUME(c.AssignmentOperator); this.SUBRULE(this.assignmentRightHandSide); }); assignment = this.RULE('assignment', () => { this.CONSUME(c.AssignmentOperator); this.SUBRULE(this.assignmentRightHandSide); }); assignmentRightHandSide = this.RULE('assignmentRightHandSide', () => { this.OR([ { ALT: () => this.SUBRULE(this.expression) }, { ALT: () => this.SUBRULE(this.structLiteral) }, { ALT: () => this.SUBRULE(this.functionExpression) }, ]); }); arrayLiteral = this.RULE('arrayLiteral', () => { this.CONSUME(t.StartBracket); this.OPTION(() => { this.SUBRULE(this.assignmentRightHandSide); this.MANY(() => { this.CONSUME1(t.Comma); this.SUBRULE2(this.assignmentRightHandSide); }); this.OPTION2(() => this.CONSUME2(t.Comma)); }); this.CONSUME(t.EndBracket); }); structLiteral = this.RULE('structLiteral', () => { this.CONSUME(t.StartBrace); this.OPTION1(() => { this.SUBRULE1(this.structLiteralEntry); this.MANY(() => { this.CONSUME1(t.Comma); this.SUBRULE2(this.structLiteralEntry); }); this.OPTION2(() => this.CONSUME2(t.Comma)); }); this.CONSUME(t.EndBrace); }); structLiteralEntry = this.RULE('structLiteralEntry', () => { this.OR([ { ALT: () => { this.OPTION(() => { this.SUBRULE(this.jsdoc); }); this.CONSUME(t.Identifier); }, }, { ALT: () => this.SUBRULE(this.stringLiteral) }, ]); this.OPTION1(() => { this.CONSUME(t.Colon); this.SUBRULE(this.assignmentRightHandSide); }); }); whileStatement = this.RULE('whileStatement', () => { this.CONSUME(t.While); this.SUBRULE(this.expression); this.SUBRULE(this.blockableStatement); }); doUntilStatement = this.RULE('doUntilStatement', () => { this.CONSUME(t.Do); this.SUBRULE(this.blockableStatement); this.CONSUME(t.Until); this.SUBRULE(this.expression); }); switchStatement = this.RULE('switchStatement', () => { this.CONSUME(t.Switch); this.SUBRULE(this.expression); this.CONSUME(t.StartBrace); this.MANY(() => this.OR([ { ALT: () => this.SUBRULE(this.caseStatement) }, { ALT: () => this.SUBRULE(this.defaultStatement) }, ])); this.CONSUME(t.EndBrace); }); caseStatement = this.RULE('caseStatement', () => { this.CONSUME(t.Case); this.SUBRULE(this.expression); this.CONSUME(t.Colon); this.SUBRULE(this.blockableStatements); }); defaultStatement = this.RULE('defaultStatement', () => { this.CONSUME(t.Default); this.CONSUME(t.Colon); this.SUBRULE(this.blockableStatements); }); breakStatement = this.RULE('breakStatement', () => { this.CONSUME(t.Break); this.optionallyConsumeSemicolon(); }); continueStatement = this.RULE('continueStatement', () => { this.CONSUME(t.Continue); this.optionallyConsumeSemicolon(); }); exitStatement = this.RULE('exitStatement', () => { this.CONSUME(t.Exit); this.optionallyConsumeSemicolon(); }); withStatement = this.RULE('withStatement', () => { this.CONSUME(t.With); this.SUBRULE(this.expression); this.SUBRULE(this.blockableStatement); }); tryStatement = this.RULE('tryStatement', () => { this.CONSUME(t.Try); this.SUBRULE1(this.blockStatement); this.OPTION1(() => this.SUBRULE2(this.catchStatement)); this.OPTION2(() => { this.CONSUME(t.Finally); this.SUBRULE3(this.blockStatement); }); }); catchStatement = this.RULE('catchStatement', () => { this.CONSUME(t.Catch); this.CONSUME(t.StartParen); this.CONSUME(t.Identifier); this.CONSUME(t.EndParen); this.SUBRULE2(this.blockStatement); }); constructor() { super([...tokens, ...categories], { nodeLocationTracking: 'full', recoveryEnabled: true, skipValidations: true, }); this.performSelfAnalysis(); } static jsonify(cst) { return JSON.stringify(cst, (key, val) => { if (key === 'tokenType') { return { name: val.name, categories: val.CATEGORIES.map((c) => c.name), isParent: val.isParent, }; } return val; }, 2); } } export function withCtxKind(ctx, kind) { return { ctxKindStack: [...ctx.ctxKindStack, kind], returns: ctx.returns, }; } export const parser = new GmlParser(); export const GmlVisitorBase = parser.getBaseCstVisitorConstructorWithDefaults(); //# sourceMappingURL=parser.js.map