From 3c54963e236db6ab4be8695971ac2ad17e6cfa39 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Wed, 20 Dec 2023 14:12:09 +0000 Subject: [PATCH 01/12] Tests, fix CodeEditor reloading, cleanup, add spans to new API --- app/gui2/shared/__tests__/yjsModel.test.ts | 4 +- app/gui2/shared/yjsModel.ts | 12 +- app/gui2/src/components/CodeEditor.vue | 16 +- .../src/components/ComponentBrowser/input.ts | 6 +- .../src/components/GraphEditor/NodeWidget.vue | 4 +- app/gui2/src/providers/widgetTree.ts | 2 +- app/gui2/src/stores/graph/graphDatabase.ts | 8 +- app/gui2/src/stores/graph/imports.ts | 11 +- app/gui2/src/stores/graph/index.ts | 60 +++---- .../src/util/ast/__tests__/abstract.test.ts | 124 +++++--------- .../util/ast/__tests__/aliasAnalysis.test.ts | 20 +-- .../ast/__tests__/fixtures/stargazers.enso | 2 +- app/gui2/src/util/ast/abstract.ts | 152 ++++++++++++------ app/gui2/src/util/ast/aliasAnalysis.ts | 18 +-- app/gui2/src/util/ast/extended.ts | 4 +- app/gui2/src/util/ast/index.ts | 12 +- app/gui2/stories/GraphNode.story.vue | 6 +- app/gui2/ydoc-server/__tests__/edits.test.ts | 3 +- app/gui2/ydoc-server/edits.ts | 6 +- app/gui2/ydoc-server/languageServerSession.ts | 27 ++-- 20 files changed, 254 insertions(+), 243 deletions(-) diff --git a/app/gui2/shared/__tests__/yjsModel.test.ts b/app/gui2/shared/__tests__/yjsModel.test.ts index 844bf83cea03..1cb99598347f 100644 --- a/app/gui2/shared/__tests__/yjsModel.test.ts +++ b/app/gui2/shared/__tests__/yjsModel.test.ts @@ -1,7 +1,7 @@ -import { rangeEncloses, rangeIntersects, type ContentRange } from 'shared/yjsModel' +import { rangeEncloses, rangeIntersects, type SourceRange } from 'shared/yjsModel' import { expect, test } from 'vitest' -type RangeTest = { a: ContentRange; b: ContentRange } +type RangeTest = { a: SourceRange; b: SourceRange } const equalRanges: RangeTest[] = [ { a: [0, 0], b: [0, 0] }, diff --git a/app/gui2/shared/yjsModel.ts b/app/gui2/shared/yjsModel.ts index 726a95f27037..49cd15004b0d 100644 --- a/app/gui2/shared/yjsModel.ts +++ b/app/gui2/shared/yjsModel.ts @@ -168,7 +168,6 @@ export class DistributedModule { } export type SourceRange = readonly [start: number, end: number] -export type RelativeRange = readonly [start: number, end: number] export class IdMap { private readonly rangeToExpr: Map @@ -238,22 +237,19 @@ export function isUuid(x: unknown): x is Uuid { return typeof x === 'string' && x.length === 36 && uuidRegex.test(x) } -/** A range represented as start and end indices. */ -export type ContentRange = [start: number, end: number] - -export function rangeEquals(a: ContentRange, b: ContentRange): boolean { +export function rangeEquals(a: SourceRange, b: SourceRange): boolean { return a[0] == b[0] && a[1] == b[1] } -export function rangeEncloses(a: ContentRange, b: ContentRange): boolean { +export function rangeEncloses(a: SourceRange, b: SourceRange): boolean { return a[0] <= b[0] && a[1] >= b[1] } -export function rangeIntersects(a: ContentRange, b: ContentRange): boolean { +export function rangeIntersects(a: SourceRange, b: SourceRange): boolean { return a[0] <= b[1] && a[1] >= b[0] } /** Whether the given range is before the other range. */ -export function rangeIsBefore(a: ContentRange, b: ContentRange): boolean { +export function rangeIsBefore(a: SourceRange, b: SourceRange): boolean { return a[1] <= b[0] } diff --git a/app/gui2/src/components/CodeEditor.vue b/app/gui2/src/components/CodeEditor.vue index c2e571c60314..6864848ddeac 100644 --- a/app/gui2/src/components/CodeEditor.vue +++ b/app/gui2/src/components/CodeEditor.vue @@ -38,8 +38,8 @@ const rootElement = ref() useAutoBlur(rootElement) const executionContextDiagnostics = computed(() => - projectStore.module - ? lsDiagnosticsToCMDiagnostics(projectStore.module.doc.getCode(), projectStore.diagnostics) + projectStore.module && graphStore.moduleCode + ? lsDiagnosticsToCMDiagnostics(graphStore.moduleCode, projectStore.diagnostics) : [], ) @@ -54,8 +54,8 @@ const expressionUpdatesDiagnostics = computed(() => { if (!update) continue const node = nodeMap.get(id) if (!node) continue - if (!node.rootSpan.astExtended) continue - const [from, to] = node.rootSpan.astExtended.span() + if (!node.rootSpan.span) continue + const [from, to] = node.rootSpan.span switch (update.payload.type) { case 'Panic': { diagnostics.push({ from, to, message: update.payload.message, severity: 'error' }) @@ -85,9 +85,10 @@ watchEffect(() => { const awareness = projectStore.awareness.internal extensions: [yCollab(yText, awareness, { undoManager }), ...] */ + if (!graphStore.moduleCode) return editorView.setState( EditorState.create({ - doc: module.doc.getCode(), + doc: graphStore.moduleCode, extensions: [ minimalSetup, syntaxHighlighting(defaultHighlightStyle as Highlighter), @@ -101,10 +102,7 @@ watchEffect(() => { const astSpan = ast.span() let foundNode: ExprId | undefined for (const [id, node] of graphStore.db.nodeIdToNode.entries()) { - if ( - node.rootSpan.astExtended && - rangeEncloses(node.rootSpan.astExtended.span(), astSpan) - ) { + if (node.rootSpan.span && rangeEncloses(node.rootSpan.span, astSpan)) { foundNode = id break } diff --git a/app/gui2/src/components/ComponentBrowser/input.ts b/app/gui2/src/components/ComponentBrowser/input.ts index ddcb0ade2226..51d321c73ff8 100644 --- a/app/gui2/src/components/ComponentBrowser/input.ts +++ b/app/gui2/src/components/ComponentBrowser/input.ts @@ -21,7 +21,7 @@ import { type QualifiedName, } from '@/util/qualifiedName' import { equalFlat } from 'lib0/array' -import { IdMap, type ContentRange, type ExprId } from 'shared/yjsModel' +import { IdMap, type ExprId, type SourceRange } from 'shared/yjsModel' import { computed, ref, type ComputedRef } from 'vue' /** Information how the component browser is used, needed for proper input initializing. */ @@ -60,7 +60,7 @@ export type EditingContext = interface Change { str: string /** Range in the original code to be replaced with `str`. */ - range: ContentRange + range: SourceRange } /** Component Browser Input Data */ @@ -399,7 +399,7 @@ export function useComponentBrowserInput( result.push({ range: span, str: segment as string }) } else { // The rest of qualified name needs to be added at the end. - const range: ContentRange = [lastEditedCharIndex, lastEditedCharIndex] + const range: SourceRange = [lastEditedCharIndex, lastEditedCharIndex] result.push({ range, str: ('.' + segment) as string }) } } diff --git a/app/gui2/src/components/GraphEditor/NodeWidget.vue b/app/gui2/src/components/GraphEditor/NodeWidget.vue index 04b3ae64f3f1..9a82471bcb29 100644 --- a/app/gui2/src/components/GraphEditor/NodeWidget.vue +++ b/app/gui2/src/components/GraphEditor/NodeWidget.vue @@ -66,8 +66,8 @@ provideWidgetUsageInfo( ) const spanStart = computed(() => { if (!(props.input instanceof Ast.Ast)) return undefined - if (props.input.astExtended == null) return undefined - return props.input.astExtended.span()[0] - tree.nodeSpanStart - whitespace.value.length + if (props.input.span == null) return undefined + return props.input.span[0] - tree.nodeSpanStart - whitespace.value.length }) diff --git a/app/gui2/src/providers/widgetTree.ts b/app/gui2/src/providers/widgetTree.ts index e47950a90c14..f77804b58ba5 100644 --- a/app/gui2/src/providers/widgetTree.ts +++ b/app/gui2/src/providers/widgetTree.ts @@ -7,7 +7,7 @@ const { provideFn, injectFn } = createContextStore( 'Widget tree', (astRoot: Ref, hasActiveAnimations: Ref) => { const nodeId = computed(() => astRoot.value.astId) - const nodeSpanStart = computed(() => astRoot.value.astExtended!.span()[0]) + const nodeSpanStart = computed(() => astRoot.value.span![0]) return proxyRefs({ astRoot, nodeId, nodeSpanStart, hasActiveAnimations }) }, ) diff --git a/app/gui2/src/stores/graph/graphDatabase.ts b/app/gui2/src/stores/graph/graphDatabase.ts index d2aa209fb4eb..be58cf23869b 100644 --- a/app/gui2/src/stores/graph/graphDatabase.ts +++ b/app/gui2/src/stores/graph/graphDatabase.ts @@ -16,9 +16,9 @@ import { methodPointerEquals, type MethodCall } from 'shared/languageServerTypes import { IdMap, visMetadataEquals, - type ContentRange, type ExprId, type NodeMetadata, + type SourceRange, type VisualizationMetadata, } from 'shared/yjsModel' import { ref, type Ref } from 'vue' @@ -89,9 +89,9 @@ export class BindingsDb { private static rangeMappings( ast: RawAstExtended, analyzer: AliasAnalyzer, - ): [MappedKeyMap, Map] { - const bindingRangeToTree = new MappedKeyMap(IdMap.keyForRange) - const bindingIdToRange = new Map() + ): [MappedKeyMap, Map] { + const bindingRangeToTree = new MappedKeyMap(IdMap.keyForRange) + const bindingIdToRange = new Map() const bindingRanges = new MappedSet(IdMap.keyForRange) for (const [binding, usages] of analyzer.aliases) { bindingRanges.add(binding) diff --git a/app/gui2/src/stores/graph/imports.ts b/app/gui2/src/stores/graph/imports.ts index ef09b89793d2..328c34c001b7 100644 --- a/app/gui2/src/stores/graph/imports.ts +++ b/app/gui2/src/stores/graph/imports.ts @@ -18,8 +18,9 @@ import { tryIdentifier, tryQualifiedName, type Identifier, - type QualifiedName, + type QualifiedName, qnSegments } from '@/util/qualifiedName' +import type { MutableModule } from '@/util/ast/abstract' // ======================== // === Imports analysis === @@ -142,13 +143,13 @@ export interface UnqualifiedImport { import: Identifier } -/** Get the string representation of the required import statement. */ -export function requiredImportToText(value: RequiredImport): string { +/** Get an AST representing the required import statement. */ +export function requiredImportToAst(module: MutableModule, value: RequiredImport): Ast.Import { switch (value.kind) { case 'Qualified': - return `import ${value.module}` + return Ast.Import.Qualified(qnSegments(value.module), module)! case 'Unqualified': - return `from ${value.from} import ${value.import}` + return Ast.Import.Unqualified(qnSegments(value.from), value.import, module)! } } diff --git a/app/gui2/src/stores/graph/index.ts b/app/gui2/src/stores/graph/index.ts index 321b012d1bed..1ffe78f50102 100644 --- a/app/gui2/src/stores/graph/index.ts +++ b/app/gui2/src/stores/graph/index.ts @@ -3,7 +3,7 @@ import { GraphDb } from '@/stores/graph/graphDatabase' import { filterOutRedundantImports, recognizeImport, - requiredImportToText, + requiredImportToAst, type Import, type RequiredImport, } from '@/stores/graph/imports' @@ -21,9 +21,9 @@ import type { StackItem } from 'shared/languageServerTypes' import { IdMap, visMetadataEquals, - type ContentRange, type ExprId, type NodeMetadata, + type SourceRange, type VisualizationIdentifier, type VisualizationMetadata, } from 'shared/yjsModel' @@ -45,7 +45,7 @@ export const useGraphStore = defineStore('graph', () => { watch(data, console.log) const metadata = computed(() => proj.module?.doc.metadata) - const textContent = ref(proj.module?.doc.getCode()) + const moduleCode = ref(proj.module?.doc.getCode()) // We need casting here, as type changes in Ref when class has private fields. // see https://github.com/vuejs/core/issues/2557 const idMap = ref(proj.module?.doc.getIdMap()) as Ref @@ -54,8 +54,8 @@ export const useGraphStore = defineStore('graph', () => { // Initialize text and idmap once module is loaded (data != null) watch(data, () => { - if (!textContent.value) { - textContent.value = proj.module?.doc.getCode() + if (!moduleCode.value) { + moduleCode.value = proj.module?.doc.getCode() idMap.value = proj.module?.doc.getIdMap() updateState() } @@ -70,7 +70,7 @@ export const useGraphStore = defineStore('graph', () => { const vizRects = reactive(new Map()) const exprRects = reactive(new Map()) const editedNodeInfo = ref() - const imports = ref<{ import: Import; span: ContentRange }[]>([]) + const imports = ref<{ import: Import; span: SourceRange }[]>([]) const methodAst = ref() const currentNodeIds = ref(new Set()) @@ -79,7 +79,7 @@ export const useGraphStore = defineStore('graph', () => { useObserveYjs(data, (event) => { if (!event.changes.keys.size) return const code = proj.module?.doc.getCode() - if (code) textContent.value = code + if (code) moduleCode.value = code const ids = proj.module?.doc.getIdMap() if (ids) idMap.value = ids if (code && ids) updateState() @@ -92,7 +92,7 @@ export const useGraphStore = defineStore('graph', () => { if (!idMap_) return module.transact(() => { const meta = module.doc.metadata - const textContentLocal = textContent.value + const textContentLocal = moduleCode.value if (!textContentLocal) return const newRoot = Ast.parseTransitional(textContentLocal, idMap_) @@ -105,7 +105,7 @@ export const useGraphStore = defineStore('graph', () => { if (node instanceof Ast.Import) { const recognized = recognizeImport(node) if (recognized) { - imports.value.push({ import: recognized, span: node.astExtended!.span() }) + imports.value.push({ import: recognized, span: node.span! }) } return false } @@ -196,26 +196,9 @@ export const useGraphStore = defineStore('graph', () => { } const edit = expressionGraph.edit() const importsToAdd = withImports ? filterOutRedundantImports(imports.value, withImports) : [] - if (importsToAdd.length > 0) { - const imports = importsToAdd.map((info) => - Ast.parseExpression(requiredImportToText(info), edit), - ) - let lastImport - // The top level of the module is always a block. - const topLevel = expressionGraph.get(root)! as Ast.BodyBlock - for (let i = 0; i < topLevel.lines.length; i++) { - const line = topLevel.lines[i]! - if (line.expression) { - if (expressionGraph.get(line.expression.node)?.innerExpression() instanceof Ast.Import) { - lastImport = i - } else { - break - } - } - } - const position = lastImport === undefined ? 0 : lastImport + 1 - topLevel.insert(edit, position, ...imports) - } + // The top level of the module is always a block. + const topLevel = expressionGraph.get(root)! as Ast.BodyBlock + addImports(edit, topLevel, importsToAdd) const currentFunc = 'main' const functionBlock = Ast.functionBlock(expressionGraph, currentFunc) if (!functionBlock) { @@ -228,6 +211,24 @@ export const useGraphStore = defineStore('graph', () => { commitEdit(edit, root, new Map([[rhs.exprId, meta]])) } + function addImports(edit: MutableModule, scope: Ast.BodyBlock, importsToAdd: RequiredImport[]) { + if (!importsToAdd.length) return + const imports = importsToAdd.map((info) => requiredImportToAst(edit, info)) + let lastImport + for (let i = 0; i < scope.lines.length; i++) { + const line = scope.lines[i]! + if (line.expression) { + if (expressionGraph.get(line.expression.node)?.innerExpression() instanceof Ast.Import) { + lastImport = i + } else { + break + } + } + } + const position = lastImport === undefined ? 0 : lastImport + 1 + scope.insert(edit, position, ...imports) + } + function deleteNode(id: ExprId) { const node = db.nodeIdToNode.get(id) if (!node) return @@ -378,6 +379,7 @@ export const useGraphStore = defineStore('graph', () => { unconnectedEdge, edges, currentNodeIds, + moduleCode, nodeRects, vizRects, exprRects, diff --git a/app/gui2/src/util/ast/__tests__/abstract.test.ts b/app/gui2/src/util/ast/__tests__/abstract.test.ts index ab97068d1b5d..d66b0a5ba0a3 100644 --- a/app/gui2/src/util/ast/__tests__/abstract.test.ts +++ b/app/gui2/src/util/ast/__tests__/abstract.test.ts @@ -1,10 +1,8 @@ import { Ast } from '@/util/ast' import * as fs from 'fs' -import * as json from 'lib0/json' import { expect, test } from 'vitest' -import { IdMap, type ExprId } from '../../../../shared/yjsModel' -import { preParseContent } from '../../../../ydoc-server/edits' -import * as fileFormat from '../../../../ydoc-server/fileFormat' +import { preParseContent, serializeIdMap } from '../../../../ydoc-server/edits' +import { deserializeIdMap } from '../../../../ydoc-server/languageServerSession' //const disabledCases = [ // ' a', @@ -373,57 +371,62 @@ test.each(parseCases)('parse: %s', (testCase) => { expect(Ast.tokenTree(root)).toEqual(testCase.tree) }) -// TODO: Edits (#8367). -/* -if (false) { - test('insert new node', () => { - const code = 'main =\n text1 = "foo"\n' - const root = Ast.parse(code) - const main = Ast.functionBlock('main') - expect(main).not.toBeNull() - insertNewNodeAST(main!, 'baz', '42') - const printed = root.print() - expect(printed.code).toEqual('main =\n text1 = "foo"\n baz = 42\n') - }) +test('insert new node', () => { + const code = 'main =\n text1 = "foo"\n' + const root = Ast.parse(code) + const main = Ast.functionBlock(root.module, 'main')! + expect(main).not.toBeNull() + const edit = root.module.edit() + const rhs = Ast.parseExpression('42', edit) + const assignment = Ast.Assignment.new(edit, 'baz', rhs) + main.push(edit, assignment) + const printed = root.code(edit) + expect(printed).toEqual('main =\n text1 = "foo"\n baz = 42\n') +}) - test('replace expression content', () => { - const code = 'main =\n text1 = "foo"\n' - const root = Ast.parse(code) - const main = Ast.functionBlock('main') - expect(main).not.toBeNull() - const newAssignment = insertNewNodeAST(main!, 'baz', '42') - replaceExpressionContentAST(newAssignment.value, '23') - const printed = root.print() - expect(printed.code).toEqual('main =\n text1 = "foo"\n baz = 23\n') - }) +test('replace expression content', () => { + const code = 'main =\n text1 = "foo"\n' + const root = Ast.parse(code) + const main = Ast.functionBlock(root.module, 'main')! + expect(main).not.toBeNull() + const assignment: Ast.Assignment = main.expressions().next().value + expect(assignment).toBeInstanceOf(Ast.Assignment) + expect(assignment.expression).not.toBeNull() + const edit = root.module.edit() + const newValue = Ast.TextLiteral.new('bar', edit) + edit.set(assignment.expression!.exprId, newValue) + const printed = root.code(edit) + expect(printed).toEqual("main =\n text1 = 'bar'\n") +}) - test('delete expression', () => { - const originalCode = 'main =\n text1 = "foo"\n' - const root = Ast.parse(originalCode) - const main = Ast.functionBlock('main') - expect(main).not.toBeNull() - const newAssignment = insertNewNodeAST(main!, 'baz', '42') - deleteExpressionAST(newAssignment.assignment) - const printed = root.print() - expect(printed.code).toEqual(originalCode) - }) -} - */ +test('delete expression', () => { + const originalCode = 'main =\n text1 = "foo"\n text2 = "bar"\n' + const root = Ast.parse(originalCode) + const main = Ast.functionBlock(root.module, 'main')! + expect(main).not.toBeNull() + const iter = main.expressions() + const _assignment1 = iter.next() + const assignment2: Ast.Assignment = iter.next().value + const edit = root.module.edit() + edit.delete(assignment2.exprId) + const printed = root.code(edit) + expect(printed).toEqual('main =\n text1 = "foo"\n') +}) test('full file IdMap round trip', () => { const content = fs.readFileSync(__dirname + '/fixtures/stargazers.enso').toString() const { code, idMapJson, metadataJson: _ } = preParseContent(content) const idMap = deserializeIdMap(idMapJson!) const ast = Ast.parseTransitional(code, idMap) - const ast_ = Ast.parseTransitional(code, idMap) + const ast_ = Ast.parseTransitional(code, deserializeIdMap(idMapJson!)) const ast2 = Ast.normalize(ast) const astTT = Ast.tokenTreeWithIds(ast) expect(ast2.code()).toBe(ast.code()) expect(Ast.tokenTreeWithIds(ast2), 'Print/parse preserves IDs').toStrictEqual(astTT) expect(Ast.tokenTreeWithIds(ast_), 'All node IDs come from IdMap').toStrictEqual(astTT) - const idMapJson2 = json.stringify(idMapToArray(idMap)) - //expect(idMapJson2).toBe(idMapJson) + const idMapJson2 = serializeIdMap(idMap) + expect(idMapJson2).toBe(idMapJson) const META_TAG = '\n\n\n#### METADATA ####' let metaContent = META_TAG + '\n' metaContent += idMapJson2 + '\n' @@ -436,42 +439,3 @@ test('full file IdMap round trip', () => { const ast3 = Ast.parseTransitional(code_, idMap_) expect(Ast.tokenTreeWithIds(ast3), 'Print/parse with serialized IdMap').toStrictEqual(astTT) }) - -function deserializeIdMap(idMapJson: string) { - const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) - const idMap = new IdMap() - for (const [{ index, size }, id] of idMapMeta) { - const range = [index.value, index.value + size.value] - if (typeof range[0] !== 'number' || typeof range[1] !== 'number') { - console.error(`Invalid range for id ${id}:`, range) - continue - } - idMap.insertKnownId([index.value, index.value + size.value], id as ExprId) - } - return idMap -} - -function idMapToArray(map: IdMap): fileFormat.IdMapEntry[] { - const entries: fileFormat.IdMapEntry[] = [] - map.entries().forEach(([key, id]) => { - const decoded = IdMap.rangeForKey(key) - const index = decoded[0] - const endIndex = decoded[1] - if (index == null || endIndex == null) return - const size = endIndex - index - entries.push([{ index: { value: index }, size: { value: size } }, id]) - }) - entries.sort(idMapCmp) - return entries -} - -function idMapCmp(a: fileFormat.IdMapEntry, b: fileFormat.IdMapEntry) { - const val1 = a[0]?.index?.value ?? 0 - const val2 = b[0]?.index?.value ?? 0 - if (val1 === val2) { - const size1 = a[0]?.size.value ?? 0 - const size2 = b[0]?.size.value ?? 0 - return size1 - size2 - } - return val1 - val2 -} diff --git a/app/gui2/src/util/ast/__tests__/aliasAnalysis.test.ts b/app/gui2/src/util/ast/__tests__/aliasAnalysis.test.ts index c09051216504..5dd95cedc752 100644 --- a/app/gui2/src/util/ast/__tests__/aliasAnalysis.test.ts +++ b/app/gui2/src/util/ast/__tests__/aliasAnalysis.test.ts @@ -28,7 +28,7 @@ import { assertDefined } from '@/util/assert' import { AliasAnalyzer } from '@/util/ast/aliasAnalysis' import { MappedKeyMap, MappedSet } from '@/util/containers' -import { IdMap, type ContentRange } from 'shared/yjsModel' +import { IdMap, type SourceRange } from 'shared/yjsModel' import { expect, test } from 'vitest' /** The type of annotation. */ @@ -54,9 +54,9 @@ class Annotation { /** Parse annotations from the annotated code. See the file-top comment for the syntax. */ function parseAnnotations(annotatedCode: string): { unannotatedCode: string - annotations: MappedKeyMap + annotations: MappedKeyMap } { - const annotations = new MappedKeyMap(IdMap.keyForRange) + const annotations = new MappedKeyMap(IdMap.keyForRange) // Iterate over all annotations (either bindings or usages). // I.e. we want to cover both `«1,x»` and `»1,x«` cases, while keeping the track of the annotation type. @@ -90,7 +90,7 @@ function parseAnnotations(annotatedCode: string): { const start = offset - accumulatedOffset const end = start + name.length - const range: ContentRange = [start, end] + const range: SourceRange = [start, end] const annotation = new Annotation(kind, id) accumulatedOffset += match.length - name.length @@ -113,10 +113,10 @@ function parseAnnotations(annotatedCode: string): { /** Alias analysis test case, typically parsed from an annotated code. */ class TestCase { /** The expected aliases. */ - readonly expectedAliases = new MappedKeyMap(IdMap.keyForRange) + readonly expectedAliases = new MappedKeyMap(IdMap.keyForRange) /** The expected unresolved symbols. */ - readonly expectedUnresolvedSymbols = new MappedSet(IdMap.keyForRange) + readonly expectedUnresolvedSymbols = new MappedSet(IdMap.keyForRange) /** * @param code The code of the program to be tested, without annotations. @@ -128,7 +128,7 @@ class TestCase { const { unannotatedCode, annotations } = parseAnnotations(annotatedCode) const testCase = new TestCase(unannotatedCode) - const prefixBindings = new Map() + const prefixBindings = new Map() for (const [range, annotation] of annotations) { if (annotation.kind === AnnotationType.Binding) { @@ -157,11 +157,11 @@ class TestCase { return testCase } - repr(range: ContentRange): string { + repr(range: SourceRange): string { return this.code.substring(range[0], range[1]) } - prettyPrint(range: ContentRange): string { + prettyPrint(range: SourceRange): string { return `${this.repr(range)}@[${range}]` } @@ -224,7 +224,7 @@ test('Annotations parsing', () => { expect(unannotatedCode).toBe(expectedUnannotatedCode) expect(annotations.size).toBe(7) const validateAnnotation = ( - range: ContentRange, + range: SourceRange, kind: AnnotationType, prefix: number, identifier: string, diff --git a/app/gui2/src/util/ast/__tests__/fixtures/stargazers.enso b/app/gui2/src/util/ast/__tests__/fixtures/stargazers.enso index 43b54bd7385e..7582b37a66e1 100644 --- a/app/gui2/src/util/ast/__tests__/fixtures/stargazers.enso +++ b/app/gui2/src/util/ast/__tests__/fixtures/stargazers.enso @@ -17,5 +17,5 @@ main = #### METADATA #### -[[{"index":{"value":0},"size":{"value":4}},"8ab8bf7c-5d93-4c2d-88a5-5fecb9818bd3"],[{"index":{"value":0},"size":{"value":28}},"ff64c701-ff49-4165-8922-b47992180f54"],[{"index":{"value":0},"size":{"value":29}},"9c4aab40-8f91-456d-acd1-ab516d85ab42"],[{"index":{"value":0},"size":{"value":30}},"ee9611de-4465-43e5-96a9-f9f87461d58f"],[{"index":{"value":0},"size":{"value":33}},"86c1df62-4ef6-4f89-ab8c-9c73e2295058"],[{"index":{"value":0},"size":{"value":561}},"e5ed3516-46ae-4da0-a6b9-bca372921284"],[{"index":{"value":0},"size":{"value":717}},"f0e482b9-cce9-4924-8844-a2e2fc434884"],[{"index":{"value":5},"size":{"value":8}},"019b1b27-d62d-48b8-9411-bcfbd1344f36"],[{"index":{"value":5},"size":{"value":12}},"cee4e30a-cd09-469c-b1a5-bf56cde28491"],[{"index":{"value":5},"size":{"value":13}},"7b8d640b-33c4-453e-97ab-2e443cb9f538"],[{"index":{"value":5},"size":{"value":14}},"ab429e68-6cea-4e85-81d8-552fb4536270"],[{"index":{"value":5},"size":{"value":17}},"084d8b53-b875-42a9-8bf4-fd537ab8ec2e"],[{"index":{"value":6},"size":{"value":4}},"23e47dcc-69bc-4cf6-96c8-a2b3ee9e418b"],[{"index":{"value":6},"size":{"value":5}},"7a8084f3-608e-4108-ac58-5ddac84589a5"],[{"index":{"value":6},"size":{"value":9}},"7850d1a8-e770-486a-878f-2da279744b25"],[{"index":{"value":6},"size":{"value":10}},"41ca6767-2895-416d-b90e-dc57e121e101"],[{"index":{"value":6},"size":{"value":18}},"59a279cc-671c-4740-ad5d-bd09324bf94d"],[{"index":{"value":6},"size":{"value":23}},"02437804-f97f-41cf-955a-c15c77e197f3"],[{"index":{"value":6},"size":{"value":27}},"730b553e-cb33-4d6f-9123-ec703eda8d66"],[{"index":{"value":6},"size":{"value":65}},"1e9ae5e7-9554-4081-9b5d-dcec49d0f30a"],[{"index":{"value":6},"size":{"value":70}},"d818b8c3-79c8-42f5-8e8a-155cc8e08aec"],[{"index":{"value":6},"size":{"value":94}},"d5cb1942-e695-4fdb-8f40-2da86b513170"],[{"index":{"value":6},"size":{"value":102}},"6e89013e-0bce-4b0a-a171-fbbcba9c41ae"],[{"index":{"value":6},"size":{"value":116}},"df96f062-9061-457a-b402-0e1576a0b2ef"],[{"index":{"value":6},"size":{"value":555}},"d44b14d4-29e1-4f99-9398-eeeac99ab3bc"],[{"index":{"value":7},"size":{"value":8}},"69392f80-c0e2-4bb9-aff6-0534b1037dae"],[{"index":{"value":7},"size":{"value":22}},"182d359c-dd9e-485f-a92a-73ce188b7c9d"],[{"index":{"value":13},"size":{"value":30}},"3e20359d-2a23-4828-b548-42571a6eb80c"],[{"index":{"value":13},"size":{"value":38}},"3ac082ce-59ba-4e10-8312-89f492e7d787"],[{"index":{"value":13},"size":{"value":63}},"197b46e5-7192-4998-a0f7-b64faccd5bbe"],[{"index":{"value":14},"size":{"value":3}},"ee185b64-f3be-40da-a774-a1cae1b0b099"],[{"index":{"value":14},"size":{"value":4}},"7f86967d-c001-4ad1-9bdf-5461757cb957"],[{"index":{"value":14},"size":{"value":5}},"82953585-5695-4bda-99ac-0fa28b77d05b"],[{"index":{"value":14},"size":{"value":8}},"0ea4d188-520c-447f-8ee4-d08ae0e18b71"],[{"index":{"value":14},"size":{"value":10}},"43adadcf-809e-482b-9ca3-7a593c4f0129"],[{"index":{"value":16},"size":{"value":13}},"0534f4ea-db33-4798-8ff4-ba557cc091c2"],[{"index":{"value":18},"size":{"value":4}},"3eee1fa9-f059-4363-badb-f46e0f37126a"],[{"index":{"value":18},"size":{"value":9}},"3421d0ee-789b-42a3-b723-48e757d2ef90"],[{"index":{"value":18},"size":{"value":11}},"9e873e65-8a45-4e34-9049-3611d14e053d"],[{"index":{"value":18},"size":{"value":15}},"35a7a108-8e5f-45ad-afa2-de37d0af7094"],[{"index":{"value":18},"size":{"value":16}},"7032dcb2-5e22-4dba-8f63-bdf1c0cbbfdf"],[{"index":{"value":18},"size":{"value":18}},"952e0689-f969-464c-8a2c-0df37362eb7c"],[{"index":{"value":18},"size":{"value":19}},"b9193be1-d4c0-402f-b0eb-9468426f707c"],[{"index":{"value":18},"size":{"value":27}},"c81486c9-cc3c-454a-8439-eaba3d5eb466"],[{"index":{"value":18},"size":{"value":53}},"06f7398f-10be-4f50-9023-72e56b53eb32"],[{"index":{"value":18},"size":{"value":82}},"660253d3-8ee5-42d4-a9ef-2446936801cd"],[{"index":{"value":18},"size":{"value":90}},"bd72fd23-5c6e-43ac-b33e-b3521dcb19ef"],[{"index":{"value":19},"size":{"value":9}},"31bdd8bd-f843-41d4-8111-6cb595fc6a41"],[{"index":{"value":19},"size":{"value":19}},"70857049-518a-4368-ad17-47b84100d98b"],[{"index":{"value":19},"size":{"value":103}},"5e36dfd0-2e5c-4bfa-b58b-71a6d543819f"],[{"index":{"value":23},"size":{"value":6}},"8ec584d0-b0b8-4d5b-a307-a6b0a5681d99"],[{"index":{"value":28},"size":{"value":5}},"308c434c-797e-4098-9d98-72c696adc3fa"],[{"index":{"value":28},"size":{"value":6}},"ee0fb6f7-0749-4bd7-bd11-7da3aad1ce67"],[{"index":{"value":28},"size":{"value":8}},"836e9680-d255-4086-940d-a11fc6f83d54"],[{"index":{"value":28},"size":{"value":9}},"90a5f7a2-e5a1-4bf4-8a3e-3d95f76c6c49"],[{"index":{"value":29},"size":{"value":9}},"fdb40049-5578-465c-9183-1222ad200e88"],[{"index":{"value":35},"size":{"value":10}},"5d86b31b-d34a-4f13-a2a8-38c7a9fbad9a"],[{"index":{"value":37},"size":{"value":63}},"9bf5d320-71ac-4054-9d12-0b575b95aee2"],[{"index":{"value":38},"size":{"value":11}},"46509492-f00b-498c-8da9-5c1481312411"],[{"index":{"value":38},"size":{"value":16}},"3860475c-0009-46a9-a002-cf051b88b450"],[{"index":{"value":38},"size":{"value":35}},"f8fb0501-f04e-49e0-82a7-ead6664df53e"],[{"index":{"value":38},"size":{"value":61}},"e201d911-01c6-4ee8-8080-1de1490e8eb5"],[{"index":{"value":38},"size":{"value":70}},"9a8c14f1-0d92-4da4-98a5-7fd8c80c0216"],[{"index":{"value":39},"size":{"value":18}},"395c3cb7-0bda-4fae-ae17-c90d757311fc"],[{"index":{"value":39},"size":{"value":83}},"62487e30-7d55-4d25-8b47-f9785e7610a8"],[{"index":{"value":40},"size":{"value":16}},"8f480777-96c7-458d-8fce-80f072d970c7"],[{"index":{"value":40},"size":{"value":25}},"677dc9f1-f804-4819-bd71-67c00ffaab2f"],[{"index":{"value":40},"size":{"value":36}},"70ab6fa0-4727-4ec5-85a8-df5e00505822"],[{"index":{"value":46},"size":{"value":5}},"d91096e4-ef97-4155-a572-6459c658761b"],[{"index":{"value":46},"size":{"value":16}},"3dad6cf7-a572-46aa-be4b-fd5ff615f69d"],[{"index":{"value":46},"size":{"value":25}},"5bb0f468-9bf9-469d-81c7-ee01ff97b2c6"],[{"index":{"value":50},"size":{"value":4}},"bd2a5c77-df2f-4a1f-b651-8307119cb3b6"],[{"index":{"value":54},"size":{"value":22}},"9cbc0994-dde4-4cff-8bbc-be133e1fc234"],[{"index":{"value":55},"size":{"value":18}},"4114b9b0-1795-4f6e-bddc-77d5e7e02e87"],[{"index":{"value":57},"size":{"value":8}},"bf367097-c1af-45b5-a08d-c0ac8860b202"],[{"index":{"value":59},"size":{"value":6}},"671d120b-4529-416b-9e45-3bd60166bd42"],[{"index":{"value":63},"size":{"value":8}},"1907c4cd-9b07-42f3-ae41-e34a4fa1023b"],[{"index":{"value":66},"size":{"value":10}},"7c454c0e-8871-432c-a127-dfefca103924"],[{"index":{"value":67},"size":{"value":1}},"4dbcdd69-d42d-4db3-b5aa-5a2356f75155"],[{"index":{"value":70},"size":{"value":13}},"bd0a69fd-bbd4-4fbf-8605-5043e898885a"],[{"index":{"value":74},"size":{"value":14}},"d6cec62e-fc24-4b22-8288-962255a18244"],[{"index":{"value":74},"size":{"value":25}},"7e63a013-8fea-49f8-b115-131b7e9f58c1"],[{"index":{"value":78},"size":{"value":16}},"f017cf2d-79af-4b16-92a1-2f4c8e87c296"],[{"index":{"value":78},"size":{"value":24}},"37ca2577-768f-40e5-a224-b70880aa147c"],[{"index":{"value":78},"size":{"value":43}},"b3f23922-4ee5-4b18-853c-bfc7764117d9"],[{"index":{"value":85},"size":{"value":10}},"9a82e132-809b-49dc-affc-7bd4126a647e"],[{"index":{"value":89},"size":{"value":10}},"b4ab57e7-b504-4119-8199-22f3e94c4f70"],[{"index":{"value":95},"size":{"value":7}},"3903a7f9-1f0d-4a23-b09c-0760c6d967e6"],[{"index":{"value":97},"size":{"value":10}},"f14c380f-c0c2-4286-9e7b-325ce84fb395"],[{"index":{"value":103},"size":{"value":18}},"4152711f-2b2f-429c-a590-487ac4787286"]] +[[{"index":{"value":0},"size":{"value":4}},"b516b65e-0486-4c1b-bf23-1840aa462646"],[{"index":{"value":0},"size":{"value":29}},"9548b686-6f72-492f-b591-fac42ecf7599"],[{"index":{"value":0},"size":{"value":717}},"586b6858-28af-4743-b1bd-314ea10ffb65"],[{"index":{"value":5},"size":{"value":8}},"fc994f1b-1b3b-4a19-aebb-59dca1e46afd"],[{"index":{"value":5},"size":{"value":13}},"48748746-b3fb-4a6b-ad00-edd99989df72"],[{"index":{"value":13},"size":{"value":1}},"c937c68e-2742-4137-b8d5-6f152937bc4d"],[{"index":{"value":14},"size":{"value":4}},"8e71848d-5044-4724-8f95-23e80e93cbc2"],[{"index":{"value":19},"size":{"value":6}},"bed2619c-1ec7-4ffd-bac2-383d72d75c18"],[{"index":{"value":26},"size":{"value":3}},"22e06a00-5271-4e60-bfc0-bcc830af8a08"],[{"index":{"value":30},"size":{"value":4}},"2ad6b391-f936-4d5b-9a6d-ac5cad1692aa"],[{"index":{"value":30},"size":{"value":30}},"7db94069-8298-4d9b-9a3e-da950fa04680"],[{"index":{"value":35},"size":{"value":8}},"ec58084a-c18c-4e61-b7af-5ea2a17bc90b"],[{"index":{"value":35},"size":{"value":14}},"7499025e-06d7-4b94-aa98-1b3a9a97c525"],[{"index":{"value":43},"size":{"value":1}},"d228441a-9c8b-4bb1-b106-d16c002ac885"],[{"index":{"value":44},"size":{"value":5}},"80297a85-df0d-4638-8072-bb2b1e05913c"],[{"index":{"value":50},"size":{"value":6}},"44a3d4a6-a41c-4e37-b905-7261ffe16207"],[{"index":{"value":57},"size":{"value":3}},"945a9875-71e6-4a35-a3fc-a31b415249a8"],[{"index":{"value":61},"size":{"value":4}},"fef02dd8-31f8-4ec6-878b-b1acd8a29ef3"],[{"index":{"value":61},"size":{"value":33}},"17b0c9b4-42f9-4b92-8181-65cc06aac06e"],[{"index":{"value":66},"size":{"value":8}},"2f10577b-1a80-416b-85a6-6f63601f7e53"],[{"index":{"value":66},"size":{"value":17}},"dd620446-b6da-4d41-8063-e1596a4852cf"],[{"index":{"value":74},"size":{"value":1}},"8b774230-717e-4308-872d-75e4540e4435"],[{"index":{"value":75},"size":{"value":8}},"a0bd51dc-e927-418b-8522-853818a45215"],[{"index":{"value":84},"size":{"value":6}},"8d557bd2-3097-4b24-9c6e-116ff0f9c7f0"],[{"index":{"value":91},"size":{"value":3}},"9868f444-91eb-405c-a0ef-9cae886d45a6"],[{"index":{"value":95},"size":{"value":4}},"9b3c0659-9bee-498a-b056-a2e4c400706b"],[{"index":{"value":95},"size":{"value":28}},"ec1e865a-7f27-46a5-be9b-62fc10861d59"],[{"index":{"value":100},"size":{"value":8}},"20c3b269-250e-408b-aa6d-fb7fdf5cf3f8"],[{"index":{"value":100},"size":{"value":12}},"fe903eb1-e1a6-4833-9107-215569c5de19"],[{"index":{"value":108},"size":{"value":1}},"18da259a-01d4-418d-9fc5-1fb0e0478023"],[{"index":{"value":109},"size":{"value":3}},"ca991818-2acc-4873-ac9b-64f58e9a292c"],[{"index":{"value":113},"size":{"value":6}},"7688fadb-9321-45c0-8191-eff2f334006b"],[{"index":{"value":120},"size":{"value":3}},"063cf784-d011-4e85-b039-bc28849e0da8"],[{"index":{"value":124},"size":{"value":6}},"8a49d7f8-ae33-48cb-be9f-70555253e6a4"],[{"index":{"value":124},"size":{"value":29}},"82fa7c47-e818-4640-a4e9-d69defcb4ac7"],[{"index":{"value":131},"size":{"value":8}},"cf25e32f-8716-4d1a-b522-b8501d26b64d"],[{"index":{"value":131},"size":{"value":22}},"6461500b-d334-41fb-a503-a40dac62cfc7"],[{"index":{"value":139},"size":{"value":1}},"539d5921-3739-417c-b336-e5eafc593cd4"],[{"index":{"value":140},"size":{"value":13}},"f13af3d0-0907-49e4-8507-3a2c08ca58c7"],[{"index":{"value":155},"size":{"value":4}},"781f86e2-40d4-467e-bd60-7f6903ce3e68"],[{"index":{"value":155},"size":{"value":561}},"8a3786b6-ed46-4a2a-98e6-80b0ecf7388c"],[{"index":{"value":160},"size":{"value":1}},"c7481d1b-f57f-459a-997c-57384f866472"],[{"index":{"value":161},"size":{"value":555}},"60213236-883a-47e4-b545-0b6de7508b60"],[{"index":{"value":166},"size":{"value":5}},"f42039de-626f-4231-9790-08fc3cb1d8a8"],[{"index":{"value":166},"size":{"value":18}},"c5303a2d-a637-4e64-a36b-a4ac16d78ab1"],[{"index":{"value":172},"size":{"value":1}},"0a5ec865-532d-47c6-b22a-b0f2718ca856"],[{"index":{"value":174},"size":{"value":1}},"61e7ac80-a4f0-4854-a946-2385e099c591"],[{"index":{"value":174},"size":{"value":10}},"0af4633f-5f39-4fa8-bd3b-c9e3f455d2ba"],[{"index":{"value":175},"size":{"value":8}},"372ed21a-b9ec-4bdb-b3eb-10444b9c1845"],[{"index":{"value":183},"size":{"value":1}},"f51c88a6-724f-4d42-8ecd-f419d3ef71b9"],[{"index":{"value":189},"size":{"value":4}},"d8d27500-118b-426a-bb12-9b42f8c84081"],[{"index":{"value":189},"size":{"value":70}},"3e24ee15-6710-41fd-bc11-696415ce3354"],[{"index":{"value":194},"size":{"value":1}},"d6289c74-faa3-4c31-965f-62244edb75b4"],[{"index":{"value":196},"size":{"value":1}},"709f0dab-7850-49ac-b8bd-67db5bd0ff0a"],[{"index":{"value":196},"size":{"value":30}},"792fd092-fa13-4c48-a615-e92850a1b2b8"],[{"index":{"value":196},"size":{"value":38}},"a365c0ea-a7d6-41ba-8c06-881973e1121a"],[{"index":{"value":196},"size":{"value":63}},"2450a0e3-9807-4c7f-9bb0-b8044aa357c8"],[{"index":{"value":197},"size":{"value":28}},"6faa8222-8b21-490b-b6f8-8a44a439c338"],[{"index":{"value":225},"size":{"value":1}},"5fa7a4f7-49b7-40ec-a2d6-2a10610e7cb6"],[{"index":{"value":227},"size":{"value":1}},"f61be04a-4215-446c-b07b-aafe11f6dd82"],[{"index":{"value":229},"size":{"value":5}},"f70ffe80-309e-4ffe-ae41-3834d55f584b"],[{"index":{"value":235},"size":{"value":1}},"f552bd04-e1ce-4288-80dd-76e9eeea7d53"],[{"index":{"value":237},"size":{"value":1}},"9201c1f9-533f-4158-b7b9-ea91a0aacc98"],[{"index":{"value":237},"size":{"value":22}},"e0e1ae7b-d1ee-4e46-9b67-8dabdb6d1f70"],[{"index":{"value":238},"size":{"value":20}},"a78d45d0-3b14-499d-8261-4d5a65b718d5"],[{"index":{"value":258},"size":{"value":1}},"77676954-52f6-47b4-bd06-1a9a892aa89f"],[{"index":{"value":264},"size":{"value":9}},"1802b407-2e62-466f-bd4b-c1c28bdc9e5b"],[{"index":{"value":264},"size":{"value":23}},"d9db1173-cb9b-4d16-8c04-9d8f2ced49e2"],[{"index":{"value":274},"size":{"value":1}},"328384e6-7abd-4879-8d59-ea9becad38e7"],[{"index":{"value":276},"size":{"value":4}},"56e8218b-9c95-4fd0-86ba-63e21dbb604b"],[{"index":{"value":276},"size":{"value":11}},"9753896d-9b8f-44c4-86b9-268a6bd361e0"],[{"index":{"value":280},"size":{"value":1}},"54c3d4d3-2184-446f-81dc-5cc472d5da36"],[{"index":{"value":281},"size":{"value":6}},"44d24b3a-d269-4696-9703-d639285517a4"],[{"index":{"value":292},"size":{"value":9}},"d27f0ee9-8fb9-4942-883d-8f6a2bc6dbb4"],[{"index":{"value":292},"size":{"value":27}},"6de0d4f4-6977-4f67-b04b-4626af181c2e"],[{"index":{"value":302},"size":{"value":1}},"4ea44395-48e7-45fd-a39d-b9a7c9ad4587"],[{"index":{"value":304},"size":{"value":9}},"95f901da-6b9d-4b7e-bee7-d45f51b92d97"],[{"index":{"value":304},"size":{"value":15}},"cb46408c-8411-4e4d-bb55-b86e224b7393"],[{"index":{"value":313},"size":{"value":1}},"9b006af5-8261-4a5b-9510-1c8bebd4097d"],[{"index":{"value":314},"size":{"value":5}},"a8d079ff-d080-4288-a622-ad68fc47aec0"],[{"index":{"value":324},"size":{"value":9}},"163b69fd-7dfe-4483-bfbf-3379b12f6381"],[{"index":{"value":324},"size":{"value":102}},"1b5a9941-2d33-4007-afcf-5f111899d052"],[{"index":{"value":334},"size":{"value":1}},"cea79c6f-6df9-4bbc-a4bc-925ba368118d"],[{"index":{"value":336},"size":{"value":9}},"d85253c6-6e27-4fcc-98ec-73f51108582e"],[{"index":{"value":336},"size":{"value":19}},"71a6435a-d563-4e59-a8d0-f5cb2dd4bdc5"],[{"index":{"value":336},"size":{"value":90}},"921eb0f0-bc2a-48e2-aca7-08610ec489ff"],[{"index":{"value":346},"size":{"value":9}},"8ed76135-eeae-4c2c-a384-6d1218f3ffd7"],[{"index":{"value":356},"size":{"value":1}},"d5202f1c-a99f-43a7-a670-8b60056bec02"],[{"index":{"value":356},"size":{"value":70}},"c825d634-bca8-4f4b-adcd-5d38aa3b55ea"],[{"index":{"value":357},"size":{"value":1}},"6f7059cf-f055-48ba-b4b0-fd23d4364b36"],[{"index":{"value":357},"size":{"value":18}},"8feb400e-957c-4c97-ac87-a0920fa67700"],[{"index":{"value":358},"size":{"value":16}},"8462d87b-859c-4c31-ac79-9f679a913062"],[{"index":{"value":374},"size":{"value":1}},"02e5c44d-9db7-4b8e-9720-432a520b324b"],[{"index":{"value":375},"size":{"value":1}},"9f582580-8c00-4650-a57c-875d8bed7ffa"],[{"index":{"value":377},"size":{"value":1}},"093d955b-cbb6-4775-b8c1-1dc7650e693f"],[{"index":{"value":377},"size":{"value":6}},"a346526d-25fa-4cae-b61b-364a857c86d3"],[{"index":{"value":378},"size":{"value":4}},"2d0e566f-8659-4a77-ae50-ed345fa5a9bd"],[{"index":{"value":382},"size":{"value":1}},"e2aa6ac8-f093-4808-9c14-b793b1e50304"],[{"index":{"value":383},"size":{"value":1}},"07e6ad9c-43ed-47d0-adf4-ff85e918fcbf"],[{"index":{"value":385},"size":{"value":1}},"4df94376-e6a8-4e3a-a134-8b822f945b0c"],[{"index":{"value":386},"size":{"value":1}},"c39a60f6-0e3a-493e-aad7-350750f06893"],[{"index":{"value":388},"size":{"value":1}},"babda7ef-5d71-4052-a005-551e2e0a43ed"],[{"index":{"value":388},"size":{"value":13}},"86d78f58-26e3-46cb-9c64-c6586be09632"],[{"index":{"value":389},"size":{"value":11}},"92ee82a2-83a3-47d2-a309-2b3e65c068b1"],[{"index":{"value":400},"size":{"value":1}},"c595c2b3-755b-427a-a4b6-0d94e6d289ba"],[{"index":{"value":401},"size":{"value":1}},"2ff3d719-a80c-4314-8cc5-2105efbd2e06"],[{"index":{"value":403},"size":{"value":1}},"4d691199-e1d9-4ddc-8023-d9cea81d1195"],[{"index":{"value":403},"size":{"value":10}},"e63b15e4-d492-4756-8d52-d5fe5f7982ba"],[{"index":{"value":404},"size":{"value":8}},"125a2771-8fa3-46f8-8052-5e5f28e66261"],[{"index":{"value":412},"size":{"value":1}},"2ad218dd-eeba-46ea-a9b9-aefd75fbb2fd"],[{"index":{"value":413},"size":{"value":1}},"3a8c3697-a509-44c2-b947-fff24cd2e0e5"],[{"index":{"value":415},"size":{"value":1}},"c7ec6aaf-6992-45c4-9518-2e6544db60fd"],[{"index":{"value":415},"size":{"value":10}},"bbb88c68-0538-4bff-b5ed-3b446d57c1f4"],[{"index":{"value":416},"size":{"value":8}},"3b2d94af-95bc-4e2f-b1ac-37edca17264b"],[{"index":{"value":424},"size":{"value":1}},"371e6de1-d891-4845-996d-e05840c7834f"],[{"index":{"value":425},"size":{"value":1}},"236f0a5f-4bb2-486d-bdeb-a1dfba8dd8f9"],[{"index":{"value":431},"size":{"value":9}},"8c8bb1be-23ae-4f54-a289-4bdad90cd836"],[{"index":{"value":431},"size":{"value":65}},"ff5fcd40-c1bd-4e06-8ec5-5dcbd16587f9"],[{"index":{"value":441},"size":{"value":1}},"878a5747-0d80-4de4-ac13-df4a4b731040"],[{"index":{"value":443},"size":{"value":9}},"67f3d07b-4924-4b71-8051-f056a1003417"],[{"index":{"value":443},"size":{"value":16}},"ddaf4c8f-3e85-4c34-9a25-cb7a842a5c22"],[{"index":{"value":443},"size":{"value":27}},"215dd0c1-c780-4906-97fe-8ebd273140ea"],[{"index":{"value":443},"size":{"value":53}},"79e438fe-b964-491b-aeb5-df9176073851"],[{"index":{"value":452},"size":{"value":1}},"4433999e-ed1b-4a32-9972-11647aabde09"],[{"index":{"value":453},"size":{"value":6}},"25a9bc7c-856d-4442-a4cd-29c6044b50bd"],[{"index":{"value":460},"size":{"value":1}},"ae834f84-d0cf-4cb8-9af6-34f31cd777c1"],[{"index":{"value":460},"size":{"value":10}},"b76e4c76-7eb4-4c56-908b-960a70bd25ea"],[{"index":{"value":461},"size":{"value":8}},"154433c8-0c2d-4d4b-ae70-6eaef0a15a67"],[{"index":{"value":469},"size":{"value":1}},"40df4394-467a-4190-91c4-46d0d7619c79"],[{"index":{"value":471},"size":{"value":16}},"29bcf3c9-748d-4dc9-bcdb-b54281c23f08"],[{"index":{"value":471},"size":{"value":25}},"d7d85219-61cb-499f-b8c2-a0b99cc35a5b"],[{"index":{"value":487},"size":{"value":1}},"2a9b40f8-57f4-4506-bc0e-b9c071417591"],[{"index":{"value":488},"size":{"value":8}},"22ae3725-4da7-4d24-8e91-2529b90ec3f2"],[{"index":{"value":501},"size":{"value":9}},"306b47e7-e412-42f7-9cac-2c5d80b8bfbe"],[{"index":{"value":501},"size":{"value":94}},"eeefefb1-ed0c-4464-b351-dd705f1d95cf"],[{"index":{"value":511},"size":{"value":1}},"9060b1d1-8c33-4308-aaee-d132e11e1d84"],[{"index":{"value":513},"size":{"value":9}},"55cd1cac-4842-4023-b2d0-e25a15897b59"],[{"index":{"value":513},"size":{"value":18}},"b4d5e3c3-26bd-4ff5-be0f-527f6427f26e"],[{"index":{"value":513},"size":{"value":82}},"b9d9516c-9a88-4655-b742-51c114f4ae9a"],[{"index":{"value":522},"size":{"value":1}},"d6491359-a23f-4a81-9dd1-74f2c7f65aa0"],[{"index":{"value":523},"size":{"value":8}},"65019ad5-c5ca-4c76-9823-630520c3e74f"],[{"index":{"value":532},"size":{"value":1}},"bd0650ed-5720-4beb-9e4a-65a0c8a669c0"],[{"index":{"value":532},"size":{"value":63}},"ad50ad2f-fc97-44b5-8150-5805b0702161"],[{"index":{"value":533},"size":{"value":11}},"c630bc89-aa83-4283-8ac4-1b19af310010"],[{"index":{"value":533},"size":{"value":16}},"a2ec69d4-c7e9-41e2-aacf-0084c0060cd8"],[{"index":{"value":533},"size":{"value":35}},"71eefafd-df91-4d72-84c5-9753792d87e2"],[{"index":{"value":533},"size":{"value":61}},"75cc5c8b-af7d-4b64-861e-5dc1f4dde72a"],[{"index":{"value":544},"size":{"value":1}},"22a13613-24ae-4333-8593-5246765b2009"],[{"index":{"value":545},"size":{"value":4}},"4e6042b0-c5dd-4a4e-b40d-e6335112f834"],[{"index":{"value":550},"size":{"value":1}},"1bb9dd7b-badc-45f8-86cb-d5ba510e3643"],[{"index":{"value":550},"size":{"value":18}},"1bc5083b-3074-4418-a3d3-2da2a0b2104f"],[{"index":{"value":551},"size":{"value":16}},"4decf385-51b0-47f7-8ac2-38b71e5609e4"],[{"index":{"value":567},"size":{"value":1}},"ae384335-af81-4469-adec-cb237c66500b"],[{"index":{"value":569},"size":{"value":14}},"cd0ee8c4-7783-4a2c-8e9a-f784202e034d"],[{"index":{"value":569},"size":{"value":25}},"21c3bc44-fcf4-4146-a737-29581b54bec1"],[{"index":{"value":583},"size":{"value":1}},"7483a859-33be-41f2-bfc1-2370672ffff6"],[{"index":{"value":584},"size":{"value":10}},"7cbafcd8-98b5-42e9-9405-c0c0860f0fa9"],[{"index":{"value":594},"size":{"value":1}},"a3e6a45c-6e9b-45b3-8df0-74bb79fc410c"],[{"index":{"value":600},"size":{"value":10}},"e1fb3ab6-761b-43dc-9c72-deb640851fcb"],[{"index":{"value":600},"size":{"value":116}},"3a25e125-d123-49dd-9698-e0222cd6cd29"],[{"index":{"value":611},"size":{"value":1}},"b113b645-a721-46de-a32f-2d38cb2374f1"],[{"index":{"value":613},"size":{"value":9}},"307dedcf-334d-4d09-9ccb-8eca2bc70f50"],[{"index":{"value":613},"size":{"value":19}},"e6a4838c-2a18-48cc-9df0-08eccb0f124a"],[{"index":{"value":613},"size":{"value":103}},"fede3b86-f1ae-4ad8-bb1a-79808e9ead78"],[{"index":{"value":622},"size":{"value":1}},"1f18551d-d5ee-47d8-922b-32165ace151d"],[{"index":{"value":623},"size":{"value":9}},"4d811caf-c21f-480a-aab2-bbe6e93d0c87"],[{"index":{"value":633},"size":{"value":1}},"62a56c30-11e1-468d-9cae-b3c6f8694345"],[{"index":{"value":633},"size":{"value":83}},"ecd71642-b58b-4ef9-8463-12530c0d5db8"],[{"index":{"value":634},"size":{"value":16}},"79877510-9347-4dc8-a4c1-a2c64d7f9038"],[{"index":{"value":634},"size":{"value":25}},"6b17d9f7-3183-4c81-87cb-ce4ac771e8cf"],[{"index":{"value":634},"size":{"value":36}},"0358f005-0981-4f88-8684-1b4f43dabe8c"],[{"index":{"value":650},"size":{"value":1}},"228a8a22-059d-42a6-8e6e-b0a6174ae1be"],[{"index":{"value":651},"size":{"value":8}},"ba3f88c7-a211-411e-a1df-64674ded7f03"],[{"index":{"value":660},"size":{"value":1}},"8ecd460e-73bf-45dd-beba-0eaec1230168"],[{"index":{"value":660},"size":{"value":10}},"0dfc806b-ee15-4f43-8554-a531bfe99267"],[{"index":{"value":661},"size":{"value":8}},"49f28f32-c3cb-454e-90d5-a2c199a448c9"],[{"index":{"value":669},"size":{"value":1}},"86d06486-02bf-46d1-af94-4757dc66cfd2"],[{"index":{"value":670},"size":{"value":1}},"d0702649-8f4c-4f23-8b88-8f467942f8ad"],[{"index":{"value":672},"size":{"value":16}},"fac32836-93f8-46c5-bed0-2abc92d0e7d6"],[{"index":{"value":672},"size":{"value":24}},"116267c3-2141-42e5-b214-99b3585a1432"],[{"index":{"value":672},"size":{"value":43}},"fc827df7-6c21-4d9c-8241-24f96944ede1"],[{"index":{"value":688},"size":{"value":1}},"9aeddca8-0a24-4f36-b98f-acb59a0dd49d"],[{"index":{"value":689},"size":{"value":7}},"371b48b1-e8c1-4046-b25e-2694962744e7"],[{"index":{"value":697},"size":{"value":1}},"86e90c3b-c60e-4d29-a3d3-0af486dbe862"],[{"index":{"value":697},"size":{"value":18}},"d6157815-856b-469e-baea-87cf873c015e"],[{"index":{"value":698},"size":{"value":16}},"ba6e5e8b-2ae9-45c1-a22a-0456efcd4d2c"],[{"index":{"value":714},"size":{"value":1}},"4ef89eb9-5748-4de9-b402-04ccaa17a45e"],[{"index":{"value":715},"size":{"value":1}},"b80a1c2a-c28f-405a-a6ea-6ea343f0bd41"]] {"ide":{"node":{"979ad5e5-51b2-44a9-be7d-7a7a11eacd9c":{"position":{"vector":[-100,80]},"intended_method":null,"uploading_file":null,"selected":false},"32b6565a-da8b-45f8-a572-d17e41e8551c":{"position":{"vector":[-217.6975,-655.00366]},"intended_method":null,"uploading_file":null,"selected":false},"aacfb99c-f4b8-4b3f-90cb-5360b3f8825b":{"position":{"vector":[-89.68533,-303.00366]},"intended_method":null,"uploading_file":null,"selected":false},"f0c3a964-010d-4353-b069-33baf477b665":{"position":{"vector":[-90.98013,-66]},"intended_method":null,"uploading_file":null,"selected":false},"2ba2db79-37f8-4cf3-be72-a2fea8fa2e8a":{"position":{"vector":[111,124]},"intended_method":null,"uploading_file":null,"selected":false},"29837be6-9525-452c-93d2-4d7c05de84a3":{"position":{"vector":[-100,-25]},"intended_method":null,"uploading_file":null,"selected":false},"51b15c1c-d818-4b51-b3ba-393f262e0349":{"position":{"vector":[-89.29175,30]},"intended_method":null,"uploading_file":null,"selected":false},"bc576467-0fa3-4752-9832-a84387581390":{"position":{"vector":[-217.78854,-601.00366]},"intended_method":null,"uploading_file":null,"selected":false},"859358af-220c-477d-9a43-6a8e6635e0c1":{"position":{"vector":[-16,16]}},"ec3e43d0-562a-4c58-8ba1-0452ed0e5a98":{"position":{"vector":[-16,-48]}},"758564f8-0f2b-48df-ac00-0fd40198ce2a":{"position":{"vector":[-16,-112]}},"8cc6820a-dea2-47c6-a591-9eb9eaad0d8f":{"position":{"vector":[-16,-176]}},"2a9b0785-42b2-4ac5-b96d-54e66ab3623f":{"position":{"vector":[-16,-240]}},"e32ae2bf-d064-4fd0-b781-60a8247192ad":{"position":{"vector":[-16,-304]}},"018ac0c0-1775-444d-bfa8-c81075db28b4":{"position":{"vector":[-16,16]}},"78f01e81-8981-43d9-aaae-7e75f3a96b90":{"position":{"vector":[-16,-48]}},"5dc7024f-d63c-40c9-9e94-f609a50fcf91":{"position":{"vector":[-16,-112]}},"c69acc89-cd9e-46a4-aae4-3cf3e320425e":{"position":{"vector":[-16,-176]}},"3bc1466f-3bdb-45db-99e4-95cbbe193905":{"position":{"vector":[-16,-240]}},"1328f438-1d92-47c7-b004-72a006737714":{"position":{"vector":[-16,-304]}},"fd71df48-0d12-4b77-9247-a9b7921553b7":{"position":{"vector":[-16,-368]}},"e112fa00-749d-493d-b702-4fd9a7fcde69":{"position":{"vector":[-16,-432]}},"955c9165-8aa9-4905-b6c8-b1958239a11d":{"position":{"vector":[-16,16]}},"0e3baddb-3203-421d-8e79-08cfb71f317a":{"position":{"vector":[-16,-48]}},"5c601c73-7841-4e86-ac4a-bcabdad3f923":{"position":{"vector":[-16,-112]}},"6ea39ab8-b282-4332-bdb1-933f22f2c1cb":{"position":{"vector":[-16,-176]}},"228d3585-d770-4f88-b9e9-2d970ca85093":{"position":{"vector":[-16,-240]}},"40b4c5ed-c0b2-4f9a-92b4-d339ab915529":{"position":{"vector":[-16,-304]}},"4b0fa481-0c4f-4fc3-8c40-c04f821f19bb":{"position":{"vector":[-16,-368]}},"63f7ada9-998e-4039-afc1-49bcb3c3dd68":{"position":{"vector":[-16,-432]}},"63852cf8-ed6a-4cba-9e92-6bd0341ecf22":{"position":{"vector":[-16,16]}},"ddcdd7ff-c04b-4bfa-83bb-3a1013f3b5ae":{"position":{"vector":[-16,-48]}},"0e873618-aafd-4e47-9caf-8f21bfc145bf":{"position":{"vector":[-16,-112]}},"fbca2fcb-b7c7-432e-9bf1-c0614fbd6592":{"position":{"vector":[-16,-176]}},"5c631091-5a98-4ebc-9719-4bb20700fab8":{"position":{"vector":[-16,-240]}},"d114de11-f1f9-4883-925a-cf61409cd899":{"position":{"vector":[-16,-304]}},"55ada33c-6385-419d-ac1e-847fb87312b4":{"position":{"vector":[-16,-368]}},"88fc6b93-e40f-4e95-ac2f-8ecec7166e35":{"position":{"vector":[-16,-432]}},"cb86c728-67a7-441b-abbd-50f6aba771b5":{"position":{"vector":[-16,16]}},"f4a3018b-f365-44f3-b2f0-ca3cc3ce5090":{"position":{"vector":[-16,-48]}},"1061145a-01f0-44b3-bca0-d32c361ec806":{"position":{"vector":[-16,-112]}},"61de8444-1deb-4d9e-8f1a-dcba45e77eb0":{"position":{"vector":[-16,-176]}},"30b758ed-75d2-4e77-adb8-6c2702430a6d":{"position":{"vector":[-16,-240]}},"ccd10ed0-19fc-403c-b7ff-77618a89c146":{"position":{"vector":[-16,-304]}},"fdfd49d2-ab2f-4e31-9252-3d5e76c5c346":{"position":{"vector":[-16,-368]}},"946a3f55-123a-4f38-a643-bce0740b45fd":{"position":{"vector":[-16,-432]}},"a27230c7-5966-4d85-ba03-7567433085c0":{"position":{"vector":[-16,16]}},"d8fc0238-092a-4e30-81b7-2d2dc24f41c7":{"position":{"vector":[-16,-48]}},"3f86856d-1653-4464-8f9e-bb55ae78bbbd":{"position":{"vector":[-16,-112]}},"e586b663-5e63-4b6c-a66d-e4fda1d565e8":{"position":{"vector":[-16,-176]}},"9202409b-3c8c-4c36-bb30-cdfb1757af5d":{"position":{"vector":[-16,-240]}},"e8eed824-66c4-47b9-9415-a9c5471bd379":{"position":{"vector":[-16,-304]}},"b5eff7d7-1b3f-4ef6-b95e-47dae83a0712":{"position":{"vector":[-16,-368]}},"87e73132-f263-4f5a-81f6-0b7a35778698":{"position":{"vector":[-16,-432]}},"6b021ec8-feb9-4d50-ab56-306ae7eac91b":{"position":{"vector":[-16,16]}},"221c4805-3be9-4665-943e-ca7ddcc26fa4":{"position":{"vector":[-16,-48]}},"cdf79b00-9821-4ee9-9a57-f73d195002d8":{"position":{"vector":[-16,-112]}},"42ab9dd7-e178-4a80-b1aa-9a16542c93e6":{"position":{"vector":[-16,-176]}},"91cbb287-3bb1-4028-b868-f8d65c8600a5":{"position":{"vector":[-16,-240]}},"ecdf47bf-6e19-4c8c-b82b-5f5c80fd5b04":{"position":{"vector":[-16,-304]}},"ee4045b1-45ab-4ec1-aa03-4e5311f452bd":{"position":{"vector":[-16,-368]}},"98d52909-599c-4c84-bf93-aaff895df107":{"position":{"vector":[-16,-432]}},"7b21b97e-be3d-4354-839c-6ff13c4c4293":{"position":{"vector":[-16,16]}},"ba240376-856a-4382-bd14-c331fa92c28b":{"position":{"vector":[-16,-48]}},"21a0db63-6ac6-4da3-8193-2e3f4ab3cb4a":{"position":{"vector":[-16,-112]}},"7f2f7a44-d94e-4b10-b57c-4cbf75dc9a3d":{"position":{"vector":[-16,-176]}},"8efaaf55-ecef-42f0-a1ce-401308cc6d34":{"position":{"vector":[-16,-240]}},"edf100d0-539e-42c2-ab37-f29359aebd6c":{"position":{"vector":[-16,-304]}},"10ed46df-f26e-4525-9a9b-cdc316c46655":{"position":{"vector":[-16,-368]}},"5a36848e-f129-466c-99fc-352124179cec":{"position":{"vector":[-16,-432]}},"3f2477bf-332a-4ce7-8949-c2b2ccdcc68c":{"position":{"vector":[-16,16]}},"7dd383bc-efec-4c12-b14f-b5b340dfabb0":{"position":{"vector":[-16,-48]}},"41746aad-6943-4c2d-bb9d-33c480bef1ca":{"position":{"vector":[-16,-112]}},"25ce242d-c65d-4ec7-b5d8-1a82d71c3bd1":{"position":{"vector":[-16,-176]}},"6dfecaeb-f23c-4e79-a98d-3869b01dcb10":{"position":{"vector":[-16,-240]}},"884e895e-6730-495a-b6a0-5821ff8696f2":{"position":{"vector":[-16,-304]}},"adf5a28c-5a12-4291-8216-e52f9fa7d823":{"position":{"vector":[-16,-368]}},"aad39e88-281d-4100-800f-39691d28a2d9":{"position":{"vector":[-16,-432]}},"15103dc9-aa36-42ac-ae3b-00b72dd89150":{"position":{"vector":[-16,16]}},"555d3a7c-fc9f-4001-8c20-bfb862474813":{"position":{"vector":[-16,-48]}},"125da953-7391-4ef3-b376-9ec1e6c01628":{"position":{"vector":[-16,-112]}},"902ef95c-480d-4f81-abf6-0506e2d8ae9e":{"position":{"vector":[-16,-176]}},"e177bb0b-d3cb-4e76-a7e0-41c7658c0261":{"position":{"vector":[-16,-240]}},"bf54c65f-c04d-4e03-bfa1-26a7f822dc23":{"position":{"vector":[-16,-304]}},"4893dabc-4614-4d20-a810-1e4616ebb653":{"position":{"vector":[-16,-368]}},"574f6d06-54ac-4578-9725-dbef28cc2a2f":{"position":{"vector":[-16,-432]}},"5a56dafc-34a1-4b9a-a01f-892ab78bae80":{"position":{"vector":[-16,16]}},"efb993c0-6f4c-4177-8d94-3596d53433ef":{"position":{"vector":[-16,-48]}},"a070d2b7-0c11-4996-b30c-845e095c0e31":{"position":{"vector":[-16,-112]}},"bd2149ec-d3fa-4440-9db8-8d991140cd5a":{"position":{"vector":[-16,-176]}},"f6a20d99-20be-431b-8650-100c03eb1282":{"position":{"vector":[-16,-240]}},"9d8975ab-e519-4177-bc78-29b70c4f7037":{"position":{"vector":[-16,-304]}},"8b00c57e-e86c-4348-9fa0-39fa19200d63":{"position":{"vector":[-16,-368]}},"b457a2d4-52fb-4630-bb71-398f10395206":{"position":{"vector":[-16,-432]}},"066c36f2-7089-4ead-bc64-bc6a5a0ac752":{"position":{"vector":[-16,16]}},"60f387fe-1e6b-4c06-bdcb-94a5747efb48":{"position":{"vector":[-16,-48]}},"5cdc76ea-e773-4b2d-9c0a-197d64722683":{"position":{"vector":[-16,-112]}},"20fc7058-4440-4476-8b9d-6cd7e5b3c0f2":{"position":{"vector":[-16,-176]}},"6047579e-56c6-4da9-9fbb-7e3081edd967":{"position":{"vector":[-16,-240]}},"12a0e350-59c3-4f80-9c6e-b7c3f068a000":{"position":{"vector":[-16,-304]}},"ca2be763-99d0-4f0c-9e40-9d96b2707122":{"position":{"vector":[-16,-368]}},"62f69a4d-d943-4806-8a08-8eaae412ac0d":{"position":{"vector":[-16,-432]}},"8bebed46-ec76-4fbb-9780-f78436ddf518":{"position":{"vector":[-16,16]}},"b5f2f3d0-6a2f-4c61-bc43-994a55c1afa7":{"position":{"vector":[-16,-48]}},"b7c2385b-6c28-42a1-ba68-d1565d524b58":{"position":{"vector":[-16,-112]}},"630c4cc9-d90a-4de6-8497-b8462ddfe01a":{"position":{"vector":[-16,-176]}},"68e8b987-0b50-4e68-950e-8bd7cc8a6a5a":{"position":{"vector":[-16,-240]}},"fc034459-c1bc-4ed6-895b-944aabb0364a":{"position":{"vector":[-16,-304]}},"2575318a-5497-42c9-a68d-7b425705a7df":{"position":{"vector":[-16,-368]}},"d3422ec0-af51-47bc-8975-1b8932eccb38":{"position":{"vector":[-16,-432]}},"2a66604b-4318-4ed3-b172-a2ad2bb56f0c":{"position":{"vector":[-158.671875,-92]}},"85b3aa41-7ef2-4438-89c3-eac343612ca0":{"position":{"vector":[-16,-48]}},"faa577b3-8e4f-4b88-b333-3473889aa478":{"position":{"vector":[-98,80]}},"9745c093-2c4c-4b54-8d0c-e43c88bb959c":{"position":{"vector":[-16,-176]}},"1c5fc2cc-cbe8-4967-b78a-737ebaa29bcc":{"position":{"vector":[-16,-240]}},"4ef9bc23-1798-46b3-911c-8572628fe372":{"position":{"vector":[-16,-304]}},"fc85dd2e-f8b4-43c6-9597-688ebda7ea56":{"position":{"vector":[-16,-368]}},"b3b0a640-b9a6-4aef-893a-a65479d15c3a":{"position":{"vector":[-16,-432]}},"080a1526-a189-4f2a-9987-3c779e156310":{"position":{"vector":[-16,16]}},"b3f2f87b-7bcf-4bef-a03d-fe47274f8906":{"position":{"vector":[-16,-48]}},"5ec8c971-0e8f-4be6-8d25-b790f2b4a3bb":{"position":{"vector":[-16,-112]}},"90c3c6b7-1822-43ac-b2d0-48a9b6ffcbfd":{"position":{"vector":[-16,-176]}},"65ef9d61-f0a1-4830-89dd-f12026aa2487":{"position":{"vector":[-16,-240]}},"e9babf2d-1fb9-4709-bfd9-30c3b0aa5423":{"position":{"vector":[-16,-304]}},"d6d75a00-cd4f-430f-9f75-3841fca7a911":{"position":{"vector":[-16,-368]}},"0a8aa4fa-26c2-4b61-ba33-63c755a1d83c":{"position":{"vector":[-16,-432]}},"7fed7cc4-d88d-4845-b1cd-9cb35b92b019":{"position":{"vector":[-16,16]}},"f3f615a4-c3cc-4637-8829-3c744976dbd8":{"position":{"vector":[-16,-48]}},"be23e37a-b61f-4869-b0d6-cf9020a11611":{"position":{"vector":[-16,-112]}},"6fef7f3e-5d22-4b2d-806b-2f1edffa6ed9":{"position":{"vector":[-16,-176]}},"48a4ef04-8acc-4735-9914-7c53c5b82c9a":{"position":{"vector":[-16,-240]}},"a5f11f7b-a0a3-4237-9542-e5740f527d4a":{"position":{"vector":[-16,-304]}},"e6ad3fc5-e8d6-4bc5-a190-acadc6a234b3":{"position":{"vector":[-16,-368]}},"03d56d0b-d654-4eab-b2de-ccca5c25f5c5":{"position":{"vector":[-16,-432]}},"3a95e5df-f29b-49ac-91b3-0f37d28bfbd9":{"position":{"vector":[-16,16]}},"51d341a2-ad1f-495a-a442-ecd3a2e2b608":{"position":{"vector":[-16,-48]}},"49713f0d-8252-460f-a035-b89b3634dec3":{"position":{"vector":[-16,-112]}},"3db3de17-9028-429e-b837-50431a09e7ff":{"position":{"vector":[-16,-176]}},"2882e464-a04e-4961-b34a-0af88b376d9b":{"position":{"vector":[-16,-240]}},"fd52e77d-c474-47d5-b618-512bcf3fe18c":{"position":{"vector":[-16,-304]}},"2439f5a0-0912-4bfa-b61c-a97fc53812fb":{"position":{"vector":[-16,-368]}},"c568cca7-6efa-42b6-a765-e142fcb8ced7":{"position":{"vector":[-16,-432]}},"9fb374ff-245d-4471-a2e0-2cb5bbbff75b":{"position":{"vector":[-16,16]}},"df2fcff4-c870-454f-b54a-fc1b9b4a0a35":{"position":{"vector":[-16,-48]}},"c5a8ea8b-b0e1-4d6b-8bdc-3a4a96c35754":{"position":{"vector":[-16,-112]}},"0981491a-f640-4681-a4f8-ba607ab148e9":{"position":{"vector":[-16,-176]}},"d826f111-f59e-42d0-8787-9b07d68559c8":{"position":{"vector":[-16,-240]}},"72d26460-5375-4c5e-b91c-01b3c9a218ec":{"position":{"vector":[-16,-304]}},"f229a41a-2c32-489f-8454-e4eb35fd9b2d":{"position":{"vector":[-16,-368]}},"e524dcad-de81-4924-83a3-19b9a8fb6ea5":{"position":{"vector":[-16,-432]}},"349e2547-a968-452c-81fd-70bc3950a352":{"position":{"vector":[-16,16]}},"1d1a4b2a-23a1-4fa3-9678-b4cdac3249ec":{"position":{"vector":[-16,-48]}},"eee89a43-2987-4789-8569-3f3079ddbdd5":{"position":{"vector":[-16,-112]}},"55be3bb2-8be0-4a44-8106-8c5403340782":{"position":{"vector":[-16,-176]}},"17f75d9a-55ab-4fbf-aead-a8fc036160cc":{"position":{"vector":[-16,-240]}},"05bb0658-a7d7-431f-8108-f86ea2293f4f":{"position":{"vector":[-16,-304]}},"c1723482-fae7-4f79-8c8e-398738691011":{"position":{"vector":[-16,-368]}},"65c00a80-1216-417d-a6e1-9f10dc44731f":{"position":{"vector":[-16,-432]}},"d7b5670b-6c1c-4487-9709-8ac79f2f6699":{"position":{"vector":[-16,16]}},"2cf7c8fc-cf17-4155-981b-17017258510b":{"position":{"vector":[-16,-48]}},"51ae5242-c237-4c09-a221-97b5ff316f90":{"position":{"vector":[-16,-112]}},"2410a214-57a8-4f67-a23f-49c53d0eca93":{"position":{"vector":[-16,-176]}},"8045a13d-b38f-49c7-95de-e6849619cc14":{"position":{"vector":[-16,-240]}},"5cfb0c89-e0c8-442e-a6bb-cc339c00a8c0":{"position":{"vector":[-16,-304]}},"cb1a40b8-24dd-487b-af54-c73aaf982f53":{"position":{"vector":[-16,-368]}},"9ff88be1-d063-48cb-b5d8-7e0ca5cd90ec":{"position":{"vector":[-16,-432]}},"3c2a2b95-832a-49c9-8f38-585db29414f1":{"position":{"vector":[-16,16]}},"24a1f784-39db-490d-808f-efec9e554d14":{"position":{"vector":[-16,-48]}},"2dd2ae1f-8c2b-46c1-8798-8efce00f757a":{"position":{"vector":[-16,-112]}},"4426e854-1f04-42b2-bb97-52b85f6c36b3":{"position":{"vector":[-16,-176]}},"4317060c-9c9f-4405-920c-6eb3449f74a5":{"position":{"vector":[-16,-240]}},"af24b77e-a3ff-4650-a5a3-1d4572445179":{"position":{"vector":[-16,-304]}},"2a21fd73-fab6-4b7b-9e66-2859a6af047f":{"position":{"vector":[-16,-368]}},"f6e01084-07b8-4c49-ba85-c201de71fdb3":{"position":{"vector":[-16,-432]}},"67046748-44c8-4679-b499-776e0d7b9c26":{"position":{"vector":[-16,16]}},"1abf96c9-c679-4f96-a4e1-01c32031cae5":{"position":{"vector":[-16,-48]}},"2a40a649-b04b-4b84-81e3-81361d690567":{"position":{"vector":[-16,-112]}},"f11e7b83-2917-4251-a4b7-8192fabb661c":{"position":{"vector":[-16,-176]}},"feb6e1fc-27f8-4a13-b38b-4bf569a548c7":{"position":{"vector":[-16,-240]}},"db6e87b3-68fd-497e-b8bf-574d5921fec9":{"position":{"vector":[-16,-304]}},"50588b35-a82e-4107-8868-5b65e84060de":{"position":{"vector":[-16,-368]}},"314985fb-e2c0-49e4-b96f-7677092e7b2c":{"position":{"vector":[-16,-432]}},"34fc4537-3ea9-4f99-8142-cf2f9c8741fa":{"position":{"vector":[-16,16]}},"e0b1a18c-20dd-457c-9f55-e0aea1fe0263":{"position":{"vector":[-16,-48]}},"7be25d72-f8ec-468d-977e-e80b7406fa83":{"position":{"vector":[-16,-112]}},"9f1b2f6c-91b7-4862-9cdb-353cce42c664":{"position":{"vector":[-16,-176]}},"e2242b92-b453-4a3b-8f8f-d68fd4102af9":{"position":{"vector":[-16,-240]}},"f493633b-580e-41b6-96bb-65865f04e4e9":{"position":{"vector":[-16,-304]}},"1820835e-5437-417e-bb28-da4b5210b742":{"position":{"vector":[-16,-368]}},"15152dcd-1dea-4379-abe0-1c20c08f07a5":{"position":{"vector":[-16,-432]}},"2ac18200-025e-4dba-896e-3db38e084894":{"position":{"vector":[-16,16]}},"becae67f-f4fd-4b94-b449-4fbb65cbc98d":{"position":{"vector":[-16,-48]}},"ba112513-baf6-497d-819b-0e59a380a4b3":{"position":{"vector":[-16,-112]}},"afa461e3-fb47-4420-8fe0-838c0d34d9c9":{"position":{"vector":[-16,-176]}},"81ae6c84-c51e-4dd5-b6d6-b04a1de222b9":{"position":{"vector":[-16,-240]}},"65224d8d-2d91-4d54-8401-a9189bd6b3ee":{"position":{"vector":[-16,-304]}},"912dfa98-548b-49d1-8428-bfae552f59fe":{"position":{"vector":[-16,-368]}},"b9587c30-ad79-464c-97cb-d10d9dcec8c3":{"position":{"vector":[-16,-432]}},"49d99b46-dc70-4779-b4c3-e72803abca10":{"position":{"vector":[-16,16]}},"411ab73e-c533-4250-b1ca-59091c442c41":{"position":{"vector":[-16,-48]}},"29bc690b-4fa0-4c4f-ba92-0d25f20865e2":{"position":{"vector":[-16,-112]}},"5506b704-20dd-4749-93fb-21ae4f04710a":{"position":{"vector":[-16,-176]}},"fbd92e50-cba8-4137-a83d-81f4b3d71697":{"position":{"vector":[-16,-240]}},"76f3f406-77d0-4841-ad9b-9c286ff8727b":{"position":{"vector":[-16,-304]}},"d9f1f5a3-31c7-47cd-a73f-a8de56e6d44d":{"position":{"vector":[-16,-368]}},"94b13e6a-ccc1-4a79-acb3-1339339e8f2e":{"position":{"vector":[-16,-432]}},"e0f7f8e2-ea33-488d-b7a7-726ed0fdd12f":{"position":{"vector":[-16,16]}},"758cb9ef-b2a2-4e34-a789-8e5e40f74255":{"position":{"vector":[-16,-48]}},"b5c247c1-0ae0-4604-a7bc-6a411b6860eb":{"position":{"vector":[-16,-112]}},"0faa9b91-894b-4083-9f5a-ea23057fbe9d":{"position":{"vector":[-16,-176]}},"bd9608c9-28ba-4e3f-a981-f76a55684b5b":{"position":{"vector":[-16,-240]}},"f9023ec1-c987-410f-8c6f-824313add0ea":{"position":{"vector":[-16,-304]}},"9cc1c4e6-032f-46b6-afc1-7fe9c80ec738":{"position":{"vector":[-16,-368]}},"0905f27f-11ff-464a-bad8-df7ce5de0b91":{"position":{"vector":[-16,-432]}},"8c2a9657-d6d2-47a1-b699-b94bc92a4499":{"position":{"vector":[-16,16]}},"65c060f5-4235-4105-b9ed-3cff1d2c2e0d":{"position":{"vector":[-16,-48]}},"62b93479-b204-4b77-b3dd-f5c06feb5086":{"position":{"vector":[-16,-112]}},"4f5996aa-049d-4993-9c52-b9b5f08a1ef5":{"position":{"vector":[-16,-176]}},"e28239ca-5027-4bf8-9955-9655de749993":{"position":{"vector":[-16,-240]}},"02e44c9b-c389-4f14-b00a-4eac27987d6d":{"position":{"vector":[-16,-304]}},"076b29f9-d0b8-4c77-a4ba-173e5f3c46f2":{"position":{"vector":[-16,-368]}},"5d8a5c50-d4d0-4582-aa97-277f1ac8fd1d":{"position":{"vector":[-16,-432]}},"e9d070f5-8b27-4853-befb-4004b66507c4":{"position":{"vector":[-16,16]}},"03abd5e7-0345-4f0d-a45d-5ab11146b620":{"position":{"vector":[-16,-48]}},"0b29ca7e-ddc9-42e5-b5ea-4390df101f7a":{"position":{"vector":[-16,-112]}},"99e289e7-9475-47d3-b76d-745d036d9b96":{"position":{"vector":[-16,-176]}},"0b3705c3-7241-468d-9d4a-4f94656b7c0d":{"position":{"vector":[-16,-240]}},"cad01145-3956-4b1e-9f3e-2e34064e1eb4":{"position":{"vector":[-16,-304]}},"8f776caf-10d5-4e00-aafc-faac9962b4cf":{"position":{"vector":[-16,-368]}},"6029eb20-8a4b-4cdc-8eea-c176ffb070a1":{"position":{"vector":[-16,-432]}},"5e36dfd0-2e5c-4bfa-b58b-71a6d543819f":{"position":{"vector":[-16,16]}},"660253d3-8ee5-42d4-a9ef-2446936801cd":{"position":{"vector":[-16,-48]}},"06f7398f-10be-4f50-9023-72e56b53eb32":{"position":{"vector":[-16,-112]}},"bd72fd23-5c6e-43ac-b33e-b3521dcb19ef":{"position":{"vector":[-16,-176]}},"35a7a108-8e5f-45ad-afa2-de37d0af7094":{"position":{"vector":[-16,-240]}},"9e873e65-8a45-4e34-9049-3611d14e053d":{"position":{"vector":[-16,-304]}},"197b46e5-7192-4998-a0f7-b64faccd5bbe":{"position":{"vector":[-16,-368]}},"43adadcf-809e-482b-9ca3-7a593c4f0129":{"position":{"vector":[-16,-432]}},"bb94e948-3ba0-4634-98cd-a2f89dc80573":{"position":{"vector":[-16,16]}},"81c0592e-e467-429b-b893-435fcbb0940c":{"position":{"vector":[-16,-48]}},"880d6e66-bc7e-4d5f-9f53-5b64c2a6327b":{"position":{"vector":[-16,-112]}},"01c802a9-69c0-4f36-ad28-b3edc8492f35":{"position":{"vector":[-16,-176]}},"be6b2e23-a736-4c3a-86e9-24caff266562":{"position":{"vector":[-16,-240]}},"344fd097-6df5-4455-8791-b3cb0e627426":{"position":{"vector":[-16,-304]}},"02135877-9fbd-4b74-a797-cfb89cae8126":{"position":{"vector":[-16,-368]}},"b581b853-624f-4f38-9afa-00c2cbf41fc3":{"position":{"vector":[-16,-432]}},"07181098-7961-4cb0-be58-25e5c13d0b9b":{"position":{"vector":[-16,16]}},"77a698ba-e57e-4c14-b18d-cc97785bb87a":{"position":{"vector":[-16,-48]}},"c89ae259-59b3-4ee9-80ed-c9e11f6c1122":{"position":{"vector":[-16,-112]}},"04535bbe-116e-4ca4-ae53-d9fe77bca542":{"position":{"vector":[-16,-176]}},"c1cd12b4-0d06-4a57-ac2f-688ab39cd582":{"position":{"vector":[-16,-240]}},"b950ac4d-e938-4858-9e4b-71522052f808":{"position":{"vector":[-16,-304]}},"3b5eadfc-6f42-4357-aca7-1e78263b92e5":{"position":{"vector":[-16,-368]}},"53079eeb-8a85-497a-ab52-98b971855d6b":{"position":{"vector":[-16,-432]}},"b25f6bc4-327d-44a6-939a-114633bb2f11":{"position":{"vector":[-16,16]}},"4bb91c45-e41d-4a65-a4f8-7555521068af":{"position":{"vector":[-16,-48]}},"d4fc7e9f-308f-459e-9bdc-2297029d9c46":{"position":{"vector":[-16,-112]}},"87f38292-9e9c-4987-a155-072b4cb74188":{"position":{"vector":[-16,-176]}},"6529228a-0750-4d9a-80b0-389b163048d2":{"position":{"vector":[-16,-240]}},"61127085-4168-4878-ad88-e7bdc2764c9c":{"position":{"vector":[-16,-304]}},"456db02e-5131-425b-990f-c8e9aa7d307b":{"position":{"vector":[-16,-368]}},"2bbb9829-916a-4e04-87ae-280b07a5cbca":{"position":{"vector":[-16,-432]}},"3b6cbab0-3030-4286-ba36-49c9b81d90ce":{"position":{"vector":[-16,16]}},"e122e3bb-6775-4ad5-87e9-2d28175c4c57":{"position":{"vector":[-16,-48]}},"32572cb8-6d46-433f-9953-7e36bd4f708a":{"position":{"vector":[-16,-112]}},"84dfe2f8-1468-41a3-b304-3edfe2d0009b":{"position":{"vector":[-16,-176]}},"c8731d55-472d-4c59-b9c1-4504796bad26":{"position":{"vector":[-16,-240]}},"23987c16-42b1-4c56-92a2-fe0ddaec01a7":{"position":{"vector":[-16,-304]}},"2d64983c-94aa-44ff-b43d-9fb557733eec":{"position":{"vector":[-16,-368]}},"64d98a51-93c3-4de8-a3ed-b5785cf6398e":{"position":{"vector":[-16,-432]}},"ab5bfca7-330e-4958-8242-458186afac60":{"position":{"vector":[-16,16]}},"82962628-b641-4988-9f06-d74f9d2179a0":{"position":{"vector":[-16,-48]}},"439b509f-5bb7-43eb-b1fc-25a7e69af0e4":{"position":{"vector":[-16,-112]}},"f4cd2f34-aa6a-4850-99e6-8fe2bcce28a9":{"position":{"vector":[-16,-176]}},"01a65843-b640-4dfc-8c12-aa4e299e90d7":{"position":{"vector":[-16,-240]}},"21e99ecd-b955-43d0-9e22-5245dde3e7d0":{"position":{"vector":[-16,-304]}},"98271a35-af72-45c5-82ad-ce18584fa624":{"position":{"vector":[-16,-368]}},"d3846f72-a168-4966-bf10-0f62b238cd99":{"position":{"vector":[-16,-432]}},"72375373-a1b8-480b-9fef-29fe5b6b4795":{"position":{"vector":[-16,16]}},"41cad0a1-3407-402d-9109-e76049f9a021":{"position":{"vector":[-16,-48]}},"3a51d343-ef59-4ece-ae37-e03a12790241":{"position":{"vector":[-16,-112]}},"87b869cc-1c7e-41d7-81fd-486478d46c36":{"position":{"vector":[-16,-176]}},"09627a5c-6f8b-45d0-a2b4-5c509f4f8314":{"position":{"vector":[-16,-240]}},"13316a31-ac73-4f92-b099-7d79c960d314":{"position":{"vector":[-16,-304]}},"82ce3ed9-aeac-4c9a-8520-3a09b94c5294":{"position":{"vector":[-16,-368]}},"3c15b2cc-c0d7-4ba6-8145-7eff4b714978":{"position":{"vector":[-16,-432]}},"0febf71e-ec1b-469d-88bc-3d05b04bfb2a":{"position":{"vector":[-16,16]}},"7e9746f6-ae0f-416e-bc10-e62922b1af3c":{"position":{"vector":[-16,-48]}},"1d8ab8f1-02e7-48af-9f3c-db502976270a":{"position":{"vector":[-16,-112]}},"29155a24-36ca-4ed2-9d10-00a0249c1317":{"position":{"vector":[-16,-176]}},"e0568646-8580-48de-bf3e-0d2153cc9e53":{"position":{"vector":[-16,-240]}},"8d2bfa18-b7a2-4d1a-af9e-81601c298ac9":{"position":{"vector":[-16,-304]}},"a4a43182-3795-45c1-918d-ca52a80addbb":{"position":{"vector":[-16,-368]}},"abc68eb2-1a86-47b1-b6b0-238cf4505e5b":{"position":{"vector":[-16,-432]}},"ec37c85f-fff7-49bf-8d87-54e9bb655c49":{"position":{"vector":[-16,16]}},"421b395f-6977-4eb3-9b7b-815c9bba1cb1":{"position":{"vector":[-16,-48]}},"b25c0229-88b1-4d5b-800e-5118037ec44a":{"position":{"vector":[-16,-112]}},"dce843e8-6715-467e-9c5d-7e26f1b96ee5":{"position":{"vector":[-16,-176]}},"75604e19-8257-4809-b937-79f15ecaed26":{"position":{"vector":[-16,-240]}},"73ebb3b9-da7f-4d00-b705-e248708d6106":{"position":{"vector":[-16,-304]}},"52ef3883-4a5e-456e-9b70-c3a97cf694e4":{"position":{"vector":[-16,-368]}},"be861d86-114a-42c3-9758-03f96a3f3f7b":{"position":{"vector":[-16,-432]}},"7bfaa147-2d41-4280-ad72-edcab504b7d4":{"position":{"vector":[-16,16]}},"e3984f5c-64a0-4bcf-9041-4c128c62f0e2":{"position":{"vector":[-16,-48]}},"8a1c85bf-14b9-4f33-8aad-2a356ebbf959":{"position":{"vector":[-16,-112]}},"70b95ec3-f676-4253-a7bf-87f0fcdf691f":{"position":{"vector":[-16,-176]}},"1a6b181a-c0ff-48fa-81b5-f40c46e28bef":{"position":{"vector":[-16,-240]}},"ad71e7f7-3522-4e9e-b028-537491e7ccbf":{"position":{"vector":[-16,-304]}},"a0436309-9ad1-49be-8693-bc5e766e2b08":{"position":{"vector":[-16,-368]}},"7816ceb6-5107-449a-a959-bbbacfc54f6b":{"position":{"vector":[-16,-432]}},"bbe2d5a9-67a3-40ce-8ac8-71894ab87875":{"position":{"vector":[-16,16]}},"3876e515-234b-46b3-a0ca-db1f592bc46b":{"position":{"vector":[-16,-48]}},"c1b0f382-6009-4f01-a319-9a42fd94f500":{"position":{"vector":[-16,-112]}},"3ab8e2d8-4e44-4b1b-9df7-39b641dba917":{"position":{"vector":[-16,-176]}},"4fdc3d4d-1c18-4116-b94b-7c89f77d7a39":{"position":{"vector":[-16,-240]}},"f0e983c5-c8f2-428a-bbe4-7eb3bcd43ebf":{"position":{"vector":[-16,-304]}},"73cd6dfb-cf02-4673-85c3-23cef9358113":{"position":{"vector":[-16,-368]}},"f6f5b08b-3bd0-4d86-9a3b-943649170166":{"position":{"vector":[-16,-432]}},"facbbda0-c985-4b57-8bf2-4894a1d8e409":{"position":{"vector":[-16,16]}},"1093d89a-2a49-44c7-a430-fb7669af2184":{"position":{"vector":[-16,-48]}},"02c51281-cc12-4b88-b53c-8a2b88b91bf9":{"position":{"vector":[-16,-112]}},"311e8a9c-e8fd-4897-9239-566008685f0a":{"position":{"vector":[-16,-176]}},"594baef4-acba-427e-acbc-9725757da173":{"position":{"vector":[-16,-240]}},"281b227a-1cee-4bf9-a7b1-d6bf7f85a37f":{"position":{"vector":[-16,-304]}},"45242a44-ac50-43ea-b2a7-ca0948cfc3cd":{"position":{"vector":[-16,-368]}},"a1313b02-a625-471b-9f66-c250d4b07cc3":{"position":{"vector":[-16,-432]}},"12561d19-6d37-44c5-8bd5-736703fe8da8":{"position":{"vector":[-16,16]}},"6b7aafd6-c068-426b-9caf-6e55903473c9":{"position":{"vector":[-16,-48]}},"4abe7350-cd71-413e-8a39-fdbc24a29f49":{"position":{"vector":[-16,-112]}},"9497acc0-f7e0-419b-9baa-c53d9ce858aa":{"position":{"vector":[-16,-176]}},"316d09cb-622a-4431-9b20-4e9f0577098e":{"position":{"vector":[-16,-240]}},"b7a50ae7-0fca-4423-8ce3-9c929106c7b5":{"position":{"vector":[-16,-304]}},"28563d91-fe82-4140-af31-8aeedac29d05":{"position":{"vector":[-16,-368]}},"c5a1bfb2-6595-400e-a65d-a976b9516a2d":{"position":{"vector":[-16,-432]}},"eed154ed-5d96-44c6-af11-f78ca81e520b":{"position":{"vector":[-16,16]}},"9521393e-dd87-45cc-98da-b51fe26941c0":{"position":{"vector":[-16,-48]}},"15dbc052-b373-4a0c-9d1e-fa55067edc89":{"position":{"vector":[-16,-112]}},"0179feb4-0fb9-406e-a29d-80067f1e5f99":{"position":{"vector":[-16,-176]}},"ca9d9dbe-4ab3-4921-bc62-56ab13886b79":{"position":{"vector":[-16,-240]}},"2a5aea92-2d67-4b57-9304-c33e511eb7ad":{"position":{"vector":[-16,-304]}},"ddcb256a-52c0-487e-b155-39c912e6a3e3":{"position":{"vector":[-16,-368]}},"6c300d7f-65be-44af-8713-1c576c9ca5f4":{"position":{"vector":[-16,-432]}},"0a0130b1-17a4-4fb5-8a3f-25274be27a9b":{"position":{"vector":[-16,16]}},"bdd460d0-659f-49ce-b5dd-1031f69966d7":{"position":{"vector":[-16,-48]}},"e6727eb2-4c78-4628-b287-92e9ca51fd1c":{"position":{"vector":[-16,-112]}},"3f21a1f8-36cf-43b6-8b9e-0f880d4818d2":{"position":{"vector":[-16,-176]}},"2db67041-5c98-4f6f-9c1a-bbde0ffad6f9":{"position":{"vector":[-16,-240]}},"73b73b8d-fc39-4c7a-aeb0-2a5ee5eb2c95":{"position":{"vector":[-16,-304]}},"3557ede4-ebf2-4c0f-be80-a7ac3f25ce5f":{"position":{"vector":[-16,-368]}},"97e94026-7225-4d45-a0b5-e389666f18c3":{"position":{"vector":[-16,-432]}},"faacb7d6-fc86-4ba0-a443-31eaad17134e":{"position":{"vector":[-16,16]}},"08495bc9-6d8a-4f4a-8d94-e9fee1719c77":{"position":{"vector":[-16,-48]}},"a80abf81-dec0-4780-a006-205ff1ae32c3":{"position":{"vector":[-16,-112]}},"b15ea09c-dc4c-4cff-80d7-b91a0a97afa0":{"position":{"vector":[-16,-176]}},"80239727-3265-4852-90f5-7842df7460ff":{"position":{"vector":[-16,-240]}},"6827a97f-58a6-45aa-beca-d5be8b52f4cf":{"position":{"vector":[-16,-304]}},"174a7fd8-7987-4691-b87f-209c95eed658":{"position":{"vector":[-16,-368]}},"946883a9-737a-4976-b96e-0491aa04e059":{"position":{"vector":[-16,-432]}},"e3ea1909-3aff-47ef-80c3-097d71844010":{"position":{"vector":[-16,16]}},"5c5fcd42-c300-48ab-b15b-d9c358ade8a0":{"position":{"vector":[-16,-48]}},"0cfe86e7-71a2-4a9a-83ab-3ee47da3cec1":{"position":{"vector":[-16,-112]}},"cd82e06e-314e-443b-a47c-d9ad6edd8de9":{"position":{"vector":[-16,-176]}},"2546c1e6-a451-4924-a539-aad5a80b5172":{"position":{"vector":[-16,-240]}},"3c8e60dd-547b-478c-bd5d-9987ed3bcbd5":{"position":{"vector":[-16,-304]}},"26b920c3-14fe-47c9-b8c4-156a0b98357a":{"position":{"vector":[-16,-368]}},"cb1afdaa-2524-4c98-b58f-99f35137f2d3":{"position":{"vector":[-16,-432]}},"386a3587-0fca-4f99-9233-201b58f427cb":{"position":{"vector":[-16,16]}},"1da7352a-f860-4954-a4b0-56e82cd91d6e":{"position":{"vector":[-16,-48]}},"1554d920-8b25-4a00-970c-c9840deac82b":{"position":{"vector":[-16,-112]}},"6e59849a-81bb-4465-a0be-064ceea6a528":{"position":{"vector":[-16,-176]}},"4a95e0a0-b51d-4e16-8977-1865299f9cd2":{"position":{"vector":[-16,-240]}},"1a100377-84b3-4e46-9e2b-e7f1d6d0f6ad":{"position":{"vector":[-16,-304]}},"77da0956-a76d-4835-bc8e-f56ba8c1e477":{"position":{"vector":[-16,-368]}},"e6075d10-24ca-494e-a60f-773d6d4c8da6":{"position":{"vector":[-16,-432]}},"977ba976-46d6-412a-9d69-7c871f5bfc5e":{"position":{"vector":[-16,16]}},"15679dc6-4138-47b4-b210-219cdfd3ae5d":{"position":{"vector":[-16,-48]}},"cf2ac8c7-a97d-4b47-8623-88ca808fd918":{"position":{"vector":[-16,-112]}},"323e43ee-fefa-4ef5-acf9-895b21646a3f":{"position":{"vector":[-16,-176]}},"f9475377-c881-45f3-8fc7-8370bf3cca08":{"position":{"vector":[-16,-240]}},"f23b8039-5c4e-4966-9a0f-d797cdd95b02":{"position":{"vector":[-16,-304]}},"f5fbc7ce-1b29-4abe-a3d7-86b088421512":{"position":{"vector":[-16,-368]}},"ecc4fadc-bdd0-4393-a8b0-fb97f9d255dc":{"position":{"vector":[-16,-432]}},"7bd6299f-be67-4e83-87e6-90e4544b661f":{"position":{"vector":[-16,16]}},"ae5340d0-a1ff-485c-b95d-5ae917701f61":{"position":{"vector":[-16,-48]}},"1eed398e-fe8a-49d8-b49f-c0d53237ecc0":{"position":{"vector":[-16,-112]}},"cf0f7a4e-8ab4-400f-940e-acc66ae737ea":{"position":{"vector":[-16,-176]}},"11233b4d-bef6-4ae3-9d41-95c7423b57c4":{"position":{"vector":[-16,-240]}},"8d325594-3115-49f6-bd6e-976441e14aee":{"position":{"vector":[-16,-304]}},"b5b6996e-b890-41d4-ba8b-b562b1d90595":{"position":{"vector":[-16,-368]}},"e69e6685-0fdf-47f5-a928-7357ab9442a8":{"position":{"vector":[-16,-432]}},"b21b55f6-2982-4e54-a38c-682d9843c36a":{"position":{"vector":[-16,16]}},"1c47484f-1fe2-42c8-8554-8d69e2a823fd":{"position":{"vector":[-16,-48]}},"063460d1-17ef-4c37-bfbe-c087f92868b4":{"position":{"vector":[-16,-112]}},"f1f8eb5b-0da8-4487-bdcc-11a15f9d3b57":{"position":{"vector":[-16,-176]}},"b0255092-91f4-4417-88e6-8f84259f2dcd":{"position":{"vector":[-16,-240]}},"08505e37-ca7e-4d62-9427-c63ecf8699d7":{"position":{"vector":[-16,-304]}},"28ff5302-74f4-4c59-9f06-db1ea3cb0098":{"position":{"vector":[-16,-368]}},"43b6a516-107b-409d-be23-e15cb1a49df4":{"position":{"vector":[-16,-432]}},"553124b4-24de-40d4-b34b-c70b528242b1":{"position":{"vector":[-16,16]}},"bac998cf-9c74-40db-a36f-c489d3a28ea2":{"position":{"vector":[-16,-48]}},"bc9f7931-d0c9-4777-b7ff-eefd10a7d596":{"position":{"vector":[-16,-112]}},"c2737827-ac70-4a39-a8d4-3e86037554fc":{"position":{"vector":[-16,-176]}},"1b79c1c5-ad82-4686-97de-1507e05010bf":{"position":{"vector":[-16,-240]}},"cbf2f5dd-3658-4fbd-9b92-067025f50945":{"position":{"vector":[-16,-304]}},"4db408e7-c6f4-44bf-b605-448b3ec7b150":{"position":{"vector":[-16,-368]}},"ae9b9917-104b-4672-83cd-8a5556165adb":{"position":{"vector":[-16,-432]}},"96380c9e-a322-45e7-a052-7105a7572294":{"position":{"vector":[-16,16]}},"4f8971ef-71cd-4d17-92bb-a25185d842a3":{"position":{"vector":[-16,-48]}},"016e95b1-1221-4508-b42e-a5f1080a7fa3":{"position":{"vector":[-16,-112]}},"3f3b9ce3-91e1-4be4-b593-9733d1f19ad3":{"position":{"vector":[-16,-176]}},"8f7cbeee-afac-4ea9-bf5d-18cf232cfaf3":{"position":{"vector":[-16,-240]}},"23625200-84f8-4053-bf19-86669b93508d":{"position":{"vector":[-16,-304]}},"75a54abd-80c1-42e1-a19e-3263fb736456":{"position":{"vector":[-16,-368]}},"0f3a8997-8dd4-4ffc-bc0c-479ebfc1c95a":{"position":{"vector":[-16,-432]}},"097eb736-0c9b-4593-bbd1-bca9c19c034a":{"position":{"vector":[-16,16]}},"9e1e266b-d350-4977-bc23-2666b5aad569":{"position":{"vector":[-16,-48]}},"ec57067b-1ae1-473e-b444-e6d705e2a8d4":{"position":{"vector":[-16,-112]}},"47c9255c-ee27-4624-8af1-13029eb2d504":{"position":{"vector":[-16,-176]}},"2da69889-b800-45ad-afe6-65c4fca8caa5":{"position":{"vector":[-16,-240]}},"786eb5b4-c9f1-4a9a-a159-951d3cd3a02b":{"position":{"vector":[-16,-304]}},"29995a52-5ad6-4a96-8519-4b70b5bb55da":{"position":{"vector":[-16,-368]}},"b5144cc8-634f-4fd6-9c78-64bd3e5280f4":{"position":{"vector":[-16,-432]}},"50237772-eef5-42a9-8804-aee579a244c9":{"position":{"vector":[-16,16]}},"ecb6a7d9-c58b-49ef-9409-39d6266b9904":{"position":{"vector":[-16,-48]}},"1dfc0920-9b9f-47ea-8089-27f28690bc04":{"position":{"vector":[-16,-112]}},"d5aed7c4-9158-4d95-bb5c-0d1b0e5e42c3":{"position":{"vector":[-16,-176]}},"db536321-ab2e-4d8a-8feb-19360c31168a":{"position":{"vector":[-16,-240]}},"a3a70310-46c2-4c87-b78e-161548346d13":{"position":{"vector":[-16,-304]}},"69ad74fc-5872-475e-8379-bf10f8d14182":{"position":{"vector":[-16,-368]}},"6f676811-1b6e-4d3f-8e2d-213b65edd2e1":{"position":{"vector":[-16,-432]}},"ac635ff5-81a3-4e1d-91e3-3733be5d24af":{"position":{"vector":[-16,16]}},"2b46c588-fdb3-4fd0-993e-ef210a312ad2":{"position":{"vector":[-16,-48]}},"cd29498d-c8f5-4640-9185-94869620131f":{"position":{"vector":[-16,-112]}},"71062b1f-ebd2-4faf-b6e5-2e026c69490e":{"position":{"vector":[-16,-176]}},"80e3e972-3e9a-460f-b041-52b826af59d1":{"position":{"vector":[-16,-240]}},"c8305873-1411-4819-a3b3-b482087d73e2":{"position":{"vector":[-16,-304]}},"cbc2d1c5-11e7-4e5e-9f02-1e47bd80b0a2":{"position":{"vector":[-16,-368]}},"8457254d-d222-405e-8b83-d335d6740359":{"position":{"vector":[-16,-432]}},"81cb6eac-5ba5-470e-9aee-b6049bf1bb6a":{"position":{"vector":[-16,16]}},"ba71d64a-2889-4f35-b8cd-78c77c1dac4a":{"position":{"vector":[-16,-48]}},"c24c11fd-d93a-4fa2-80e8-dadefe2ac3b7":{"position":{"vector":[-16,-112]}},"0b4f2629-4169-475b-b9b0-6d5c7f37b889":{"position":{"vector":[-16,-176]}},"0fff1192-ac56-42b0-93da-9ef03de398d7":{"position":{"vector":[-16,-240]}},"c72e72d5-cd48-4e20-98d5-3f2a22e3e61b":{"position":{"vector":[-16,-304]}},"8b946392-1b48-4819-8dae-28c337f291c6":{"position":{"vector":[-16,-368]}},"babda3a7-f869-48ff-b930-04a19fda1ad4":{"position":{"vector":[-16,-432]}},"a9539256-edb1-4f43-89a4-6f7578a1ab5c":{"position":{"vector":[-16,16]}},"1ee20dab-4452-4539-be1c-87c50073f98c":{"position":{"vector":[-16,-48]}},"1beab273-afe9-45b8-a475-b8fa6a194f85":{"position":{"vector":[-16,-112]}},"4218f57e-7f8e-403d-a6e2-048a44b1a866":{"position":{"vector":[-16,-176]}},"2ca70a28-a674-4bb3-bb44-c28b510208a5":{"position":{"vector":[-16,-240]}},"8e1b9087-de9a-4f9d-a6f1-fb127b5d1894":{"position":{"vector":[-16,-304]}},"10968327-f8bf-411d-9ca8-b65f120de381":{"position":{"vector":[-16,-368]}},"9e4371bd-2377-471b-bbaa-817a8cf0125a":{"position":{"vector":[-16,-432]}},"6eb6f52d-adb7-4ac6-aece-7fb2f7e3d356":{"position":{"vector":[-16,16]}},"466e02c4-3a0d-442b-b292-b0074fa352c8":{"position":{"vector":[-16,-48]}},"0e951fa1-f880-45b2-8b36-b26ab16cd98c":{"position":{"vector":[-16,-112]}},"35314934-f891-441d-9113-9300b7c7cc9b":{"position":{"vector":[-16,-176]}},"f1e18f4a-5f53-470f-be94-63468384d743":{"position":{"vector":[-16,-240]}},"55de3224-a375-4e68-a843-5dc11b6cee10":{"position":{"vector":[-16,-304]}},"67b22b00-d831-4f09-80db-c0ff1c64d8e3":{"position":{"vector":[-16,-368]}},"129422df-3465-4e24-90b5-4a54b2f5bf61":{"position":{"vector":[-16,-432]}},"0be44a15-8301-4e19-bf26-826c0ff4043b":{"position":{"vector":[-16,16]}},"6d64b2a0-acb5-4fad-bd34-0a081ff12bff":{"position":{"vector":[-16,-48]}},"9cdf57d5-b72d-4ec9-84ab-e4209b5233d3":{"position":{"vector":[-16,-112]}},"ada0f268-c7eb-421f-a34c-8ba937d1aa8d":{"position":{"vector":[-16,-176]}},"89928d4d-2a3a-4d5e-81e2-8b9e13070a83":{"position":{"vector":[-16,-240]}},"843b5268-6e8d-4b47-b4a0-67021bafe6fb":{"position":{"vector":[-16,-304]}},"5f2885b3-102f-41c4-96a5-195fd1f9ee82":{"position":{"vector":[-16,-368]}},"a472211b-5366-41df-b877-3e8031097385":{"position":{"vector":[-16,-432]}},"eaaefe32-0873-4ec5-9769-7571ed0507d9":{"position":{"vector":[-16,16]}},"eab4f7ad-87ae-4bfd-818a-08f039b69aa8":{"position":{"vector":[-16,-48]}},"b3842e09-8747-4461-b23d-fb6935c2831a":{"position":{"vector":[-16,-112]}},"77dc64fd-0cc8-436e-97d6-168cdb053d11":{"position":{"vector":[-16,-176]}},"53bd0209-1c75-4067-a04e-b9d150e03fb0":{"position":{"vector":[-16,-240]}},"f167d0cc-a2ac-4058-b79a-0aac0ecaffb0":{"position":{"vector":[-16,-304]}},"ad4fba6b-7303-4a95-975c-eae758a375aa":{"position":{"vector":[-16,-368]}},"3cc86f58-accd-4c1f-adfe-644f13076281":{"position":{"vector":[-16,-432]}},"5ea43327-ee63-420c-a071-db06ad9cd8ca":{"position":{"vector":[-16,16]}},"667ac9aa-2dcc-48ef-bbba-89dfdeacf35b":{"position":{"vector":[-16,-48]}},"92d3f09d-2c4f-4180-8838-6f2560d79666":{"position":{"vector":[-16,-112]}},"26379829-6179-4fcf-b795-ad57ea724ac1":{"position":{"vector":[-16,-176]}},"7751926b-ea94-46f4-b41e-3888d0d4bd42":{"position":{"vector":[-16,-240]}},"c9a9e32f-c86c-471f-aa60-7ed5541a3467":{"position":{"vector":[-16,-304]}},"156bb85e-04aa-4773-9215-1f7afcdbc11f":{"position":{"vector":[-16,-368]}},"faf2c0c4-e695-4516-8b46-420b4bce5eb1":{"position":{"vector":[-16,-432]}},"8ef8110e-edda-46f8-b148-90f1724e0971":{"position":{"vector":[-16,16]}},"d4956e99-bdf8-472c-b46a-69979174f9a6":{"position":{"vector":[-16,-48]}},"aae9eab7-8fe6-4a94-a3d9-f77d00d84466":{"position":{"vector":[-16,-112]}},"e6eb93c4-fd24-4586-ba02-eae5b4e16a26":{"position":{"vector":[-16,-176]}},"64945a9f-c4f1-4c69-8ac2-c8f011efde9c":{"position":{"vector":[-16,-240]}},"7d849095-3532-40f0-a798-a330bf34c6c0":{"position":{"vector":[-16,-304]}},"759a3042-06f2-4e38-93cd-763eb1f2d925":{"position":{"vector":[-16,-368]}},"d41392c9-2a14-4cc0-9e3f-9fd7ff544293":{"position":{"vector":[-16,-432]}},"c409d2b6-6956-420d-b646-deb7bfa5bb5b":{"position":{"vector":[-16,16]}},"41dd6fc3-eeac-45ce-b30f-7e9240fc10d2":{"position":{"vector":[-16,-48]}},"6113a43d-1e7d-45d5-8eac-601167947284":{"position":{"vector":[-16,-112]}},"abe42896-a9c3-400f-8a17-fc2d50279194":{"position":{"vector":[-16,-176]}},"2d6cc664-0db2-4f28-b0ff-3d44e720ff4f":{"position":{"vector":[-16,-240]}},"e8f2c12b-f001-4319-af59-e9a02db12be9":{"position":{"vector":[-16,-304]}},"e1b90130-06c0-4d01-a50d-90941f51d61b":{"position":{"vector":[-16,-368]}},"99749713-539b-48e5-9ddd-00bf177a3b74":{"position":{"vector":[-16,-432]}},"151585b7-d011-4b12-ad89-67a43fded2ec":{"position":{"vector":[-16,16]}},"a776b4a3-8fad-43d9-b018-507be16cfbf3":{"position":{"vector":[-16,-48]}},"de6c1459-f274-462d-a73d-0bab041809cb":{"position":{"vector":[-16,-112]}},"3a5cd7ca-1f61-4a2b-b9d5-13554a0205e7":{"position":{"vector":[-16,-176]}},"34d412d3-70aa-4dc2-bb92-450cb0f4c3b1":{"position":{"vector":[-16,-240]}},"32ae014b-2560-424c-af67-fea8f46be604":{"position":{"vector":[-16,-304]}},"2f2e4c8a-ab3a-4502-a931-3383797cdd65":{"position":{"vector":[-16,-368]}},"6fece21b-28bf-446c-8fb6-24e31327f4ed":{"position":{"vector":[-16,-432]}},"45125d4a-ed55-41a1-8517-1ce98a5a689e":{"position":{"vector":[-16,16]}},"370b208f-519a-4550-998e-689ed8beca23":{"position":{"vector":[-16,-48]}},"b44a6b17-4381-479e-9b67-23752546b854":{"position":{"vector":[-16,-112]}},"feea301c-0cb5-456e-87df-9b4973bd9262":{"position":{"vector":[-16,-176]}},"f4cdff3a-1012-4525-a1bf-2075c9c41634":{"position":{"vector":[-16,-240]}},"a1aa0a6b-4256-479d-a456-87b0eb6deab7":{"position":{"vector":[-16,-304]}},"1436580e-eb8a-43c0-a4a5-070d74601b82":{"position":{"vector":[-16,-368]}},"39c14cae-28d6-4bf0-b28b-34c4a2f9b82e":{"position":{"vector":[-16,-432]}},"4455d4ae-851e-4313-9f22-155670cc2298":{"position":{"vector":[-16,16]}},"1deff8a3-99df-4176-a637-3f4ae8e18767":{"position":{"vector":[-16,-48]}},"ff2b6150-3dc7-4173-ad6a-3f20d41029ed":{"position":{"vector":[-16,-112]}},"18ac7aa5-25e9-4cc9-a736-46971e9f6b66":{"position":{"vector":[-16,-176]}},"285b16cc-be2c-44fc-8729-38020cc2746c":{"position":{"vector":[-16,-240]}},"21fb800c-7a7e-40cc-9533-c9cb29013f06":{"position":{"vector":[-16,-304]}},"a5c86658-84ed-4d8b-9177-337351a24652":{"position":{"vector":[-16,-368]}},"fac3d1ef-5662-4647-bd3d-ecf62931a8c3":{"position":{"vector":[-16,-432]}},"872b8ddf-7351-4840-a962-5746d403b3a8":{"position":{"vector":[-16,16]}},"96bb98d7-8897-4075-81dc-07a4ee46c8af":{"position":{"vector":[-16,-48]}},"102b293a-54b9-4fa8-a1cd-ce99b6c05169":{"position":{"vector":[-16,-112]}},"a6c6a8a7-6cfb-4fde-9ada-b5f6bf2ab416":{"position":{"vector":[-16,-176]}},"31816a1c-135a-47a2-9831-c635ced677f7":{"position":{"vector":[-16,-240]}},"5957ebb4-88cf-4af7-a2b0-5a5d3d901417":{"position":{"vector":[-16,-304]}},"ec178c15-7113-4591-81f7-a05055cde5a2":{"position":{"vector":[-16,-368]}},"da660099-485e-44b8-94d4-54453b2185bf":{"position":{"vector":[-16,-432]}},"f2f870b9-716b-4407-88d7-2407dc648713":{"position":{"vector":[-16,16]}},"8b1cd9e0-90a4-40a1-862d-e55109d66a33":{"position":{"vector":[-16,-48]}},"de54bd6d-62e9-46e7-842b-9e40abcbef9f":{"position":{"vector":[-16,-112]}},"f8e87a86-ada5-49df-b265-47c370945f26":{"position":{"vector":[-16,-176]}},"2c491c02-2752-41f6-9087-5369114a9566":{"position":{"vector":[-16,-240]}},"e882addf-5b4a-45af-8249-948969565b01":{"position":{"vector":[-16,-304]}},"bc990025-a934-4a18-ac99-1a35fc5ba1e1":{"position":{"vector":[-16,-368]}},"40792898-0bd7-4904-b5c5-76ead6ab2e64":{"position":{"vector":[-16,-432]}},"736b69ec-5bb9-4786-a7f7-5a1571d963cf":{"position":{"vector":[-16,16]}},"8706f8d3-d8bd-4d9e-9831-f1b87f90b639":{"position":{"vector":[-16,-48]}},"502da123-2838-4269-bda1-78bc04e371a9":{"position":{"vector":[-16,-112]}},"d1a990b8-8e05-456a-99d0-98ecf7667ee2":{"position":{"vector":[-16,-176]}},"31c0a496-ce6d-4566-9cee-a854d22d2898":{"position":{"vector":[-16,-240]}},"90e6a311-7719-4f30-97ed-1bb4d8a8a412":{"position":{"vector":[-16,-304]}},"22428678-b8c8-4079-950c-c1ae8b7facbd":{"position":{"vector":[-16,-368]}},"1bfb4052-37d4-4def-a491-646eb2df3815":{"position":{"vector":[-16,-432]}},"9911601f-14d9-4a8d-b3d9-63cc91a71df4":{"position":{"vector":[-16,16]}},"ba62bef3-09c0-45a1-b52f-438389782181":{"position":{"vector":[-16,-48]}},"c10e2ad2-43e2-497c-9baa-62e5e02c0e5e":{"position":{"vector":[-16,-112]}},"f948d312-0c4d-4a07-afee-8cc3137e4ea8":{"position":{"vector":[-16,-176]}},"90c28a60-fa41-422f-be3e-51e3e4fe5d9f":{"position":{"vector":[-16,-240]}},"703b41c7-9f0c-4fc4-adf6-9bf76a122053":{"position":{"vector":[-16,-304]}},"2712b6ed-a3d3-460e-af55-34b782d45a0a":{"position":{"vector":[-16,-368]}},"681fe777-af2c-41aa-a42b-a278d23be1f7":{"position":{"vector":[-16,-432]}},"d5dcb4ac-c248-4715-a802-76a8ae26d413":{"position":{"vector":[-16,16]}},"0c3c9c25-c55d-42eb-a0a8-7df78fbf62df":{"position":{"vector":[-16,-48]}},"c4a8cb3e-33c6-477f-a307-852d57970445":{"position":{"vector":[-16,-112]}},"3f56f8e9-93fd-4336-9c4e-601623345241":{"position":{"vector":[-16,-176]}},"a90e1358-dd5f-42cf-afeb-d315e4e9b80d":{"position":{"vector":[-16,-240]}},"6a458455-40aa-47fd-a9ae-7821651f2113":{"position":{"vector":[-16,-304]}},"e0f4bba8-f407-48c8-83ab-0472240e0d0f":{"position":{"vector":[-16,-368]}},"52b6dcc8-9f9c-422e-8d64-424c78f736d6":{"position":{"vector":[-16,-432]}},"d94bda12-457f-4827-9aac-6406b9bda32f":{"position":{"vector":[-16,16]}},"fc0ddd88-72c6-48f4-b393-e9044b1c00ac":{"position":{"vector":[-16,-48]}},"285a0f48-e0bb-482e-a54d-b0f6fe5fad3e":{"position":{"vector":[-16,-112]}},"cf8070c9-6656-4b26-8eb4-a9ab7cc08f41":{"position":{"vector":[-16,-176]}},"f6a35d50-e512-4299-b13f-86b3f056a328":{"position":{"vector":[-16,-240]}},"684e8a9d-8e6d-4259-a74a-ff09158ee877":{"position":{"vector":[-16,-304]}},"16b9e2bd-b096-48f5-b564-813d82d51940":{"position":{"vector":[-16,-368]}},"ce9ad592-20ed-4dbc-a4fc-bdf723fb4547":{"position":{"vector":[-16,-432]}},"1dcafa76-b5ea-4e97-97e3-bf6f57fcc346":{"position":{"vector":[-16,16]}},"9fca1258-f118-48ca-959c-e2b2267e3099":{"position":{"vector":[-16,-48]}},"ba9700fb-284e-4f65-83e0-9658cf6d68d8":{"position":{"vector":[-16,-112]}},"5d3fbbe0-ebb0-4eb8-9437-1b1bf4d9cc8a":{"position":{"vector":[-16,-176]}},"c42fa075-14ef-4172-892c-3f4f1cbbafef":{"position":{"vector":[-16,-240]}},"706c3535-cb4d-45d1-b5c3-1a99ededc5b4":{"position":{"vector":[-16,-304]}},"54d765ce-9c17-4635-b4be-ace890cfefb1":{"position":{"vector":[-16,-368]}},"feef3ae3-d7c0-4add-a7fc-90f316b470ac":{"position":{"vector":[-16,-432]}},"1990ab69-5389-49f0-a214-7f9dec7c63f0":{"position":{"vector":[-16,16]}},"6be35eaa-f549-41c0-baae-1fa2db107af1":{"position":{"vector":[-16,-48]}},"d17f0bb9-c8f9-48b2-81e6-c385cdb2ecab":{"position":{"vector":[-16,-112]}},"a6dad965-d095-4fa1-8b79-c2802a4865c9":{"position":{"vector":[-16,-176]}},"fc92183e-1417-4c30-b3ce-131d513ac07e":{"position":{"vector":[-16,-240]}},"e4e41fa5-3900-4433-a639-c111b3eb85b6":{"position":{"vector":[-16,-304]}},"c63ec556-c528-4952-aa54-ffe55b104f4f":{"position":{"vector":[-16,-368]}},"e7e9c881-e61d-4741-aafa-35e7e4b75859":{"position":{"vector":[-16,-432]}},"3f679734-e6b3-4978-a859-73a9c0c04ac0":{"position":{"vector":[-16,16]}},"9412010c-4fba-4467-9c2b-da9b9acfa0fa":{"position":{"vector":[-16,-48]}},"d6d42ef4-cd6a-4c6a-b559-a9ba2500af41":{"position":{"vector":[-16,-112]}},"4545bed5-d66a-4b94-9b75-83de188f5651":{"position":{"vector":[-16,-176]}},"cd4e3169-89ac-479b-b953-dfd90c2e135c":{"position":{"vector":[-16,-240]}},"2a430e1a-1142-40a5-8c56-140d0aee2b03":{"position":{"vector":[-16,-304]}},"e8c28507-a4f3-4ca8-87ee-6fc15c061425":{"position":{"vector":[-16,-368]}},"4519a9af-eeea-4201-9f15-08c9685462ca":{"position":{"vector":[-16,-432]}},"b10bd10b-d479-4b92-86a8-fd29e9119598":{"position":{"vector":[-16,16]}},"7017b9f8-64a0-4a33-a215-f33c301fba41":{"position":{"vector":[-16,-48]}},"a762b1c3-c0a8-470d-a361-b1d98cc30451":{"position":{"vector":[-16,-112]}},"c912b715-ea69-499a-84e9-3bad2eb99942":{"position":{"vector":[-16,-176]}},"f08272ef-e6cd-48bc-8d9c-def8d45c4e33":{"position":{"vector":[-16,-240]}},"96a0a30f-c405-4ec6-a536-4b2e756bb88f":{"position":{"vector":[-16,-304]}},"d2cf7fa8-3aa4-494c-8e8d-3501f142013f":{"position":{"vector":[-16,-368]}},"cc5b0712-28c5-4183-acb6-bf82b112e392":{"position":{"vector":[-16,-432]}},"2561d049-37ee-4492-a156-e0d6e3f56b1a":{"position":{"vector":[-16,16]}},"b97787ca-94d7-4e94-8a28-de3fd824a976":{"position":{"vector":[-16,-48]}},"6c9a8b41-5808-423c-bb1b-c97dedf91479":{"position":{"vector":[-16,-112]}},"7beedc65-bc5f-4a6e-b9f6-b9c650712874":{"position":{"vector":[-16,-176]}},"23a543c2-4a0f-4729-8960-dc924e5fb238":{"position":{"vector":[-16,-240]}},"d6ce90f1-38f8-42f4-9082-748005a27dbe":{"position":{"vector":[-16,-304]}},"a9541abe-42bb-47ee-94ee-107fd2fd7913":{"position":{"vector":[-16,-368]}},"1a39cdbe-733d-460d-9f60-c43f853bb8ec":{"position":{"vector":[-16,-432]}},"0d1cf20c-a493-49da-8632-02beac307a47":{"position":{"vector":[-16,16]}},"e47f9cee-2ef9-47d2-b023-679bfabd3ea3":{"position":{"vector":[-16,-48]}},"05ab4f7b-722b-4ab7-91fd-d0935e524e19":{"position":{"vector":[-16,-112]}},"d7bff18d-1244-47ef-950f-d9d651845a9e":{"position":{"vector":[-16,-176]}},"2da878d0-0b26-4c1c-91b0-8ce396edc7b3":{"position":{"vector":[-16,-240]}},"63378679-024d-454f-954e-2003597ea18d":{"position":{"vector":[-16,-304]}},"a0584d88-aa70-4a34-8523-031d973d8774":{"position":{"vector":[-16,-368]}},"4419b40f-11d0-460c-af31-c606223736e4":{"position":{"vector":[-16,-432]}},"f19012d4-4f73-453d-962b-b20d6ae89fba":{"position":{"vector":[-16,16]}},"b2974678-e25d-4a63-917f-9c5b4f9441c3":{"position":{"vector":[-16,-48]}},"4061a80e-0226-43cf-9eaf-e8743a67facb":{"position":{"vector":[-16,-112]}},"56d4ec06-3c66-4c22-bc72-51869fda6e7a":{"position":{"vector":[-16,-176]}},"bf76de40-8373-47f3-a241-a6324d3dc8c5":{"position":{"vector":[-16,-240]}},"c29f6794-ffdd-4ff2-afed-3a557ae90108":{"position":{"vector":[-16,-304]}},"3377d866-8e93-46d0-9da4-0877160d5563":{"position":{"vector":[-16,-368]}},"7b7f1a67-a103-4301-bb9e-a4201f9403a3":{"position":{"vector":[-16,-432]}},"47d0b2b0-3507-4f54-a087-aa7b46a6c9ba":{"position":{"vector":[-16,16]}},"ad18ffaa-37ca-4ef8-b504-bd0786c419e4":{"position":{"vector":[-16,-48]}},"6c82b2f1-c003-44be-aba5-227cff499078":{"position":{"vector":[-16,-112]}},"f658ad0e-b996-4f93-bff2-d1b10ec0dcdf":{"position":{"vector":[-16,-176]}},"8f67201b-eb03-47fd-9e14-349c62c88ad6":{"position":{"vector":[-16,-240]}},"94d62dbf-32ce-4843-8cf5-70c016cf8f8e":{"position":{"vector":[-16,-304]}},"400ce578-85a5-4f53-989b-1ebb9cb170d0":{"position":{"vector":[-16,-368]}},"d84ece98-a08a-4792-b5ef-95ee58f72214":{"position":{"vector":[-16,-432]}},"b73ec1f0-c3c2-4af3-bcb5-b3c2bdfb0b47":{"position":{"vector":[-16,16]}},"732ea968-e234-4753-82cd-40e4978b4d7a":{"position":{"vector":[-16,-48]}},"c66653b9-9f75-418d-944e-3cd4655aabb6":{"position":{"vector":[-16,-112]}},"bd8c785b-ffd1-462f-84fe-9acacc4edc54":{"position":{"vector":[-16,-176]}},"7fa50ad6-fe4a-4c26-b808-850d71077cce":{"position":{"vector":[-16,-240]}},"71156ac7-a438-4c6e-a51a-0c9527546656":{"position":{"vector":[-16,-304]}},"92f35d01-a92d-47d1-af00-74dc7fcedafb":{"position":{"vector":[-16,-368]}},"97b3291e-160b-4bda-95db-14ee535e6a37":{"position":{"vector":[-16,-432]}},"f1b6ab4d-8b26-4ce0-b553-82891a7a4fd2":{"position":{"vector":[-16,16]}},"7a18bd61-366c-40ef-870d-e3627e0a863d":{"position":{"vector":[-16,-48]}},"7b79ea0d-cb91-40ae-9ab5-197fc398a304":{"position":{"vector":[-16,-112]}},"1faed1e3-8240-49f9-b549-b3032fe30182":{"position":{"vector":[-16,-176]}},"b256ad15-debf-4170-90be-3877a5e0f605":{"position":{"vector":[-16,-240]}},"7d92d28a-4427-404c-ad1b-59e8d5b6146c":{"position":{"vector":[-16,-304]}},"381fe7ea-a82d-43a1-a432-0267df76cda6":{"position":{"vector":[-16,-368]}},"56be49ad-8709-4dcc-aa58-a313540aea2e":{"position":{"vector":[-16,-432]}},"41b19f50-c0a7-4c8d-99f2-d8da66f0bb5e":{"position":{"vector":[-16,16]}},"5e5376ca-e4bc-4742-83e3-55f94878d392":{"position":{"vector":[-16,-48]}},"4fa30af4-b41c-4513-acc7-4c4f98c7e7aa":{"position":{"vector":[-16,-112]}},"daaa9775-63c2-4826-9a82-6156fcf61a96":{"position":{"vector":[-16,-176]}},"e32b00ad-ec24-4631-bd2f-157b11158e22":{"position":{"vector":[-16,-240]}},"efdaa11f-2542-4c81-a19d-18bd063501c8":{"position":{"vector":[-16,-304]}},"c9d57667-4354-43e6-983b-21cd77835319":{"position":{"vector":[-16,-368]}},"9ad1fcdb-f6c4-430a-a72a-a8d3557328aa":{"position":{"vector":[-16,-432]}},"ef79993c-641f-4f70-9d89-781e2a2a3ad9":{"position":{"vector":[-16,16]}},"02e05c0c-103e-4284-804f-ad56c618e061":{"position":{"vector":[-16,-48]}},"b6323ae9-4303-4632-a45a-8ddfcbeb6049":{"position":{"vector":[-16,-112]}},"de11cb69-91f0-460d-b4f1-16e8fac80db1":{"position":{"vector":[-16,-176]}},"2053ddab-2ae3-4c38-93cf-e8a0f3910a93":{"position":{"vector":[-16,-240]}},"ad75cd13-0dee-49c3-9adc-2f60c87f1a5c":{"position":{"vector":[-16,-304]}},"23afe3b2-442b-49dc-8515-949ce8250756":{"position":{"vector":[-16,-368]}},"207fc98b-97b9-4592-b875-fdb9e89cd840":{"position":{"vector":[-16,-432]}},"809c8b78-c4b9-4ba4-967b-89c1897d8c71":{"position":{"vector":[-16,16]}},"0e06da16-2b80-4ee7-a79e-109f36c0103f":{"position":{"vector":[-16,-48]}},"9ff76080-b5ad-4e83-9ee0-b25b0fb8c4c8":{"position":{"vector":[-16,-112]}},"de662d1f-50d2-49ea-9f0c-9ff33e9fb8a9":{"position":{"vector":[-16,-176]}},"cb12c818-89e3-47fc-bd02-593d64b6d394":{"position":{"vector":[-16,-240]}},"cce34798-5f20-45f5-9054-e74a97196460":{"position":{"vector":[-16,-304]}},"51acda7b-456b-4f0d-855b-ea23dbbfbf12":{"position":{"vector":[-16,-368]}},"ea089963-970a-4fb4-99d3-d3146117f3b6":{"position":{"vector":[-16,-432]}},"8e309c79-22d1-4b60-9d8a-85fa7ee7ae81":{"position":{"vector":[-16,16]}},"0638e3ee-6a24-482a-866a-1872cb11eef9":{"position":{"vector":[-16,-48]}},"f29d3c35-0925-4eca-8c0a-cfd577d0db54":{"position":{"vector":[-16,-112]}},"c5903227-b1d2-4638-9924-d9a5abb05910":{"position":{"vector":[-16,-176]}},"d5ee1aeb-416c-4656-8b20-b50c388cb0de":{"position":{"vector":[-16,-240]}},"070b66d7-1c6c-457a-aa23-03a2dca952e1":{"position":{"vector":[-16,-304]}},"d4a788b5-80e8-4db5-ba82-7fa1e16a9dff":{"position":{"vector":[-16,-368]}},"56f1f8d9-6c03-4df8-af05-5045dd6e0a01":{"position":{"vector":[-16,-432]}},"45314255-7994-4890-98aa-ac2a6e965605":{"position":{"vector":[-16,16]}},"107f2f97-1ee0-4166-8fa6-9a53b634ed56":{"position":{"vector":[-16,-48]}},"adaf2704-29c6-4c0b-973d-442f733390e0":{"position":{"vector":[-16,-112]}},"7a04156b-55cf-4899-95c9-a0262dc6f64e":{"position":{"vector":[-16,-176]}},"3d4aa358-5ca5-4689-89d2-5f4e7e475295":{"position":{"vector":[-16,-240]}},"64ec9df4-38c7-4f83-88aa-2ec3099b8c2d":{"position":{"vector":[-16,-304]}},"c9f326f5-a3ba-48b2-a18f-4fc71156906c":{"position":{"vector":[-16,-368]}},"9359a915-d5cd-44cf-8c04-9d15f29c0074":{"position":{"vector":[-16,-432]}},"936b1ffd-d99f-45c4-84ab-c9922c7d93be":{"position":{"vector":[-16,16]}},"6975cf94-3f2a-4dfb-9943-19da64c48082":{"position":{"vector":[-16,-48]}},"99376b3c-5f7d-421e-a503-fbf198b904fc":{"position":{"vector":[-16,-112]}},"f6701a8d-130f-4f58-a3c2-718cc7679817":{"position":{"vector":[-16,-176]}},"c1453ea7-16e0-4e48-81e5-eee3c8eaf66d":{"position":{"vector":[-16,-240]}},"2869e714-14d8-4c18-b211-c595f17112bd":{"position":{"vector":[-16,-304]}},"48482667-ebf6-4290-8358-0ca5ab75b8ee":{"position":{"vector":[-16,-368]}},"3bcac191-6940-4286-ac3f-7a45c97aadaf":{"position":{"vector":[-16,-432]}},"804617da-e824-40a5-8c8d-87427b742523":{"position":{"vector":[-16,16]}},"38689130-4755-43d2-a905-d46685bc0ef0":{"position":{"vector":[-16,-48]}},"39f10340-f065-4c0b-a90d-dc4b10e5c606":{"position":{"vector":[-16,-112]}},"5eced59f-d9f4-400c-babe-93879a1780a4":{"position":{"vector":[-16,-176]}},"ffd54167-c04c-47c4-9496-bef9cf8b1c79":{"position":{"vector":[-16,-240]}},"10652055-691a-475d-bd75-543670318b3c":{"position":{"vector":[-16,-304]}},"43c7ef8b-c979-4f7e-aec2-a0b22b993bd5":{"position":{"vector":[-16,-368]}},"d3924a25-183b-462b-8efc-80f893cf9f66":{"position":{"vector":[-16,-432]}},"a91b78df-1fef-4d28-97d0-016ab5f5de83":{"position":{"vector":[-155,52]}},"0e678bab-83b4-41f3-aab0-4e8dee8259a6":{"position":{"vector":[-16,-48]}},"d190a0a6-dfc3-4fc2-9b85-322e48d6aa61":{"position":{"vector":[-16,-112]}},"c6e62baf-5804-4aae-8577-e14c4f8df3ed":{"position":{"vector":[-16,-176]}},"d127132d-a053-4837-8f1e-4b9d680e1420":{"position":{"vector":[-16,-240]}},"56e71fa8-55f4-4f90-b30d-4b0425bfe169":{"position":{"vector":[-16,-304]}},"b72df064-f847-49d9-bf59-99101d7a1aa2":{"position":{"vector":[-16,-368]}},"553de885-3085-46d8-86bb-bde3346675ec":{"position":{"vector":[-16,-432]}},"58df7412-b9a6-4fc0-a3a3-4abd10068351":{"position":{"vector":[-16,16]}},"fc73d6c2-80a2-4886-9cb3-71218879da59":{"position":{"vector":[-16,-48]}},"91b51f11-fb1d-46a2-8afb-2077987d5fc7":{"position":{"vector":[-16,-112]}},"0f33d591-c447-4b18-b584-9e20c2344778":{"position":{"vector":[-16,-176]}},"05200a5d-ab4a-4700-94d9-8b1c3b2cdbda":{"position":{"vector":[-16,-240]}},"bb8bd66e-cdf5-4439-8e61-50a77dae9420":{"position":{"vector":[-16,-304]}},"b7945ad7-ba18-4ebc-8d44-a5585d604c6c":{"position":{"vector":[-16,-368]}},"820ede41-865f-45a0-bc51-fe5eb5cfae6c":{"position":{"vector":[-16,-432]}},"2c6c3693-8fda-4684-8701-1c9693d57bba":{"position":{"vector":[-16,16]}},"64277e63-dda7-4341-8789-5baecf350c8b":{"position":{"vector":[-16,-48]}},"08c9e5b7-03d4-4c04-b01e-5cf4176b6f1e":{"position":{"vector":[-16,-112]}},"9ce2bd6d-17f1-4a02-82a1-ad865e3748dc":{"position":{"vector":[-16,-176]}},"15f1af25-d13e-4e81-8770-eb59fdadbe09":{"position":{"vector":[-16,-240]}},"1693537d-c80d-41b2-ac69-88b1bcc24e03":{"position":{"vector":[-16,-304]}},"13950f38-1685-47ac-8bdb-39f487357f00":{"position":{"vector":[-16,-368]}},"4f37a28c-c8a2-413a-9b25-efb5aa1f0d20":{"position":{"vector":[-16,-432]}},"7336615f-1985-4a76-be67-a2198f0d2a00":{"position":{"vector":[-16,16]}},"81344ea2-d629-4e05-b346-41763a01b8f5":{"position":{"vector":[-16,-48]}},"9a098ac3-474d-4992-8ec6-066473bf2c98":{"position":{"vector":[-16,-112]}},"de73cdbb-e7f7-4c00-909c-a9821fbd8aca":{"position":{"vector":[-16,-176]}},"547c7500-ed5a-4a57-9917-2c1f1b6daf2c":{"position":{"vector":[-16,-240]}},"09f30e57-c2ff-4827-97e9-9668f86b2039":{"position":{"vector":[-16,-304]}},"7d9647f1-4ac2-44eb-ade8-645717a18383":{"position":{"vector":[-16,-368]}},"3472c5f9-9398-4066-8579-13b18be2184b":{"position":{"vector":[-16,-432]}},"f1138d44-97da-457c-9e09-541314d25955":{"position":{"vector":[-16,16]}},"81a758c4-d612-4d41-accc-2f15a702ef41":{"position":{"vector":[-16,-48]}},"1983b6e2-5517-44b3-8d48-d2efd50661fb":{"position":{"vector":[-16,-112]}},"61a8a5ae-dd3f-47fe-98b7-20108a1d1d67":{"position":{"vector":[-16,-176]}},"4138c1bf-1451-4841-9678-0d9f9e3f9288":{"position":{"vector":[-16,-240]}},"7c4d0a6a-2ae1-44ab-94ae-1b40e445c7c2":{"position":{"vector":[-16,-304]}},"6b125efb-3e8e-4b12-b7d2-c658453e10a6":{"position":{"vector":[-16,-368]}},"92f416a8-48b8-4838-a429-81d9a7ff1070":{"position":{"vector":[-16,-432]}},"4bde5c48-2322-4bbb-a7c1-1f3327a65dbf":{"position":{"vector":[-16,16]}},"c1d3b037-d195-42b4-b8f2-75981dd65cc4":{"position":{"vector":[-16,-48]}},"91894d13-ff07-4655-9e7b-8256e7680c3c":{"position":{"vector":[-16,-112]}},"a858c651-a5a9-4e4c-b416-d6253f1acf95":{"position":{"vector":[-16,-176]}},"24efcde1-b5dc-4f9a-829f-035cefab9b9b":{"position":{"vector":[-16,-240]}},"9cd2d658-4b58-4aa0-a1d0-93ebf5619bcc":{"position":{"vector":[-16,-304]}},"a05145f5-ebe2-4779-9b0a-ce1cc9a2306b":{"position":{"vector":[-16,-368]}},"94c4036a-ff97-46f6-aa2b-3a95efdb4923":{"position":{"vector":[-16,-432]}},"389303aa-4281-4d20-94f2-1e58a79a0b2b":{"position":{"vector":[-16,16]}},"9c8131f0-fec6-4a75-92ca-b079d61da2dd":{"position":{"vector":[-16,-48]}},"a7f8c098-c49c-46e9-883e-95f5968985cb":{"position":{"vector":[-16,-112]}},"7e93e68b-9d15-4081-af0b-ba35a418e8cc":{"position":{"vector":[-16,-176]}},"820d5f3c-f388-40e4-82b7-3e5924d8d4c0":{"position":{"vector":[-16,-240]}},"cb97ad81-7fb4-4d34-af72-57afb5c46144":{"position":{"vector":[-16,-304]}},"0c9ca9d0-0bf6-4999-8121-4610c1e64b85":{"position":{"vector":[-16,-368]}},"9c5cda5a-802b-42d0-858b-ee09e2094002":{"position":{"vector":[-16,-432]}},"f422f364-2f49-4833-8616-dfd970cde907":{"position":{"vector":[-16,16]}},"0f286b67-38b7-41f4-a81c-9c03b5de6191":{"position":{"vector":[-16,-48]}},"0fe2496f-0de0-4f52-a269-81bd718dd443":{"position":{"vector":[-16,-112]}},"fc6d520d-97c7-4528-8c05-0b6098553715":{"position":{"vector":[-16,-176]}},"f45ca4bb-8e81-4a8b-aa97-566e9db7cfcf":{"position":{"vector":[-16,-240]}},"d8b370c9-1346-4b32-be6e-9f877a0124d4":{"position":{"vector":[-16,-304]}},"4e21f578-b358-44d0-9f03-45f81bb02ab2":{"position":{"vector":[-16,-368]}},"d2ccd225-2b8e-4ceb-b5d5-4ae28acb6079":{"position":{"vector":[-16,-432]}},"273bb184-bc32-43fb-9060-c5a3f4501288":{"position":{"vector":[-16,16]}},"9a14277d-3312-45c9-a103-44396f1fc34d":{"position":{"vector":[-16,-48]}},"306bdae5-2240-43f3-bf28-1b5de3491bce":{"position":{"vector":[-16,-112]}},"a9b9ae04-0301-4514-bf96-6fe6facd9423":{"position":{"vector":[-16,-176]}},"d60da697-ba30-47ee-9124-e22b998cde34":{"position":{"vector":[-16,-240]}},"00305da4-c213-44dd-b20b-d7d8adfd536b":{"position":{"vector":[-16,-304]}},"40303fd0-3112-4ce6-8b5a-968c3925e4be":{"position":{"vector":[-16,-368]}},"24f7334b-4b72-4e17-896e-c433c8c3a101":{"position":{"vector":[-16,-432]}},"04851171-2413-4912-919e-672a334c65ad":{"position":{"vector":[-16,16]}},"064a2ce9-e253-4111-9368-cee1d4dc654d":{"position":{"vector":[-16,-48]}},"c03a2921-daf9-432e-898d-1a9ced126e57":{"position":{"vector":[-16,-112]}},"84ba4cfa-95f5-4508-8ba1-075fd5598f88":{"position":{"vector":[-16,-176]}},"85882fb1-cd2a-48a7-a557-1e7895167c4d":{"position":{"vector":[-16,-240]}},"a3244051-7636-41c5-bb40-863a451260a9":{"position":{"vector":[-16,-304]}},"fbd500b2-6e08-4b40-82e9-a414f75e6214":{"position":{"vector":[-16,-368]}},"33e060ad-ba52-4c63-8456-6bdb9e96955f":{"position":{"vector":[-16,-432]}},"4457764f-5d8e-4a20-83c7-1252fd5489b6":{"position":{"vector":[-16,16]}},"bd317eac-2d82-4248-98da-fa27bd41f6fc":{"position":{"vector":[-16,-48]}},"4ff041b7-20df-4cf8-b8de-f253bb1e5260":{"position":{"vector":[-16,-112]}},"c4c3886f-afb5-46f3-b08d-b15a8058321b":{"position":{"vector":[-16,-176]}},"09e62d7a-5c78-4fe5-8e9c-568d7ea7393a":{"position":{"vector":[-16,-240]}},"d349eb0e-c563-4d9b-b2f2-1fc9c5f566c1":{"position":{"vector":[-16,-304]}},"5299cc74-d31a-4e89-8114-6474a09d0659":{"position":{"vector":[-16,-368]}},"2e225119-7405-419a-8133-f141349b0513":{"position":{"vector":[-16,-432]}},"d2f3b840-2711-464b-bf2c-d3c83d9bf6f1":{"position":{"vector":[-16,16]}},"38b630a9-fa22-45f4-a81a-6fbfd2c2e1e8":{"position":{"vector":[-16,-48]}},"c3e0b474-dba2-4566-917c-8b4ef1e9fa55":{"position":{"vector":[-16,-112]}},"257dab77-112e-4d73-b40f-4e11bfb5be8d":{"position":{"vector":[-16,-176]}},"8ebe2eef-6fe6-4e40-84a2-2f7960a57c1e":{"position":{"vector":[-16,-240]}},"f4f4bc3e-f5cd-4c10-9754-4d41a6b733e0":{"position":{"vector":[-16,-304]}},"6475379b-fb6e-41f0-93ae-f86c96ddbb02":{"position":{"vector":[-16,-368]}},"5f2d7105-6fe0-4281-9eef-e85022a70aea":{"position":{"vector":[-16,-432]}},"580c7384-c823-402e-b1f1-68aa4ca6a83a":{"position":{"vector":[-16,16]}},"06d72538-0366-4fa4-a50c-a0d10d6480ec":{"position":{"vector":[-16,-48]}},"f8b140cb-9c73-4978-b3d4-aa4e3374c4a1":{"position":{"vector":[-16,-112]}},"3af10800-bda1-4e21-8bd9-d036a80e03cb":{"position":{"vector":[-16,-176]}},"4c7f3955-842c-4794-8207-da41bff96f51":{"position":{"vector":[-16,-240]}},"68714fdb-aa6a-460e-887d-956f0b1b91ed":{"position":{"vector":[-16,-304]}},"a7aa556e-1fa3-43c1-a41b-b7eb273c9b5d":{"position":{"vector":[-16,-368]}},"3bb23b1f-5227-476d-9d51-aaa3f5f694af":{"position":{"vector":[-16,-432]}},"93d5d54e-9708-49ba-8453-119fa87166ed":{"position":{"vector":[-16,16]}},"4d476d94-8301-4678-b1d4-59d9c90410d0":{"position":{"vector":[-16,-48]}},"324a25e5-650f-4c47-87fa-261f309fad18":{"position":{"vector":[-16,-112]}},"f6f103fe-913e-43db-acfc-a437a0604a01":{"position":{"vector":[-16,-176]}},"ad34638f-e21b-4ba8-a606-693696defcbb":{"position":{"vector":[-16,-240]}},"6d45bb0c-346c-4fea-adb8-f7b891fa5afe":{"position":{"vector":[-16,-304]}},"0bf96635-1504-482b-8a9e-75bbafb576e4":{"position":{"vector":[-16,-368]}},"8c4dd14b-43eb-4026-a65e-16bdde536273":{"position":{"vector":[-16,-432]}},"c87767b3-6bdc-43fc-b735-a0dcb87a6aff":{"position":{"vector":[-16,16]}},"c3ba5548-5153-4cbc-9b2b-9a060b44fa07":{"position":{"vector":[-16,-48]}},"f43348be-7e2f-4318-9662-c1eb0623f2cc":{"position":{"vector":[-16,-112]}},"e5178413-59e9-4153-b6db-92c6cdaaf2be":{"position":{"vector":[-16,-176]}},"e699ebc3-c6d1-43c1-a23c-034506f736a9":{"position":{"vector":[-16,-240]}},"588dcbb7-bb33-4a79-9d72-06e765fada10":{"position":{"vector":[-16,-304]}},"9354afc0-83f4-4876-9f5e-b8b196d473fc":{"position":{"vector":[-16,-368]}},"dc2df512-3a5d-47ab-b074-6e8afb986714":{"position":{"vector":[-16,-432]}},"44216231-f86f-40b0-9e39-0a23ac4d00c6":{"position":{"vector":[-16,16]}},"74a09099-d5af-4142-a313-c837245d4226":{"position":{"vector":[-16,-48]}},"0b8c6caa-ec86-4a99-8727-2dbde665203b":{"position":{"vector":[-16,-112]}},"b346fda2-fd91-4ba8-8a9d-b7767c2a4063":{"position":{"vector":[-16,-176]}},"63cf6493-f352-4107-997f-6ccd618c3557":{"position":{"vector":[-16,-240]}},"da0df99c-6623-437c-a044-f59c6517a068":{"position":{"vector":[-16,-304]}},"49fbfef4-9e87-4d8d-902b-81cf413b51a8":{"position":{"vector":[-16,-368]}},"4fbdc5e3-d82f-4d7c-9113-718fec955b55":{"position":{"vector":[-16,-432]}},"9802ebec-5f9e-4829-a069-7b04d43e2933":{"position":{"vector":[-16,16]}},"8272e8bb-dc36-49b6-b81f-50e823bf60a8":{"position":{"vector":[-16,-48]}},"ae08c773-5a37-4723-8b0e-6d92350d50d0":{"position":{"vector":[-16,-112]}},"48508db1-863c-46d5-b11b-c9845ba79926":{"position":{"vector":[-16,-176]}},"aeb374b1-f42b-42a9-86ba-83ccb6927224":{"position":{"vector":[-16,-240]}},"ad055dda-dc9c-470c-b9ac-234c114903b1":{"position":{"vector":[-16,-304]}},"1cde192e-0b90-4fa1-bd38-691be591bdb1":{"position":{"vector":[-16,-368]}},"16ef874c-91fc-44d3-bf1a-febe656acbda":{"position":{"vector":[-16,-432]}},"4c5072e3-2271-4b28-b8d6-71a504d73804":{"position":{"vector":[-16,16]}},"bcfd4e66-c6fc-44b8-b9bb-e4dc36b75680":{"position":{"vector":[-16,-48]}},"95b6feda-7339-44ae-84b0-ebf591aea9c7":{"position":{"vector":[-16,-112]}},"e1e93ae8-92e3-40d0-800f-9a64308a2404":{"position":{"vector":[-16,-176]}},"47cc36ef-196e-400d-8785-a2993d3d0c4a":{"position":{"vector":[-16,-240]}},"a235d710-5f1a-4115-8ba2-5f076709b903":{"position":{"vector":[-16,-304]}},"e8072a7e-5ed8-4403-bf09-55ee14f75bda":{"position":{"vector":[-16,-368]}},"3e474621-5f19-42a0-8cfc-c40047a58df6":{"position":{"vector":[-16,-432]}},"2cd6f1b4-902b-4b6d-8409-2a6a4ab22ac0":{"position":{"vector":[-16,16]}},"a004e7d7-4cf2-4b29-a7bc-5f458aa35dcb":{"position":{"vector":[-16,-48]}},"001132d4-847f-4213-b663-2270b10f0ee2":{"position":{"vector":[-16,-112]}},"96fa8089-3318-4d7e-8f5d-1c5381f9477b":{"position":{"vector":[-16,-176]}},"e61ccf4c-1b83-4e73-9b14-73c8f00df7f0":{"position":{"vector":[-16,-240]}},"f7570a80-d26e-4bb1-bd66-c803566ba559":{"position":{"vector":[-16,-304]}},"5b546bed-7573-44bb-8b1c-870118c2479e":{"position":{"vector":[-16,-368]}},"0ebdac4b-0048-4f3c-998f-5a56a2ca9e01":{"position":{"vector":[-16,-432]}},"6eb2992f-6f55-4eda-a115-44d12ea31c5c":{"position":{"vector":[-16,16]}},"13a00ce2-b3ce-456b-a932-375efeb02a7b":{"position":{"vector":[-16,-48]}},"0e224a22-4cb7-4b22-a090-a43521ebe5e9":{"position":{"vector":[-16,-112]}},"648c8b1f-757d-4963-8198-a214cf559859":{"position":{"vector":[-16,-176]}},"e07abf11-873b-46d4-b870-beb99c553a6c":{"position":{"vector":[-16,-240]}},"634aaf59-38d7-4bed-a127-7a802fac6643":{"position":{"vector":[-16,-304]}},"932c6bf4-9708-4d8f-9648-4a95eff4b931":{"position":{"vector":[-16,-368]}},"442cd985-78bc-421e-94a0-1ed4c11294a9":{"position":{"vector":[-16,-432]}},"44acbd30-4329-48fb-b227-fc3cb173f62c":{"position":{"vector":[-16,16]}},"ef8bad06-e257-406d-acd2-752d7c067648":{"position":{"vector":[-16,-48]}},"6f5020a2-ff76-4237-8588-ee096ad895ef":{"position":{"vector":[-16,-112]}},"ceaba98a-53c1-497a-9fda-98c1cb6a4f5b":{"position":{"vector":[-16,-176]}},"8bd58e23-35e2-45dc-9d39-41ad5de1c9ca":{"position":{"vector":[-16,-240]}},"48ab6084-4fad-4e5f-80ca-0239a79a7e8a":{"position":{"vector":[-16,-304]}},"361bc6f4-f062-4387-96b0-1d47fa2e1c19":{"position":{"vector":[-16,-368]}},"4d9da68a-7f3b-4569-b305-31104fcf3717":{"position":{"vector":[-16,-432]}},"96b70a3e-8651-484a-a7f4-56c82128af46":{"position":{"vector":[-16,16]}},"49d6e992-1292-4c3c-a1ff-2bddb92a48fc":{"position":{"vector":[-16,-48]}},"6b56325b-05b9-4599-b421-5baad807422b":{"position":{"vector":[-16,-112]}},"9a52cc3f-0af0-4eb1-875f-965c8c80f8bd":{"position":{"vector":[-16,-176]}},"255cbb60-7197-44e9-9957-34da20866802":{"position":{"vector":[-16,-240]}},"7085ff39-5857-430a-84c0-13a11932ffb2":{"position":{"vector":[-16,-304]}},"e4c7e03b-b751-4917-85c3-c3fa374048f8":{"position":{"vector":[-16,-368]}},"e8b66a5c-f918-45f5-9188-147fecb9d23c":{"position":{"vector":[-16,-432]}},"b48d5fbd-b4b2-4851-93c0-cc1ea5dc07bc":{"position":{"vector":[-16,16]}},"0af898f9-edc8-4df4-80e8-db6a01791397":{"position":{"vector":[-16,-48]}},"9f6af6eb-a0b7-4312-84c7-364b91c3eb90":{"position":{"vector":[-16,-112]}},"9f3302f4-808a-4f86-abb0-00fc954c2464":{"position":{"vector":[-16,-176]}},"22b25b48-b272-417f-b313-bc48df3558cd":{"position":{"vector":[-16,-240]}},"5ed64eb7-399b-4f6b-820b-a3b00281b745":{"position":{"vector":[-16,-304]}},"812af0ad-438c-4781-b44b-c0673503d13f":{"position":{"vector":[-16,-368]}},"bcf3bf20-2b8e-4f79-ad14-c3b8385c43c2":{"position":{"vector":[-16,-432]}},"b2a1eb3c-f11d-4e03-b54a-7de00ba6b752":{"position":{"vector":[-16,16]}},"f86c3575-0bd4-4503-9897-dab8c1a9c845":{"position":{"vector":[-16,-48]}},"4a118273-d041-4116-8eb5-740b206e23c8":{"position":{"vector":[-16,-112]}},"b19d24e0-39fc-42eb-8ae9-57fde3743271":{"position":{"vector":[-16,-176]}},"a5f1dc1f-3412-41a7-bc44-10f22469b3ee":{"position":{"vector":[-16,-240]}},"fe3817a5-f18d-4258-a181-eefd10e317bc":{"position":{"vector":[-16,-304]}},"cfe38d0d-40ef-43a6-82c5-949f4a457bd8":{"position":{"vector":[-16,-368]}},"3f18972d-1bcf-4f8a-8c11-1002a8768934":{"position":{"vector":[-16,-432]}},"a4ab7856-9858-4103-96a6-fd34baac21ad":{"position":{"vector":[-16,16]}},"5c49b0fd-1b03-4905-a255-b70e6f2a75b2":{"position":{"vector":[-16,-48]}},"7d20ccd1-85e9-49cb-851c-6443685274d6":{"position":{"vector":[-16,-112]}},"7466f321-c890-4b9b-a2d3-1a9845a69645":{"position":{"vector":[-16,-176]}},"fc471547-796b-428f-bb73-85564de56685":{"position":{"vector":[-16,-240]}},"e0ad7a8d-568e-4703-879f-d64bd382e32b":{"position":{"vector":[-16,-304]}},"c1bea0ec-c105-4762-8b17-5e9834b31f35":{"position":{"vector":[-16,-368]}},"15321697-7d97-46ce-a58b-f3d9522b98ee":{"position":{"vector":[-16,-432]}},"9d6aef83-75e8-4413-82c8-738e5b875ff1":{"position":{"vector":[-16,16]}},"c492f86e-a097-4536-b1ef-1c80aff36829":{"position":{"vector":[-16,-48]}},"4cb64ace-f03f-4896-b08d-7210c3718507":{"position":{"vector":[-16,-112]}},"605767ee-d122-4138-b7a6-a15f016751f5":{"position":{"vector":[-16,-176]}},"f3350fd8-830b-400b-aeb6-7757be3ad092":{"position":{"vector":[-16,-240]}},"4cad8541-5c84-4f31-a7a4-262ad17d1b8b":{"position":{"vector":[-16,-304]}},"2224f68d-ad72-4073-a034-ac43be35a24a":{"position":{"vector":[-16,-368]}},"7db5af47-072f-4c1d-8868-e9e669ce4022":{"position":{"vector":[-16,-432]}},"451dccf8-15d6-45fd-8f35-a452404ef6d2":{"position":{"vector":[-16,16]}},"dedd32f7-a655-4307-9dcc-ecc2ff4a47e1":{"position":{"vector":[-16,-48]}},"7bf0eca2-35b0-49bf-a47a-e485e12118a0":{"position":{"vector":[-16,-112]}},"42705917-b21e-4b9a-a524-9c677ba1252f":{"position":{"vector":[-16,-176]}},"cd4b42e5-292c-4e6d-a5f6-e06690637946":{"position":{"vector":[-16,-240]}},"4233a007-6506-4686-a1d9-e56c007e0806":{"position":{"vector":[-16,-304]}},"2c83ba3c-ae5c-4492-875a-3ba58cb7044b":{"position":{"vector":[-16,-368]}},"a09ca77c-eddf-4d5c-92f0-4da299bb417b":{"position":{"vector":[-16,-432]}},"5fd3a55d-376f-4ff2-a174-82323119f6d6":{"position":{"vector":[-16,16]}},"4869fcf9-7c73-4174-aaec-4834e9cee8fe":{"position":{"vector":[-16,-48]}},"14bf6f44-86b3-49da-a9fc-dd160c3d9570":{"position":{"vector":[-16,-112]}},"a96d0097-5fb7-44ed-9a49-c0bb56bf1c68":{"position":{"vector":[-16,-176]}},"06ca5b67-1e7a-43d9-8dd0-9ed4a7ba023d":{"position":{"vector":[-16,-240]}},"0ca80886-c629-4371-959c-cd9cd03b5467":{"position":{"vector":[-16,-304]}},"5a3d15bc-0b37-48cb-80f2-bcce675a8fe7":{"position":{"vector":[-16,-368]}},"3b3097c6-5598-4b1c-b2cf-20481252f3cc":{"position":{"vector":[-16,-432]}},"7b3bbee2-627e-46ab-b069-fec758f15644":{"position":{"vector":[-16,16]}},"fb9b4945-84ae-4944-bf3f-1645b53043d6":{"position":{"vector":[-16,-48]}},"7d219362-9da1-4a18-8ea1-1ed2c333f416":{"position":{"vector":[-16,-112]}},"02c72a9d-17ec-4a86-8573-ab39e6bed63d":{"position":{"vector":[-16,-176]}},"ba3e84c1-dd18-4e0a-9158-40238e5a40e7":{"position":{"vector":[-16,-240]}},"69adf5a0-4df4-46e8-8a0e-34268213cb07":{"position":{"vector":[-16,-304]}},"464c4c79-49c7-436e-9dc6-81f867fbbb12":{"position":{"vector":[-16,-368]}},"e20ac6e6-656e-4c5b-9247-e4fd6bf50e9b":{"position":{"vector":[-16,-432]}},"c07a9b2e-36a4-40fb-be69-90fb353b41e1":{"position":{"vector":[-16,16]}},"5ea22b0d-bc1d-49a8-8591-5be4c16b1193":{"position":{"vector":[-16,-48]}},"f3677b77-fb97-40be-926f-bb97ac5d92d4":{"position":{"vector":[-16,-112]}},"37dde192-fb4e-40ce-8f24-ae548c80f707":{"position":{"vector":[-16,-176]}},"14e86222-d0e2-42d0-a1e1-23bb218c5f27":{"position":{"vector":[-16,-240]}},"fcde817f-1f9a-4881-acae-1e7fc59af36e":{"position":{"vector":[-16,-304]}},"8ea59b79-2ee6-4424-8d53-3b0e4b0ef8ef":{"position":{"vector":[-16,-368]}},"3c1e18aa-67fd-4f33-bf5b-e2a71bde2b9f":{"position":{"vector":[-16,-432]}},"78d7d6cc-ddcd-46fc-808c-2ad17b8a8fe2":{"position":{"vector":[-16,16]}},"c3568c51-d41d-407c-92ca-ed635b90f973":{"position":{"vector":[-16,-48]}},"58f826a0-d852-4c99-80bd-6f90907b34a8":{"position":{"vector":[-16,-112]}},"e28525d2-76db-492a-b58f-ae83ced43db8":{"position":{"vector":[-16,-176]}},"90292abc-b6c8-48c7-8b4e-cf76104cebbb":{"position":{"vector":[-16,-240]}},"6505c9d1-5406-4421-9f17-f4d7e84e3170":{"position":{"vector":[-16,-304]}},"b7f1123e-5204-4cdb-8b73-fa6e18ac5045":{"position":{"vector":[-16,-368]}},"c5cd2a9e-f57e-4edf-8bc9-de74231919c2":{"position":{"vector":[-16,-432]}},"c491905e-c335-44f1-89f2-6dce5a707e12":{"position":{"vector":[-16,16]}},"59803c11-4f08-49dc-b79e-02b9b7732132":{"position":{"vector":[-16,-48]}},"bba2788a-2877-45a2-a224-5791b0b74847":{"position":{"vector":[-16,-112]}},"7cfba1a5-bcaf-407b-ac47-330f1f848c58":{"position":{"vector":[-16,-176]}},"1784ef7f-2a58-4350-b589-4b236bd43548":{"position":{"vector":[-16,-240]}},"be20a9b6-0a72-4b3f-bd2f-f70af1fcb128":{"position":{"vector":[-16,-304]}},"31080a10-82f0-4320-a0a2-c961d0af1b3b":{"position":{"vector":[-16,-368]}},"7dc040c4-e006-4c3b-b3d6-15f737a96fab":{"position":{"vector":[-16,-432]}},"30c16bf2-39f1-43b0-b1c1-7a3a8a670cf1":{"position":{"vector":[-16,16]}},"803ad013-f4c8-40bd-9394-25175bdbd5a8":{"position":{"vector":[-16,-48]}},"3b9d7275-36e8-43a7-bd11-badcfa2bc05b":{"position":{"vector":[-16,-112]}},"a3a8e421-4339-403a-bbc1-5f4e6196fd28":{"position":{"vector":[-16,-176]}},"9cf28727-b549-4a3e-970d-c87214acf254":{"position":{"vector":[-16,-240]}},"3ed84923-d12e-49fe-829c-0a396640dc63":{"position":{"vector":[-16,-304]}},"25d8efa6-fbfd-480f-9e4a-f3ce947a8812":{"position":{"vector":[-16,-368]}},"99d5c9e2-d8c6-4a73-8b77-76979339a785":{"position":{"vector":[-16,-432]}},"860db0ed-31d9-485d-9633-660414024054":{"position":{"vector":[-16,16]}},"ad51a04b-f820-4712-9214-76ea09da7c05":{"position":{"vector":[-16,-48]}},"3652ba98-b8cf-445f-b1af-baf46443ef6e":{"position":{"vector":[-16,-112]}},"e205abed-7efb-4d3e-92a3-a8be0dea865c":{"position":{"vector":[-16,-176]}},"5038da37-eeb0-4911-aee4-3fa2b2ffafff":{"position":{"vector":[-16,-240]}},"97a81277-7725-4aac-85f5-57155fe037e8":{"position":{"vector":[-16,-304]}},"5af8f3d6-bdf7-4761-b82d-82af468895b3":{"position":{"vector":[-16,-368]}},"12e876d1-310c-424c-b5d8-2a06c5715d86":{"position":{"vector":[-16,-432]}},"f61c1ca5-28c3-4c5e-bb73-18bbdbe34c0d":{"position":{"vector":[-16,16]}},"c282a7f9-9d09-4279-869e-7b18afc7252b":{"position":{"vector":[-16,-48]}},"43d2ad3f-0a72-4875-af1a-737bf9d70ba4":{"position":{"vector":[-16,-112]}},"8a5ccd8b-f38a-4545-9ca8-98f8c6a6fffa":{"position":{"vector":[-16,-176]}},"8b31af8b-7469-48a6-af2a-17a5929d6b38":{"position":{"vector":[-16,-240]}},"9c86a972-ebd1-449d-a62c-51b5dc06da09":{"position":{"vector":[-16,-304]}},"4b9cb481-ffcc-4da5-9fed-9503f4c4d6f3":{"position":{"vector":[-16,-368]}},"f2244b55-6e70-430f-be04-5923365c3593":{"position":{"vector":[-16,-432]}},"276110b5-2d51-486f-9cc0-66913dde5fb7":{"position":{"vector":[-16,16]}},"810ffa67-b0a0-42fd-91b9-0dadfc72c75c":{"position":{"vector":[-16,-48]}},"e678cfc1-f879-4153-a72f-f29fc8d5d396":{"position":{"vector":[-16,-112]}},"63a2a3fa-21b4-4f17-97ca-dd363e382785":{"position":{"vector":[-16,-176]}},"c5202df9-930b-469d-94c8-ffb3caf3c6a6":{"position":{"vector":[-16,-240]}},"1ceb275d-376c-4e32-a5b8-4de7c81dc7ff":{"position":{"vector":[-16,-304]}},"c4c725ed-4512-4540-b5c9-04662308cefb":{"position":{"vector":[-16,-368]}},"83edc962-09e6-474f-9a92-ba4eadef878b":{"position":{"vector":[-16,-432]}},"15518170-2a6f-481e-abe5-3a46f3e413ab":{"position":{"vector":[-16,16]}},"8eb43c61-abdf-4ee6-96e7-34bcfa6a79ed":{"position":{"vector":[-16,-48]}},"96d950cc-d950-459a-a786-eb42f1eb91cc":{"position":{"vector":[-16,-112]}},"2fb79436-b800-4063-9e99-b8ee4ee5770c":{"position":{"vector":[-16,-176]}},"f907d515-b781-4ce8-904a-0655a8dd7a96":{"position":{"vector":[-16,-240]}},"6a34cd98-e60e-4801-bb9f-3fd17d4a403b":{"position":{"vector":[-16,-304]}},"b13bb83c-453e-46aa-8340-71b7fefa824a":{"position":{"vector":[-16,-368]}},"c18b2593-b6b6-4f59-a01f-0fbfb4aab6ae":{"position":{"vector":[-16,-432]}},"a5a8119a-c41c-487f-8066-bc4fe5806a38":{"position":{"vector":[-16,16]}},"fe150fbb-5634-4cbf-bd69-3639d1765fdb":{"position":{"vector":[-16,-48]}},"7166c0d5-1cea-42e7-819b-754a2d8139dc":{"position":{"vector":[-16,-112]}},"10466064-c69e-452d-ad24-12e026fd2877":{"position":{"vector":[-16,-176]}},"ad1dbe6b-7d30-4e94-a052-596c799da371":{"position":{"vector":[-16,-240]}},"e1f03bc5-ffa4-4fb0-a65f-136dcaa05215":{"position":{"vector":[-16,-304]}},"9e23bb4c-0e49-4be6-aebe-50350ee9e359":{"position":{"vector":[-16,-368]}},"b4b60e83-9507-42ab-9386-7fb28ae82329":{"position":{"vector":[-16,-432]}},"f916ec51-2f1e-4aa4-af80-e3e59befdc1e":{"position":{"vector":[-16,16]}},"c4d1d535-9ed3-4996-bab8-a19e7713b5dc":{"position":{"vector":[-16,-48]}},"7887cecc-908b-4fc8-886a-38ae8193d896":{"position":{"vector":[-16,-112]}},"14d3b133-2f33-47cb-933d-0399ac6e1edb":{"position":{"vector":[-16,-176]}},"2edb024a-ed17-4c5e-b847-66f4f6e02de8":{"position":{"vector":[-16,-240]}},"efded32a-e7a8-42b7-b561-59fb4524b6c4":{"position":{"vector":[-16,-304]}},"c4103578-d362-49bb-be48-057b26656665":{"position":{"vector":[-16,-368]}},"b049188e-c204-4e51-a450-284ad7394f7e":{"position":{"vector":[-16,-432]}},"26ded60f-e757-42b9-ba3e-3fc6a3748620":{"position":{"vector":[-16,16]}},"2d4c5cf8-cd17-47a6-b96a-ea964b87fc6d":{"position":{"vector":[-16,-48]}},"70a29db7-b43f-4edf-8660-57e6574c8e90":{"position":{"vector":[-16,-112]}},"625e58e7-3fbb-42ab-9b1a-a66f847c3660":{"position":{"vector":[-16,-176]}},"3baa7716-3ef1-458a-bcc5-43966b776d37":{"position":{"vector":[-16,-240]}},"b14d59a2-85c8-4b22-8ff0-7c357f0510ed":{"position":{"vector":[-16,-304]}},"6503271c-0596-41d7-8aac-ce7bb9ff1739":{"position":{"vector":[-16,-368]}},"650a8d94-2fe6-4264-892c-dda8404faa90":{"position":{"vector":[-16,-432]}},"699bbac1-d7e6-4e45-8cc0-ab0fdbee9ea8":{"position":{"vector":[-16,16]}},"fdbe5c3b-5683-44da-afe5-8d21eb1c7b87":{"position":{"vector":[-16,-48]}},"063e307b-df20-492b-9ee7-154aa8f025ba":{"position":{"vector":[-16,-112]}},"ff220837-879f-487c-b4f0-a0bd632f99f9":{"position":{"vector":[-16,-176]}},"0714adad-c569-4430-93bc-0d4fb3264459":{"position":{"vector":[-16,-240]}},"fe3af546-7e5e-4206-8263-6916630dcecf":{"position":{"vector":[-16,-304]}},"0648d6e4-7c51-4ded-93ca-71ccd2f2dc87":{"position":{"vector":[-16,-368]}},"50449864-cf93-496b-805a-f53ccfc32099":{"position":{"vector":[-16,-432]}},"6752dd49-40b8-4b9f-93de-6b5ec0fb9206":{"position":{"vector":[-16,16]}},"7061e31e-efd2-4cf4-a89c-faa177330af9":{"position":{"vector":[-16,-48]}},"20cf2899-a064-48c2-85b7-a7dc67ae006d":{"position":{"vector":[-16,-112]}},"c8c6d019-15c8-4746-977d-0b0edc1f6bd7":{"position":{"vector":[-16,-176]}},"3dba25da-8295-41bb-9dae-c92b1b007702":{"position":{"vector":[-16,-240]}},"15e4c8aa-d41c-40af-89c0-1612f5143ae7":{"position":{"vector":[-16,-304]}},"bcaf4fa4-81ee-47ac-9ee3-af13ecbc6143":{"position":{"vector":[-16,-368]}},"49c01d3a-4bb4-4d8b-897c-9528529df81f":{"position":{"vector":[-16,-432]}},"e7ce4a0b-c465-49a8-b7b0-588f353dbfca":{"position":{"vector":[-16,16]}},"c30433f6-0d10-4128-a83a-9a93dc61de67":{"position":{"vector":[-16,-48]}},"924fac0a-9ab6-4427-976f-f4c241eccfc1":{"position":{"vector":[-16,-112]}},"7906ae8f-53f5-43fd-b5de-ba5ae9ea4a47":{"position":{"vector":[-16,-176]}},"dbe12316-db8e-42b8-aa04-1f81b0b15d37":{"position":{"vector":[-16,-240]}},"c3c41975-8a98-489b-90a8-4365b580e72b":{"position":{"vector":[-16,-304]}},"d510dc00-5d7b-478f-8bd9-aa0ee90f6423":{"position":{"vector":[-16,-368]}},"ee4352ec-36b0-4bbd-92ac-2a0660564785":{"position":{"vector":[-16,-432]}},"bb1ed559-bf26-4a4c-8ec0-476f9c37250b":{"position":{"vector":[-16,16]}},"a6b5c524-1efa-4500-8bd5-d46d7e6793c1":{"position":{"vector":[-16,-48]}},"4abec610-bf51-446a-b95b-27952fecb585":{"position":{"vector":[-16,-112]}},"7f2bcdc2-a744-43e2-8099-9a1c67c0b36f":{"position":{"vector":[-16,-176]}},"fbab2043-8981-4836-9aa0-9c43184f4254":{"position":{"vector":[-16,-240]}},"f387deb4-7226-467c-b2bb-44a667b7d4d0":{"position":{"vector":[-16,-304]}},"d71d3be7-89c0-44ae-9bb5-bda522964f3e":{"position":{"vector":[-16,-368]}},"95cba6b5-63d4-435a-bda5-f01994dfc2f0":{"position":{"vector":[-16,-432]}},"eb43edc4-787e-407c-bcec-1e6f0699fd1f":{"position":{"vector":[-16,16]}},"4f6de035-8544-42dd-a614-a3f10aecfb01":{"position":{"vector":[-16,-48]}},"8748a907-e741-4117-9b1f-cf0901e29eb5":{"position":{"vector":[-16,-112]}},"96442fb3-b2fd-4a8e-9c24-945744dfda6e":{"position":{"vector":[-16,-176]}},"3adec9bb-d515-43b9-9c24-b4f9ed905458":{"position":{"vector":[-16,-240]}},"d5fe73a3-1714-4e33-a613-c6db381034eb":{"position":{"vector":[-16,-304]}},"e6471c08-6cbd-4392-941a-295737d6e533":{"position":{"vector":[-16,-368]}},"d0716672-75ab-4ea6-9e66-a3686e076bee":{"position":{"vector":[-16,-432]}},"1b5c8834-4c38-462a-b090-2cef1601756e":{"position":{"vector":[-16,16]}},"edcc5f1b-ad0c-45a5-9792-b1e7f7b8af74":{"position":{"vector":[-16,-48]}},"df826740-c3e3-4623-8163-6440a550a28e":{"position":{"vector":[-16,-112]}},"7c06e823-f154-4c91-bcbe-2e6f25363c47":{"position":{"vector":[-16,-176]}},"9d0ae391-3cde-4827-95ae-40a73ae03c5c":{"position":{"vector":[-16,-240]}},"d82c7d42-faa8-4c25-9788-61db8a498d3f":{"position":{"vector":[-16,-304]}},"f02a0af8-b3f4-4245-8c02-4e1ba32011a8":{"position":{"vector":[-16,-368]}},"639d2d22-7f3e-4f99-83a7-9e997354dd12":{"position":{"vector":[-16,-432]}},"b4c1942e-aa9e-4412-9268-63e33bcbfff7":{"position":{"vector":[-16,16]}},"db109975-3fba-49bb-b36f-9ef17c206c64":{"position":{"vector":[-16,-48]}},"f4b122b3-0ad8-4b89-981d-a206f8b3deb2":{"position":{"vector":[-16,-112]}},"20e74c5e-d0ca-473e-bb2d-da30ef0a9112":{"position":{"vector":[-16,-176]}},"a962e9ac-6eca-4765-94d7-70c7d1291d31":{"position":{"vector":[-16,-240]}},"c11c3240-8384-4f7e-941a-ea4b6d3bd5c6":{"position":{"vector":[-16,-304]}},"708d9f3d-a560-4d64-81d5-5695c6d88734":{"position":{"vector":[-16,-368]}},"219bc344-0f2e-46e0-bbdb-f94a06b78179":{"position":{"vector":[-16,-432]}},"93f453a7-55b7-4410-9955-14a83d3ff44f":{"position":{"vector":[-16,16]}},"5b7650ba-6f98-403f-bd95-69b82675d629":{"position":{"vector":[-16,-48]}},"3d26b8e0-9881-48d3-acb7-ecdab45d1ee5":{"position":{"vector":[-16,-112]}},"3ce1a15e-ce0f-44b2-b030-1767b371bc5b":{"position":{"vector":[-16,-176]}},"89acad82-b74e-437b-92f4-fe17660b46d4":{"position":{"vector":[-16,-240]}},"8f744822-e4d4-4743-a0dc-819209b0bf05":{"position":{"vector":[-16,-304]}},"7434a3df-4ec3-4a7d-aaf1-4fc53d36273e":{"position":{"vector":[-16,-368]}},"1654a8e7-8acb-465c-9fa9-c4e5574c7b9b":{"position":{"vector":[-16,-432]}},"e9184c65-efb2-4daa-8088-e4a2d3ae3a40":{"position":{"vector":[-16,16]}},"5964cbfc-2ab1-48ee-8f7a-072f19602c17":{"position":{"vector":[-16,-48]}},"7efdd445-971f-4a83-a318-23bba652e39b":{"position":{"vector":[-16,-112]}},"d8627ea5-0e25-4453-b2ce-69694d8aa21a":{"position":{"vector":[-16,-176]}},"c777a03a-4db9-4597-86f5-47c62446fccb":{"position":{"vector":[-16,-240]}},"e463e4c2-10a5-43fa-b17e-30c0bc052b26":{"position":{"vector":[-16,-304]}},"090a71be-84eb-46c9-96cc-ed4a3af39ee1":{"position":{"vector":[-16,-368]}},"b904d16f-7318-4d87-827f-77fbf73e154b":{"position":{"vector":[-16,-432]}},"fa6815be-c100-4f63-b7ef-451edbcc183c":{"position":{"vector":[-16,16]}},"0c348ffc-6e5d-41f1-b717-a07c4de81d82":{"position":{"vector":[-16,-48]}},"c5bd58bf-fc9c-4fc6-8b58-95e48c0a3857":{"position":{"vector":[-16,-112]}},"51a3ae8d-11c6-462c-bf8b-77b13130afd8":{"position":{"vector":[-16,-176]}},"5f3e19c4-10b6-44cd-98a2-ea517ee861e6":{"position":{"vector":[-16,-240]}},"57752100-cd35-43fa-a293-100ca21e69b0":{"position":{"vector":[-16,-304]}},"269c6ba4-e234-4d6f-8eae-71885445609f":{"position":{"vector":[-16,-368]}},"ebb73246-97e1-44f2-b2a9-31fd078bae54":{"position":{"vector":[-16,-432]}}},"import":{},"project":null}} \ No newline at end of file diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index f42e642eba07..374e588b88f2 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -7,7 +7,7 @@ import type { LazyObject } from '@/util/parserSupport' import { unsafeEntries } from '@/util/record' import * as random from 'lib0/random' import { reactive } from 'vue' -import type { ExprId } from '../../../shared/yjsModel' +import type { ExprId, SourceRange } from '../../../shared/yjsModel' import { IdMap } from '../../../shared/yjsModel' export interface Module { @@ -20,34 +20,37 @@ export interface Module { } export class MutableModule implements Module { - base: Module | null - nodes: Map + readonly base: Module | null + readonly nodes: Map astExtended: Map | null + spans: Map | null constructor( base: Module | null, nodes: Map, astExtended: Map | null, + spans: Map | null, ) { this.base = base this.nodes = nodes this.astExtended = astExtended + this.spans = spans } static Observable(): MutableModule { - const nodes = reactive(new Map()) - const astExtended = reactive(new Map()) - return new MutableModule(null, nodes, astExtended) + const nodes = reactive(new Map()) + const astExtended = reactive(new Map()) + const spans = reactive(new Map()) + return new MutableModule(null, nodes, astExtended, spans) } - static Transient(): MutableModule { + static Transient(base?: Module): MutableModule { const nodes = new Map() - return new MutableModule(null, nodes, null) + return new MutableModule(base ?? null, nodes, null, null) } edit(): MutableModule { - const nodes = new Map() - return new MutableModule(this, nodes, null) + return MutableModule.Transient(this) } get raw(): MutableModule { @@ -63,10 +66,18 @@ export class MutableModule implements Module { } this.astExtended = astExtended } + if (edit.spans) { + const spans = this.spans ?? new Map() + for (const [id, ast] of edit.spans.entries()) { + spans.set(id, ast) + } + this.spans = spans + } for (const [id, ast] of edit.nodes.entries()) { if (ast === null) { this.nodes.delete(id) this.astExtended?.delete(id) + this.spans?.delete(id) } else { this.nodes.set(id, ast) } @@ -79,6 +90,7 @@ export class MutableModule implements Module { if (!edit.nodes.has(id)) { this.nodes.delete(id) this.astExtended?.delete(id) + this.spans?.delete(id) } } this.apply(edit) @@ -191,6 +203,12 @@ export abstract class Ast { return this.exprId } + /** Return this node's span, if it belongs to a module with an associated span map. */ + get span(): SourceRange | undefined { + const spans = this.module.raw.spans + if (spans) return spans.get(this.astId) + } + /** Returns child subtrees, without information about the whitespace between them. */ *children(): IterableIterator { for (const child of this.concreteChildren()) { @@ -268,26 +286,23 @@ export abstract class Ast { const module_ = moduleOverride ?? this.module let code = '' for (const child of this.concreteChildren()) { - if (child.node != null && !(child.node instanceof Token) && module_.get(child.node) === null) + if (!(child.node instanceof Token) && module_.get(child.node) === null) continue if (child.whitespace != null) { code += child.whitespace } else if (code.length != 0) { - // TODO for #8367: Identify cases where a space should not be inserted. code += ' ' } - if (child.node != null) { - if (child.node instanceof Token) { - const tokenStart = offset + code.length - const tokenCode = child.node.code() - const span = tokenKey(tokenStart, tokenCode.length) - info.tokens.set(span, child.node.astId) - code += tokenCode - } else { - code += module_ - .get(child.node)! - ._print(info, offset + code.length, indent, moduleOverride) - } + if (child.node instanceof Token) { + const tokenStart = offset + code.length + const tokenCode = child.node.code() + const span = tokenKey(tokenStart, tokenCode.length) + info.tokens.set(span, child.node.exprId) + code += tokenCode + } else { + code += module_ + .get(child.node)! + ._print(info, offset + code.length, indent, moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) @@ -490,10 +505,35 @@ export class PropertyAccess extends OprApp { module: MutableModule, id: AstId | undefined, lhs: NodeChild | null, - opr: NodeChild, + opr: NodeChild | undefined, rhs: NodeChild | null, ) { - super(module, id, lhs, [opr], rhs) + const oprs = [opr ?? { whitespace: '', node: new Token('.', newTokenId(), RawAst.Token.Type.Operator) }] + super(module, id, lhs, oprs, rhs) + } + + static new( + module: MutableModule, + lhs: Ast | null, + rhs: Token | null, + ): PropertyAccess { + const lhs_ = lhs ? { node: lhs.exprId } : null + const rhs_ = rhs ? { whitespace: '', node: (new Ident(module, undefined, { whitespace: '', node: rhs })).exprId } : null + return new PropertyAccess(module, undefined, lhs_, undefined, rhs_) + } + + static Sequence(segments: string[], module?: MutableModule): PropertyAccess | Ident | undefined { + const module_ = module ?? MutableModule.Transient() + let path + for (const s of segments) { + const t = new Token(s, newTokenId(), RawAst.Token.Type.Ident) + if (!path) { + path = Ident.new(module_, s) + continue + } + path = PropertyAccess.new(module_, path, t) + } + return path } } @@ -517,6 +557,12 @@ export class Generic extends Ast { } type MultiSegmentAppSegment = { header: NodeChild; body: NodeChild | null } +function multiSegmentAppSegment(header: string, body: Ast): MultiSegmentAppSegment { + return { + header: { node: new Token(header, newTokenId(), RawAst.Token.Type.Ident) }, + body: { node: body.exprId }, + } +} export class Import extends Ast { _polyglot: MultiSegmentAppSegment | null @@ -569,6 +615,24 @@ export class Import extends Ast { this._hiding = hiding } + static Qualified(path: string[], module?: MutableModule): Import | undefined { + const module_ = module ?? MutableModule.Transient() + const path_ = PropertyAccess.Sequence(path, module) + if (!path_) return + const import_ = multiSegmentAppSegment('import', path_) + return new Import(module_, undefined, null, null, import_, null, null, null) + } + + static Unqualified(path: string[], name: string, module?: MutableModule): Import | undefined { + const module_ = module ?? MutableModule.Transient() + const path_ = PropertyAccess.Sequence(path, module) + if (!path_) return + const name_ = Ident.new(module_, name) + const from = multiSegmentAppSegment('from', path_) + const import_ = multiSegmentAppSegment('import', name_) + return new Import(module_, undefined, null, from, import_, null, null, null) + } + *concreteChildren(): IterableIterator { const segment = (segment: MultiSegmentAppSegment | null) => { const parts = [] @@ -606,12 +670,12 @@ export class TextLiteral extends Ast { this._close = close } - static new(rawText: string): TextLiteral { - const module = MutableModule.Transient() - const text = Token.new(escape(rawText)) - return new TextLiteral(module, undefined, { node: Token.new("'") }, null, [{ node: text }], { - node: Token.new("'"), - }) + static new(rawText: string, moduleIn?: MutableModule): TextLiteral { + const module = moduleIn ?? MutableModule.Transient() + const open = { node: Token.new("'") } + const elements = [{ whitespace: '', node: Token.new(escape(rawText)) }] + const close = { whitespace: '', node: Token.new("'") } + return new TextLiteral(module, undefined, open, null, elements, close) } *concreteChildren(): IterableIterator { @@ -1227,32 +1291,12 @@ export function functionBlock(module: Module, name: string): BodyBlock | null { return method.body } -/* -export function insertNewNodeAST( - block: BodyBlock, - ident: string, - expression: string, -): { assignment: AstId; value: AstId } { - const value = RawCode.new(undefined, expression)._id - const assignment = Assignment.new(undefined, ident, undefined, { node: value }) - block.pushExpression(assignment) - return { assignment: assignment._id, value } -} - -export function deleteExpressionAST(ast: Ast) { - ast.delete() -} - -export function replaceExpressionContentAST(id: AstId, code: string) { - return RawCode.new(id, code) -} - */ - export function parseTransitional(code: string, idMap: IdMap): Ast { const rawAst = RawAstExtended.parse(code, idMap) const nodes = new Map() const tokens = new Map() const astExtended = new Map() + const spans = new Map() rawAst.visitRecursive((nodeOrToken: RawAstExtended) => { const start = nodeOrToken.span()[0] const length = nodeOrToken.span()[1] - nodeOrToken.span()[0] @@ -1275,6 +1319,7 @@ export function parseTransitional(code: string, idMap: IdMap): Ast { id = newNodeId() } astExtended.set(id, node) + spans.set(id, node.span()) const key = nodeKey(start, length, node.inner.type) const ids = nodes.get(key) if (ids !== undefined) { @@ -1288,6 +1333,7 @@ export function parseTransitional(code: string, idMap: IdMap): Ast { const tokensOut = new Map() const newRoot = Ast.parse({ info: { nodes, tokens, tokensOut }, code }) newRoot.module.raw.astExtended = astExtended + newRoot.module.raw.spans = spans idMap.clear() // TODO (optimization): Use ID-match info collected while abstracting. /* diff --git a/app/gui2/src/util/ast/aliasAnalysis.ts b/app/gui2/src/util/ast/aliasAnalysis.ts index eef1a7d4c145..58d813263ebf 100644 --- a/app/gui2/src/util/ast/aliasAnalysis.ts +++ b/app/gui2/src/util/ast/aliasAnalysis.ts @@ -9,7 +9,7 @@ import { } from '@/util/ast' import { MappedKeyMap, MappedSet, NonEmptyStack } from '@/util/containers' import type { LazyObject } from '@/util/parserSupport' -import { IdMap, rangeIsBefore, type ContentRange } from 'shared/yjsModel' +import { IdMap, rangeIsBefore, type SourceRange } from 'shared/yjsModel' const ACCESSOR_OPERATOR = '.' @@ -32,7 +32,7 @@ class Scope { * @param parent The parent scope. */ constructor( - public range?: ContentRange, + public range?: SourceRange, public parent?: Scope, ) {} @@ -43,7 +43,7 @@ class Scope { * scope, so the variables are not visible before they are defined. If not provided, the lookup will include all * symbols from the scope. */ - resolve(identifier: string, location?: ContentRange): RawAst.Token | undefined { + resolve(identifier: string, location?: SourceRange): RawAst.Token | undefined { const localBinding = this.bindings.get(identifier) if ( localBinding != null && @@ -94,7 +94,7 @@ export function identifierKind(identifier: string): IdentifierType { export class AliasAnalyzer { /** All symbols that are not yet resolved (i.e. that were not bound in the analyzed tree). */ - readonly unresolvedSymbols = new MappedSet(IdMap.keyForRange) + readonly unresolvedSymbols = new MappedSet(IdMap.keyForRange) /** The AST representation of the code. */ readonly ast: RawAst.Tree @@ -108,9 +108,7 @@ export class AliasAnalyzer { /** The stack for keeping track whether we are in a pattern or expression context. */ private readonly contexts: NonEmptyStack = new NonEmptyStack(Context.Expression) - public readonly aliases = new MappedKeyMap>( - IdMap.keyForRange, - ) + public readonly aliases = new MappedKeyMap>(IdMap.keyForRange) /** * @param code text representation of the code. @@ -126,7 +124,7 @@ export class AliasAnalyzer { } /** Invoke the given function in a new temporary scope. */ - withNewScopeOver(nodeOrRange: ContentRange | RawAst.Tree | RawAst.Token, f: () => undefined) { + withNewScopeOver(nodeOrRange: SourceRange | RawAst.Tree | RawAst.Token, f: () => undefined) { const range = parsedTreeOrTokenRange(nodeOrRange) const scope = new Scope(range, this.scopes.top) this.scopes.withPushed(scope, f) @@ -145,7 +143,7 @@ export class AliasAnalyzer { log(() => `Binding ${identifier}@[${range}]`) scope.bindings.set(identifier, token) assert(!this.aliases.has(range), `Token at ${range} is already bound.`) - this.aliases.set(range, new MappedSet(IdMap.keyForRange)) + this.aliases.set(range, new MappedSet(IdMap.keyForRange)) } addConnection(source: RawAst.Token, target: RawAst.Token) { @@ -320,7 +318,7 @@ export class AliasAnalyzer { ? parsedTreeOrTokenRange(arrow)[1] : parsedTreeOrTokenRange(pattern)[1] - const armRange: ContentRange = [armStart, armEnd] + const armRange: SourceRange = [armStart, armEnd] this.withNewScopeOver(armRange, () => { this.withContext(Context.Pattern, () => { this.processTree(caseLine.case?.pattern) diff --git a/app/gui2/src/util/ast/extended.ts b/app/gui2/src/util/ast/extended.ts index 8b42059052cd..4057938390af 100644 --- a/app/gui2/src/util/ast/extended.ts +++ b/app/gui2/src/util/ast/extended.ts @@ -15,7 +15,7 @@ import type { Opt } from '@/util/data/opt' import * as encoding from 'lib0/encoding' import * as sha256 from 'lib0/hash/sha256' import * as map from 'lib0/map' -import type { ContentRange, ExprId, IdMap } from 'shared/yjsModel' +import type { ExprId, IdMap, SourceRange } from 'shared/yjsModel' import { markRaw } from 'vue' type ExtractType = T extends ReadonlyArray @@ -147,7 +147,7 @@ export class AstExtended boolean): RawAst export function findAstWithRange( root: RawAst.Tree | RawAst.Token, - range: ContentRange, + range: SourceRange, ): RawAst.Tree | RawAst.Token | undefined { for (const child of childrenAstNodes(root)) { const [begin, end] = parsedTreeOrTokenRange(child) @@ -165,19 +165,19 @@ export function visitRecursive( * @returns Object with `start` and `end` properties; index of first character in the `node` * and first character _not_ being in the `node`. */ -export function parsedTreeRange(tree: RawAst.Tree): ContentRange { +export function parsedTreeRange(tree: RawAst.Tree): SourceRange { const start = tree.whitespaceStartInCodeParsed + tree.whitespaceLengthInCodeParsed const end = start + tree.childrenLengthInCodeParsed return [start, end] } -export function parsedTokenRange(token: RawAst.Token): ContentRange { +export function parsedTokenRange(token: RawAst.Token): SourceRange { const start = token.startInCodeBuffer const end = start + token.lengthInCodeBuffer return [start, end] } -export function parsedTreeOrTokenRange(node: HasAstRange): ContentRange { +export function parsedTreeOrTokenRange(node: HasAstRange): SourceRange { if (RawAst.Tree.isInstance(node)) return parsedTreeRange(node) else if (RawAst.Token.isInstance(node)) return parsedTokenRange(node) else return node diff --git a/app/gui2/stories/GraphNode.story.vue b/app/gui2/stories/GraphNode.story.vue index 71f59176b904..af05bba9b18e 100644 --- a/app/gui2/stories/GraphNode.story.vue +++ b/app/gui2/stories/GraphNode.story.vue @@ -12,7 +12,7 @@ import { Vec2 } from '@/util/data/vec2' import { useNavigator } from '@/util/navigator' import { Rect } from '@/util/rect' import { reactive, watchEffect } from 'vue' -import { IdMap, type ContentRange } from '../shared/yjsModel' +import { IdMap, type SourceRange } from '../shared/yjsModel' import { createSetupComponent } from './histoire/utils' const doc = new Y.Doc() @@ -29,7 +29,7 @@ const fullscreenVis = ref(false) const position = computed(() => new Vec2(nodeX.value, nodeY.value)) -function updateContent(updates: [range: ContentRange, content: string][]) { +function updateContent(updates: [range: SourceRange, content: string][]) { let content = nodeContent.value for (const [[start, end], replacement] of updates) { content = content.slice(0, start) + replacement + content.slice(end) @@ -53,7 +53,7 @@ const node = computed((): Node => { const mockRects = reactive(new Map()) watchEffect((onCleanup) => { - const id = node.value.rootSpan.astId + const id = node.value.rootSpan.exprId mockRects.set(id, Rect.Zero) onCleanup(() => { mockRects.delete(id) diff --git a/app/gui2/ydoc-server/__tests__/edits.test.ts b/app/gui2/ydoc-server/__tests__/edits.test.ts index f34ab599df82..47f24296d1ca 100644 --- a/app/gui2/ydoc-server/__tests__/edits.test.ts +++ b/app/gui2/ydoc-server/__tests__/edits.test.ts @@ -1,8 +1,7 @@ import { fc, test } from '@fast-check/vitest' import { Position, TextEdit } from 'shared/languageServerTypes' import { describe, expect } from 'vitest' -import * as Y from 'yjs' -import { applyDiffAsTextEdits, convertDeltaToTextEdits } from '../edits' +import { applyDiffAsTextEdits } from '../edits' // ====================== // === Test utilities === diff --git a/app/gui2/ydoc-server/edits.ts b/app/gui2/ydoc-server/edits.ts index 1a09506206ab..dc56ffc106c2 100644 --- a/app/gui2/ydoc-server/edits.ts +++ b/app/gui2/ydoc-server/edits.ts @@ -60,7 +60,7 @@ export function applyDocumentUpdates( let metaContent = META_TAG + '\n' if (idMapUpdated || synced.idMapJson == null) { - const idMapJson = json.stringify(idMapToArray(doc.getIdMap())) + const idMapJson = serializeIdMap(doc.getIdMap()) metaContent += idMapJson + '\n' } else { metaContent += (synced.idMapJson ?? '[]') + '\n' @@ -183,6 +183,10 @@ export function preParseContent(content: string): PreParsedContent { return { code, idMapJson, metadataJson } } +export function serializeIdMap(map: IdMap): string { + return json.stringify(idMapToArray(map)) +} + function idMapToArray(map: IdMap): fileFormat.IdMapEntry[] { const entries: fileFormat.IdMapEntry[] = [] map.entries().forEach(([rangeBuffer, id]) => { diff --git a/app/gui2/ydoc-server/languageServerSession.ts b/app/gui2/ydoc-server/languageServerSession.ts index 12221fc343b1..fb08b7c7213a 100644 --- a/app/gui2/ydoc-server/languageServerSession.ts +++ b/app/gui2/ydoc-server/languageServerSession.ts @@ -245,6 +245,20 @@ function pushPathSegment(path: Path, segment: string): Path { return { rootId: path.rootId, segments: [...path.segments, segment] } } +export function deserializeIdMap(idMapJson: string) { + const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) + const idMap = new IdMap() + for (const [{ index, size }, id] of idMapMeta) { + const range = [index.value, index.value + size.value] + if (typeof range[0] !== 'number' || typeof range[1] !== 'number') { + console.error(`Invalid range for id ${id}:`, range) + continue + } + idMap.insertKnownId([index.value, index.value + size.value], id as ExprId) + } + return idMap +} + enum LsSyncState { Closed, Opening, @@ -475,21 +489,10 @@ class ModulePersistence extends ObservableV2<{ removed: () => void }> { private syncFileContents(content: string, version: Checksum) { this.doc.ydoc.transact(() => { const { code, idMapJson, metadataJson } = preParseContent(content) - const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) const metadata = fileFormat.tryParseMetadataOrFallback(metadataJson) const nodeMeta = metadata.ide.node - const idMap = new IdMap() - for (const [{ index, size }, id] of idMapMeta) { - const start = index.value - const end = index.value + size.value - const range: SourceRange = [start, end] - if (typeof start !== 'number' || typeof end !== 'number') { - console.error(`Invalid range for id ${id}:`, range) - continue - } - idMap.insertKnownId(range, id as ExprId) - } + const idMap = idMapJson ? deserializeIdMap(idMapJson) : new IdMap() this.doc.setIdMap(idMap) const keysToDelete = new Set(this.doc.metadata.keys()) From c18a1237f659b1f8a7e1d99caad09fd983c020f0 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Thu, 21 Dec 2023 14:34:01 +0000 Subject: [PATCH 02/12] Improve and test imports generation --- app/gui2/src/stores/graph/imports.ts | 76 ++++++- app/gui2/src/stores/graph/index.ts | 36 +--- app/gui2/src/util/ast/abstract.ts | 309 +++++++++++++++------------ 3 files changed, 250 insertions(+), 171 deletions(-) diff --git a/app/gui2/src/stores/graph/imports.ts b/app/gui2/src/stores/graph/imports.ts index 328c34c001b7..cafdc418eef0 100644 --- a/app/gui2/src/stores/graph/imports.ts +++ b/app/gui2/src/stores/graph/imports.ts @@ -9,18 +9,19 @@ import { type SuggestionEntry, } from '@/stores/suggestionDatabase/entry' import { Ast } from '@/util/ast' +import { MutableModule } from '@/util/ast/abstract' import { unwrap } from '@/util/data/result' import { identifierUnchecked, normalizeQualifiedName, qnFromSegments, + qnSegments, qnSplit, tryIdentifier, tryQualifiedName, type Identifier, - type QualifiedName, qnSegments + type QualifiedName, } from '@/util/qualifiedName' -import type { MutableModule } from '@/util/ast/abstract' // ======================== // === Imports analysis === @@ -68,8 +69,8 @@ export function recognizeImport(ast: Ast.Import): Import | null { const import_ = ast.import_ const all = ast.all const hiding = ast.hiding - const module = - from != null ? parseQualifiedName(from) : import_ != null ? parseQualifiedName(import_) : null + const moduleAst = from ?? import_ + const module = moduleAst ? parseQualifiedName(moduleAst) : null if (!module) return null if (all) { const except = (hiding != null ? parseIdents(hiding) : []) ?? [] @@ -143,13 +144,45 @@ export interface UnqualifiedImport { import: Identifier } -/** Get an AST representing the required import statement. */ -export function requiredImportToAst(module: MutableModule, value: RequiredImport): Ast.Import { +/** Insert the given imports into the given block at an appropriate location. */ +export function addImports( + edit: MutableModule, + scope: Ast.BodyBlock, + importsToAdd: RequiredImport[], +) { + const imports = importsToAdd.map((info) => requiredImportToAst(info, edit)) + const position = newImportsLocation(edit, scope) + scope.insert(edit, position, ...imports) +} + +/** Return a suitable location in the given block to insert an import statement. + * + * The location chosen will be before the first non-import line, and after all preexisting imports. + * If there are any blank lines in that range, it will be before them. + */ +function newImportsLocation(module: Ast.Module, scope: Ast.BodyBlock): number { + let lastImport + for (let i = 0; i < scope.lines.length; i++) { + const line = scope.lines[i]! + if (line.expression) { + if (module.get(line.expression.node)?.innerExpression() instanceof Ast.Import) { + lastImport = i + } else { + break + } + } + } + return lastImport === undefined ? 0 : lastImport + 1 +} + +/** Create an AST representing the required import statement. */ +function requiredImportToAst(value: RequiredImport, module?: MutableModule): Ast.Import { + const module_ = module ?? MutableModule.Transient() switch (value.kind) { case 'Qualified': - return Ast.Import.Qualified(qnSegments(value.module), module)! + return Ast.Import.Qualified(qnSegments(value.module), module_)! case 'Unqualified': - return Ast.Import.Unqualified(qnSegments(value.from), value.import, module)! + return Ast.Import.Unqualified(qnSegments(value.from), value.import, module_)! } } @@ -570,4 +603,31 @@ if (import.meta.vitest) { ])('Recognizing import $code', ({ code, expected }) => { expect(parseImport(code)).toStrictEqual(expected) }) + + test.each([ + { + import: { + kind: 'Unqualified', + from: unwrap(tryQualifiedName('Standard.Base.Table')), + import: unwrap(tryIdentifier('Table')), + } satisfies RequiredImport, + expected: 'from Standard.Base.Table import Table', + }, + { + import: { + kind: 'Qualified', + module: unwrap(tryQualifiedName('Standard.Base.Data')), + } satisfies RequiredImport, + expected: 'import Standard.Base.Data', + }, + { + import: { + kind: 'Qualified', + module: unwrap(tryQualifiedName('local')), + } satisfies RequiredImport, + expected: 'import local', + }, + ])('Generating import $expected', ({ import: import_, expected }) => { + expect(requiredImportToAst(import_).code()).toStrictEqual(expected) + }) } diff --git a/app/gui2/src/stores/graph/index.ts b/app/gui2/src/stores/graph/index.ts index 1ffe78f50102..8b8f1c68c14b 100644 --- a/app/gui2/src/stores/graph/index.ts +++ b/app/gui2/src/stores/graph/index.ts @@ -1,9 +1,9 @@ import { nonDictatedPlacement } from '@/components/ComponentBrowser/placement' import { GraphDb } from '@/stores/graph/graphDatabase' import { + addImports, filterOutRedundantImports, recognizeImport, - requiredImportToAst, type Import, type RequiredImport, } from '@/stores/graph/imports' @@ -49,7 +49,7 @@ export const useGraphStore = defineStore('graph', () => { // We need casting here, as type changes in Ref when class has private fields. // see https://github.com/vuejs/core/issues/2557 const idMap = ref(proj.module?.doc.getIdMap()) as Ref - const expressionGraph: Module = MutableModule.Observable() + const astModule: Module = MutableModule.Observable() const moduleRoot = ref() // Initialize text and idmap once module is loaded (data != null) @@ -96,7 +96,7 @@ export const useGraphStore = defineStore('graph', () => { if (!textContentLocal) return const newRoot = Ast.parseTransitional(textContentLocal, idMap_) - expressionGraph.replace(newRoot.module) + astModule.replace(newRoot.module) moduleRoot.value = newRoot.exprId module.doc.setIdMap(idMap_) @@ -194,13 +194,13 @@ export const useGraphStore = defineStore('graph', () => { console.error(`BUG: Cannot add node: No module root.`) return } - const edit = expressionGraph.edit() + const edit = astModule.edit() const importsToAdd = withImports ? filterOutRedundantImports(imports.value, withImports) : [] // The top level of the module is always a block. - const topLevel = expressionGraph.get(root)! as Ast.BodyBlock - addImports(edit, topLevel, importsToAdd) + const topLevel = astModule.get(root)! as Ast.BodyBlock + if (importsToAdd) addImports(edit, topLevel, importsToAdd) const currentFunc = 'main' - const functionBlock = Ast.functionBlock(expressionGraph, currentFunc) + const functionBlock = Ast.functionBlock(astModule, currentFunc) if (!functionBlock) { console.error(`BUG: Cannot add node: No current function.`) return @@ -211,24 +211,6 @@ export const useGraphStore = defineStore('graph', () => { commitEdit(edit, root, new Map([[rhs.exprId, meta]])) } - function addImports(edit: MutableModule, scope: Ast.BodyBlock, importsToAdd: RequiredImport[]) { - if (!importsToAdd.length) return - const imports = importsToAdd.map((info) => requiredImportToAst(edit, info)) - let lastImport - for (let i = 0; i < scope.lines.length; i++) { - const line = scope.lines[i]! - if (line.expression) { - if (expressionGraph.get(line.expression.node)?.innerExpression() instanceof Ast.Import) { - lastImport = i - } else { - break - } - } - } - const position = lastImport === undefined ? 0 : lastImport + 1 - scope.insert(edit, position, ...imports) - } - function deleteNode(id: ExprId) { const node = db.nodeIdToNode.get(id) if (!node) return @@ -240,7 +222,7 @@ export const useGraphStore = defineStore('graph', () => { console.error(`BUG: Cannot delete node: No module root.`) return } - const edit = expressionGraph.edit() + const edit = astModule.edit() edit.delete(node.outerExprId) commitEdit(edit, root) } @@ -252,7 +234,7 @@ export const useGraphStore = defineStore('graph', () => { } function setExpressionContent(id: ExprId, content: string) { - const edit = expressionGraph.edit() + const edit = astModule.edit() edit.set(Ast.asNodeId(id), Ast.RawCode.new(content, edit)) const root = moduleRoot.value if (!root) return diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index 374e588b88f2..db7bf7a74fb9 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -206,7 +206,7 @@ export abstract class Ast { /** Return this node's span, if it belongs to a module with an associated span map. */ get span(): SourceRange | undefined { const spans = this.module.raw.spans - if (spans) return spans.get(this.astId) + if (spans) return spans.get(this.exprId) } /** Returns child subtrees, without information about the whitespace between them. */ @@ -286,8 +286,7 @@ export abstract class Ast { const module_ = moduleOverride ?? this.module let code = '' for (const child of this.concreteChildren()) { - if (!(child.node instanceof Token) && module_.get(child.node) === null) - continue + if (!(child.node instanceof Token) && module_.get(child.node) === null) continue if (child.whitespace != null) { code += child.whitespace } else if (code.length != 0) { @@ -300,9 +299,7 @@ export abstract class Ast { info.tokens.set(span, child.node.exprId) code += tokenCode } else { - code += module_ - .get(child.node)! - ._print(info, offset + code.length, indent, moduleOverride) + code += module_.get(child.node)!._print(info, offset + code.length, indent, moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) @@ -317,23 +314,23 @@ export abstract class Ast { } export class App extends Ast { - _func: NodeChild - _leftParen: NodeChild | null - _argumentName: NodeChild | null - _equals: NodeChild | null - _arg: NodeChild - _rightParen: NodeChild | null + private readonly func_: NodeChild + private readonly leftParen_: NodeChild | null + private readonly argumentName_: NodeChild | null + private readonly equals_: NodeChild | null + private readonly arg_: NodeChild + private readonly rightParen_: NodeChild | null get function(): Ast { - return this.module.get(this._func.node)! + return this.module.get(this.func_.node)! } get argumentName(): Token | null { - return this._argumentName?.node ?? null + return this.argumentName_?.node ?? null } get argument(): Ast { - return this.module.get(this._arg.node)! + return this.module.get(this.arg_.node)! } constructor( @@ -348,22 +345,22 @@ export class App extends Ast { treeType: RawAst.Tree.Type, ) { super(module, id, treeType) - this._func = func - this._leftParen = leftParen - this._argumentName = name - this._equals = equals - this._arg = arg - this._rightParen = rightParen + this.func_ = func + this.leftParen_ = leftParen + this.argumentName_ = name + this.equals_ = equals + this.arg_ = arg + this.rightParen_ = rightParen } *concreteChildren(): IterableIterator { - yield this._func - if (this._leftParen) yield this._leftParen - if (this._argumentName) yield this._argumentName - if (this._equals) - yield { whitespace: this._equals.whitespace ?? this._arg.whitespace, node: this._equals.node } - yield this._arg - if (this._rightParen) yield this._rightParen + yield this.func_ + if (this.leftParen_) yield this.leftParen_ + if (this.argumentName_) yield this.argumentName_ + if (this.equals_) + yield { whitespace: this.equals_.whitespace ?? this.arg_.whitespace, node: this.equals_.node } + yield this.arg_ + if (this.rightParen_) yield this.rightParen_ } } @@ -418,15 +415,15 @@ function namedApp( } export class UnaryOprApp extends Ast { - _opr: NodeChild - _arg: NodeChild | null + private readonly opr: NodeChild + private readonly arg: NodeChild | null get operator(): Token { - return this._opr.node + return this.opr.node } get argument(): Ast | null { - const id = this._arg?.node + const id = this.arg?.node return id ? this.module.get(id) : null } @@ -437,13 +434,13 @@ export class UnaryOprApp extends Ast { arg: NodeChild | null, ) { super(module, id, RawAst.Tree.Type.UnaryOprApp) - this._opr = opr - this._arg = arg + this.opr = opr + this.arg = arg } *concreteChildren(): IterableIterator { - yield this._opr - if (this._arg) yield this._arg + yield this.opr + if (this.arg) yield this.arg } } @@ -459,25 +456,25 @@ export class NegationOprApp extends UnaryOprApp { } export class OprApp extends Ast { - _lhs: NodeChild | null - _opr: NodeChild[] - _rhs: NodeChild | null + private readonly lhs_: NodeChild | null + private readonly opr_: NodeChild[] + private readonly rhs_: NodeChild | null get lhs(): Ast | null { - return this._lhs ? this.module.get(this._lhs.node) : null + return this.lhs_ ? this.module.get(this.lhs_.node) : null } get operator(): Result { - const first = this._opr[0]?.node - if (first && this._opr.length < 2 && first instanceof Token) { + const first = this.opr_[0]?.node + if (first && this.opr_.length < 2 && first instanceof Token) { return Ok(first) } else { - return Err(this._opr) + return Err(this.opr_) } } get rhs(): Ast | null { - return this._rhs ? this.module.get(this._rhs.node) : null + return this.rhs_ ? this.module.get(this.rhs_.node) : null } constructor( @@ -488,15 +485,15 @@ export class OprApp extends Ast { rhs: NodeChild | null, ) { super(module, id, RawAst.Tree.Type.OprApp) - this._lhs = lhs - this._opr = opr - this._rhs = rhs + this.lhs_ = lhs + this.opr_ = opr + this.rhs_ = rhs } *concreteChildren(): IterableIterator { - if (this._lhs) yield this._lhs - for (const opr of this._opr) yield opr - if (this._rhs) yield this._rhs + if (this.lhs_) yield this.lhs_ + for (const opr of this.opr_) yield opr + if (this.rhs_) yield this.rhs_ } } @@ -508,17 +505,17 @@ export class PropertyAccess extends OprApp { opr: NodeChild | undefined, rhs: NodeChild | null, ) { - const oprs = [opr ?? { whitespace: '', node: new Token('.', newTokenId(), RawAst.Token.Type.Operator) }] + const oprs = [ + opr ?? { whitespace: '', node: new Token('.', newTokenId(), RawAst.Token.Type.Operator) }, + ] super(module, id, lhs, oprs, rhs) } - static new( - module: MutableModule, - lhs: Ast | null, - rhs: Token | null, - ): PropertyAccess { + static new(module: MutableModule, lhs: Ast | null, rhs: Token | null): PropertyAccess { const lhs_ = lhs ? { node: lhs.exprId } : null - const rhs_ = rhs ? { whitespace: '', node: (new Ident(module, undefined, { whitespace: '', node: rhs })).exprId } : null + const rhs_ = rhs + ? { whitespace: '', node: new Ident(module, undefined, { whitespace: '', node: rhs }).exprId } + : null return new PropertyAccess(module, undefined, lhs_, undefined, rhs_) } @@ -539,7 +536,7 @@ export class PropertyAccess extends OprApp { /** Representation without any type-specific accessors, for tree types that don't require any special treatment. */ export class Generic extends Ast { - _children: NodeChild[] + private readonly children_: NodeChild[] constructor( module: MutableModule, @@ -548,11 +545,11 @@ export class Generic extends Ast { treeType?: RawAst.Tree.Type, ) { super(module, id, treeType) - this._children = children ?? [] + this.children_ = children ?? [] } concreteChildren(): IterableIterator { - return this._children.values() + return this.children_.values() } } @@ -565,35 +562,35 @@ function multiSegmentAppSegment(header: string, body: Ast): MultiSegmentAppSegme } export class Import extends Ast { - _polyglot: MultiSegmentAppSegment | null - _from: MultiSegmentAppSegment | null - _import: MultiSegmentAppSegment - _all: NodeChild | null - _as: MultiSegmentAppSegment | null - _hiding: MultiSegmentAppSegment | null + private readonly polyglot_: MultiSegmentAppSegment | null + private readonly from_: MultiSegmentAppSegment | null + private readonly import__: MultiSegmentAppSegment + private readonly all_: NodeChild | null + private readonly as_: MultiSegmentAppSegment | null + private readonly hiding_: MultiSegmentAppSegment | null get polyglot(): Ast | null { - return this._polyglot?.body ? this.module.get(this._polyglot.body.node) : null + return this.polyglot_?.body ? this.module.get(this.polyglot_.body.node) : null } get from(): Ast | null { - return this._from?.body ? this.module.get(this._from.body.node) : null + return this.from_?.body ? this.module.get(this.from_.body.node) : null } get import_(): Ast | null { - return this._import?.body ? this.module.get(this._import.body.node) : null + return this.import__?.body ? this.module.get(this.import__.body.node) : null } get all(): Token | null { - return this._all?.node ?? null + return this.all_?.node ?? null } get as(): Ast | null { - return this._as?.body ? this.module.get(this._as.body.node) : null + return this.as_?.body ? this.module.get(this.as_.body.node) : null } get hiding(): Ast | null { - return this._hiding?.body ? this.module.get(this._hiding.body.node) : null + return this.hiding_?.body ? this.module.get(this.hiding_.body.node) : null } constructor( @@ -607,12 +604,12 @@ export class Import extends Ast { hiding: MultiSegmentAppSegment | null, ) { super(module, id, RawAst.Tree.Type.Import) - this._polyglot = polyglot - this._from = from - this._import = import_ - this._all = all - this._as = as - this._hiding = hiding + this.polyglot_ = polyglot + this.from_ = from + this.import__ = import_ + this.all_ = all + this.as_ = as + this.hiding_ = hiding } static Qualified(path: string[], module?: MutableModule): Import | undefined { @@ -640,20 +637,20 @@ export class Import extends Ast { if (segment?.body) parts.push(segment.body) return parts } - yield* segment(this._polyglot) - yield* segment(this._from) - yield* segment(this._import) - if (this._all) yield this._all - yield* segment(this._as) - yield* segment(this._hiding) + yield* segment(this.polyglot_) + yield* segment(this.from_) + yield* segment(this.import__) + if (this.all_) yield this.all_ + yield* segment(this.as_) + yield* segment(this.hiding_) } } export class TextLiteral extends Ast { - _open: NodeChild | null - _newline: NodeChild | null - _elements: NodeChild[] - _close: NodeChild | null + private readonly open_: NodeChild | null + private readonly newline_: NodeChild | null + private readonly elements_: NodeChild[] + private readonly close_: NodeChild | null constructor( module: MutableModule, @@ -664,10 +661,10 @@ export class TextLiteral extends Ast { close: NodeChild | null, ) { super(module, id, RawAst.Tree.Type.TextLiteral) - this._open = open - this._newline = newline - this._elements = elements - this._close = close + this.open_ = open + this.newline_ = newline + this.elements_ = elements + this.close_ = close } static new(rawText: string, moduleIn?: MutableModule): TextLiteral { @@ -679,30 +676,59 @@ export class TextLiteral extends Ast { } *concreteChildren(): IterableIterator { - if (this._open) yield this._open - if (this._newline) yield this._newline - yield* this._elements - if (this._close) yield this._close + if (this.open_) yield this.open_ + if (this.newline_) yield this.newline_ + yield* this.elements_ + if (this.close_) yield this.close_ + } +} + +export class Documented extends Ast { + private readonly open_: NodeChild | null + private readonly elements_: NodeChild[] + private readonly newlines_: NodeChild[] + private readonly expression_: NodeChild | null + + constructor( + module: MutableModule, + id: AstId | undefined, + open: NodeChild | null, + elements: NodeChild[], + newlines: NodeChild[], + expression: NodeChild | null, + ) { + super(module, id, RawAst.Tree.Type.Documented) + this.open_ = open + this.elements_ = elements + this.newlines_ = newlines + this.expression_ = expression + } + + *concreteChildren(): IterableIterator { + if (this.open_) yield this.open_ + yield* this.elements_ + yield* this.newlines_ + if (this.expression_) yield this.expression_ } } export class Invalid extends Ast { - _expression: NodeChild + private readonly expression_: NodeChild constructor(module: MutableModule, id: AstId | undefined, expression: NodeChild) { super(module, id, RawAst.Tree.Type.Invalid) - this._expression = expression + this.expression_ = expression } *concreteChildren(): IterableIterator { - yield this._expression + yield this.expression_ } } export class Group extends Ast { - _open: NodeChild | undefined - _expression: NodeChild | null - _close: NodeChild | undefined + private readonly open_: NodeChild | undefined + private readonly expression_: NodeChild | null + private readonly close_: NodeChild | undefined constructor( module: MutableModule, @@ -712,48 +738,48 @@ export class Group extends Ast { close: NodeChild | undefined, ) { super(module, id, RawAst.Tree.Type.Group) - this._open = open - this._expression = expression - this._close = close + this.open_ = open + this.expression_ = expression + this.close_ = close } *concreteChildren(): IterableIterator { - if (this._open) yield this._open - if (this._expression) yield this._expression - if (this._close) yield this._close + if (this.open_) yield this.open_ + if (this.expression_) yield this.expression_ + if (this.close_) yield this.close_ } } export class NumericLiteral extends Ast { - _tokens: NodeChild[] + private readonly tokens_: NodeChild[] constructor(module: MutableModule, id: AstId | undefined, tokens: NodeChild[]) { super(module, id, RawAst.Tree.Type.Number) - this._tokens = tokens ?? [] + this.tokens_ = tokens ?? [] } concreteChildren(): IterableIterator { - return this._tokens.values() + return this.tokens_.values() } } type FunctionArgument = NodeChild[] export class Function extends Ast { - _name: NodeChild - _args: FunctionArgument[] - _equals: NodeChild - _body: NodeChild | null + private readonly name_: NodeChild + private readonly args_: FunctionArgument[] + private readonly equals_: NodeChild + private readonly body_: NodeChild | null // FIXME for #8367: This should not be nullable. If the `ExprId` has been deleted, the same placeholder logic should be applied // here and in `rawChildren` (and indirectly, `print`). get name(): Ast | null { - return this.module.get(this._name.node) + return this.module.get(this.name_.node) } get body(): Ast | null { - return this._body ? this.module.get(this._body.node) : null + return this.body_ ? this.module.get(this.body_.node) : null } *bodyExpressions(): IterableIterator { - const body = this._body ? this.module.get(this._body.node) : null + const body = this.body_ ? this.module.get(this.body_.node) : null if (body instanceof BodyBlock) { yield* body.expressions() } else if (body !== null) { @@ -769,25 +795,25 @@ export class Function extends Ast { body: NodeChild | null, ) { super(module, id, RawAst.Tree.Type.Function) - this._name = name - this._args = args - this._equals = equals - this._body = body + this.name_ = name + this.args_ = args + this.equals_ = equals + this.body_ = body } *concreteChildren(): IterableIterator { - yield this._name - for (const arg of this._args) yield* arg - yield { whitespace: this._equals.whitespace ?? ' ', node: this._equals.node } - if (this._body !== null) { - yield this._body + yield this.name_ + for (const arg of this.args_) yield* arg + yield { whitespace: this.equals_.whitespace ?? ' ', node: this.equals_.node } + if (this.body_ !== null) { + yield this.body_ } } } export class Assignment extends Ast { - private pattern_: NodeChild - private equals_: NodeChild - private expression_: NodeChild + private readonly pattern_: NodeChild + private readonly equals_: NodeChild + private readonly expression_: NodeChild get pattern(): Ast | null { return this.module.get(this.pattern_.node) } @@ -916,7 +942,7 @@ export class BodyBlock extends Ast { } export class Ident extends Ast { - public token: NodeChild + private readonly token: NodeChild constructor(module: MutableModule, id: AstId | undefined, token: NodeChild) { super(module, id, RawAst.Tree.Type.Ident) @@ -935,7 +961,7 @@ export class Ident extends Ast { } export class Wildcard extends Ast { - public token: NodeChild + private readonly token: NodeChild constructor(module: MutableModule, id: AstId | undefined, token: NodeChild) { super(module, id, RawAst.Tree.Type.Wildcard) @@ -956,11 +982,11 @@ export class Wildcard extends Ast { } export class RawCode extends Ast { - _code: NodeChild + private readonly code_: NodeChild constructor(module: MutableModule, id: AstId | undefined, code: NodeChild) { super(module, id) - this._code = code + this.code_ = code } static new(code: string, moduleIn?: MutableModule, id?: AstId | undefined): RawCode { @@ -970,7 +996,7 @@ export class RawCode extends Ast { } *concreteChildren(): IterableIterator { - yield this._code + yield this.code_ } } @@ -1143,12 +1169,23 @@ function abstractTree( for (const e of tree.elements) { elements.push(...visitChildren(e)) } - visitChildren(tree) const close = tree.close ? recurseToken(tree.close) : null const id = nodesExpected.get(spanKey)?.pop() node = new TextLiteral(module, id, open, newline, elements, close).exprId break } + case RawAst.Tree.Type.Documented: { + const open = recurseToken(tree.documentation.open) + const elements = [] + for (const e of tree.documentation.elements) { + elements.push(...visitChildren(e)) + } + const newlines = Array.from(tree.documentation.newlines, recurseToken) + const id = nodesExpected.get(spanKey)?.pop() + const expression = tree.expression ? recurseTree(tree.expression) : null + node = new Documented(module, id, open, elements, newlines, expression).exprId + break + } case RawAst.Tree.Type.Import: { const recurseSegment = (segment: RawAst.MultiSegmentAppSegment) => ({ header: recurseToken(segment.header), From 700a09040361b42d0dc69e2720ea789f2be3767d Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Fri, 22 Dec 2023 13:13:16 +0000 Subject: [PATCH 03/12] Testing --- app/gui2/shared/yjsModel.ts | 26 ++++++++++ .../src/components/ComponentBrowser/input.ts | 2 +- app/gui2/src/components/GraphEditor.vue | 2 +- .../src/components/GraphEditor/GraphNode.vue | 2 +- .../__tests__/widgetRegistry.test.ts | 4 +- app/gui2/src/stores/graph/graphDatabase.ts | 10 ++-- app/gui2/src/stores/graph/imports.ts | 16 +++++- app/gui2/src/stores/graph/index.ts | 7 ++- .../src/util/ast/__tests__/abstract.test.ts | 24 ++++----- .../src/util/ast/__tests__/callTree.test.ts | 2 +- app/gui2/src/util/ast/__tests__/match.test.ts | 2 +- .../src/util/ast/__tests__/prefixes.test.ts | 2 +- app/gui2/src/util/ast/abstract.ts | 52 ++++++++++--------- app/gui2/src/util/ast/match.ts | 2 +- app/gui2/ydoc-server/edits.ts | 30 +---------- app/gui2/ydoc-server/languageServerSession.ts | 17 +----- app/gui2/ydoc-server/serialization.ts | 49 +++++++++++++++++ 17 files changed, 148 insertions(+), 101 deletions(-) create mode 100644 app/gui2/ydoc-server/serialization.ts diff --git a/app/gui2/shared/yjsModel.ts b/app/gui2/shared/yjsModel.ts index 49cd15004b0d..7daf78026e68 100644 --- a/app/gui2/shared/yjsModel.ts +++ b/app/gui2/shared/yjsModel.ts @@ -230,6 +230,32 @@ export class IdMap { } return true } + + validate() { + const uniqueValues = new Set(this.rangeToExpr.values()) + if (uniqueValues.size < this.rangeToExpr.size) { + console.warn(`Duplicate UUID in IdMap`) + } + } + + clone(): IdMap { + return new IdMap(this.entries()) + } + + // Debugging. + compare(other: IdMap) { + console.info(`IdMap.compare -------`) + const allKeys = new Set() + for (const key of this.rangeToExpr.keys()) allKeys.add(key) + for (const key of other.rangeToExpr.keys()) allKeys.add(key) + for (const key of allKeys) { + const mine = this.rangeToExpr.get(key) + const yours = other.rangeToExpr.get(key) + if (mine !== yours) { + console.info(`IdMap.compare[${key}]: ${mine} -> ${yours}`) + } + } + } } const uuidRegex = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/ diff --git a/app/gui2/src/components/ComponentBrowser/input.ts b/app/gui2/src/components/ComponentBrowser/input.ts index 51d321c73ff8..c88859cfc256 100644 --- a/app/gui2/src/components/ComponentBrowser/input.ts +++ b/app/gui2/src/components/ComponentBrowser/input.ts @@ -423,7 +423,7 @@ export function useComponentBrowserInput( } break case 'editNode': - code.value = graphDb.nodeIdToNode.get(usage.node)?.rootSpan.repr() ?? '' + code.value = graphDb.nodeIdToNode.get(usage.node)?.rootSpan.code() ?? '' selection.value = { start: usage.cursorPos, end: usage.cursorPos } break } diff --git a/app/gui2/src/components/GraphEditor.vue b/app/gui2/src/components/GraphEditor.vue index 64500dc4ecab..d87b6ad90cbc 100644 --- a/app/gui2/src/components/GraphEditor.vue +++ b/app/gui2/src/components/GraphEditor.vue @@ -405,7 +405,7 @@ function copyNodeContent() { const id = nodeSelection.selected.values().next().value const node = graphStore.db.nodeIdToNode.get(id) if (!node) return - const content = node.rootSpan.repr() + const content = node.rootSpan.code() const metadata = projectStore.module?.getNodeMetadata(id) ?? undefined const copiedNode: CopiedNode = { expression: content, metadata } const clipboardData: ClipboardData = { nodes: [copiedNode] } diff --git a/app/gui2/src/components/GraphEditor/GraphNode.vue b/app/gui2/src/components/GraphEditor/GraphNode.vue index 5bde53f1df33..4de5e1a55ba3 100644 --- a/app/gui2/src/components/GraphEditor/GraphNode.vue +++ b/app/gui2/src/components/GraphEditor/GraphNode.vue @@ -348,7 +348,7 @@ function portGroupStyle(port: PortData) { >
- {{ node.pattern?.repr() ?? '' }} + {{ node.pattern?.code() ?? '' }}
{ }), ) - const someAst = Ast.parse('foo') - const blankAst = Ast.parse('_') + const someAst = Ast.parseBlock('foo') + const blankAst = Ast.parseBlock('_') const somePlaceholder = new ArgumentPlaceholder( 0, { diff --git a/app/gui2/src/stores/graph/graphDatabase.ts b/app/gui2/src/stores/graph/graphDatabase.ts index be58cf23869b..045cd0b7df4f 100644 --- a/app/gui2/src/stores/graph/graphDatabase.ts +++ b/app/gui2/src/stores/graph/graphDatabase.ts @@ -153,8 +153,8 @@ export class GraphDb { if (entry.pattern == null) return [] const ports = new Set() entry.pattern.visitRecursive((ast) => { - if (this.bindings.bindings.has(ast.astId)) { - ports.add(ast.astId) + if (this.bindings.bindings.has(ast.exprId)) { + ports.add(ast.exprId) return false } return true @@ -313,11 +313,11 @@ export class GraphDb { } mockNode(binding: string, id: Ast.AstId, code?: string): Node { - const pattern = Ast.parse(binding) + const pattern = Ast.parseBlock(binding) const node: Node = { outerExprId: id, pattern, - rootSpan: Ast.parse(code ?? '0'), + rootSpan: Ast.parseBlock(code ?? '0'), position: Vec2.Zero, vis: undefined, } @@ -342,7 +342,7 @@ export function mockNode(exprId?: Ast.AstId): Node { return { outerExprId: exprId ?? (random.uuidv4() as Ast.AstId), pattern: undefined, - rootSpan: Ast.parse('0'), + rootSpan: Ast.parseBlock('0'), position: Vec2.Zero, vis: undefined, } diff --git a/app/gui2/src/stores/graph/imports.ts b/app/gui2/src/stores/graph/imports.ts index cafdc418eef0..5f99c3ebc8bc 100644 --- a/app/gui2/src/stores/graph/imports.ts +++ b/app/gui2/src/stores/graph/imports.ts @@ -501,7 +501,7 @@ if (import.meta.vitest) { const parseImport = (code: string): Import | null => { let ast = null - Ast.parse(code).visitRecursive((node) => { + Ast.parseBlock(code).visitRecursive((node) => { if (node instanceof Ast.Import) { ast = node return false @@ -630,4 +630,18 @@ if (import.meta.vitest) { ])('Generating import $expected', ({ import: import_, expected }) => { expect(requiredImportToAst(import_).code()).toStrictEqual(expected) }) + + test('Insert after other imports in module', () => { + const module_ = Ast.parseBlock('from Standard.Base import all\n\nmain = 42\n') + const edit = module_.module.edit() + addImports(edit, module_, [{ kind: 'Qualified', module: unwrap(tryQualifiedName('Standard.Visualization')) }]) + expect(module_.code(edit)).toBe('from Standard.Base import all\nimport Standard.Visualization\n\nmain = 42\n') + }) + + test('Insert import in module with no other imports', () => { + const module_ = Ast.parseBlock('main = 42\n') + const edit = module_.module.edit() + addImports(edit, module_, [{ kind: 'Qualified', module: unwrap(tryQualifiedName('Standard.Visualization')) }]) + expect(module_.code(edit)).toBe('import Standard.Visualization\nmain = 42\n') + }) } diff --git a/app/gui2/src/stores/graph/index.ts b/app/gui2/src/stores/graph/index.ts index 8b8f1c68c14b..99248af53286 100644 --- a/app/gui2/src/stores/graph/index.ts +++ b/app/gui2/src/stores/graph/index.ts @@ -42,7 +42,6 @@ export const useGraphStore = defineStore('graph', () => { proj.setObservedFileName('Main.enso') const data = computed(() => proj.module?.doc.data) - watch(data, console.log) const metadata = computed(() => proj.module?.doc.metadata) const moduleCode = ref(proj.module?.doc.getCode()) @@ -57,7 +56,7 @@ export const useGraphStore = defineStore('graph', () => { if (!moduleCode.value) { moduleCode.value = proj.module?.doc.getCode() idMap.value = proj.module?.doc.getIdMap() - updateState() + if (moduleCode.value && idMap.value) updateState() } }) @@ -205,7 +204,7 @@ export const useGraphStore = defineStore('graph', () => { console.error(`BUG: Cannot add node: No current function.`) return } - const rhs = Ast.parseExpression(expression, edit) + const rhs = Ast.parse(expression, edit) const assignment = Ast.Assignment.new(edit, ident, rhs) functionBlock.push(edit, assignment) commitEdit(edit, root, new Map([[rhs.exprId, meta]])) @@ -330,7 +329,7 @@ export const useGraphStore = defineStore('graph', () => { ) { const ast = module.get(root) if (!ast) return - const printed = Ast.print(ast, module) + const printed = Ast.print(ast.exprId, module) const module_ = proj.module if (!module_) return const idMap = new IdMap() diff --git a/app/gui2/src/util/ast/__tests__/abstract.test.ts b/app/gui2/src/util/ast/__tests__/abstract.test.ts index d66b0a5ba0a3..477033c2366e 100644 --- a/app/gui2/src/util/ast/__tests__/abstract.test.ts +++ b/app/gui2/src/util/ast/__tests__/abstract.test.ts @@ -1,11 +1,12 @@ import { Ast } from '@/util/ast' import * as fs from 'fs' import { expect, test } from 'vitest' -import { preParseContent, serializeIdMap } from '../../../../ydoc-server/edits' -import { deserializeIdMap } from '../../../../ydoc-server/languageServerSession' +import { preParseContent } from '../../../../ydoc-server/edits' +import { deserializeIdMap, serializeIdMap } from '../../../../ydoc-server/serialization' //const disabledCases = [ // ' a', +// 'a ', //] const cases = [ 'Console.', @@ -68,7 +69,6 @@ const cases = [ 'export prj.Data.Foo', 'foo a b c = x', ':', - 'a ', 'a \n', "'''\n and some \\u000Aescapes\\'", 'a = \n x', @@ -348,16 +348,16 @@ const cases = [ ] test.each(cases)('parse/print round trip: %s', (code) => { // Get an AST. - const root = Ast.parse(code) + const root = Ast.parseBlock(code) // Print AST back to source. - const printed = Ast.print(root) + const printed = Ast.print(root.exprId, root.module) const info1 = printed.info expect(printed.code).toEqual(code) // Re-parse. - const root1 = Ast.parse(printed) + const root1 = Ast.parseBlock(printed) // Check that Identities match original AST. - const reprinted = Ast.print(root1) + const reprinted = Ast.print(root1.exprId, root1.module) expect(reprinted.info.nodes).toEqual(info1.nodes) expect(reprinted.info.tokens).toEqual(info1.tokens) }) @@ -367,17 +367,17 @@ const parseCases = [ { code: '(foo)', tree: ['', ['(', ['foo'], ')']] }, ] test.each(parseCases)('parse: %s', (testCase) => { - const root = Ast.parse(testCase.code) + const root = Ast.parseBlock(testCase.code) expect(Ast.tokenTree(root)).toEqual(testCase.tree) }) test('insert new node', () => { const code = 'main =\n text1 = "foo"\n' - const root = Ast.parse(code) + const root = Ast.parseBlock(code) const main = Ast.functionBlock(root.module, 'main')! expect(main).not.toBeNull() const edit = root.module.edit() - const rhs = Ast.parseExpression('42', edit) + const rhs = Ast.parse('42', edit) const assignment = Ast.Assignment.new(edit, 'baz', rhs) main.push(edit, assignment) const printed = root.code(edit) @@ -386,7 +386,7 @@ test('insert new node', () => { test('replace expression content', () => { const code = 'main =\n text1 = "foo"\n' - const root = Ast.parse(code) + const root = Ast.parseBlock(code) const main = Ast.functionBlock(root.module, 'main')! expect(main).not.toBeNull() const assignment: Ast.Assignment = main.expressions().next().value @@ -401,7 +401,7 @@ test('replace expression content', () => { test('delete expression', () => { const originalCode = 'main =\n text1 = "foo"\n text2 = "bar"\n' - const root = Ast.parse(originalCode) + const root = Ast.parseBlock(originalCode) const main = Ast.functionBlock(root.module, 'main')! expect(main).not.toBeNull() const iter = main.expressions() diff --git a/app/gui2/src/util/ast/__tests__/callTree.test.ts b/app/gui2/src/util/ast/__tests__/callTree.test.ts index 8b0ed226b129..d1c9fa706b41 100644 --- a/app/gui2/src/util/ast/__tests__/callTree.test.ts +++ b/app/gui2/src/util/ast/__tests__/callTree.test.ts @@ -35,7 +35,7 @@ function testArgs(paddedExpression: string, pattern: string) { .filter(isSome) test(`argument list: ${paddedExpression} ${pattern}`, () => { - const parsedBlock = Ast.parse(expression) + const parsedBlock = Ast.parseBlock(expression) assert(parsedBlock instanceof Ast.BodyBlock) // necessary for type inference const expressions = Array.from(parsedBlock.expressions()) const first = expressions[0] diff --git a/app/gui2/src/util/ast/__tests__/match.test.ts b/app/gui2/src/util/ast/__tests__/match.test.ts index efab8c98970f..a7f1fc8b93c0 100644 --- a/app/gui2/src/util/ast/__tests__/match.test.ts +++ b/app/gui2/src/util/ast/__tests__/match.test.ts @@ -80,7 +80,7 @@ test.each([ extracted: ['with_enabled_context', "'current_context_name'", 'a + b'], }, ])('`isMatch` and `extractMatches`', ({ target, pattern, extracted }) => { - const targetAst = Ast.parseExpression(target) + const targetAst = Ast.parse(target) const module = targetAst.module const patternAst = Pattern.parse(pattern) expect( diff --git a/app/gui2/src/util/ast/__tests__/prefixes.test.ts b/app/gui2/src/util/ast/__tests__/prefixes.test.ts index bb266e8a1f28..6cb65e551143 100644 --- a/app/gui2/src/util/ast/__tests__/prefixes.test.ts +++ b/app/gui2/src/util/ast/__tests__/prefixes.test.ts @@ -67,7 +67,7 @@ test.each([ }, ])('modify', ({ prefixes: lines, modifications, source, target }) => { const prefixes = Prefixes.FromLines(lines as any) - const sourceAst = Ast.parseExpression(source) + const sourceAst = Ast.parse(source) const edit = sourceAst.module.edit() const modificationAsts = Object.fromEntries( Object.entries(modifications).map(([k, v]) => [ diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index db7bf7a74fb9..ef12179b35f0 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -120,7 +120,7 @@ export class MutableModule implements Module { } export function normalize(rootIn: Ast): Ast { - const printed = print(rootIn) + const printed = print(rootIn.exprId, rootIn.module) const module = MutableModule.Transient() const tree = parseEnso(printed.code) const rootOut = abstract(module, tree, printed.code, printed.info).node @@ -185,7 +185,7 @@ export abstract class Ast { } serialize(): string { - return JSON.stringify(print(this)) + return JSON.stringify(print(this.exprId, this.module)) } static deserialize(serialized: string): Ast { @@ -230,11 +230,7 @@ export abstract class Ast { abstract concreteChildren(): IterableIterator code(module?: Module): string { - return print(this, module).code - } - - repr(): string { - return this.code() + return print(this.exprId, module ?? this.module).code } typeName(): string | undefined { @@ -242,7 +238,8 @@ export abstract class Ast { return RawAst.Tree.typeNames[this.treeType] } - static parse(source: PrintedSource | string, inModule?: MutableModule | undefined): BodyBlock { + /** Parse the input as a block. */ + static parseBlock(source: PrintedSource | string, inModule?: MutableModule | undefined): BodyBlock { const code = typeof source === 'object' ? source.code : source const ids = typeof source === 'object' ? source.info : undefined const tree = parseEnso(code) @@ -253,8 +250,9 @@ export abstract class Ast { return ast as BodyBlock } - static parseExpression(source: PrintedSource | string, module?: MutableModule): Ast { - const ast = Ast.parse(source, module) + /** Parse the input. If it contains a single expression at the top level, return it; otherwise, return a block. */ + static parse(source: PrintedSource | string, module?: MutableModule): Ast { + const ast = Ast.parseBlock(source, module) const [expr] = ast.expressions() return expr instanceof Ast ? expr : ast } @@ -277,7 +275,7 @@ export abstract class Ast { module.set(this.exprId, this) } - _print( + printSubtree( info: InfoMap, offset: number, indent: string, @@ -299,7 +297,7 @@ export abstract class Ast { info.tokens.set(span, child.node.exprId) code += tokenCode } else { - code += module_.get(child.node)!._print(info, offset + code.length, indent, moduleOverride) + code += module_.get(child.node)!.printSubtree(info, offset + code.length, indent, moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) @@ -908,7 +906,7 @@ export class BodyBlock extends Ast { } } - _print( + printSubtree( info: InfoMap, offset: number, indent: string, @@ -920,14 +918,18 @@ export class BodyBlock extends Ast { // Skip deleted lines (and associated whitespace). if (line.expression?.node != null && module_.get(line.expression.node) === null) continue code += line.newline?.whitespace ?? '' - code += line.newline?.node.code() ?? '\n' + const newlineCode = line.newline?.node.code() + // Only print a newline if this isn't the first line in the output, or it's a comment. + if (offset || code || newlineCode?.startsWith('#')) { + // If this isn't the first line in the output, but there is a concrete newline token: + // if it's a zero-length newline, ignore it and print a normal newline. + code += newlineCode || '\n' + } if (line.expression !== null) { code += line.expression.whitespace ?? indent - if (line.expression.node !== null) { - code += module_ - .get(line.expression.node)! - ._print(info, offset + code.length, indent + ' ', moduleOverride) - } + code += module_ + .get(line.expression.node)! + .printSubtree(info, offset + code.length, indent + ' ', moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) @@ -1272,13 +1274,13 @@ interface PrintedSource { } /** Return stringification with associated ID map. This is only exported for testing. */ -export function print(ast: Ast, module?: Module | undefined): PrintedSource { +export function print(ast: AstId, module: Module): PrintedSource { const info: InfoMap = { nodes: new Map(), tokens: new Map(), tokensOut: new Map(), } - const code = ast._print(info, 0, '', module) + const code = module.get(ast)!.printSubtree(info, 0, '', module) return { info, code } } @@ -1329,7 +1331,7 @@ export function functionBlock(module: Module, name: string): BodyBlock | null { } export function parseTransitional(code: string, idMap: IdMap): Ast { - const rawAst = RawAstExtended.parse(code, idMap) + const rawAst = RawAstExtended.parse(code, idMap.clone()) const nodes = new Map() const tokens = new Map() const astExtended = new Map() @@ -1368,7 +1370,7 @@ export function parseTransitional(code: string, idMap: IdMap): Ast { return true }) const tokensOut = new Map() - const newRoot = Ast.parse({ info: { nodes, tokens, tokensOut }, code }) + const newRoot = Ast.parseBlock({ info: { nodes, tokens, tokensOut }, code }) newRoot.module.raw.astExtended = astExtended newRoot.module.raw.spans = spans idMap.clear() @@ -1381,7 +1383,7 @@ export function parseTransitional(code: string, idMap: IdMap): Ast { idMap.insertKnownId( ... ) } */ - const printed = print(newRoot) + const printed = print(newRoot.exprId, newRoot.module) for (const [key, ids] of printed.info.nodes) { const range = keyToRange(key) idMap.insertKnownId([range.start, range.end], ids[0]!) @@ -1393,8 +1395,8 @@ export function parseTransitional(code: string, idMap: IdMap): Ast { return newRoot } +export const parseBlock = Ast.parseBlock export const parse = Ast.parse -export const parseExpression = Ast.parseExpression export function deserialize(serialized: string): Ast { return Ast.deserialize(serialized) diff --git a/app/gui2/src/util/ast/match.ts b/app/gui2/src/util/ast/match.ts index af4fd5dd5e4e..494cf75c341f 100644 --- a/app/gui2/src/util/ast/match.ts +++ b/app/gui2/src/util/ast/match.ts @@ -7,7 +7,7 @@ export class Pattern { private readonly placeholder: string constructor(template: string, placeholder: string) { - this.tokenTree = Ast.tokenTree(Ast.parseExpression(template)) + this.tokenTree = Ast.tokenTree(Ast.parse(template)) this.template = template this.placeholder = placeholder } diff --git a/app/gui2/ydoc-server/edits.ts b/app/gui2/ydoc-server/edits.ts index dc56ffc106c2..4511ecc5682c 100644 --- a/app/gui2/ydoc-server/edits.ts +++ b/app/gui2/ydoc-server/edits.ts @@ -9,6 +9,7 @@ import * as Y from 'yjs' import { TextEdit } from '../shared/languageServerTypes' import { IdMap, ModuleDoc, type NodeMetadata, type VisualizationMetadata } from '../shared/yjsModel' import * as fileFormat from './fileFormat' +import { serializeIdMap } from './serialization' interface AppliedUpdates { edits: TextEdit[] @@ -183,35 +184,6 @@ export function preParseContent(content: string): PreParsedContent { return { code, idMapJson, metadataJson } } -export function serializeIdMap(map: IdMap): string { - return json.stringify(idMapToArray(map)) -} - -function idMapToArray(map: IdMap): fileFormat.IdMapEntry[] { - const entries: fileFormat.IdMapEntry[] = [] - map.entries().forEach(([rangeBuffer, id]) => { - const decoded = IdMap.rangeForKey(rangeBuffer) - const index = decoded[0] - const endIndex = decoded[1] - if (index == null || endIndex == null) return - const size = endIndex - index - entries.push([{ index: { value: index }, size: { value: size } }, id]) - }) - entries.sort(idMapCmp) - return entries -} - -function idMapCmp(a: fileFormat.IdMapEntry, b: fileFormat.IdMapEntry) { - const val1 = a[0]?.index?.value ?? 0 - const val2 = b[0]?.index?.value ?? 0 - if (val1 === val2) { - const size1 = a[0]?.size.value ?? 0 - const size2 = b[0]?.size.value ?? 0 - return size1 - size2 - } - return val1 - val2 -} - export function applyDiffAsTextEdits( lineOffset: number, oldString: string, diff --git a/app/gui2/ydoc-server/languageServerSession.ts b/app/gui2/ydoc-server/languageServerSession.ts index fb08b7c7213a..291b905a2e43 100644 --- a/app/gui2/ydoc-server/languageServerSession.ts +++ b/app/gui2/ydoc-server/languageServerSession.ts @@ -8,10 +8,8 @@ import { Checksum, FileEdit, Path, response } from '../shared/languageServerType import { exponentialBackoff, printingCallbacks } from '../shared/retry' import { DistributedProject, - ExprId, IdMap, ModuleDoc, - SourceRange, type NodeMetadata, type Uuid, } from '../shared/yjsModel' @@ -23,6 +21,7 @@ import { } from './edits' import * as fileFormat from './fileFormat' import { WSSharedDoc } from './ydoc' +import { deserializeIdMap } from './serialization' const SOURCE_DIR = 'src' const EXTENSION = '.enso' @@ -245,20 +244,6 @@ function pushPathSegment(path: Path, segment: string): Path { return { rootId: path.rootId, segments: [...path.segments, segment] } } -export function deserializeIdMap(idMapJson: string) { - const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) - const idMap = new IdMap() - for (const [{ index, size }, id] of idMapMeta) { - const range = [index.value, index.value + size.value] - if (typeof range[0] !== 'number' || typeof range[1] !== 'number') { - console.error(`Invalid range for id ${id}:`, range) - continue - } - idMap.insertKnownId([index.value, index.value + size.value], id as ExprId) - } - return idMap -} - enum LsSyncState { Closed, Opening, diff --git a/app/gui2/ydoc-server/serialization.ts b/app/gui2/ydoc-server/serialization.ts new file mode 100644 index 000000000000..4ed6c9407133 --- /dev/null +++ b/app/gui2/ydoc-server/serialization.ts @@ -0,0 +1,49 @@ +/** Translation of `yjsModel` types to and from the `fileFormat` representation. */ + +import * as fileFormat from './fileFormat' +import { ExprId, IdMap } from '../shared/yjsModel' +import * as json from 'lib0/json' + +export function deserializeIdMap(idMapJson: string) { + const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) + const idMap = new IdMap() + for (const [{ index, size }, id] of idMapMeta) { + const range = [index.value, index.value + size.value] + if (typeof range[0] !== 'number' || typeof range[1] !== 'number') { + console.error(`Invalid range for id ${id}:`, range) + continue + } + idMap.insertKnownId([index.value, index.value + size.value], id as ExprId) + } + return idMap +} + +export function serializeIdMap(map: IdMap): string { + map.validate() + return json.stringify(idMapToArray(map)) +} + +function idMapToArray(map: IdMap): fileFormat.IdMapEntry[] { + const entries: fileFormat.IdMapEntry[] = [] + map.entries().forEach(([rangeBuffer, id]) => { + const decoded = IdMap.rangeForKey(rangeBuffer) + const index = decoded[0] + const endIndex = decoded[1] + if (index == null || endIndex == null) return + const size = endIndex - index + entries.push([{ index: { value: index }, size: { value: size } }, id]) + }) + entries.sort(idMapCmp) + return entries +} + +function idMapCmp(a: fileFormat.IdMapEntry, b: fileFormat.IdMapEntry) { + const val1 = a[0]?.index?.value ?? 0 + const val2 = b[0]?.index?.value ?? 0 + if (val1 === val2) { + const size1 = a[0]?.size.value ?? 0 + const size2 = b[0]?.size.value ?? 0 + return size1 - size2 + } + return val1 - val2 +} From d9610ece5e28c861252db31abf68bbbf93373372 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Fri, 22 Dec 2023 14:48:55 +0000 Subject: [PATCH 04/12] Simplify AA --- app/gui2/src/stores/graph/index.ts | 1 + app/gui2/src/util/ast/aliasAnalysis.ts | 24 +++++++----------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/app/gui2/src/stores/graph/index.ts b/app/gui2/src/stores/graph/index.ts index a67a4a474b22..8de0bf1647ad 100644 --- a/app/gui2/src/stores/graph/index.ts +++ b/app/gui2/src/stores/graph/index.ts @@ -242,6 +242,7 @@ export const useGraphStore = defineStore('graph', () => { setExpressionContent(node.rootSpan.exprId, content) } + /** Deprecated; use `commitEdit` */ function setExpressionContent(id: ExprId, content: string) { const edit = astModule.edit() edit.set(Ast.asNodeId(id), Ast.RawCode.new(content, edit)) diff --git a/app/gui2/src/util/ast/aliasAnalysis.ts b/app/gui2/src/util/ast/aliasAnalysis.ts index 58d813263ebf..af30f27c6f09 100644 --- a/app/gui2/src/util/ast/aliasAnalysis.ts +++ b/app/gui2/src/util/ast/aliasAnalysis.ts @@ -71,21 +71,15 @@ export enum IdentifierType { Variable = 'Variable', } -/** Check, what kind of identifier the given string is. - * - * Note that the results should not be relied upon for ill-formed identifiers. - */ -export function identifierKind(identifier: string): IdentifierType { +/** Check what kind of identifier the given token is. */ +export function identifierKind(token: RawAst.Token.Ident): IdentifierType { // Identifier kinds, as per draft Enso spec: // https://github.com/enso-org/design/blob/wip/wd/enso-spec/epics/enso-spec-1.0/03.%20Code%20format%20and%20layout.md - // Regex that matches any character that is allowed as part of operator identifier. - const operatorCharacter = /[!$%&*+\-/<>^~|:\\=.]/ - const firstCharacter = identifier.charAt(0) - if (firstCharacter.match(operatorCharacter)) { + if (token.isOperatorLexically) { return IdentifierType.Operator - } else if (firstCharacter === '_') { + } else if (token.liftLevel > 0) { return IdentifierType.TypeVariable - } else if (firstCharacter === firstCharacter.toUpperCase()) { + } else if (token.isTypeOrConstructor) { return IdentifierType.Type } else { return IdentifierType.Variable @@ -183,12 +177,8 @@ export class AliasAnalyzer { } processToken(token?: RawAst.Token): void { - if (token == null) { - return - } - - const repr = readTokenSpan(token, this.code) - if (identifierKind(repr) === IdentifierType.Variable) { + if (token?.type !== RawAst.Token.Type.Ident) return + if (identifierKind(token) === IdentifierType.Variable) { if (this.contexts.top === Context.Pattern) { this.bind(token) } else { From 0089d6a143f94fa7526a0c4567b717cf92efabfe Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Fri, 22 Dec 2023 14:49:11 +0000 Subject: [PATCH 05/12] fmt --- app/gui2/src/stores/graph/imports.ts | 12 +++++++++--- app/gui2/src/util/ast/abstract.ts | 9 +++++++-- app/gui2/ydoc-server/languageServerSession.ts | 2 +- app/gui2/ydoc-server/serialization.ts | 4 ++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/gui2/src/stores/graph/imports.ts b/app/gui2/src/stores/graph/imports.ts index 5f99c3ebc8bc..bc1ec20b6b60 100644 --- a/app/gui2/src/stores/graph/imports.ts +++ b/app/gui2/src/stores/graph/imports.ts @@ -634,14 +634,20 @@ if (import.meta.vitest) { test('Insert after other imports in module', () => { const module_ = Ast.parseBlock('from Standard.Base import all\n\nmain = 42\n') const edit = module_.module.edit() - addImports(edit, module_, [{ kind: 'Qualified', module: unwrap(tryQualifiedName('Standard.Visualization')) }]) - expect(module_.code(edit)).toBe('from Standard.Base import all\nimport Standard.Visualization\n\nmain = 42\n') + addImports(edit, module_, [ + { kind: 'Qualified', module: unwrap(tryQualifiedName('Standard.Visualization')) }, + ]) + expect(module_.code(edit)).toBe( + 'from Standard.Base import all\nimport Standard.Visualization\n\nmain = 42\n', + ) }) test('Insert import in module with no other imports', () => { const module_ = Ast.parseBlock('main = 42\n') const edit = module_.module.edit() - addImports(edit, module_, [{ kind: 'Qualified', module: unwrap(tryQualifiedName('Standard.Visualization')) }]) + addImports(edit, module_, [ + { kind: 'Qualified', module: unwrap(tryQualifiedName('Standard.Visualization')) }, + ]) expect(module_.code(edit)).toBe('import Standard.Visualization\nmain = 42\n') }) } diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index c3a252a12841..7b07d8f3d768 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -236,7 +236,10 @@ export abstract class Ast { } /** Parse the input as a block. */ - static parseBlock(source: PrintedSource | string, inModule?: MutableModule | undefined): BodyBlock { + static parseBlock( + source: PrintedSource | string, + inModule?: MutableModule | undefined, + ): BodyBlock { const code = typeof source === 'object' ? source.code : source const ids = typeof source === 'object' ? source.info : undefined const tree = parseEnso(code) @@ -294,7 +297,9 @@ export abstract class Ast { info.tokens.set(span, child.node.exprId) code += tokenCode } else { - code += module_.get(child.node)!.printSubtree(info, offset + code.length, indent, moduleOverride) + code += module_ + .get(child.node)! + .printSubtree(info, offset + code.length, indent, moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) diff --git a/app/gui2/ydoc-server/languageServerSession.ts b/app/gui2/ydoc-server/languageServerSession.ts index 291b905a2e43..f42446fd0c1d 100644 --- a/app/gui2/ydoc-server/languageServerSession.ts +++ b/app/gui2/ydoc-server/languageServerSession.ts @@ -20,8 +20,8 @@ import { translateVisualizationFromFile, } from './edits' import * as fileFormat from './fileFormat' -import { WSSharedDoc } from './ydoc' import { deserializeIdMap } from './serialization' +import { WSSharedDoc } from './ydoc' const SOURCE_DIR = 'src' const EXTENSION = '.enso' diff --git a/app/gui2/ydoc-server/serialization.ts b/app/gui2/ydoc-server/serialization.ts index 4ed6c9407133..9a3c950e933e 100644 --- a/app/gui2/ydoc-server/serialization.ts +++ b/app/gui2/ydoc-server/serialization.ts @@ -1,8 +1,8 @@ /** Translation of `yjsModel` types to and from the `fileFormat` representation. */ -import * as fileFormat from './fileFormat' -import { ExprId, IdMap } from '../shared/yjsModel' import * as json from 'lib0/json' +import { ExprId, IdMap } from '../shared/yjsModel' +import * as fileFormat from './fileFormat' export function deserializeIdMap(idMapJson: string) { const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) From 9779641fbd955b6f8a1f2f745ae445b6c8f47e0a Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sat, 23 Dec 2023 15:33:28 +0000 Subject: [PATCH 06/12] Splicing, setExpression and block lines --- .../components/GraphEditor/NodeWidgetTree.vue | 2 +- .../graph/__tests__/graphDatabase.test.ts | 2 +- app/gui2/src/stores/graph/imports.ts | 7 +- app/gui2/src/stores/graph/index.ts | 15 +- .../src/util/ast/__tests__/abstract.test.ts | 18 ++- .../src/util/ast/__tests__/callTree.test.ts | 2 +- app/gui2/src/util/ast/abstract.ts | 136 ++++++++++++------ app/gui2/src/util/ast/extended.ts | 13 -- 8 files changed, 129 insertions(+), 66 deletions(-) diff --git a/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue b/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue index d133727681d4..064406b2db67 100644 --- a/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue +++ b/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue @@ -36,7 +36,7 @@ function handleWidgetUpdates(value: unknown, origin: PortId) { } else if (typeof value === 'string') { graph.setExpressionContent(origin, value) } else if (value instanceof Ast.Ast) { - graph.setExpressionContent(origin, value.code()) + graph.setExpression(origin, value) } else if (value == null) { graph.setExpressionContent(origin, '_') } else { diff --git a/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts b/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts index 31223835e5d2..5f1c38eee836 100644 --- a/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts +++ b/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts @@ -32,7 +32,7 @@ test('Reading graph from definition', () => { const db = GraphDb.Mock() const ast = Ast.parseTransitional(code, idMap) assert(ast instanceof Ast.BodyBlock) - const expressions = Array.from(ast.expressions()) + const expressions = Array.from(ast.statements()) const func = expressions[0] assert(func instanceof Ast.Function) db.readFunctionAst(func, (_) => ({ x: 0.0, y: 0.0, vis: null })) diff --git a/app/gui2/src/stores/graph/imports.ts b/app/gui2/src/stores/graph/imports.ts index bc1ec20b6b60..ceeb1fac330d 100644 --- a/app/gui2/src/stores/graph/imports.ts +++ b/app/gui2/src/stores/graph/imports.ts @@ -162,10 +162,11 @@ export function addImports( */ function newImportsLocation(module: Ast.Module, scope: Ast.BodyBlock): number { let lastImport - for (let i = 0; i < scope.lines.length; i++) { - const line = scope.lines[i]! + const lines = scope.lines() + for (let i = 0; i < lines.length; i++) { + const line = lines[i]! if (line.expression) { - if (module.get(line.expression.node)?.innerExpression() instanceof Ast.Import) { + if (line.expression.node?.innerExpression() instanceof Ast.Import) { lastImport = i } else { break diff --git a/app/gui2/src/stores/graph/index.ts b/app/gui2/src/stores/graph/index.ts index 8de0bf1647ad..5857ff0dd801 100644 --- a/app/gui2/src/stores/graph/index.ts +++ b/app/gui2/src/stores/graph/index.ts @@ -242,15 +242,21 @@ export const useGraphStore = defineStore('graph', () => { setExpressionContent(node.rootSpan.exprId, content) } - /** Deprecated; use `commitEdit` */ - function setExpressionContent(id: ExprId, content: string) { + function setExpression(id: ExprId, content: Ast.Ast) { const edit = astModule.edit() - edit.set(Ast.asNodeId(id), Ast.RawCode.new(content, edit)) + edit.set(Ast.asNodeId(id), content) const root = moduleRoot.value - if (!root) return + if (!root) { + console.error(`BUG: Cannot update node: No module root.`) + return + } commitEdit(edit, root) } + function setExpressionContent(id: ExprId, content: string) { + setExpression(id, Ast.RawCode.new(content)) + } + function transact(fn: () => void) { return proj.module?.transact(fn) } @@ -412,6 +418,7 @@ export const useGraphStore = defineStore('graph', () => { createNode, deleteNode, setNodeContent, + setExpression, setExpressionContent, setNodePosition, setNodeVisualizationId, diff --git a/app/gui2/src/util/ast/__tests__/abstract.test.ts b/app/gui2/src/util/ast/__tests__/abstract.test.ts index 477033c2366e..53c55d8b928e 100644 --- a/app/gui2/src/util/ast/__tests__/abstract.test.ts +++ b/app/gui2/src/util/ast/__tests__/abstract.test.ts @@ -389,7 +389,7 @@ test('replace expression content', () => { const root = Ast.parseBlock(code) const main = Ast.functionBlock(root.module, 'main')! expect(main).not.toBeNull() - const assignment: Ast.Assignment = main.expressions().next().value + const assignment: Ast.Assignment = main.statements().next().value expect(assignment).toBeInstanceOf(Ast.Assignment) expect(assignment.expression).not.toBeNull() const edit = root.module.edit() @@ -404,7 +404,7 @@ test('delete expression', () => { const root = Ast.parseBlock(originalCode) const main = Ast.functionBlock(root.module, 'main')! expect(main).not.toBeNull() - const iter = main.expressions() + const iter = main.statements() const _assignment1 = iter.next() const assignment2: Ast.Assignment = iter.next().value const edit = root.module.edit() @@ -439,3 +439,17 @@ test('full file IdMap round trip', () => { const ast3 = Ast.parseTransitional(code_, idMap_) expect(Ast.tokenTreeWithIds(ast3), 'Print/parse with serialized IdMap').toStrictEqual(astTT) }) + +test('Block lines interface', () => { + const block = Ast.parseBlock('VLE \nSISI\nGNIK \n') + // Sort alphabetically, but keep the blank line at the end. + const reordered = block.lines().sort((a, b) => { + if (a.expression?.node.code() === b.expression?.node.code()) return 0 + if (!a.expression) return 1 + if (!b.expression) return -1 + return a.expression.node.code() < b.expression.node.code() ? -1 : 1 + }) + const newBlock = Ast.BodyBlock.new(reordered) + // Note that trailing whitespace belongs to the following line. + expect(newBlock.code()).toBe('GNIK \nSISI\nVLE \n') +}) diff --git a/app/gui2/src/util/ast/__tests__/callTree.test.ts b/app/gui2/src/util/ast/__tests__/callTree.test.ts index d1c9fa706b41..552aca6845f6 100644 --- a/app/gui2/src/util/ast/__tests__/callTree.test.ts +++ b/app/gui2/src/util/ast/__tests__/callTree.test.ts @@ -37,7 +37,7 @@ function testArgs(paddedExpression: string, pattern: string) { test(`argument list: ${paddedExpression} ${pattern}`, () => { const parsedBlock = Ast.parseBlock(expression) assert(parsedBlock instanceof Ast.BodyBlock) // necessary for type inference - const expressions = Array.from(parsedBlock.expressions()) + const expressions = Array.from(parsedBlock.statements()) const first = expressions[0] assert(first !== undefined) const ast = first diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index 7b07d8f3d768..41c5606bd31f 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -85,6 +85,7 @@ export class MutableModule implements Module { } } + /** Replace the contents of this module with the contents of the specified module. */ replace(editIn: Module) { const edit = editIn.raw for (const id of this.nodes.keys()) { @@ -108,13 +109,37 @@ export class MutableModule implements Module { } set(id: AstId, ast: Ast) { - this.nodes.set(id, ast) + const ast_ = id === ast.exprId ? ast : ast.cloneTo(this, id) + this.splice(id, ast_) + this.nodes.set(id, ast_) + } + + /** Insert references to the given node and all its descendants into this module, + * if they aren't already accessible. */ + splice(id: AstId, ast: Ast) { + let moduleInChain: Module | null = this + while (moduleInChain) { + if (ast.module === moduleInChain) return + moduleInChain = moduleInChain.raw.base + } + if (!moduleInChain) this.nodes.set(id, ast) + for (const child of ast.concreteChildren()) { + if (!(child.node instanceof Token)) this.splice(child.node, ast.module.get(child.node)!) + } } getExtended(id: AstId): RawAstExtended | undefined { return this.astExtended?.get(id) ?? this.base?.getExtended(id) } + /** Remove the expression with the specified ID. + * + * If the expression is optional in its parent, it will be removed entirely. + * E.g. if it is a direct child of a `BodyBlock`, its line will be eliminated. + * + * If the expression is a required part of the structure of its parent, it will be replaced with a placeholder. + * In most contexts within an expression, this will be `_`. + */ delete(id: AstId) { this.nodes.set(id, null) } @@ -135,7 +160,7 @@ declare const brandTokenId: unique symbol export type AstId = ExprId & { [brandAstId]: never } export type TokenId = ExprId & { [brandTokenId]: never } -function newNodeId(): AstId { +function newAstId(): AstId { return random.uuidv4() as AstId } function newTokenId(): TokenId { @@ -253,7 +278,7 @@ export abstract class Ast { /** Parse the input. If it contains a single expression at the top level, return it; otherwise, return a block. */ static parse(source: PrintedSource | string, module?: MutableModule): Ast { const ast = Ast.parseBlock(source, module) - const [expr] = ast.expressions() + const [expr] = ast.statements() return expr instanceof Ast ? expr : ast } @@ -270,7 +295,7 @@ export abstract class Ast { protected constructor(module: MutableModule, id?: AstId, treeType?: RawAst.Tree.Type) { this.module = module - this.exprId = id ?? newNodeId() + this.exprId = id ?? newAstId() this.treeType = treeType module.set(this.exprId, this) } @@ -307,6 +332,14 @@ export abstract class Ast { infos.unshift(this.exprId) return code } + + /** Return a shallow copy of this node, with the specified `module` and `exprId` properties. */ + cloneTo(module: Module, exprId: AstId): Ast { + const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this) + clone.module = module + clone.exprId = exprId + return clone + } } export class App extends Ast { @@ -508,6 +541,7 @@ export class PropertyAccess extends OprApp { } static new(module: MutableModule, lhs: Ast | null, rhs: Token | null): PropertyAccess { + if (lhs) module.splice(lhs.exprId, lhs) const lhs_ = lhs ? { node: lhs.exprId } : null const rhs_ = rhs ? { whitespace: '', node: new Ident(module, undefined, { whitespace: '', node: rhs }).exprId } @@ -777,7 +811,7 @@ export class Function extends Ast { *bodyExpressions(): IterableIterator { const body = this.body_ ? this.module.get(this.body_.node) : null if (body instanceof BodyBlock) { - yield* body.expressions() + yield* body.statements() } else if (body !== null) { yield body } @@ -830,6 +864,7 @@ export class Assignment extends Ast { } static new(module: MutableModule, ident: string, expression: Ast): Assignment { + module.splice(expression.exprId, expression) const pattern = { node: Ident.new(module, ident).exprId } return new Assignment(module, undefined, pattern, undefined, { whitespace: ' ', @@ -849,56 +884,76 @@ export class Assignment extends Ast { } } -interface BlockLine { - newline?: NodeChild +interface RawBlockLine { + newline?: NodeChild | undefined expression: NodeChild | null } +interface BlockLine { + newline?: NodeChild | undefined + expression: NodeChild | null +} + export class BodyBlock extends Ast { - readonly lines: BlockLine[]; - - *expressions(): IterableIterator { - for (const line of this.lines) { - if (line.expression) { - const node = this.module.get(line.expression.node) - if (node) { - yield node - } else { - console.warn(`Missing node:`, line.expression.node) - } + private readonly lines_: RawBlockLine[] + + static new(lines: BlockLine[], module?: MutableModule): BodyBlock { + const module_ = module ?? MutableModule.Transient() + const rawLines = lines.map((line) => { + if (line.expression) module_.splice(line.expression.node.exprId, line.expression.node) + return { + newline: line.newline, + expression: line.expression + ? { + whitespace: line.expression.whitespace, + node: line.expression.node.exprId, + } + : null, } - } + }) + return new BodyBlock(module_, undefined, rawLines) + } + + lines(): BlockLine[] { + return this.lines_.map((line) => { + const expression = line.expression ? this.module.get(line.expression.node) : null + return { + newline: line.newline, + expression: expression + ? { + whitespace: line.expression?.whitespace, + node: expression, + } + : null, + } + }) } - constructor(module: MutableModule, id: AstId | undefined, lines: BlockLine[]) { - super(module, id, RawAst.Tree.Type.BodyBlock) - this.lines = lines + *statements(): IterableIterator { + for (const line of this.lines()) { + if (line.expression) yield line.expression.node + } } - // TODO: Edits (#8367) - /* - static new(id: AstId | undefined, expressions: Ast[]): Block { - return new Block( - id, - expressions.map((e) => ({ expression: { node: e._id } })), - ) + constructor(module: MutableModule, id: AstId | undefined, lines: RawBlockLine[]) { + super(module, id, RawAst.Tree.Type.BodyBlock) + this.lines_ = lines } - */ push(module: MutableModule, node: Ast) { - new BodyBlock(module, this.exprId, [...this.lines, { expression: { node: node.exprId } }]) + new BodyBlock(module, this.exprId, [...this.lines_, { expression: { node: node.exprId } }]) } /** Insert the given expression(s) starting at the specified line index. */ insert(module: MutableModule, index: number, ...nodes: Ast[]) { - const before = this.lines.slice(0, index) + const before = this.lines_.slice(0, index) const insertions = Array.from(nodes, (node) => ({ expression: { node: node.exprId } })) - const after = this.lines.slice(index) + const after = this.lines_.slice(index) new BodyBlock(module, this.exprId, [...before, ...insertions, ...after]) } *concreteChildren(): IterableIterator { - for (const line of this.lines) { + for (const line of this.lines_) { yield line.newline ?? { node: new Token('\n', newTokenId(), RawAst.Token.Type.Newline) } if (line.expression !== null) yield line.expression } @@ -912,13 +967,13 @@ export class BodyBlock extends Ast { ): string { const module_ = moduleOverride ?? this.module let code = '' - for (const line of this.lines) { + for (const line of this.lines_) { // Skip deleted lines (and associated whitespace). if (line.expression?.node != null && module_.get(line.expression.node) === null) continue code += line.newline?.whitespace ?? '' const newlineCode = line.newline?.node.code() // Only print a newline if this isn't the first line in the output, or it's a comment. - if (offset || code || newlineCode?.startsWith('#')) { + if (offset || code || newlineCode?.startsWith('#') || !line.expression) { // If this isn't the first line in the output, but there is a concrete newline token: // if it's a zero-length newline, ignore it and print a normal newline. code += newlineCode || '\n' @@ -968,12 +1023,11 @@ export class Wildcard extends Ast { this.token = token } - static new(): Wildcard { - const module = MutableModule.Transient() - const ast = new Wildcard(module, undefined, { + static new(module?: MutableModule): Wildcard { + const module_ = module ?? MutableModule.Transient() + return new Wildcard(module_, undefined, { node: new Token('_', newTokenId(), RawAst.Token.Type.Wildcard), }) - return ast } *concreteChildren(): IterableIterator { @@ -1353,7 +1407,7 @@ export function parseTransitional(code: string, idMap: IdMap): Ast { if (!preexisting.isTree(RawAst.Tree.Type.Invalid)) { console.warn(`Unexpected duplicate UUID in tree`, id) } - id = newNodeId() + id = newAstId() } astExtended.set(id, node) spans.set(id, node.span()) diff --git a/app/gui2/src/util/ast/extended.ts b/app/gui2/src/util/ast/extended.ts index 4057938390af..eb5ffca5ef8a 100644 --- a/app/gui2/src/util/ast/extended.ts +++ b/app/gui2/src/util/ast/extended.ts @@ -3,7 +3,6 @@ import { Token, Tree } from '@/generated/ast' import { assert } from '@/util/assert' import { childrenAstNodesOrTokens, - debugAst, parseEnso, parsedTreeOrTokenRange, readAstOrTokenSpan, @@ -71,14 +70,6 @@ export class AstExtended>( type?: T, ): this is AstExtended, HasIdMap> { @@ -113,10 +104,6 @@ export class AstExtended( mapper: (t: T) => Opt, ): AstExtended | undefined { From 3f37d880dcd46d6da6305c9bede25613bb4f4e02 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sat, 23 Dec 2023 16:17:24 +0000 Subject: [PATCH 07/12] Cleanup --- .../__tests__/widgetRegistry.test.ts | 4 +- app/gui2/src/stores/graph/graphDatabase.ts | 6 +- .../src/util/ast/__tests__/callTree.test.ts | 8 +- app/gui2/src/util/ast/abstract.ts | 74 ++++++++++--------- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/app/gui2/src/providers/__tests__/widgetRegistry.test.ts b/app/gui2/src/providers/__tests__/widgetRegistry.test.ts index 2033df91f0e8..1ece5c999073 100644 --- a/app/gui2/src/providers/__tests__/widgetRegistry.test.ts +++ b/app/gui2/src/providers/__tests__/widgetRegistry.test.ts @@ -54,8 +54,8 @@ describe('WidgetRegistry', () => { }), ) - const someAst = Ast.parseBlock('foo') - const blankAst = Ast.parseBlock('_') + const someAst = Ast.parse('foo') + const blankAst = Ast.parse('_') const somePlaceholder = new ArgumentPlaceholder( 0, { diff --git a/app/gui2/src/stores/graph/graphDatabase.ts b/app/gui2/src/stores/graph/graphDatabase.ts index 0b2966236693..ed0a82c6487e 100644 --- a/app/gui2/src/stores/graph/graphDatabase.ts +++ b/app/gui2/src/stores/graph/graphDatabase.ts @@ -314,11 +314,11 @@ export class GraphDb { } mockNode(binding: string, id: Ast.AstId, code?: string): Node { - const pattern = Ast.parseBlock(binding) + const pattern = Ast.parse(binding) const node: Node = { outerExprId: id, pattern, - rootSpan: Ast.parseBlock(code ?? '0'), + rootSpan: Ast.parse(code ?? '0'), position: Vec2.Zero, vis: undefined, } @@ -343,7 +343,7 @@ export function mockNode(exprId?: Ast.AstId): Node { return { outerExprId: exprId ?? (random.uuidv4() as Ast.AstId), pattern: undefined, - rootSpan: Ast.parseBlock('0'), + rootSpan: Ast.parse('0'), position: Vec2.Zero, vis: undefined, } diff --git a/app/gui2/src/util/ast/__tests__/callTree.test.ts b/app/gui2/src/util/ast/__tests__/callTree.test.ts index 552aca6845f6..87dd33b6a7d1 100644 --- a/app/gui2/src/util/ast/__tests__/callTree.test.ts +++ b/app/gui2/src/util/ast/__tests__/callTree.test.ts @@ -23,6 +23,7 @@ const mockSuggestion: SuggestionEntry = { isPrivate: false, isUnstable: false, aliases: [], + annotations: [], } function testArgs(paddedExpression: string, pattern: string) { @@ -35,12 +36,7 @@ function testArgs(paddedExpression: string, pattern: string) { .filter(isSome) test(`argument list: ${paddedExpression} ${pattern}`, () => { - const parsedBlock = Ast.parseBlock(expression) - assert(parsedBlock instanceof Ast.BodyBlock) // necessary for type inference - const expressions = Array.from(parsedBlock.statements()) - const first = expressions[0] - assert(first !== undefined) - const ast = first + const ast = Ast.parse(expression) const methodCall: MethodCall = { methodPointer: { diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index 41c5606bd31f..59a9589c4098 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -884,49 +884,20 @@ export class Assignment extends Ast { } } -interface RawBlockLine { - newline?: NodeChild | undefined - expression: NodeChild | null -} - -interface BlockLine { - newline?: NodeChild | undefined - expression: NodeChild | null -} - export class BodyBlock extends Ast { private readonly lines_: RawBlockLine[] static new(lines: BlockLine[], module?: MutableModule): BodyBlock { const module_ = module ?? MutableModule.Transient() - const rawLines = lines.map((line) => { + for (const line of lines) { if (line.expression) module_.splice(line.expression.node.exprId, line.expression.node) - return { - newline: line.newline, - expression: line.expression - ? { - whitespace: line.expression.whitespace, - node: line.expression.node.exprId, - } - : null, - } - }) + } + const rawLines = lines.map(lineToRaw) return new BodyBlock(module_, undefined, rawLines) } lines(): BlockLine[] { - return this.lines_.map((line) => { - const expression = line.expression ? this.module.get(line.expression.node) : null - return { - newline: line.newline, - expression: expression - ? { - whitespace: line.expression?.whitespace, - node: expression, - } - : null, - } - }) + return this.lines_.map((line) => lineFromRaw(line, this.module)) } *statements(): IterableIterator { @@ -973,7 +944,7 @@ export class BodyBlock extends Ast { code += line.newline?.whitespace ?? '' const newlineCode = line.newline?.node.code() // Only print a newline if this isn't the first line in the output, or it's a comment. - if (offset || code || newlineCode?.startsWith('#') || !line.expression) { + if (offset || code || newlineCode?.startsWith('#')) { // If this isn't the first line in the output, but there is a concrete newline token: // if it's a zero-length newline, ignore it and print a normal newline. code += newlineCode || '\n' @@ -996,6 +967,41 @@ export class BodyBlock extends Ast { } } +interface RawBlockLine { + newline?: NodeChild | undefined + expression: NodeChild | null +} + +interface BlockLine { + newline?: NodeChild | undefined + expression: NodeChild | null +} + +function lineFromRaw(raw: RawBlockLine, module: Module): BlockLine { + const expression = raw.expression ? module.get(raw.expression.node) : null + return { + newline: raw.newline, + expression: expression + ? { + whitespace: raw.expression?.whitespace, + node: expression, + } + : null, + } +} + +function lineToRaw(line: BlockLine): RawBlockLine { + return { + newline: line.newline, + expression: line.expression + ? { + whitespace: line.expression.whitespace, + node: line.expression.node.exprId, + } + : null, + } +} + export class Ident extends Ast { private readonly token: NodeChild From 44305b05e2e9fc43f4041be0744e859d6317de2f Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sat, 23 Dec 2023 19:00:02 +0000 Subject: [PATCH 08/12] Ast-based function update logic --- .../GraphEditor/widgets/WidgetFunction.vue | 68 ++++---- app/gui2/src/util/ast/abstract.ts | 149 +++++++++++------- 2 files changed, 133 insertions(+), 84 deletions(-) diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue index 4ba61ed138fb..211469a53ea5 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue @@ -101,15 +101,12 @@ const widgetConfiguration = computed(() => { /** * Process an argument value update. Takes care of inserting assigned placeholder values, as well as * handling deletions of arguments and rewriting the applications to named as appropriate. - * - * FIXME: This method has to be rewritten usign AST manipulation instead of string concatenation - * once AST updates are implemented. Depends on #8367 */ function handleArgUpdate(value: unknown, origin: PortId): boolean { const app = application.value if (app instanceof ArgumentApplication) { // Find the updated argument by matching origin port/expression with the appropriate argument. - // We are insterested only in updates at the top level of the argument AST. Updates from nested + // We are interested only in updates at the top level of the argument AST. Updates from nested // widgets do not need to be processed at the function application level. const argApp = [...app.iterApplications()].find( (app) => 'portId' in app.argument && app.argument.portId === origin, @@ -118,13 +115,17 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { // Perform appropriate AST update, either insertion or deletion. if (value != null && argApp?.argument instanceof ArgumentPlaceholder) { /* Case: Inserting value to a placeholder. */ - const codeToInsert = value instanceof Ast.Ast ? value.code() : value - const argCode = argApp.argument.insertAsNamed - ? `${argApp.argument.info.name}=${codeToInsert}` - : codeToInsert - - // FIXME[#8367]: Create proper application AST instead of concatenating strings. - props.onUpdate(`${argApp.appTree.code()} ${argCode}`, argApp.appTree.exprId) + const edit = argApp.appTree.module.edit() + let newArg: Ast.Ast | undefined + if (value instanceof Ast.Ast) newArg = value + else if (typeof value === 'string') newArg = Ast.parse(value, edit) + if (!newArg) { + console.error(`Don't know how to put this in a tree`, value) + return true + } + const name = argApp.argument.insertAsNamed ? argApp.argument.info.name : null + const ast = Ast.App.new(argApp.appTree, name, newArg, edit) + props.onUpdate(ast, argApp.appTree.exprId) return true } else if (value == null && argApp?.argument instanceof ArgumentAst) { /* Case: Removing existing argument. */ @@ -147,44 +148,49 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { } else if (argApp.appTree instanceof Ast.App && argApp.appTree.argumentName == null) { /* Case: Removing positional prefix argument. */ - // Since the update of this kind can affect following arguments, it is necessary to - // construct the new AST for the whole application in order to update it. Because we lack - // the ability to do AST manipulation directly, we are forced to do it by string operations. - // FIXME[#8367]: Edit application AST instead of concatenating strings. - let newRepr = '' + // Since the update of this kind can affect following arguments, it may be necessary to + // replace the AST for multiple levels of application. + // The unmodified LHS subtree of the subtree that is being replaced. + let innerBound: Ast.Ast | undefined + // The top level of the subtree that is being replaced. + let outerBound: Ast.Ast | undefined + // The levels of the application tree to apply to `innerBound` to yield the new `outerBound` expression. + const newArgs: { name: Ast.Token | null; value: Ast.Ast }[] = [] // Traverse the application chain, starting from the outermost application and going // towards the innermost target. for (let innerApp of app.iterApplications()) { if (innerApp === argApp) { // Found the application with the argument to remove. Skip the argument and use the // application target's code. This is the final iteration of the loop. - newRepr = `${argApp.appTree.function.code().trimEnd()} ${newRepr.trimStart()}` - // Perform the actual update, since we already have the whole new application code - // collected. - props.onUpdate(newRepr, app.appTree.exprId) - return true + innerBound = argApp.appTree.function + break } else { // Process an argument to the right of the removed argument. assert(innerApp.appTree instanceof Ast.App) - const argRepr = innerApp.appTree - .code() - .substring(innerApp.appTree.function.code().length) - .trim() - if ( + const argNeedsRewrite = innerApp.argument instanceof ArgumentAst && innerApp.appTree.argumentName == null && innerApp.argument.info != null - ) { + if (argNeedsRewrite || newArgs) { // Positional arguments following the deleted argument must all be rewritten to named. - newRepr = `${innerApp.argument.info.name}=${argRepr} ${newRepr.trimStart()}` + newArgs.unshift({ + name: innerApp.appTree.argumentName, + value: innerApp.appTree.argument, + }) } else { - // All other arguments are copied as-is. - newRepr = `${argRepr} ${newRepr.trimStart()}` + // We haven't reached the subtree that needs to be modified yet. + outerBound = innerApp.appTree } } } - assertUnreachable() + assert(innerBound !== undefined) + assert(outerBound !== undefined) + const edit = outerBound.module.edit() + let newAst = innerBound + for (const arg of newArgs) newAst = Ast.App.new(newAst, arg.name, arg.value, edit) + props.onUpdate(newAst, outerBound.exprId) + return true } else if (value == null && argApp.argument instanceof ArgumentPlaceholder) { /* Case: Removing placeholder value. */ // Do nothing. The argument already doesn't exist, so there is nothing to update. diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index 59a9589c4098..0bdca96863ae 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -1,4 +1,5 @@ import * as RawAst from '@/generated/ast' +import { assert } from '@/util/assert' import { parseEnso } from '@/util/ast' import { AstExtended as RawAstExtended } from '@/util/ast/extended' import type { Opt } from '@/util/data/opt' @@ -109,23 +110,29 @@ export class MutableModule implements Module { } set(id: AstId, ast: Ast) { - const ast_ = id === ast.exprId ? ast : ast.cloneTo(this, id) - this.splice(id, ast_) - this.nodes.set(id, ast_) - } - - /** Insert references to the given node and all its descendants into this module, - * if they aren't already accessible. */ - splice(id: AstId, ast: Ast) { - let moduleInChain: Module | null = this - while (moduleInChain) { - if (ast.module === moduleInChain) return - moduleInChain = moduleInChain.raw.base - } - if (!moduleInChain) this.nodes.set(id, ast) - for (const child of ast.concreteChildren()) { - if (!(child.node instanceof Token)) this.splice(child.node, ast.module.get(child.node)!) + this.splice(ast, id) + } + + /** Copy the given node and all its descendants into this module. */ + splice(ast: Ast, id?: AstId): Ast + splice(ast: null, id?: AstId): null + splice(ast: undefined, id?: AstId): undefined + splice(ast: Ast | null, id?: AstId): Ast | null + splice(ast: Ast | undefined, id?: AstId): Ast | undefined + splice(ast: Ast | null | undefined, id?: AstId): Ast | null | undefined { + if (!ast) return ast + const id_ = id ?? newAstId() + const ast_ = ast.cloneWithId(this, id_) + for (const child of ast_.concreteChildren()) { + if (!(child.node instanceof Token)) { + const childInForeignModule = ast.module.get(child.node) + assert(childInForeignModule !== null) + const spliced = this.splice(childInForeignModule) + child.node = spliced.exprId + } } + this.nodes.set(id_, ast_) + return ast_ } getExtended(id: AstId): RawAstExtended | undefined { @@ -333,15 +340,50 @@ export abstract class Ast { return code } - /** Return a shallow copy of this node, with the specified `module` and `exprId` properties. */ - cloneTo(module: Module, exprId: AstId): Ast { - const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this) + /** Return a copy of this node, with the specified `module` and `exprId` properties. + * + * The node's owned data is deep-copied, although note that child subtrees are stored as IDs, + * so a full deep copy requires recursively cloning child nodes. + */ + cloneWithId(module: Module, exprId: AstId): this { + for (const child of this.concreteChildren()) { + assert(child !== undefined, 'concrete children are valid') + } + const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), cloneObjectTree(this)) clone.module = module clone.exprId = exprId - return clone + const ast: this = clone + for (const child of ast.concreteChildren()) { + assert(child !== undefined, 'cloned children are valid') + } + return ast } } +function cloneObjectTree(object: Object): Object { + const mapEntry = ([name, value]: [string, unknown]) => [ + name, + value && typeof value === 'object' ? cloneObjectTree(value) : value, + ] + return Object.fromEntries(Object.entries(object).map(mapEntry)) +} + +function spaced(node: T): NodeChild { + return { whitespace: ' ', node } +} + +function unspaced(node: T): NodeChild { + return { whitespace: '', node } +} + +function autospaced(node: T): NodeChild { + return { node } +} + +function spacedIf(node: T, isSpaced: boolean): NodeChild { + return { whitespace: isSpaced ? ' ' : '', node } +} + export class App extends Ast { private readonly func_: NodeChild private readonly leftParen_: NodeChild | null @@ -350,6 +392,18 @@ export class App extends Ast { private readonly arg_: NodeChild private readonly rightParen_: NodeChild | null + static new(func: Ast, name: string | Token | null, arg: Ast, module?: MutableModule) { + const edit = module ?? MutableModule.Transient() + const func_ = unspaced(edit.splice(func).exprId) + const nameToken = + typeof name === 'string' ? new Token(name, newTokenId(), RawAst.Token.Type.Ident) : null + const name_ = nameToken ? spaced(nameToken) : null + const equals = name ? unspaced(new Token('=', newTokenId(), RawAst.Token.Type.Operator)) : null + const arg_ = spacedIf(edit.splice(arg).exprId, !name) + const treeType = name ? RawAst.Tree.Type.NamedApp : RawAst.Tree.Type.App + return new App(edit, undefined, func_, null, name_, equals, arg_, null, treeType) + } + get function(): Ast { return this.module.get(this.func_.node)! } @@ -535,16 +589,15 @@ export class PropertyAccess extends OprApp { rhs: NodeChild | null, ) { const oprs = [ - opr ?? { whitespace: '', node: new Token('.', newTokenId(), RawAst.Token.Type.Operator) }, + opr ?? unspaced(new Token('.', newTokenId(), RawAst.Token.Type.Operator)), ] super(module, id, lhs, oprs, rhs) } static new(module: MutableModule, lhs: Ast | null, rhs: Token | null): PropertyAccess { - if (lhs) module.splice(lhs.exprId, lhs) - const lhs_ = lhs ? { node: lhs.exprId } : null + const lhs_ = lhs ? unspaced(module.splice(lhs).exprId) : null const rhs_ = rhs - ? { whitespace: '', node: new Ident(module, undefined, { whitespace: '', node: rhs }).exprId } + ? unspaced(new Ident(module, undefined, unspaced(rhs)).exprId) : null return new PropertyAccess(module, undefined, lhs_, undefined, rhs_) } @@ -586,8 +639,8 @@ export class Generic extends Ast { type MultiSegmentAppSegment = { header: NodeChild; body: NodeChild | null } function multiSegmentAppSegment(header: string, body: Ast): MultiSegmentAppSegment { return { - header: { node: new Token(header, newTokenId(), RawAst.Token.Type.Ident) }, - body: { node: body.exprId }, + header: unspaced(new Token(header, newTokenId(), RawAst.Token.Type.Ident)), + body: spaced(body.exprId), } } @@ -699,9 +752,9 @@ export class TextLiteral extends Ast { static new(rawText: string, moduleIn?: MutableModule): TextLiteral { const module = moduleIn ?? MutableModule.Transient() - const open = { node: Token.new("'") } - const elements = [{ whitespace: '', node: Token.new(escape(rawText)) }] - const close = { whitespace: '', node: Token.new("'") } + const open = unspaced(Token.new("'")) + const elements = [unspaced(Token.new(escape(rawText)))] + const close = unspaced(Token.new("'")) return new TextLiteral(module, undefined, open, null, elements, close) } @@ -859,17 +912,13 @@ export class Assignment extends Ast { ) { super(module, id, RawAst.Tree.Type.Assignment) this.pattern_ = pattern - this.equals_ = equals ?? { node: new Token('=', newTokenId(), RawAst.Token.Type.Operator) } + this.equals_ = equals ?? spacedIf(new Token('=', newTokenId(), RawAst.Token.Type.Operator), !!expression.whitespace) this.expression_ = expression } static new(module: MutableModule, ident: string, expression: Ast): Assignment { - module.splice(expression.exprId, expression) - const pattern = { node: Ident.new(module, ident).exprId } - return new Assignment(module, undefined, pattern, undefined, { - whitespace: ' ', - node: expression.exprId, - }) + const pattern = unspaced(Ident.new(module, ident).exprId) + return new Assignment(module, undefined, pattern, undefined, spaced(module.splice(expression).exprId)) } *concreteChildren(): IterableIterator { @@ -889,10 +938,7 @@ export class BodyBlock extends Ast { static new(lines: BlockLine[], module?: MutableModule): BodyBlock { const module_ = module ?? MutableModule.Transient() - for (const line of lines) { - if (line.expression) module_.splice(line.expression.node.exprId, line.expression.node) - } - const rawLines = lines.map(lineToRaw) + const rawLines = lines.map((line) => lineToRaw(line, module_)) return new BodyBlock(module_, undefined, rawLines) } @@ -912,13 +958,13 @@ export class BodyBlock extends Ast { } push(module: MutableModule, node: Ast) { - new BodyBlock(module, this.exprId, [...this.lines_, { expression: { node: node.exprId } }]) + new BodyBlock(module, this.exprId, [...this.lines_, { expression: unspaced(node.exprId) }]) } /** Insert the given expression(s) starting at the specified line index. */ insert(module: MutableModule, index: number, ...nodes: Ast[]) { const before = this.lines_.slice(0, index) - const insertions = Array.from(nodes, (node) => ({ expression: { node: node.exprId } })) + const insertions = Array.from(nodes, (node) => ({ expression: unspaced(node.exprId) })) const after = this.lines_.slice(index) new BodyBlock(module, this.exprId, [...before, ...insertions, ...after]) } @@ -990,13 +1036,14 @@ function lineFromRaw(raw: RawBlockLine, module: Module): BlockLine { } } -function lineToRaw(line: BlockLine): RawBlockLine { +function lineToRaw(line: BlockLine, module: MutableModule): RawBlockLine { + const expression = module.splice(line.expression?.node) return { newline: line.newline, - expression: line.expression + expression: expression ? { - whitespace: line.expression.whitespace, - node: line.expression.node.exprId, + whitespace: line.expression?.whitespace, + node: expression.exprId, } : null, } @@ -1011,9 +1058,7 @@ export class Ident extends Ast { } static new(module: MutableModule, code: string): Ident { - return new Ident(module, undefined, { - node: new Token(code, newTokenId(), RawAst.Token.Type.Ident), - }) + return new Ident(module, undefined, unspaced(new Token(code, newTokenId(), RawAst.Token.Type.Ident))) } *concreteChildren(): IterableIterator { @@ -1031,9 +1076,7 @@ export class Wildcard extends Ast { static new(module?: MutableModule): Wildcard { const module_ = module ?? MutableModule.Transient() - return new Wildcard(module_, undefined, { - node: new Token('_', newTokenId(), RawAst.Token.Type.Wildcard), - }) + return new Wildcard(module_, undefined, unspaced(new Token('_', newTokenId(), RawAst.Token.Type.Wildcard))) } *concreteChildren(): IterableIterator { @@ -1052,7 +1095,7 @@ export class RawCode extends Ast { static new(code: string, moduleIn?: MutableModule, id?: AstId | undefined): RawCode { const token = new Token(code, newTokenId(), RawAst.Token.Type.Ident) const module = moduleIn ?? MutableModule.Transient() - return new RawCode(module, id, { node: token }) + return new RawCode(module, id, unspaced(token)) } *concreteChildren(): IterableIterator { From c4fcdbc95e1347c3a7c3914998fe6b9ada284f6f Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sun, 24 Dec 2023 15:51:20 +0000 Subject: [PATCH 09/12] Fix space and splice logic --- .../src/util/ast/__tests__/abstract.test.ts | 19 ++++- app/gui2/src/util/ast/abstract.ts | 79 +++++++++++-------- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/app/gui2/src/util/ast/__tests__/abstract.test.ts b/app/gui2/src/util/ast/__tests__/abstract.test.ts index 53c55d8b928e..159314987b6c 100644 --- a/app/gui2/src/util/ast/__tests__/abstract.test.ts +++ b/app/gui2/src/util/ast/__tests__/abstract.test.ts @@ -3,6 +3,7 @@ import * as fs from 'fs' import { expect, test } from 'vitest' import { preParseContent } from '../../../../ydoc-server/edits' import { deserializeIdMap, serializeIdMap } from '../../../../ydoc-server/serialization' +import { MutableModule } from '../abstract' //const disabledCases = [ // ' a', @@ -371,7 +372,7 @@ test.each(parseCases)('parse: %s', (testCase) => { expect(Ast.tokenTree(root)).toEqual(testCase.tree) }) -test('insert new node', () => { +test('Insert new expression', () => { const code = 'main =\n text1 = "foo"\n' const root = Ast.parseBlock(code) const main = Ast.functionBlock(root.module, 'main')! @@ -384,7 +385,7 @@ test('insert new node', () => { expect(printed).toEqual('main =\n text1 = "foo"\n baz = 42\n') }) -test('replace expression content', () => { +test('Replace expression content', () => { const code = 'main =\n text1 = "foo"\n' const root = Ast.parseBlock(code) const main = Ast.functionBlock(root.module, 'main')! @@ -394,12 +395,14 @@ test('replace expression content', () => { expect(assignment.expression).not.toBeNull() const edit = root.module.edit() const newValue = Ast.TextLiteral.new('bar', edit) + expect(newValue.code()).toBe("'bar'") edit.set(assignment.expression!.exprId, newValue) + expect(edit.get(assignment.expression!.exprId)?.code()).toBe("'bar'") const printed = root.code(edit) expect(printed).toEqual("main =\n text1 = 'bar'\n") }) -test('delete expression', () => { +test('Delete expression', () => { const originalCode = 'main =\n text1 = "foo"\n text2 = "bar"\n' const root = Ast.parseBlock(originalCode) const main = Ast.functionBlock(root.module, 'main')! @@ -453,3 +456,13 @@ test('Block lines interface', () => { // Note that trailing whitespace belongs to the following line. expect(newBlock.code()).toBe('GNIK \nSISI\nVLE \n') }) + +test('Splice', () => { + const module = MutableModule.Observable() + const edit = module.edit() + const ident = Ast.Ident.new(edit, 'foo') + expect(ident.code()).toBe('foo') + const spliced = module.splice(ident) + expect(spliced.module).toBe(module) + expect(spliced.code()).toBe('foo') +}) diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index 0bdca96863ae..dec1ab0c515b 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -110,13 +110,15 @@ export class MutableModule implements Module { } set(id: AstId, ast: Ast) { - this.splice(ast, id) + if (ast.exprId !== id || ast.module !== this) { + this.splice(ast, id) + } else { + this.nodes.set(id, ast) + } } /** Copy the given node and all its descendants into this module. */ splice(ast: Ast, id?: AstId): Ast - splice(ast: null, id?: AstId): null - splice(ast: undefined, id?: AstId): undefined splice(ast: Ast | null, id?: AstId): Ast | null splice(ast: Ast | undefined, id?: AstId): Ast | undefined splice(ast: Ast | null | undefined, id?: AstId): Ast | null | undefined { @@ -304,13 +306,13 @@ export abstract class Ast { this.module = module this.exprId = id ?? newAstId() this.treeType = treeType - module.set(this.exprId, this) + module.nodes.set(this.exprId, this) } printSubtree( info: InfoMap, offset: number, - indent: string, + parentIndent: string | null, moduleOverride?: Module | undefined, ): string { const module_ = moduleOverride ?? this.module @@ -331,7 +333,7 @@ export abstract class Ast { } else { code += module_ .get(child.node)! - .printSubtree(info, offset + code.length, indent, moduleOverride) + .printSubtree(info, offset + code.length, parentIndent, moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) @@ -346,26 +348,26 @@ export abstract class Ast { * so a full deep copy requires recursively cloning child nodes. */ cloneWithId(module: Module, exprId: AstId): this { - for (const child of this.concreteChildren()) { - assert(child !== undefined, 'concrete children are valid') - } - const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), cloneObjectTree(this)) - clone.module = module - clone.exprId = exprId - const ast: this = clone - for (const child of ast.concreteChildren()) { - assert(child !== undefined, 'cloned children are valid') - } - return ast + const cloned = clone(this) + // No one else has a reference to this object, so we can ignore `readonly` and mutate it. + Object.assign(cloned, { module, exprId }) + return cloned } } -function cloneObjectTree(object: Object): Object { - const mapEntry = ([name, value]: [string, unknown]) => [ - name, - value && typeof value === 'object' ? cloneObjectTree(value) : value, - ] - return Object.fromEntries(Object.entries(object).map(mapEntry)) +function clone(value: T): T { + if (value instanceof Array) { + return Array.from(value, clone) as T + } + if (value && typeof value === 'object') { + return cloneObject(value) + } + return value +} +function cloneObject(object: T): T { + const mapEntry = ([name, value]: [string, unknown]) => [name, clone(value)] + const properties = Object.fromEntries(Object.entries(object).map(mapEntry)) + return Object.assign(Object.create(Object.getPrototypeOf(object)), properties) } function spaced(node: T): NodeChild { @@ -637,9 +639,9 @@ export class Generic extends Ast { } type MultiSegmentAppSegment = { header: NodeChild; body: NodeChild | null } -function multiSegmentAppSegment(header: string, body: Ast): MultiSegmentAppSegment { +function multiSegmentAppSegment(whitespace: string, header: string, body: Ast): MultiSegmentAppSegment { return { - header: unspaced(new Token(header, newTokenId(), RawAst.Token.Type.Ident)), + header: { whitespace, node: new Token(header, newTokenId(), RawAst.Token.Type.Ident) }, body: spaced(body.exprId), } } @@ -699,7 +701,7 @@ export class Import extends Ast { const module_ = module ?? MutableModule.Transient() const path_ = PropertyAccess.Sequence(path, module) if (!path_) return - const import_ = multiSegmentAppSegment('import', path_) + const import_ = multiSegmentAppSegment('', 'import', path_) return new Import(module_, undefined, null, null, import_, null, null, null) } @@ -708,8 +710,8 @@ export class Import extends Ast { const path_ = PropertyAccess.Sequence(path, module) if (!path_) return const name_ = Ident.new(module_, name) - const from = multiSegmentAppSegment('from', path_) - const import_ = multiSegmentAppSegment('import', name_) + const from = multiSegmentAppSegment('', 'from', path_) + const import_ = multiSegmentAppSegment(' ', 'import', name_) return new Import(module_, undefined, null, from, import_, null, null, null) } @@ -958,7 +960,7 @@ export class BodyBlock extends Ast { } push(module: MutableModule, node: Ast) { - new BodyBlock(module, this.exprId, [...this.lines_, { expression: unspaced(node.exprId) }]) + new BodyBlock(module, this.exprId, [...this.lines_, { expression: autospaced(node.exprId) }]) } /** Insert the given expression(s) starting at the specified line index. */ @@ -979,10 +981,11 @@ export class BodyBlock extends Ast { printSubtree( info: InfoMap, offset: number, - indent: string, + parentIndent: string | null, moduleOverride?: Module | undefined, ): string { const module_ = moduleOverride ?? this.module + let blockIndent: string | undefined let code = '' for (const line of this.lines_) { // Skip deleted lines (and associated whitespace). @@ -996,10 +999,20 @@ export class BodyBlock extends Ast { code += newlineCode || '\n' } if (line.expression !== null) { - code += line.expression.whitespace ?? indent + if (blockIndent === undefined) { + if ((line.expression.whitespace?.length ?? 0) > (parentIndent?.length ?? 0)) { + blockIndent = line.expression.whitespace! + } else if (parentIndent !== null) { + blockIndent = parentIndent + ' ' + } else { + blockIndent = '' + } + } + const validIndent = (line.expression.whitespace?.length ?? 0) > (parentIndent?.length ?? 0) + code += validIndent ? line.expression.whitespace : blockIndent code += module_ .get(line.expression.node)! - .printSubtree(info, offset + code.length, indent + ' ', moduleOverride) + .printSubtree(info, offset + code.length, blockIndent, moduleOverride) } } const span = nodeKey(offset, code.length, this.treeType) @@ -1381,7 +1394,7 @@ export function print(ast: AstId, module: Module): PrintedSource { tokens: new Map(), tokensOut: new Map(), } - const code = module.get(ast)!.printSubtree(info, 0, '', module) + const code = module.get(ast)!.printSubtree(info, 0, null, module) return { info, code } } From 6558daf9318e725598d087c93b0e6dd0806e5797 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sun, 24 Dec 2023 16:08:31 +0000 Subject: [PATCH 10/12] fix WUH --- .../components/GraphEditor/widgets/WidgetFunction.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue index 211469a53ea5..f24b04ea23a7 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue @@ -154,7 +154,7 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { // The unmodified LHS subtree of the subtree that is being replaced. let innerBound: Ast.Ast | undefined // The top level of the subtree that is being replaced. - let outerBound: Ast.Ast | undefined + let outerBound = argApp.appTree // The levels of the application tree to apply to `innerBound` to yield the new `outerBound` expression. const newArgs: { name: Ast.Token | null; value: Ast.Ast }[] = [] // Traverse the application chain, starting from the outermost application and going @@ -169,10 +169,11 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { // Process an argument to the right of the removed argument. assert(innerApp.appTree instanceof Ast.App) const argNeedsRewrite = - innerApp.argument instanceof ArgumentAst && + newArgs || + (innerApp.argument instanceof ArgumentAst && innerApp.appTree.argumentName == null && - innerApp.argument.info != null - if (argNeedsRewrite || newArgs) { + innerApp.argument.info != null) + if (argNeedsRewrite) { // Positional arguments following the deleted argument must all be rewritten to named. newArgs.unshift({ name: innerApp.appTree.argumentName, @@ -185,7 +186,6 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { } } assert(innerBound !== undefined) - assert(outerBound !== undefined) const edit = outerBound.module.edit() let newAst = innerBound for (const arg of newArgs) newAst = Ast.App.new(newAst, arg.name, arg.value, edit) From 4615b8b87441b440e5d7dcdd9f2286512e118999 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sun, 24 Dec 2023 16:08:51 +0000 Subject: [PATCH 11/12] fmt --- .../GraphEditor/widgets/WidgetFunction.vue | 4 +- app/gui2/src/util/ast/abstract.ts | 38 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue index f24b04ea23a7..0e554c0dc39b 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue @@ -171,8 +171,8 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { const argNeedsRewrite = newArgs || (innerApp.argument instanceof ArgumentAst && - innerApp.appTree.argumentName == null && - innerApp.argument.info != null) + innerApp.appTree.argumentName == null && + innerApp.argument.info != null) if (argNeedsRewrite) { // Positional arguments following the deleted argument must all be rewritten to named. newArgs.unshift({ diff --git a/app/gui2/src/util/ast/abstract.ts b/app/gui2/src/util/ast/abstract.ts index dec1ab0c515b..ecbd41f10b73 100644 --- a/app/gui2/src/util/ast/abstract.ts +++ b/app/gui2/src/util/ast/abstract.ts @@ -590,17 +590,13 @@ export class PropertyAccess extends OprApp { opr: NodeChild | undefined, rhs: NodeChild | null, ) { - const oprs = [ - opr ?? unspaced(new Token('.', newTokenId(), RawAst.Token.Type.Operator)), - ] + const oprs = [opr ?? unspaced(new Token('.', newTokenId(), RawAst.Token.Type.Operator))] super(module, id, lhs, oprs, rhs) } static new(module: MutableModule, lhs: Ast | null, rhs: Token | null): PropertyAccess { const lhs_ = lhs ? unspaced(module.splice(lhs).exprId) : null - const rhs_ = rhs - ? unspaced(new Ident(module, undefined, unspaced(rhs)).exprId) - : null + const rhs_ = rhs ? unspaced(new Ident(module, undefined, unspaced(rhs)).exprId) : null return new PropertyAccess(module, undefined, lhs_, undefined, rhs_) } @@ -639,7 +635,11 @@ export class Generic extends Ast { } type MultiSegmentAppSegment = { header: NodeChild; body: NodeChild | null } -function multiSegmentAppSegment(whitespace: string, header: string, body: Ast): MultiSegmentAppSegment { +function multiSegmentAppSegment( + whitespace: string, + header: string, + body: Ast, +): MultiSegmentAppSegment { return { header: { whitespace, node: new Token(header, newTokenId(), RawAst.Token.Type.Ident) }, body: spaced(body.exprId), @@ -914,13 +914,21 @@ export class Assignment extends Ast { ) { super(module, id, RawAst.Tree.Type.Assignment) this.pattern_ = pattern - this.equals_ = equals ?? spacedIf(new Token('=', newTokenId(), RawAst.Token.Type.Operator), !!expression.whitespace) + this.equals_ = + equals ?? + spacedIf(new Token('=', newTokenId(), RawAst.Token.Type.Operator), !!expression.whitespace) this.expression_ = expression } static new(module: MutableModule, ident: string, expression: Ast): Assignment { const pattern = unspaced(Ident.new(module, ident).exprId) - return new Assignment(module, undefined, pattern, undefined, spaced(module.splice(expression).exprId)) + return new Assignment( + module, + undefined, + pattern, + undefined, + spaced(module.splice(expression).exprId), + ) } *concreteChildren(): IterableIterator { @@ -1071,7 +1079,11 @@ export class Ident extends Ast { } static new(module: MutableModule, code: string): Ident { - return new Ident(module, undefined, unspaced(new Token(code, newTokenId(), RawAst.Token.Type.Ident))) + return new Ident( + module, + undefined, + unspaced(new Token(code, newTokenId(), RawAst.Token.Type.Ident)), + ) } *concreteChildren(): IterableIterator { @@ -1089,7 +1101,11 @@ export class Wildcard extends Ast { static new(module?: MutableModule): Wildcard { const module_ = module ?? MutableModule.Transient() - return new Wildcard(module_, undefined, unspaced(new Token('_', newTokenId(), RawAst.Token.Type.Wildcard))) + return new Wildcard( + module_, + undefined, + unspaced(new Token('_', newTokenId(), RawAst.Token.Type.Wildcard)), + ) } *concreteChildren(): IterableIterator { From 8f961b435529c54f762fec686fbe8d30c9a849c2 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sun, 24 Dec 2023 17:51:48 +0000 Subject: [PATCH 12/12] fix WUH --- .../GraphEditor/widgets/WidgetFunction.vue | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue index 0e554c0dc39b..1543180612f8 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue @@ -156,7 +156,7 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { // The top level of the subtree that is being replaced. let outerBound = argApp.appTree // The levels of the application tree to apply to `innerBound` to yield the new `outerBound` expression. - const newArgs: { name: Ast.Token | null; value: Ast.Ast }[] = [] + const newArgs: { name: string | null; value: Ast.Ast }[] = [] // Traverse the application chain, starting from the outermost application and going // towards the innermost target. for (let innerApp of app.iterApplications()) { @@ -168,15 +168,14 @@ function handleArgUpdate(value: unknown, origin: PortId): boolean { } else { // Process an argument to the right of the removed argument. assert(innerApp.appTree instanceof Ast.App) - const argNeedsRewrite = - newArgs || - (innerApp.argument instanceof ArgumentAst && - innerApp.appTree.argumentName == null && - innerApp.argument.info != null) - if (argNeedsRewrite) { + const infoName = + innerApp.argument instanceof ArgumentAst && innerApp.argument.info != null + ? innerApp.argument.info?.name ?? null + : null + if (newArgs.length || (!innerApp.appTree.argumentName && infoName)) { // Positional arguments following the deleted argument must all be rewritten to named. newArgs.unshift({ - name: innerApp.appTree.argumentName, + name: infoName, value: innerApp.appTree.argument, }) } else {