From 2f5c151014fd9a2de27f49b5436d9b61bd6a6b6b Mon Sep 17 00:00:00 2001 From: Kevin Frei Date: Wed, 13 Nov 2024 21:24:17 -0800 Subject: [PATCH] Meep script refactor (#69) * Moved the AutoConst transform to a separate file to prep for DriveBase consumption * Moved everything into a Closure because Javascript classes are terrible * Added some comments, stopped splitting, then joining after comment removal * Tweaked stuff for the flip tool a bit * Got the toVec() -> vec() change from a different diff --- package.json | 2 +- scripts/flip.ts | 64 +- scripts/helpers/AutoConstTransformer.ts | 481 +++ .../helpers/CommentStateMachine.excalidraw | 3078 +++++++++++++++++ scripts/helpers/removeComments.ts | 11 +- scripts/preprocessor.ts | 497 +-- tsconfig.json | 3 +- 7 files changed, 3619 insertions(+), 517 deletions(-) create mode 100644 scripts/helpers/AutoConstTransformer.ts create mode 100644 scripts/helpers/CommentStateMachine.excalidraw diff --git a/package.json b/package.json index c6add0fe..08b7fc6c 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "- // i+": "", "- // i.": " The '../' before the scripts line is because it's called from MeepMeepTesting directory", "meep": "bun run ../scripts/preprocessor.ts", - "testmeep": "bun run scripts/preprocessor.ts /Users/freik/src/ftc/CenterStage2023/MeepMeepTesting/build '[/Users/freik/src/ftc/CenterStage2023/Sixteen750/src/main/java/org/firstinspires/ftc/sixteen750/AutoConstants.java, /Users/freik/src/ftc/CenterStage2023/LearnBot/src/main/java/org/firstinspires/ftc/learnbot/opmodes/BasicAuto.java, /Users/freik/src/ftc/CenterStage2023/MeepMeepTesting/src/main/java/com/example/meepmeeptesting/AutoConstantsBlue.java, /Users/freik/src/ftc/CenterStage2023/MeepMeepTesting/src/main/java/com/example/meepmeeptesting/AutoConstantsRed.java, /Users/freik/src/ftc/CenterStage2023/RoadRunnerQuickStart/src/main/java/org/firstinspires/ftc/teamcode/drive/opmode/AutomaticFeedforwardTuner.java, /Users/freik/src/ftc/CenterStage2023/Twenty403/src/main/java/org/firstinspires/ftc/twenty403/AutoConstants.java]'", + "testmeep": "bun run scripts/preprocessor.ts ./MeepMeepTesting/build/test TEST_CLASS ./Sixteen750/src/main/java/org/firstinspires/ftc/sixteen750/AutoConstants.java ./Sixteen750/src/main/java/org/firstinspires/ftc/sixteen750/subsystems/DrivebaseSubsystem.java", "- // j-": "", "- // j ": "This is the script that presents the students with a workflow", "work": "bun run scripts/workflow.ts", diff --git a/scripts/flip.ts b/scripts/flip.ts index cd94480c..6376c17b 100644 --- a/scripts/flip.ts +++ b/scripts/flip.ts @@ -23,46 +23,42 @@ import { argv } from 'process'; const { readFile, writeFile } = promises; type FileList = { - name: string; + key: string; files: string[]; }; + +const technoLib: FileList = { + key: 'TechnoLibLocal', + files: [ + 'LearnBot/build.gradle', + 'Sixteen750/build.gradle', + 'Twenty403/build.gradle', + 'build.dependencies.gradle', + 'settings.gradle', + ], +}; +const botList: FileList = { + key: 'BUILD ALL BOTS', + files: ['settings.gradle', 'build.gradle'], +}; +const meepList: FileList = { + key: 'MeepMeepLocal', + files: ['MeepMeepTesting/build.gradle', 'settings.gradle'], +}; // This is a map of keys (the argument to call flip.js with) to objects that // are a name (the tag at the end of the comment) and an array of files to // scan & process const fileList = new Map([ - [ - 'lib', - { - name: 'TechnoLibLocal', - files: [ - 'LearnBot/build.gradle', - 'Sixteen750/build.gradle', - 'Twenty403/build.gradle', - 'build.dependencies.gradle', - 'settings.gradle', - ], - }, - ], - [ - 'bot', - { name: 'BUILD ALL BOTS', files: ['settings.gradle', 'build.gradle'] }, - ], - [ - 'meepmeep', - { - name: 'MeepMeepLocal', - files: [ - 'MeepMeepTesting/build.gradle', - 'settings.gradle', - ] - } - ] + ['lib', technoLib], + ['bot', botList], + ['meepmeep', meepList], + ['meep', meepList], ]); // For any line that ends with '// FLIP: id', // toggle the line comment 'status' -function toggleLine(lineFull: string, str: string) { - const commentMarker = '// FLIP: ' + str; +function toggleLine(lineFull: string, id: string) { + const commentMarker = '// FLIP: ' + id; const line = lineFull.trimEnd(); // If the line doesn't end with the comment marker, don't change it at all if (!line.endsWith(commentMarker)) { @@ -80,11 +76,11 @@ function toggleLine(lineFull: string, str: string) { } // Read the file, flip the comments for lines with markers, the write it back -async function toggleFile(file: string, str: string) { +async function toggleFile(file: string, key: string) { try { const contents = await readFile(file, 'utf-8'); const resultArray = contents.split('\n'); - const toggled = resultArray.map((elem) => toggleLine(elem, str)); + const toggled = resultArray.map((elem) => toggleLine(elem, key)); await writeFile(file, toggled.join('\n')); } catch (e) { // Some file access problem :( @@ -100,9 +96,9 @@ async function toggleLinesWithComments(arg: string) { 'This script only understands :' + [...fileList.keys()].join(', '), ); } - let { name: str, files } = elem; + let { key, files } = elem; for (let filename of files) { - await toggleFile(filename, str); + await toggleFile(filename, key); } } diff --git a/scripts/helpers/AutoConstTransformer.ts b/scripts/helpers/AutoConstTransformer.ts new file mode 100644 index 00000000..b2131394 --- /dev/null +++ b/scripts/helpers/AutoConstTransformer.ts @@ -0,0 +1,481 @@ +import { + chkBothOf, + chkFieldType, + hasFieldType, + isArray, + isNonNullable, + isNumber, +} from '@freik/typechk'; +import { + BaseJavaCstVisitorWithDefaults, + CstNode, + PackageDeclarationCtx, + ImportDeclarationCtx, + ClassDeclarationCtx, + NormalClassDeclarationCtx, + TypeIdentifierCtx, + ClassBodyDeclarationCtx, + ClassMemberDeclarationCtx, + FieldDeclarationCtx, + UnannTypeCtx, + UnannReferenceTypeCtx, + UnannClassOrInterfaceTypeCtx, + VariableDeclaratorListCtx, + VariableDeclaratorCtx, + VariableDeclaratorIdCtx, + VariableInitializerCtx, + ExpressionCtx, + LambdaExpressionCtx, + LambdaBodyCtx, + ConditionalExpressionCtx, + BinaryExpressionCtx, + UnaryExpressionCtx, + PrimaryCtx, + PrimaryPrefixCtx, + PrimarySuffixCtx, + FqnOrRefTypeCtx, + FqnOrRefTypePartFirstCtx, + FqnOrRefTypePartCommonCtx, + NewExpressionCtx, + UnqualifiedClassInstanceCreationExpressionCtx, + ArgumentListCtx, + parse, +} from 'java-parser'; +import { MakeStack } from '@freik/containers'; + +/*** BEGIN CONFIGURATION STUFF ***/ +// These are types the parser is transforming: +const typeMap = new Map([ + ['ConfigurablePose', 'Pose2d'], + ['ConfigurablePoseD', 'Pose2d'], + [ + 'Function,TrajectorySequence>', + 'Supplier', + ], +]); +// This is a map of old import names to new ones for the generated code. +const importMap = new Map([ + [ + 'com.technototes.path.geometry.ConfigurablePoseD', + 'com.acmerobotics.roadrunner.geometry.Pose2d', + ], + [ + 'com.technototes.path.geometry.ConfigurablePose', + 'com.acmerobotics.roadrunner.geometry.Pose2d', + ], + [ + 'com.technototes.path.trajectorysequence.TrajectorySequence', + 'com.acmerobotics.roadrunner.trajectory.Trajectory', + ], + [ + 'com.technototes.path.trajectorysequence.TrajectorySequenceBuilder', + 'com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder', + ], +]); +// This is a set of imports that we want to remove from the generated code. +const removeImports = new Set([ + 'com.acmerobotics.dashboard.config.Config', + 'com.technototes.library.command.Command', + 'com.technototes.library.command.SequentialCommandGroup', + 'com.technototes.path.command.TrajectorySequenceCommand', +]); +const extraImports = [ + 'static java.lang.Math.toRadians', + 'java.util.function.Supplier', +]; +/*** END CONFIGURATION STUFF ***/ + +// Context-free Helper functions: + +function noWhitespace(input: string): string { + return input.replace(/\s/g, ''); +} + +function unsupported(name: string, obj: object): void { + if (obj && obj[name]) { + throw new Error(`${name} is unsupported by the MeepMeep synchronizer`); + } +} + +function required(obj: unknown, message?: string): obj is NonNullable { + if (!obj) { + throw new Error(message ?? 'Required type not found :('); + } + return true; +} + +function assert(obj: unknown, message: string): obj is NonNullable { + if (!obj) { + throw new Error(message); + } + return true; +} + +enum TokenKind { + NewType, + LambdaParam, + DeclType, +} +type Token = { kind: TokenKind; value: string }; +function MakeToken(kind: TokenKind, value: string) { + return { kind, value }; +} + +export type AutoConstantsTransformer = { + transformFile: (fileContents: string) => void; + collectImports: () => string; + getTransformedCode: () => string[]; +}; + +export function MakeAutoConstantsTransformer(): AutoConstantsTransformer { + // Closure state: + + const imports = new Set(extraImports); + const theCode: string[] = []; + let curFile = ''; + let prev = ''; + let classHelpersEmitted = false; + + // A little "context" stack + const tokenStack = MakeStack(); + + // A really dumb symbol table (That I'm only filling, but never using...) + const symbolTypes = new Map(); + + // Closure-scoped functions: + + function codeSpit(...args: string[]): void { + if (prev.length > 0) { + args = [prev, ...args]; + prev = ''; + } + if (args.length > 1) { + theCode.push(args.join('')); + } else { + theCode.push(args[0]); + } + } + + function codeAdd(...args: string[]): void { + prev += args.join(''); + } + + function codeReset() { + prev = ''; + } + + function emitClassHelpers() { + // TODO: Read the attributes from the Drivebase file! + if (classHelpersEmitted) { + return; + } + classHelpersEmitted = true; + // public static MinVelocityConstraint MIN_VEL = new MinVelocityConstraint(Arrays.asList( + // new AngularVelocityConstraint(60 /*MAX_ANG_VEL*/), + // new MecanumVelocityConstraint(60 /*MAX_VEL*/, 14 /*TRACK_WIDTH*/))); + // public static ProfileAccelerationConstraint PROF_ACCEL = new ProfileAccelerationConstraint(20/*MAX_ACCEL*/); + codeSpit('public static Function fwdFunc;'); // = pose -> new TrajectoryBuilder(pose, MIN_VEL, PROF_ACCEL);")`); + codeSpit('public static Function revFunc;'); // = pose -> new TrajectoryBuilder(pose, MIN_VEL, PROF_ACCEL);")`); + } + + // Produces a single, unique set of import statements from the group of files. + function collectImports(): string { + return [...imports] + .filter((val) => !removeImports.has(val)) + .sort() + .map((i) => `import ${i};`) + .join('\n'); + } + + /* Begin non-parser visitor helpers */ + function readCode(start: number, end: number): string { + return curFile.substring(start, end + 1); + } + + function getItemContent(f: unknown): string { + if ( + hasFieldType( + f, + 'location', + chkBothOf( + chkFieldType('startOffset', isNumber), + chkFieldType('endOffset', isNumber), + ), + ) + ) { + return readCode(f.location.startOffset, f.location.endOffset); + } + return ''; + } + + function getContent(field: unknown, sep?: string): string { + return isArray(field) + ? field.map(getItemContent).join(sep ?? ' ') + : getItemContent(field); + } + /* End non-parser visitor helpers */ + + class AutoConstTransformer extends BaseJavaCstVisitorWithDefaults { + constructor() { + super(); + this.validateVisitor(); + } + + transformFile(fileContents: string) { + curFile = fileContents; + this.visit(parse(fileContents)); + } + + maybeVisit(field: unknown): void { + if (!isNonNullable(field)) return; + this.visit(field as CstNode); + } + + mustVisit(obj: unknown): void { + if (isNonNullable(obj)) { + this.maybeVisit(obj as CstNode); + } else { + throw new Error(`Missing required child element`); + } + } + + // Rewire the package: + packageDeclaration(ctx: PackageDeclarationCtx, param?: unknown) { + codeSpit( + '// Original package: ', + ctx.Identifier.map((token) => token.image).join('.'), + ); + // codeSpit('package ', packageDir.join('.'), ';'); + } + + // Copy, reroute, or remove imports: + importDeclaration(ctx: ImportDeclarationCtx, param?: unknown) { + const stat = ctx.Static ? 'static ' : ''; + const star = ctx.Star ? '.*' : ''; + const imprt = ctx.packageOrTypeName + .map((cst) => cst.children.Identifier.map((tok) => tok.image).join('.')) + .join('.'); + const actual = importMap.has(imprt) ? importMap.get(imprt) : imprt; + const key = `${stat}${actual}${star}`; + imports.add(key); + } + + classDeclaration(ctx: ClassDeclarationCtx, param?: any) { + // Class declarations are smashed to just be public static: + codeAdd('public static class '); + classHelpersEmitted = false; + this.mustVisit(ctx.normalClassDeclaration); + } + normalClassDeclaration(ctx: NormalClassDeclarationCtx, param?: any) { + // No extends/implements are carried over + this.mustVisit(ctx.typeIdentifier); + codeSpit(' {'); + this.mustVisit(ctx.classBody); + codeSpit('}'); + } + // spit out the type identifier + typeIdentifier(ctx: TypeIdentifierCtx, param?: any) { + codeAdd(ctx.Identifier.map((tok) => tok.image).join('.')); + } + // We're picky about the type of class bodies supported + classBodyDeclaration(ctx: ClassBodyDeclarationCtx, param?: any) { + // No constructor! + unsupported('constructorDeclaration', ctx); + // No instance initializers! + unsupported('instanceInitializer', ctx); + // No static initializers! + unsupported('staticInitializer', ctx); + this.mustVisit(ctx.classMemberDeclaration); + } + classMemberDeclaration(ctx: ClassMemberDeclarationCtx, param?: any) { + // We don't support methods! + unsupported('methodDeclaration', ctx); + // We don't support interfaces, either. + unsupported('interfaceDeclaration', ctx); + this.maybeVisit(ctx.fieldDeclaration); + this.maybeVisit(ctx.classDeclaration); + } + fieldDeclaration(ctx: FieldDeclarationCtx, param?: any) { + emitClassHelpers(); + // Add the field modifiers + codeAdd(getContent(ctx.fieldModifier), ' '); + this.mustVisit(ctx.unannType); + this.mustVisit(ctx.variableDeclaratorList); + if (tokenStack.peek()?.kind === TokenKind.DeclType) { + tokenStack.pop(); + } else { + // No constants, random variables, whatever. Delete 'em! + codeReset(); + } + } + unannType(ctx: UnannTypeCtx, param?: any) { + this.maybeVisit(ctx.unannReferenceType); + if (ctx.unannPrimitiveTypeWithOptionalDimsSuffix) { + codeAdd(getContent(ctx.unannPrimitiveTypeWithOptionalDimsSuffix)); + } + } + unannReferenceType(ctx: UnannReferenceTypeCtx, param?: any) { + this.mustVisit(ctx.unannClassOrInterfaceType); + } + unannClassOrInterfaceType(ctx: UnannClassOrInterfaceTypeCtx, param?: any) { + const typeName = noWhitespace(getContent(ctx.unannClassType)); + tokenStack.push(MakeToken(TokenKind.DeclType, typeName)); + codeAdd(typeMap.get(typeName) || typeName); + } + variableDeclaratorList(ctx: VariableDeclaratorListCtx, param?: any) { + this.mustVisit(ctx.variableDeclarator); + } + variableDeclarator(ctx: VariableDeclaratorCtx, param?: any) { + this.mustVisit(ctx.variableDeclaratorId); + this.mustVisit(ctx.variableInitializer); + } + variableDeclaratorId(ctx: VariableDeclaratorIdCtx, param?: any) { + if (required(ctx.Identifier, 'Unsupported Identifier-less varDeclID')) { + const varName = ctx.Identifier[0].image; + codeAdd(' ', varName, ' = '); + // This is setting us up to keep track of variable name types, + // so we can yoink ".toPose()" modifiers later in the file... + const declType = tokenStack.peek(); + if (declType?.kind === TokenKind.DeclType) { + symbolTypes.set(varName, declType.value); + } + } + } + variableInitializer(ctx: VariableInitializerCtx, param?: any) { + this.mustVisit(ctx.expression); + } + expression(ctx: ExpressionCtx, param?: any) { + this.maybeVisit(ctx.lambdaExpression); + this.maybeVisit(ctx.conditionalExpression); + } + lambdaExpression(ctx: LambdaExpressionCtx, param?: any) { + const params = getContent(ctx.lambdaParameters, ', '); + // We need to transform b -> b.apply(...) to () -> func.apply(...) + // Push the parameter(s) to the lambda for the lambdaBody to process + tokenStack.push(MakeToken(TokenKind.LambdaParam, params)); + this.mustVisit(ctx.lambdaBody); + } + lambdaBody(ctx: LambdaBodyCtx, param?: any) { + const expr = noWhitespace(getContent(ctx.expression)); + // console.log('Expr: ', expr); + unsupported('block', ctx); + const top = tokenStack.pop(); + if ( + top && + top.kind === TokenKind.LambdaParam && + expr.startsWith(`${top.value}.apply(`) + ) { + const cleanupExpr = expr + .substring(top.value.length) + .replaceAll('.toPose()', '') + .replaceAll('.toVec()', '.vec()'); + // This is a hack to deal with the fact that it looks like + // MeepMeep doesn't allow us to use the .setReversed method. + // It's not perfect, but it lets MeepMeep drive the bot backwards. + const setRev = '.setReversed(true)'; + const thisFunc = cleanupExpr.includes(setRev) ? 'revFunc' : 'fwdFunc'; + codeSpit('() -> ', thisFunc, cleanupExpr.replace(setRev, ''), ';'); + } else if (top) { + codeSpit(top.value, ' -> ', expr); + } else { + throw new Error('Unexpected Lambda body stack'); + } + } + // Conditional expression is the container for all non-lambdas + // which is definitely a little weird, but whatever... + conditionalExpression(ctx: ConditionalExpressionCtx, param?: any) { + unsupported('QuestionMark', ctx); + unsupported('Colon', ctx); + this.mustVisit(ctx.binaryExpression); + } + binaryExpression(ctx: BinaryExpressionCtx, param?: any) { + assert( + !( + ctx.AssignmentOperator || + ctx.BinaryOperator || + ctx.Greater || + ctx.Instanceof || + ctx.Less || + ctx.pattern || + ctx.referenceType + ), + 'unsupported child of binary expression', + ); + this.maybeVisit(ctx.expression); + this.mustVisit(ctx.unaryExpression); + } + unaryExpression(ctx: UnaryExpressionCtx, param?: any) { + this.mustVisit(ctx.primary); + } + primary(ctx: PrimaryCtx, param?: any) { + // This is a list... + this.mustVisit(ctx.primaryPrefix); + this.maybeVisit(ctx.primarySuffix); + } + primaryPrefix(ctx: PrimaryPrefixCtx, param?: any) { + this.maybeVisit(ctx.newExpression); + this.maybeVisit(ctx.fqnOrRefType); + this.maybeVisit(ctx.literal); + } + primarySuffix(ctx: PrimarySuffixCtx, param?: any) { + // console.log('primarySuffix', ctx); + codeAdd(getContent(ctx)); + } + fqnOrRefType(ctx: FqnOrRefTypeCtx, param?: any) { + this.mustVisit(ctx.fqnOrRefTypePartFirst); + codeAdd(getContent(ctx.fqnOrRefTypePartRest)); + } + fqnOrRefTypePartFirst(ctx: FqnOrRefTypePartFirstCtx, param?: any) { + this.mustVisit(ctx.fqnOrRefTypePartCommon); + } + fqnOrRefTypePartCommon(ctx: FqnOrRefTypePartCommonCtx, param?: any) { + const lambdaArg = tokenStack.peek(); + if (lambdaArg && lambdaArg.kind === TokenKind.LambdaParam) { + codeAdd('func'); + } else { + codeAdd(getContent(ctx)); + } + } + newExpression(ctx: NewExpressionCtx, param?: any) { + // console.log('new', ctx); + codeAdd('new'); + this.maybeVisit(ctx.unqualifiedClassInstanceCreationExpression); + } + unqualifiedClassInstanceCreationExpression( + ctx: UnqualifiedClassInstanceCreationExpressionCtx, + param?: any, + ) { + const typeName = getContent(ctx.classOrInterfaceTypeToInstantiate); + codeAdd(' ', typeMap.get(typeName) || typeName); + tokenStack.push(MakeToken(TokenKind.NewType, typeName)); + this.maybeVisit(ctx.argumentList); + tokenStack.pop(); + } + argumentList(ctx: ArgumentListCtx, param?: any) { + codeAdd('('); + const top = tokenStack.peek(); + ctx.expression.forEach((node, idx) => { + const prefix = idx === 0 ? '' : ', '; + const code = getContent(node); + if ( + idx === 2 && + top.kind === TokenKind.NewType && + top.value === 'ConfigurablePoseD' + ) { + codeAdd(prefix, 'toRadians(', code, ')'); + } else { + codeAdd(prefix, code); + } + }); + codeSpit(');'); + } + } + const transformer = new AutoConstTransformer(); + return { + transformFile: (fileContents: string) => { + transformer.transformFile(fileContents); + }, + collectImports, + getTransformedCode: () => theCode, + }; +} diff --git a/scripts/helpers/CommentStateMachine.excalidraw b/scripts/helpers/CommentStateMachine.excalidraw new file mode 100644 index 00000000..c9382ca5 --- /dev/null +++ b/scripts/helpers/CommentStateMachine.excalidraw @@ -0,0 +1,3078 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 294, + "versionNonce": 1143152497, + "index": "a0", + "isDeleted": false, + "id": "3q1sn9-Qf8tUY3SyIph-x", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1138.3999633789062, + "y": 504.9999694824219, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 86.79998779296876, + "height": 36.000015258789055, + "seed": 1383897567, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ue2Qtei9cxMrOWLIs_xr_" + }, + { + "id": "WElOnJZYUwdjOBufXPCms", + "type": "arrow" + }, + { + "id": "LPk3PmilvqFnuDmpMVt_A", + "type": "arrow" + }, + { + "id": "m8MsmheFNRRbHKkoNjKQ9", + "type": "arrow" + }, + { + "id": "WqxN2O4nrqlapOIZqRj7c", + "type": "arrow" + }, + { + "id": "hH3xMqLNlEyB6YkFrsVWu", + "type": "arrow" + }, + { + "id": "YWdvy3MifDMSum0WWzLdC", + "type": "arrow" + }, + { + "id": "M6qLvmi8o9UKEsK8xdYs3", + "type": "arrow" + }, + { + "id": "fSDMaj2ndf8pHlXfQh-_B", + "type": "arrow" + }, + { + "id": "Wu03QNDAyGrScCchj5hWl", + "type": "arrow" + } + ], + "updated": 1728799123339, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 258, + "versionNonce": 60773169, + "index": "a0V", + "isDeleted": false, + "id": "ue2Qtei9cxMrOWLIs_xr_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1158.869972229004, + "y": 510.4999771118164, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 45.85997009277344, + "height": 25, + "seed": 2136334641, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "Plain", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3q1sn9-Qf8tUY3SyIph-x", + "originalText": "Plain", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 324, + "versionNonce": 1521847327, + "index": "a2", + "isDeleted": false, + "id": "wnGx-Ga0bE4BFqfUzBh90", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1006.6000671386719, + "y": 373.5999984741211, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 138.7999877929688, + "height": 35, + "seed": 1688995327, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "MM-gJFnGTjFqaWN_EfZgh" + }, + { + "id": "YWdvy3MifDMSum0WWzLdC", + "type": "arrow" + }, + { + "id": "1cOH0Tt9Aqj84h3VL3QHQ", + "type": "arrow" + }, + { + "id": "LIjbYoGyESXlzk9C_qgeN", + "type": "arrow" + }, + { + "id": "P0YVc9786c7HOxFakJJ7d", + "type": "arrow" + }, + { + "id": "M6qLvmi8o9UKEsK8xdYs3", + "type": "arrow" + } + ], + "updated": 1728799123339, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 321, + "versionNonce": 797951249, + "index": "a3", + "isDeleted": false, + "id": "MM-gJFnGTjFqaWN_EfZgh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1024.2601013183594, + "y": 378.5999984741211, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 103.47991943359375, + "height": 25, + "seed": 1414931871, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "In \" String", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "wnGx-Ga0bE4BFqfUzBh90", + "originalText": "In \" String", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 458, + "versionNonce": 2119174481, + "index": "a4", + "isDeleted": false, + "id": "c55oP0_iHqRwG-jmMTRyS", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1005.3999328613281, + "y": 593.5000305175781, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 138.7999877929688, + "height": 35, + "seed": 844688927, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "mzkhMtOeNprM1DwDB6Hoa" + }, + { + "id": "WqxN2O4nrqlapOIZqRj7c", + "type": "arrow" + }, + { + "id": "DTTsjLLg_YZ8Ic142XX2J", + "type": "arrow" + }, + { + "id": "KnUty7bIXX1DlMF-0I5Bp", + "type": "arrow" + }, + { + "id": "fSDMaj2ndf8pHlXfQh-_B", + "type": "arrow" + }, + { + "id": "sz0p-VylkYWg5nvlI2Qtf", + "type": "arrow" + } + ], + "updated": 1728799123339, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 451, + "versionNonce": 1280768753, + "index": "a5", + "isDeleted": false, + "id": "mzkhMtOeNprM1DwDB6Hoa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1024.589973449707, + "y": 598.5000305175781, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 100.41990661621094, + "height": 25, + "seed": 868459825, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "In ' String", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c55oP0_iHqRwG-jmMTRyS", + "originalText": "In ' String", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 427, + "versionNonce": 1435283519, + "index": "a7", + "isDeleted": false, + "id": "YTN-ckYgg-8-JRhhHSDjm", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1325.0000305175781, + "y": 366.9000244140625, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 138.7999877929688, + "height": 60, + "seed": 1718866495, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "1Plw4RD86gaHUmP6fdsmq" + }, + { + "id": "j9QbMfQe4C9QPZwm7JczW", + "type": "arrow" + }, + { + "id": "Y9Iu3BRzOjnqwUIWyhp2c", + "type": "arrow" + }, + { + "id": "PJYo__Aa9aXnrR41c5IfT", + "type": "arrow" + }, + { + "id": "1xY7OjXHXAikG461MZcse", + "type": "arrow" + } + ], + "updated": 1728799123339, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 446, + "versionNonce": 444363985, + "index": "a8", + "isDeleted": false, + "id": "1Plw4RD86gaHUmP6fdsmq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1337.0100631713867, + "y": 371.9000244140625, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 114.77992248535156, + "height": 50, + "seed": 682142431, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "In Multiline \nComment", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "YTN-ckYgg-8-JRhhHSDjm", + "originalText": "In Multiline Comment", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 586, + "versionNonce": 453293873, + "index": "a9", + "isDeleted": false, + "id": "S7a2AIojLrv85XaArLDJo", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1251.0001525878906, + "y": 619.9999694824219, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 138.7999877929688, + "height": 60, + "seed": 2044539487, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "9RB4qWW4QNtb7goii-UvG" + }, + { + "id": "WElOnJZYUwdjOBufXPCms", + "type": "arrow" + }, + { + "id": "Bbl8eivezf-Kvsx9uw5mF", + "type": "arrow" + }, + { + "id": "eKmw0jad963UizPnZNBp1", + "type": "arrow" + } + ], + "updated": 1728799123339, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 639, + "versionNonce": 50530975, + "index": "aA", + "isDeleted": false, + "id": "9RB4qWW4QNtb7goii-UvG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1256.1901931762695, + "y": 624.9999694824219, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 128.41990661621094, + "height": 50, + "seed": 131482385, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728799079399, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "In Single-\nLine Comment", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "S7a2AIojLrv85XaArLDJo", + "originalText": "In Single-Line Comment", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 249, + "versionNonce": 1747422047, + "index": "aB", + "isDeleted": false, + "id": "hH3xMqLNlEyB6YkFrsVWu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1027.8352034324512, + "y": 518.7130773414558, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 101.76477215348632, + "height": 4.087002004247324, + "seed": 1969589887, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728798797966, + "link": null, + "locked": false, + "startBinding": { + "elementId": "8fo7fBOZVOS4Ww_yPUq4U", + "focus": -0.04238805727508804, + "gap": 4.053635040844007, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": -0.09606032204782834, + "gap": 8.79998779296875, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 101.76477215348632, + 4.087002004247324 + ] + ], + "elbowed": false + }, + { + "type": "arrow", + "version": 611, + "versionNonce": 493914641, + "index": "aD", + "isDeleted": false, + "id": "WElOnJZYUwdjOBufXPCms", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1247.0001831054688, + "y": 655.5531103967273, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 38.65003056082878, + "height": 103.55311039672733, + "seed": 1942840991, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "V0-I5BmNPzIFL-49u4Wva" + } + ], + "updated": 1728799099993, + "link": null, + "locked": false, + "startBinding": { + "elementId": "S7a2AIojLrv85XaArLDJo", + "focus": -0.8250509906482295, + "gap": 3.999969482421875, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": -0.5534911494479003, + "gap": 11.000015258789062, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -34.60028076171875, + -41.153085982664834 + ], + [ + -38.65003056082878, + -103.55311039672733 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 40, + "versionNonce": 1415412337, + "index": "aE", + "isDeleted": false, + "id": "V0-I5BmNPzIFL-49u4Wva", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1202.209976196289, + "y": 634.2999877929688, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 22.779998779296875, + "height": 25, + "seed": 2016958257, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "\\n", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "WElOnJZYUwdjOBufXPCms", + "originalText": "\\n", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 321, + "versionNonce": 1176658207, + "index": "aF", + "isDeleted": false, + "id": "TGAYEfTexZjwKEU3HXks3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1328.6000061035156, + "y": 502.80004119873047, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 107.20001220703128, + "height": 35.5999755859375, + "seed": 1673870015, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Em5ITnF3Sg4oFNoh2L0Xe" + }, + { + "id": "LPk3PmilvqFnuDmpMVt_A", + "type": "arrow" + }, + { + "id": "j9QbMfQe4C9QPZwm7JczW", + "type": "arrow" + }, + { + "id": "Bbl8eivezf-Kvsx9uw5mF", + "type": "arrow" + }, + { + "id": "Wu03QNDAyGrScCchj5hWl", + "type": "arrow" + } + ], + "updated": 1728798797964, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 299, + "versionNonce": 1820787793, + "index": "aG", + "isDeleted": false, + "id": "Em5ITnF3Sg4oFNoh2L0Xe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1340.820053100586, + "y": 508.1000289916992, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 82.75991821289062, + "height": 25, + "seed": 851618751, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "Single '/'", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TGAYEfTexZjwKEU3HXks3", + "originalText": "Single '/'", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 346, + "versionNonce": 1592795839, + "index": "aH", + "isDeleted": false, + "id": "LPk3PmilvqFnuDmpMVt_A", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1226.8001708984375, + "y": 521.3461107677836, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 99.1998291015625, + "height": 17.346110767783557, + "seed": 166562527, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "8Xqf8b4Cp_6jDCMQhzHGT" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": 0.5147262270494141, + "gap": 1.6002197265625, + "fixedPoint": null + }, + "endBinding": { + "elementId": "TGAYEfTexZjwKEU3HXks3", + "focus": -0.4766268510302827, + "gap": 2.600006103515625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 36, + -17.346110767783557 + ], + [ + 99.1998291015625, + -0.4547017049604847 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 28, + "versionNonce": 1050956337, + "index": "aI", + "isDeleted": false, + "id": "8Xqf8b4Cp_6jDCMQhzHGT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1256.3001708984375, + "y": 491.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 13, + "height": 25, + "seed": 441574161, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "/", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LPk3PmilvqFnuDmpMVt_A", + "originalText": "/", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 425, + "versionNonce": 2064264927, + "index": "aJ", + "isDeleted": false, + "id": "Bbl8eivezf-Kvsx9uw5mF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1394.883410646036, + "y": 542, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 78.16392021998058, + "height": 75.20001220703125, + "seed": 1121375999, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "DZXLle-UV3LrRJg7TAT-W" + } + ], + "updated": 1728799079399, + "link": null, + "locked": false, + "startBinding": { + "elementId": "TGAYEfTexZjwKEU3HXks3", + "focus": -0.42487095481378734, + "gap": 3.5999832153320312, + "fixedPoint": null + }, + "endBinding": { + "elementId": "S7a2AIojLrv85XaArLDJo", + "focus": -0.4584034863653802, + "gap": 2.799957275390625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -32.083361817911054, + 44 + ], + [ + -78.16392021998058, + 75.20001220703125 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 28, + "versionNonce": 1604117521, + "index": "aK", + "isDeleted": false, + "id": "DZXLle-UV3LrRJg7TAT-W", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1383.10009765625, + "y": 614.2999877929688, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 13, + "height": 25, + "seed": 1714948529, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "/", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Bbl8eivezf-Kvsx9uw5mF", + "originalText": "/", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 144, + "versionNonce": 1638378673, + "index": "aL", + "isDeleted": false, + "id": "j9QbMfQe4C9QPZwm7JczW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1374.3937295540686, + "y": 498, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8.398370367299549, + "height": 69.19998168945312, + "seed": 83992351, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "xb5TU0mEuoJpixM_mxNm6" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "TGAYEfTexZjwKEU3HXks3", + "focus": -0.18918677135623635, + "gap": 4.800041198730469, + "fixedPoint": null + }, + "endBinding": { + "elementId": "YTN-ckYgg-8-JRhhHSDjm", + "focus": 0.1050368981335527, + "gap": 1.899993896484375, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 8.398370367299549, + -69.19998168945312 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 30, + "versionNonce": 1230888159, + "index": "aLV", + "isDeleted": false, + "id": "xb5TU0mEuoJpixM_mxNm6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1373.3429147377183, + "y": 450.90000915527344, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 10.5, + "height": 25, + "seed": 351819281, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "*", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "j9QbMfQe4C9QPZwm7JczW", + "originalText": "*", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 603, + "versionNonce": 1393415121, + "index": "aO", + "isDeleted": false, + "id": "VGPLX7Te8egFi8JG2xNVh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1310.5998840332031, + "y": 255.69996643066406, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 140, + "height": 35, + "seed": 584999743, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "dD4mN-bAPQaOP_3oSvbBh" + }, + { + "id": "8j6WQ-X45t0bSp5OQeZ6b", + "type": "arrow" + }, + { + "id": "m8MsmheFNRRbHKkoNjKQ9", + "type": "arrow" + }, + { + "id": "Y9Iu3BRzOjnqwUIWyhp2c", + "type": "arrow" + }, + { + "id": "PJYo__Aa9aXnrR41c5IfT", + "type": "arrow" + } + ], + "updated": 1728798797964, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 682, + "versionNonce": 205966783, + "index": "aP", + "isDeleted": false, + "id": "dD4mN-bAPQaOP_3oSvbBh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1315.9799346923828, + "y": 260.69996643066406, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 129.23989868164062, + "height": 25, + "seed": 1152802993, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "'*' in Multiline", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "VGPLX7Te8egFi8JG2xNVh", + "originalText": "'*' in Multiline", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 362, + "versionNonce": 1315421951, + "index": "aQ", + "isDeleted": false, + "id": "8j6WQ-X45t0bSp5OQeZ6b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 6.266493333614673, + "x": 1365.4120966018286, + "y": 248.11880856538545, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.147031052123126, + "height": 46.69057362867863, + "seed": 1288545119, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "jgOVJDe7IvJ9UFXwUlbiC" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "VGPLX7Te8egFi8JG2xNVh", + "focus": -0.13183490241325496, + "gap": 7.2797746086301345, + "fixedPoint": null + }, + "endBinding": { + "elementId": "VGPLX7Te8egFi8JG2xNVh", + "focus": 0.20218200132247793, + "gap": 4.449362390842225, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -8.19577660863272, + -43.278570261458064 + ], + [ + 44.951254443490406, + -37.35247566422493 + ], + [ + 34.81570437376513, + 3.4120033672205636 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 34, + "versionNonce": 1778513649, + "index": "aR", + "isDeleted": false, + "id": "jgOVJDe7IvJ9UFXwUlbiC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 6.266493333614673, + "x": 1379.6032393632504, + "y": 190.18605716300843, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 10.5, + "height": 25, + "seed": 1033628671, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "*", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8j6WQ-X45t0bSp5OQeZ6b", + "originalText": "*", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 288, + "versionNonce": 2083976305, + "index": "aS", + "isDeleted": false, + "id": "m8MsmheFNRRbHKkoNjKQ9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1307.199951171875, + "y": 281.8573126019455, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 98.62286147970212, + "height": 218.54265077696073, + "seed": 1394597759, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "JXuzngkMBCulVtP0bi-Uy" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "VGPLX7Te8egFi8JG2xNVh", + "focus": 0.7243487689117751, + "gap": 3.399932861328125, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": 0.5111838405997338, + "gap": 4.600006103515625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -77.599853515625, + 72.94270570860135 + ], + [ + -98.62286147970212, + 218.54265077696073 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 30, + "versionNonce": 273656319, + "index": "aT", + "isDeleted": false, + "id": "JXuzngkMBCulVtP0bi-Uy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1223.10009765625, + "y": 342.3000183105469, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 13, + "height": 25, + "seed": 322562847, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "/", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "m8MsmheFNRRbHKkoNjKQ9", + "originalText": "/", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 177, + "versionNonce": 1043695391, + "index": "aU", + "isDeleted": false, + "id": "Y9Iu3BRzOjnqwUIWyhp2c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1373.7672190199698, + "y": 294, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.1273478061598325, + "height": 69.20001220703125, + "seed": 672503711, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "75f49B_WUfqNdIRC8yJr2" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "VGPLX7Te8egFi8JG2xNVh", + "focus": 0.08324027033417955, + "gap": 3.3000335693359375, + "fixedPoint": null + }, + "endBinding": { + "elementId": "YTN-ckYgg-8-JRhhHSDjm", + "focus": -0.35713672617105024, + "gap": 3.70001220703125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.1273478061598325, + 69.20001220703125 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 31, + "versionNonce": 342681855, + "index": "aV", + "isDeleted": false, + "id": "75f49B_WUfqNdIRC8yJr2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1345.3535695309524, + "y": 316.1000061035156, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.699951171875, + "height": 25, + "seed": 1467689727, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(else)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Y9Iu3BRzOjnqwUIWyhp2c", + "originalText": "(else)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 140, + "versionNonce": 253836881, + "index": "aW", + "isDeleted": false, + "id": "PJYo__Aa9aXnrR41c5IfT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1417.8781386960998, + "y": 358, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.482165652361573, + "height": 63.59999084472656, + "seed": 568282047, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "QhjroXjmRKXyk1_i8vAAg" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "YTN-ckYgg-8-JRhhHSDjm", + "focus": 0.3482055944950832, + "gap": 8.9000244140625, + "fixedPoint": null + }, + "endBinding": { + "elementId": "VGPLX7Te8egFi8JG2xNVh", + "focus": -0.5013935722652734, + "gap": 3.700042724609375, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.482165652361573, + -63.59999084472656 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 32, + "versionNonce": 1676765809, + "index": "aX", + "isDeleted": false, + "id": "QhjroXjmRKXyk1_i8vAAg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1411.887055869919, + "y": 313.7000045776367, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 10.5, + "height": 25, + "seed": 920978655, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "*", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PJYo__Aa9aXnrR41c5IfT", + "originalText": "*", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 148, + "versionNonce": 631565823, + "index": "aY", + "isDeleted": false, + "id": "WqxN2O4nrqlapOIZqRj7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1147.2000732421875, + "y": 545.2000122070312, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 98.8001708984375, + "height": 40.800048828125, + "seed": 2090695647, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "qQTkSyinQNtrNU_hYneHd" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": -0.6946419382937693, + "gap": 4.2000274658203125, + "fixedPoint": null + }, + "endBinding": { + "elementId": "c55oP0_iHqRwG-jmMTRyS", + "focus": -0.5107874121702929, + "gap": 7.499969482421875, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -82.800048828125, + 12.4000244140625 + ], + [ + -98.8001708984375, + 40.800048828125 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 48, + "versionNonce": 1928549983, + "index": "aZ", + "isDeleted": false, + "id": "qQTkSyinQNtrNU_hYneHd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1061.9000244140625, + "y": 545.1000366210938, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5, + "height": 25, + "seed": 836298335, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "'", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "WqxN2O4nrqlapOIZqRj7c", + "originalText": "'", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 48, + "versionNonce": 2042449873, + "index": "aa", + "isDeleted": false, + "id": "YWdvy3MifDMSum0WWzLdC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1164.4000244140625, + "y": 502.79998779296875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 46, + "height": 87.199951171875, + "seed": 476096511, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "rhAi6GjIvEXWGTN5PrXlG" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": -0.12749628844549243, + "gap": 2.199981689453125, + "fixedPoint": null + }, + "endBinding": { + "elementId": "wnGx-Ga0bE4BFqfUzBh90", + "focus": -0.3748569936682562, + "gap": 7.000038146972656, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -46, + -87.199951171875 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 24, + "versionNonce": 806826623, + "index": "ab", + "isDeleted": false, + "id": "rhAi6GjIvEXWGTN5PrXlG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1137.4000244140625, + "y": 446.70001220703125, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8, + "height": 25, + "seed": 565722769, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "\"", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "YWdvy3MifDMSum0WWzLdC", + "originalText": "\"", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 427, + "versionNonce": 1925855391, + "index": "ac", + "isDeleted": false, + "id": "7XxMAFrLR6CZkUfjKIoew", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 994.6000061035156, + "y": 257.70001220703125, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 138.7999877929688, + "height": 37, + "seed": 254365727, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "YNK44F3-NR15nq37Cb6AV" + }, + { + "id": "1cOH0Tt9Aqj84h3VL3QHQ", + "type": "arrow" + }, + { + "id": "LIjbYoGyESXlzk9C_qgeN", + "type": "arrow" + } + ], + "updated": 1728798993180, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 472, + "versionNonce": 545940689, + "index": "ad", + "isDeleted": false, + "id": "YNK44F3-NR15nq37Cb6AV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1040.3400268554688, + "y": 263.70001220703125, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 47.3199462890625, + "height": 25, + "seed": 969752305, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993180, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "esc-\"", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7XxMAFrLR6CZkUfjKIoew", + "originalText": "esc-\"", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 120, + "versionNonce": 123539903, + "index": "ae", + "isDeleted": false, + "id": "1cOH0Tt9Aqj84h3VL3QHQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1077.9040631886298, + "y": 369.20001220703125, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 10.109011939160382, + "height": 75.5, + "seed": 1323824191, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "yWBOVhYSXlAvi7HzA-fJM" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "wnGx-Ga0bE4BFqfUzBh90", + "focus": 0.06741108751339424, + "gap": 4.399986267089844, + "fixedPoint": null + }, + "endBinding": { + "elementId": "7XxMAFrLR6CZkUfjKIoew", + "focus": -0.01841475041121545, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -10.109011939160382, + -75.5 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 30, + "versionNonce": 503673119, + "index": "af", + "isDeleted": false, + "id": "yWBOVhYSXlAvi7HzA-fJM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1066.3495572190495, + "y": 318.95001220703125, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 13, + "height": 25, + "seed": 952262879, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "\\", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "1cOH0Tt9Aqj84h3VL3QHQ", + "originalText": "\\", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 121, + "versionNonce": 1980050865, + "index": "ag", + "isDeleted": false, + "id": "LIjbYoGyESXlzk9C_qgeN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1032.2319426153058, + "y": 300, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.3063018302955243, + "height": 66.39999389648438, + "seed": 1132270687, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "G_y99MSiS3P_N71-UWc-F" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "7XxMAFrLR6CZkUfjKIoew", + "focus": 0.46957296768044643, + "gap": 7.29998779296875, + "fixedPoint": null + }, + "endBinding": { + "elementId": "wnGx-Ga0bE4BFqfUzBh90", + "focus": -0.5582914367132411, + "gap": 7.200004577636719, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 3.3063018302955243, + 66.39999389648438 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 32, + "versionNonce": 1142748241, + "index": "agV", + "isDeleted": false, + "id": "G_y99MSiS3P_N71-UWc-F", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1015.1951063478364, + "y": 320.6999969482422, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 37.379974365234375, + "height": 25, + "seed": 1553221553, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(all)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LIjbYoGyESXlzk9C_qgeN", + "originalText": "(all)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 127, + "versionNonce": 980582879, + "index": "ah", + "isDeleted": false, + "id": "P0YVc9786c7HOxFakJJ7d", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1001.6000366210938, + "y": 410.79998779296875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 64, + "height": 74, + "seed": 79245439, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "4zVSIbiM8_kjLB4yvsXJV" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "wnGx-Ga0bE4BFqfUzBh90", + "focus": 0.3921625737164388, + "gap": 5.000030517578125, + "fixedPoint": null + }, + "endBinding": { + "elementId": "wnGx-Ga0bE4BFqfUzBh90", + "focus": -0.1330413167564949, + "gap": 3.000030517578125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -54, + 30.4000244140625 + ], + [ + -62, + -43.5999755859375 + ], + [ + 2, + -30.39996337890625 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 39, + "versionNonce": 1661186225, + "index": "ai", + "isDeleted": false, + "id": "4zVSIbiM8_kjLB4yvsXJV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 909.1867318149268, + "y": 391.054722070956, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.699951171875, + "height": 25, + "seed": 221725201, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(else)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "P0YVc9786c7HOxFakJJ7d", + "originalText": "(else)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 80, + "versionNonce": 1029066641, + "index": "aj", + "isDeleted": false, + "id": "M6qLvmi8o9UKEsK8xdYs3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1052.4000244140625, + "y": 414.79998779296875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 85.199951171875, + "height": 88, + "seed": 1549584543, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "3CpQ2zFEcjEHyMMIwEzft" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "wnGx-Ga0bE4BFqfUzBh90", + "focus": 0.5390805592879072, + "gap": 6.199989318847656, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": -0.4051264002684709, + "gap": 2.199981689453125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 85.199951171875, + 88 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 24, + "versionNonce": 764179089, + "index": "ak", + "isDeleted": false, + "id": "3CpQ2zFEcjEHyMMIwEzft", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1091, + "y": 446.29998779296875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8, + "height": 25, + "seed": 521003775, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "\"", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "M6qLvmi8o9UKEsK8xdYs3", + "originalText": "\"", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 490, + "versionNonce": 969619551, + "index": "al", + "isDeleted": false, + "id": "S_YJDcRE9yjYsXShDOWEQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 993.0000305175781, + "y": 700.1000061035156, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 138.7999877929688, + "height": 35, + "seed": 135814335, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "dYcYjrVCfmbV7Rcr4reek" + }, + { + "id": "DTTsjLLg_YZ8Ic142XX2J", + "type": "arrow" + }, + { + "id": "sz0p-VylkYWg5nvlI2Qtf", + "type": "arrow" + } + ], + "updated": 1728798797964, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 515, + "versionNonce": 674022175, + "index": "am", + "isDeleted": false, + "id": "dYcYjrVCfmbV7Rcr4reek", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1040.2700576782227, + "y": 705.1000061035156, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 44.25993347167969, + "height": 25, + "seed": 110020447, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "esc-'", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "S_YJDcRE9yjYsXShDOWEQ", + "originalText": "esc-'", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 69, + "versionNonce": 1565870911, + "index": "an", + "isDeleted": false, + "id": "DTTsjLLg_YZ8Ic142XX2J", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1091.2265301218606, + "y": 636, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5.217612846811335, + "height": 57.60003662109375, + "seed": 999058655, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "yIbvFFqL7RkQpk10qZbs0" + } + ], + "updated": 1728798797966, + "link": null, + "locked": false, + "startBinding": { + "elementId": "c55oP0_iHqRwG-jmMTRyS", + "focus": -0.26285081789596054, + "gap": 7.499969482421875, + "fixedPoint": null + }, + "endBinding": { + "elementId": "S_YJDcRE9yjYsXShDOWEQ", + "focus": 0.30196280502342954, + "gap": 6.499969482421875, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -5.217612846811335, + 57.60003662109375 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 28, + "versionNonce": 1335358783, + "index": "ao", + "isDeleted": false, + "id": "yIbvFFqL7RkQpk10qZbs0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1082.117723698455, + "y": 652.3000183105469, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 13, + "height": 25, + "seed": 1443084639, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "\\", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "DTTsjLLg_YZ8Ic142XX2J", + "originalText": "\\", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 54, + "versionNonce": 1172786225, + "index": "aq", + "isDeleted": false, + "id": "sz0p-VylkYWg5nvlI2Qtf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1050.0704432658085, + "y": 695.1000061035156, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.22398494583785578, + "height": 61.5999755859375, + "seed": 1648982271, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Q13ydAd6-xvoizsMqXqyQ" + } + ], + "updated": 1728798797966, + "link": null, + "locked": false, + "startBinding": { + "elementId": "S_YJDcRE9yjYsXShDOWEQ", + "focus": -0.17867471908654806, + "gap": 5, + "fixedPoint": [ + 0.4106626404567258, + -0.14285714285714285 + ] + }, + "endBinding": { + "elementId": "c55oP0_iHqRwG-jmMTRyS", + "focus": 0.35158363817934035, + "gap": 5, + "fixedPoint": [ + 0.3242081809103296, + 1.1428571428571428 + ] + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.22398494583785578, + -61.5999755859375 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 30, + "versionNonce": 234635825, + "index": "aqV", + "isDeleted": false, + "id": "Q13ydAd6-xvoizsMqXqyQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1031.4924485561103, + "y": 651.8000183105469, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 37.379974365234375, + "height": 25, + "seed": 1823719345, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(all)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "sz0p-VylkYWg5nvlI2Qtf", + "originalText": "(all)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 133, + "versionNonce": 871683615, + "index": "at", + "isDeleted": false, + "id": "KnUty7bIXX1DlMF-0I5Bp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1000.7999877929688, + "y": 623.3345733956071, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 56, + "height": 64, + "seed": 567279903, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Uf2tB0vT1d3686ovHmHen" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "c55oP0_iHqRwG-jmMTRyS", + "focus": 0.4539904101125047, + "gap": 4.599945068359375, + "fixedPoint": null + }, + "endBinding": { + "elementId": "c55oP0_iHqRwG-jmMTRyS", + "focus": -0.1217707478952487, + "gap": 7.399932861328125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -54, + 25.77097558450771 + ], + [ + -56, + -38.22902441549229 + ], + [ + -2.79998779296875, + -25.962739087933187 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 31, + "versionNonce": 1584845841, + "index": "au", + "isDeleted": false, + "id": "Uf2tB0vT1d3686ovHmHen", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 912.2117041178814, + "y": 603.6862164909245, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.699951171875, + "height": 25, + "seed": 1195655231, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798993764, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(else)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "KnUty7bIXX1DlMF-0I5Bp", + "originalText": "(else)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 97, + "versionNonce": 1392664401, + "index": "av", + "isDeleted": false, + "id": "fSDMaj2ndf8pHlXfQh-_B", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1133.8160409422949, + "y": 588, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 35.91967915526175, + "height": 41.5999755859375, + "seed": 462456127, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "XqElUFZyyVugIxMOWfcEA" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "c55oP0_iHqRwG-jmMTRyS", + "focus": 0.5325925856087116, + "gap": 5.500030517578125, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": -0.17254521734491615, + "gap": 5.4000396728515625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 10.098939510674882, + -14.79998779296875 + ], + [ + 35.91967915526175, + -41.5999755859375 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 25, + "versionNonce": 1990882929, + "index": "aw", + "isDeleted": false, + "id": "XqElUFZyyVugIxMOWfcEA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1141.4149804529698, + "y": 560.7000122070312, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5, + "height": 25, + "seed": 1791312113, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797965, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "'", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fSDMaj2ndf8pHlXfQh-_B", + "originalText": "'", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 127, + "versionNonce": 777055889, + "index": "ax", + "isDeleted": false, + "id": "Wu03QNDAyGrScCchj5hWl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1326.800048828125, + "y": 537.5999755859375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 105.5999755859375, + "height": 31.20001220703125, + "seed": 1860928863, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "FSY23J-1X7Xd6XlnCMZZ7" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "TGAYEfTexZjwKEU3HXks3", + "focus": 0.3574354770816999, + "gap": 1.799957275390625, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3q1sn9-Qf8tUY3SyIph-x", + "focus": 0.06857829581259621, + "gap": 2.2000274658203125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -48.39990234375, + 31.20001220703125 + ], + [ + -105.5999755859375, + 5.60003662109375 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 29, + "versionNonce": 831778751, + "index": "ay", + "isDeleted": false, + "id": "FSY23J-1X7Xd6XlnCMZZ7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1251.5501708984375, + "y": 556.2999877929688, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.699951171875, + "height": 25, + "seed": 781996497, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(else)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Wu03QNDAyGrScCchj5hWl", + "originalText": "(else)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 521, + "versionNonce": 2099488895, + "index": "az", + "isDeleted": false, + "id": "eKmw0jad963UizPnZNBp1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1392.1286636419977, + "y": 643.3954028439575, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 49.7874454326103, + "height": 57.596929713997476, + "seed": 2066841983, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "24Avr2rUpYuG-Q48v9ATN" + } + ], + "updated": 1728799094059, + "link": null, + "locked": false, + "startBinding": { + "elementId": "S7a2AIojLrv85XaArLDJo", + "focus": 0.3866443455796108, + "gap": 2.3285232611383435, + "fixedPoint": null + }, + "endBinding": { + "elementId": "S7a2AIojLrv85XaArLDJo", + "focus": -0.16404147577843772, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 43.707889078826405, + -17.722391978999696 + ], + [ + 47.658903860925136, + 39.87453773499778 + ], + [ + -2.1285415716851634, + 22.269063404018368 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 30, + "versionNonce": 420127825, + "index": "b00", + "isDeleted": false, + "id": "24Avr2rUpYuG-Q48v9ATN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1449.5709412966514, + "y": 705.3451000068218, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.699951171875, + "height": 25, + "seed": 1144312543, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728799067823, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(else)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "eKmw0jad963UizPnZNBp1", + "originalText": "(else)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 138, + "versionNonce": 514487935, + "index": "b01", + "isDeleted": false, + "id": "1xY7OjXHXAikG461MZcse", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1468.4000244140625, + "y": 379.60003662109375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 50.7999267578125, + "height": 72.79998779296875, + "seed": 1638070687, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "ZAhssgmPksuEGikM9Q61F" + } + ], + "updated": 1728798797965, + "link": null, + "locked": false, + "startBinding": { + "elementId": "YTN-ckYgg-8-JRhhHSDjm", + "focus": 0.2978357030752464, + "gap": 4.600006103515625, + "fixedPoint": null + }, + "endBinding": { + "elementId": "YTN-ckYgg-8-JRhhHSDjm", + "focus": -0.28054528458588235, + "gap": 3.400054931640625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 49.5999755859375, + -24.4000244140625 + ], + [ + 49.199951171875, + 48.39996337890625 + ], + [ + -1.199951171875, + 29.199951171875 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 33, + "versionNonce": 977030143, + "index": "b02", + "isDeleted": false, + "id": "ZAhssgmPksuEGikM9Q61F", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1497.183686269585, + "y": 380.5853302421153, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.699951171875, + "height": 25, + "seed": 893490257, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "(else)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "1xY7OjXHXAikG461MZcse", + "originalText": "(else)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "diamond", + "version": 219, + "versionNonce": 972059679, + "index": "b03", + "isDeleted": false, + "id": "8fo7fBOZVOS4Ww_yPUq4U", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 892.4000244140625, + "y": 482.4000244140625, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 131.60009765625003, + "height": 70, + "seed": 1198550463, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "SkCW4Nw1v8fCHonH_C-xz" + }, + { + "id": "hH3xMqLNlEyB6YkFrsVWu", + "type": "arrow" + } + ], + "updated": 1728798797964, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 121, + "versionNonce": 1922539857, + "index": "b04", + "isDeleted": false, + "id": "SkCW4Nw1v8fCHonH_C-xz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 930.3700637817383, + "y": 504.9000244140625, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 55.85997009277344, + "height": 25, + "seed": 166681553, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728798797964, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 5, + "text": "Start", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8fo7fBOZVOS4Ww_yPUq4U", + "originalText": "Start", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/scripts/helpers/removeComments.ts b/scripts/helpers/removeComments.ts index ca0016af..db9ff170 100644 --- a/scripts/helpers/removeComments.ts +++ b/scripts/helpers/removeComments.ts @@ -4,11 +4,11 @@ const IN_SQSTRING = Symbol('in sqstring'); const IN_MLCOMMENT = Symbol('in multiline comment'); const IN_SLCOMMENT = Symbol('in singleline comment'); -export function removeComments(contents: string): string[] { +export function removeComments(contents: string): string { // This is a big ol' state-machine-based parser to remove - // comments. I should draw the state machine out explicitly - // sometime. Currently, I'm assuming it's not perfect, but - // it works well enough for now... + // comments. See the "CommentStateMachine.excalidraw" file + // for a basic understanding of the state-machine being run. + // I assume it's not perfect, but it works well enough for now... let result = ''; let state = PLAIN; let justSawSlash = false; @@ -68,6 +68,7 @@ export function removeComments(contents: string): string[] { if (!justSawSlash && char !== '\r') { // For a front-slash not in a string, don't output it. // It might be the beginning of a comment. We handle it above... + // Also: Strip out any carriage returns. result += char; } } @@ -75,5 +76,5 @@ export function removeComments(contents: string): string[] { if (justSawSlash && state === PLAIN) { result += '/'; } - return result.split('\n'); + return result; } diff --git a/scripts/preprocessor.ts b/scripts/preprocessor.ts index 5f316a4c..f25de672 100644 --- a/scripts/preprocessor.ts +++ b/scripts/preprocessor.ts @@ -1,466 +1,29 @@ import * as fs from 'fs/promises'; import * as path from 'path'; -import { - ArgumentListCtx, - BaseJavaCstVisitorWithDefaults, - BinaryExpressionCtx, - ClassBodyDeclarationCtx, - ClassDeclarationCtx, - ClassMemberDeclarationCtx, - ConditionalExpressionCtx, - CstNode, - ExpressionCtx, - FieldDeclarationCtx, - FqnOrRefTypeCtx, - FqnOrRefTypePartCommonCtx, - FqnOrRefTypePartFirstCtx, - ImportDeclarationCtx, - LambdaBodyCtx, - LambdaExpressionCtx, - NewExpressionCtx, - NormalClassDeclarationCtx, - PackageDeclarationCtx, - PrimaryCtx, - PrimaryPrefixCtx, - PrimarySuffixCtx, - TypeIdentifierCtx, - UnannClassOrInterfaceTypeCtx, - UnannReferenceTypeCtx, - UnannTypeCtx, - UnaryExpressionCtx, - UnqualifiedClassInstanceCreationExpressionCtx, - VariableDeclaratorCtx, - VariableDeclaratorIdCtx, - VariableDeclaratorListCtx, - VariableInitializerCtx, - parse, -} from 'java-parser'; -import { - chkBothOf, - chkFieldType, - hasFieldType, - isArray, - isNonNullable, - isNumber, -} from '@freik/typechk'; -import { MakeStack } from '@freik/containers'; import { removeComments } from './helpers/removeComments.js'; +import { MakeAutoConstantsTransformer } from './helpers/AutoConstTransformer.js'; /*** BEGIN CONFIGURATION STUFF ***/ + // This is the package name that we're going to use for our generated code. const packageDir = ['com', 'robotcode', 'shared']; -// This is a set of imports that we want to remove from the generated code. -const removeImports = new Set([ - 'com.acmerobotics.dashboard.config.Config', - 'com.technototes.library.command.Command', - 'com.technototes.library.command.SequentialCommandGroup', - 'com.technototes.path.command.TrajectorySequenceCommand', -]); -// This is a map of old import names to new ones for the generated code. -const importMap = new Map([ - [ - 'com.technototes.path.geometry.ConfigurablePoseD', - 'com.acmerobotics.roadrunner.geometry.Pose2d', - ], - [ - 'com.technototes.path.geometry.ConfigurablePose', - 'com.acmerobotics.roadrunner.geometry.Pose2d', - ], - [ - 'com.technototes.path.trajectorysequence.TrajectorySequence', - 'com.acmerobotics.roadrunner.trajectory.Trajectory', - ], - [ - 'com.technototes.path.trajectorysequence.TrajectorySequenceBuilder', - 'com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder', - ], -]); -const extraImports = [ - 'static java.lang.Math.toRadians', - 'java.util.function.Supplier', -]; -const typeMap = new Map([ - ['ConfigurablePose', 'Pose2d'], - ['ConfigurablePoseD', 'Pose2d'], - [ - 'Function,TrajectorySequence>', - 'Supplier', - ], -]); /*** END CONFIGURATION STUFF ***/ +// The first two command line arguments are the bun binary and this script const [, , outDir, className, ...filesAsString] = process.argv; -// console.log("Output Directory:", outDir); -// console.log("filesAsString:", filesAsString); const outputLocation = path.join(outDir, ...packageDir); // We're only finding files that include "auto" and "const" in their paths. -// const filesNoBrackets = filesAsString.substring(1, filesAsString.length - 1); -const files = filesAsString.filter( +const constantsFiles = filesAsString.filter( (val) => val.toLocaleLowerCase().indexOf('auto') >= 0 && val.toLocaleLowerCase().indexOf('const') >= 0 && val.toLocaleLowerCase().indexOf('meepmeep') < 0, -); // filesNoBrackets.split(', ') - -// A really dumb symbol table (That I'm only filling, but never using...) -const symbolTypes = new Map(); -let classHelpersEmitted = false; - -// Some output state: -const imports = new Set(extraImports); - -const theCode: string[] = []; - -let prev = ''; - -function codeSpit(...args: string[]): void { - if (prev.length > 0) { - args = [prev, ...args]; - prev = ''; - } - if (args.length > 1) { - theCode.push(args.join('')); - } else { - theCode.push(args[0]); - } -} - -function codeAdd(...args: string[]): void { - prev += args.join(''); -} - -function codeReset() { - prev = ''; -} - -function unsupported(name: string, obj: object): void { - if (obj && obj[name]) { - throw new Error(`${name} is unsupported by the MeepMeep synchronizer`); - } -} -function required(obj: unknown, message?: string): obj is NonNullable { - if (!obj) { - throw new Error(message ?? 'Required type not found :('); - } - return true; -} -function assert(obj: unknown, message: string): obj is NonNullable { - if (!obj) { - throw new Error(message); - } - return true; -} - -let curFile: string = ''; - -function getCode(start: number, end: number): string { - return curFile.substring(start, end + 1); -} - -function getItemContent(f: unknown): string { - if ( - hasFieldType( - f, - 'location', - chkBothOf( - chkFieldType('startOffset', isNumber), - chkFieldType('endOffset', isNumber), - ), - ) - ) { - return getCode(f.location.startOffset, f.location.endOffset); - } - return ''; -} - -function getContent(field: unknown, sep?: string): string { - return isArray(field) - ? field.map(getItemContent).join(sep ?? ' ') - : getItemContent(field); -} - -function noWhitespace(input: string): string { - return input.replace(/\s/g, ''); -} - -function emitClassHelpers() { - if (classHelpersEmitted) { - return; - } - classHelpersEmitted = true; - // public static MinVelocityConstraint MIN_VEL = new MinVelocityConstraint(Arrays.asList( - // new AngularVelocityConstraint(60 /*MAX_ANG_VEL*/), - // new MecanumVelocityConstraint(60 /*MAX_VEL*/, 14 /*TRACK_WIDTH*/))); - // public static ProfileAccelerationConstraint PROF_ACCEL = new ProfileAccelerationConstraint(20/*MAX_ACCEL*/); - codeSpit('public static Function fwdFunc;'); // = pose -> new TrajectoryBuilder(pose, MIN_VEL, PROF_ACCEL);")`); - codeSpit('public static Function revFunc;'); // = pose -> new TrajectoryBuilder(pose, MIN_VEL, PROF_ACCEL);")`); -} - -// A little "context" stack -enum TokenKind { - NewType, - LambdaParam, - DeclType, -} -type Token = { kind: TokenKind; value: string }; -const MakeToken = (kind: TokenKind, value: string) => ({ kind, value }); -const tokenStack = MakeStack(); - -class AutoConstVisitor extends BaseJavaCstVisitorWithDefaults { - output: string[]; - constructor() { - super(); - this.output = []; - this.validateVisitor(); - } - - maybeVisit(field: unknown): void { - if (!isNonNullable(field)) return; - this.visit(field as CstNode); - } - - mustVisit(obj: unknown): void { - if (isNonNullable(obj)) { - this.maybeVisit(obj as CstNode); - } else { - throw new Error(`Missing required child element`); - } - } - - // Rewire the package: - packageDeclaration(ctx: PackageDeclarationCtx, param?: unknown) { - codeSpit( - '// Original package: ', - ctx.Identifier.map((token) => token.image).join('.'), - ); - // codeSpit('package ', packageDir.join('.'), ';'); - } - - // Copy, reroute, or remove imports: - importDeclaration(ctx: ImportDeclarationCtx, param?: unknown) { - const stat = ctx.Static ? 'static ' : ''; - const star = ctx.Star ? '.*' : ''; - const imprt = ctx.packageOrTypeName - .map((cst) => cst.children.Identifier.map((tok) => tok.image).join('.')) - .join('.'); - const actual = importMap.has(imprt) ? importMap.get(imprt) : imprt; - const key = `${stat}${actual}${star}`; - imports.add(key); - } - - classDeclaration(ctx: ClassDeclarationCtx, param?: any) { - // Class declarations are smashed to just be public static: - codeAdd('public static class '); - classHelpersEmitted = false; - this.mustVisit(ctx.normalClassDeclaration); - } - normalClassDeclaration(ctx: NormalClassDeclarationCtx, param?: any) { - // No extends/implements are carried over - this.mustVisit(ctx.typeIdentifier); - codeSpit(' {'); - this.mustVisit(ctx.classBody); - codeSpit('}'); - } - // spit out the type identifier - typeIdentifier(ctx: TypeIdentifierCtx, param?: any) { - codeAdd(ctx.Identifier.map((tok) => tok.image).join('.')); - } - // We're picky about the type of class bodies supported - classBodyDeclaration(ctx: ClassBodyDeclarationCtx, param?: any) { - // No constructor! - unsupported('constructorDeclaration', ctx); - // No instance initializers! - unsupported('instanceInitializer', ctx); - // No static initializers! - unsupported('staticInitializer', ctx); - this.mustVisit(ctx.classMemberDeclaration); - } - classMemberDeclaration(ctx: ClassMemberDeclarationCtx, param?: any) { - // We don't support methods! - unsupported('methodDeclaration', ctx); - // We don't support interfaces, either. - unsupported('interfaceDeclaration', ctx); - this.maybeVisit(ctx.fieldDeclaration); - this.maybeVisit(ctx.classDeclaration); - } - fieldDeclaration(ctx: FieldDeclarationCtx, param?: any) { - emitClassHelpers(); - // Add the field modifiers - codeAdd(getContent(ctx.fieldModifier), ' '); - this.mustVisit(ctx.unannType); - this.mustVisit(ctx.variableDeclaratorList); - if (tokenStack.peek()?.kind === TokenKind.DeclType) { - tokenStack.pop(); - } else { - // No constants, random variables, whatever. Delete 'em! - codeReset(); - } - } - unannType(ctx: UnannTypeCtx, param?: any) { - this.maybeVisit(ctx.unannReferenceType); - if (ctx.unannPrimitiveTypeWithOptionalDimsSuffix) { - codeAdd(getContent(ctx.unannPrimitiveTypeWithOptionalDimsSuffix)); - } - } - unannReferenceType(ctx: UnannReferenceTypeCtx, param?: any) { - this.mustVisit(ctx.unannClassOrInterfaceType); - } - unannClassOrInterfaceType(ctx: UnannClassOrInterfaceTypeCtx, param?: any) { - const typeName = noWhitespace(getContent(ctx.unannClassType)); - tokenStack.push(MakeToken(TokenKind.DeclType, typeName)); - codeAdd(typeMap.get(typeName) || typeName); - } - variableDeclaratorList(ctx: VariableDeclaratorListCtx, param?: any) { - this.mustVisit(ctx.variableDeclarator); - } - variableDeclarator(ctx: VariableDeclaratorCtx, param?: any) { - this.mustVisit(ctx.variableDeclaratorId); - this.mustVisit(ctx.variableInitializer); - } - variableDeclaratorId(ctx: VariableDeclaratorIdCtx, param?: any) { - if (required(ctx.Identifier, 'Unsupported Identifier-less varDeclID')) { - const varName = ctx.Identifier[0].image; - codeAdd(' ', varName, ' = '); - // This is setting us up to keep track of variable name types, - // so we can yoink ".toPose()" modifiers later in the file... - const declType = tokenStack.peek(); - if (declType?.kind === TokenKind.DeclType) { - symbolTypes.set(varName, declType.value); - } - } - } - variableInitializer(ctx: VariableInitializerCtx, param?: any) { - this.mustVisit(ctx.expression); - } - expression(ctx: ExpressionCtx, param?: any) { - this.maybeVisit(ctx.lambdaExpression); - this.maybeVisit(ctx.conditionalExpression); - } - lambdaExpression(ctx: LambdaExpressionCtx, param?: any) { - const params = getContent(ctx.lambdaParameters, ', '); - // We need to transform b -> b.apply(...) to () -> func.apply(...) - // Push the parameter(s) to the lambda for the lambdaBody to process - tokenStack.push(MakeToken(TokenKind.LambdaParam, params)); - this.mustVisit(ctx.lambdaBody); - } - lambdaBody(ctx: LambdaBodyCtx, param?: any) { - const expr = noWhitespace(getContent(ctx.expression)); - // console.log('Expr: ', expr); - - unsupported('block', ctx); - const top = tokenStack.pop(); - if ( - top && - top.kind === TokenKind.LambdaParam && - expr.startsWith(`${top.value}.apply(`) - ) { - const cleanupExpr = expr - .substring(top.value.length) - .replaceAll('.toPose()', '') - .replaceAll('.toVec()', '.vec()'); - // This is a hack to deal with the fact that it looks like - // MeepMeep doesn't allow us to use the .setReversed method. - // It's not perfect, but it lets MeepMeep drive the bot backwards. - const setRev = '.setReversed(true)'; - const thisFunc = cleanupExpr.includes(setRev) ? 'revFunc' : 'fwdFunc'; - codeSpit('() -> ', thisFunc, cleanupExpr.replace(setRev, ''), ';'); - } else if (top) { - codeSpit(top.value, ' -> ', expr); - } else { - throw new Error('Unexpected Lambda body stack'); - } - } - // Conditional expression is the container for all non-lambdas - // which is definitely a little weird, but whatever... - conditionalExpression(ctx: ConditionalExpressionCtx, param?: any) { - unsupported('QuestionMark', ctx); - unsupported('Colon', ctx); - this.mustVisit(ctx.binaryExpression); - } - binaryExpression(ctx: BinaryExpressionCtx, param?: any) { - assert( - !( - ctx.AssignmentOperator || - ctx.BinaryOperator || - ctx.Greater || - ctx.Instanceof || - ctx.Less || - ctx.pattern || - ctx.referenceType - ), - 'unsupported child of binary expression', - ); - this.maybeVisit(ctx.expression); - this.mustVisit(ctx.unaryExpression); - } - unaryExpression(ctx: UnaryExpressionCtx, param?: any) { - this.mustVisit(ctx.primary); - } - primary(ctx: PrimaryCtx, param?: any) { - // This is a list... - this.mustVisit(ctx.primaryPrefix); - this.maybeVisit(ctx.primarySuffix); - } - primaryPrefix(ctx: PrimaryPrefixCtx, param?: any) { - this.maybeVisit(ctx.newExpression); - this.maybeVisit(ctx.fqnOrRefType); - this.maybeVisit(ctx.literal); - } - primarySuffix(ctx: PrimarySuffixCtx, param?: any) { - // console.log('primarySuffix', ctx); - codeAdd(getContent(ctx)); - } - fqnOrRefType(ctx: FqnOrRefTypeCtx, param?: any) { - this.mustVisit(ctx.fqnOrRefTypePartFirst); - codeAdd(getContent(ctx.fqnOrRefTypePartRest)); - } - fqnOrRefTypePartFirst(ctx: FqnOrRefTypePartFirstCtx, param?: any) { - this.mustVisit(ctx.fqnOrRefTypePartCommon); - } - fqnOrRefTypePartCommon(ctx: FqnOrRefTypePartCommonCtx, param?: any) { - const lambdaArg = tokenStack.peek(); - if (lambdaArg && lambdaArg.kind === TokenKind.LambdaParam) { - codeAdd('func'); - } else { - codeAdd(getContent(ctx)); - } - } - newExpression(ctx: NewExpressionCtx, param?: any) { - // console.log('new', ctx); - codeAdd('new'); - this.maybeVisit(ctx.unqualifiedClassInstanceCreationExpression); - } - unqualifiedClassInstanceCreationExpression( - ctx: UnqualifiedClassInstanceCreationExpressionCtx, - param?: any, - ) { - const typeName = getContent(ctx.classOrInterfaceTypeToInstantiate); - codeAdd(' ', typeMap.get(typeName) || typeName); - tokenStack.push(MakeToken(TokenKind.NewType, typeName)); - this.maybeVisit(ctx.argumentList); - tokenStack.pop(); - } - argumentList(ctx: ArgumentListCtx, param?: any) { - codeAdd('('); - const top = tokenStack.peek(); - ctx.expression.forEach((node, idx) => { - const prefix = idx === 0 ? '' : ', '; - const code = getContent(node); - if ( - idx === 2 && - top.kind === TokenKind.NewType && - top.value === 'ConfigurablePoseD' - ) { - codeAdd(prefix, 'toRadians(', code, ')'); - } else { - codeAdd(prefix, code); - } - }); - codeSpit(');'); - } -} +); +const drivebaseFile = filesAsString.find( + (name) => name.toLocaleLowerCase().indexOf('drivebase') >= 0, +); async function main(): Promise { // Make the output directory structure @@ -470,29 +33,17 @@ async function main(): Promise { console.error(e); } - const transformer = new AutoConstVisitor(); + // TODO: Extract the drivebase specs/RR setup code from the bot, + // then pass those values into the Constants Transformer + + const transformer = MakeAutoConstantsTransformer(); // Remove the comments then parse the file - for (const file of files) { + for (const file of constantsFiles) { const origFileContents = await fs.readFile(file, 'utf8'); const contents = removeComments(origFileContents); - curFile = contents.join('\n'); - const cstNode = parse(curFile); - transformer.visit(cstNode); + transformer.transformFile(contents); } - /* - parsedFiles.forEach((cst, key) => { - console.log('Name: ', key); - if (hasField(cst.children, 'ordinaryCompilationUnit')) { - console.log(typeof cst.children.ordinaryCompilationUnit); - console.log(cst.children.ordinaryCompilationUnit); - } - }); - */ - // console.log(parsedFiles); - - // TODO: Extract the drivebase specs/RR setup code from the bot, maybe? - let output = `package ${packageDir.join('.')}; /* * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -504,9 +55,9 @@ async function main(): Promise { * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * * Any edits you make will get obliterated when you build. - * Instead, make edits to th${files.length > 1 ? 'ese files' : 'is file'}: + * Instead, make edits to th${constantsFiles.length > 1 ? 'ese files' : 'is file'}: * - * ${files.join('\n * ')} + * ${constantsFiles.join('\n * ')} * * and your changes will be reflected in here when you next build. * @@ -516,28 +67,22 @@ async function main(): Promise { * * If you're not using fully qualified names, and things are still messed up * you should reach out to Kevin to figure out what's going on. + * */ `; /* You can move this before the ` to see the raw input to the script: + * ${process.argv.join("\n * ")}; + */ - output += collectImports(); + output += transformer.collectImports(); output += `\n\npublic class ${className} {\n`; - output += theCode.join('\n '); + output += transformer.getTransformedCode().join('\n '); output += '\n}\n'; await fs.writeFile(path.join(outputLocation, `${className}.java`), output); } -// Produces a single, unique set of import statements from the group of files. -function collectImports(): string { - return [...imports] - .filter((val) => !removeImports.has(val)) - .sort() - .map((i) => `import ${i};`) - .join('\n'); -} - main().catch(console.error); diff --git a/tsconfig.json b/tsconfig.json index 56354e38..dbbeba89 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "allowJs": false, "esModuleInterop": true, "noEmit": true, - "pretty": true + "pretty": true, + "skipLibCheck": true }, "include": ["scripts/*.ts", "scripts/helpers/*.ts"] }