From 422f204a6a9376748f239186a8b406f6b66256c3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 16 Dec 2023 20:19:45 -0400 Subject: [PATCH 1/8] toLeopard: store # of times to repeat statically when block-shaped --- src/io/leopard/toLeopard.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index d1820ed..665ea03 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -1327,10 +1327,17 @@ export default function toLeopard( const times = inputToJS(block.inputs.TIMES, InputShape.Number); const substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack); - blockSource = `for (let i = 0; i < ${times}; i++) { - ${substack}; - ${warp ? "" : "yield;"} - }`; + if (block.inputs.TIMES.type === "number") { + blockSource = `for (let i = 0; i < ${times}; i++) { + ${substack}; + ${warp ? "" : "yield;"} + }`; + } else { + blockSource = `for (let i = 0, times = ${times}; i < times; i++) { + ${substack}; + ${warp ? "" : "yield;"} + }`; + } break; } From cd7ebee53bfabfbcf8fa1470397d07ae78902a0a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 22:10:47 -0300 Subject: [PATCH 2/8] toLeopard: add dynamic-repeat snapshot test Snapshot includes current output as of this commit... --- .../__snapshots__/dynamic-repeat.test.ts.snap | 76 ++++++++++++++++++ src/__tests__/dynamic-repeat.sb3 | Bin 0 -> 7511 bytes src/__tests__/dynamic-repeat.test.ts | 14 ++++ 3 files changed, 90 insertions(+) create mode 100644 src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap create mode 100644 src/__tests__/dynamic-repeat.sb3 create mode 100644 src/__tests__/dynamic-repeat.test.ts diff --git a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap new file mode 100644 index 0000000..3af2565 --- /dev/null +++ b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap @@ -0,0 +1,76 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`dynamic-repeat.sb3 -> leopard 1`] = ` +"import { + Sprite, + Trigger, + Watcher, + Costume, + Color, + Sound, +} from "https://unpkg.com/leopard@^1/dist/index.esm.js"; + +export default class Tests extends Sprite { + constructor(...args) { + super(...args); + + this.costumes = [ + new Costume("Gobo-a", "./Tests/costumes/Gobo-a.svg", { x: 47, y: 55 }), + ]; + + this.sounds = []; + + this.triggers = [ + new Trigger(Trigger.GREEN_FLAG, this.whenGreenFlagClicked), + ]; + } + + *avoidAbscuring(times, i) { + for (let i = 0; i < 1; i++) { + for (let i = 0, times = this.x + 6 * this.toNumber(i); i < times; i++) { + this.x += this.toNumber(times); + yield; + } + yield; + } + } + + *avoidAbscuring2(times1, times2, times3, times, i3, i2, i1, i) { + for ( + let i = 0, + times = + this.x + + (this.toNumber(i) + + this.toNumber(i1) + + (this.toNumber(i2) + this.toNumber(i3))); + i < times; + i++ + ) { + this.x += + this.toNumber(times1) + + this.toNumber(times2) + + (this.toNumber(times3) + this.toNumber(times)); + yield; + } + } + + *whenGreenFlagClicked() { + this.x = 0; + for (let i = 0; i < 2; i++) { + this.x += 1; + yield; + } + for (let i = 0, times = 2 + 2 * 0.5; i < times; i++) { + this.x += 1; + yield; + } + for (let i = 0, times = this.x + 4; i < times; i++) { + this.x += 1; + yield; + } + yield* this.avoidAbscuring(1, 1); + yield* this.avoidAbscuring2(0.15, 0.3, 0.25, 0.3, 1, 1, 2, 4); + } +} +" +`; diff --git a/src/__tests__/dynamic-repeat.sb3 b/src/__tests__/dynamic-repeat.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..c9844ac6c14b28b2f59ef3b33827be5d7f3c412b GIT binary patch literal 7511 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 { + const file = fs.readFileSync(path.join(__dirname, filename)); + return Project.fromSb3(file); +} + +test("dynamic-repeat.sb3 -> leopard", async () => { + const project = await loadProject("dynamic-repeat.sb3"); + expect(project.toLeopard()["Tests/Tests.js"]).toMatchSnapshot(); +}); From 7e0cbda3e103ed7dc2bbd0d1a8dc159d2ed2f787 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 22:16:09 -0300 Subject: [PATCH 3/8] toLeopard: enter expected dynamic-repeat snapshot Manually entered. As of this commit, the test fails. Note that we're fully expecting i2 to be used twice in the nested repeat example - although awkward, this is perfectly OK from a JavaScript perspective. --- .../__snapshots__/dynamic-repeat.test.ts.snap | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap index 3af2565..f5f97dc 100644 --- a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap +++ b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap @@ -26,8 +26,12 @@ export default class Tests extends Sprite { } *avoidAbscuring(times, i) { - for (let i = 0; i < 1; i++) { - for (let i = 0, times = this.x + 6 * this.toNumber(i); i < times; i++) { + for (let i2 = 0; i2 < 1; i2++) { + for ( + let i2 = 0, times2 = this.x + 6 * this.toNumber(i); + i2 < times2; + i2++ + ) { this.x += this.toNumber(times); yield; } @@ -37,14 +41,14 @@ export default class Tests extends Sprite { *avoidAbscuring2(times1, times2, times3, times, i3, i2, i1, i) { for ( - let i = 0, - times = + let i4 = 0, + times4 = this.x + (this.toNumber(i) + this.toNumber(i1) + (this.toNumber(i2) + this.toNumber(i3))); - i < times; - i++ + i4 < times4; + i4++ ) { this.x += this.toNumber(times1) + From 124b9335d6f3fd7f5ecdbfe409a3b487db72bd3e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 22:35:11 -0300 Subject: [PATCH 4/8] toLeopard: don't overshadow "times" custom block input --- src/io/leopard/toLeopard.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 665ea03..505f7d1 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -1333,7 +1333,17 @@ export default function toLeopard( ${warp ? "" : "yield;"} }`; } else { - blockSource = `for (let i = 0, times = ${times}; i < times; i++) { + let timesVar: string; + if (script && customBlockArgNameMap.has(script)) { + // Avoid overshadowing the name of a custom block input, which is just a normal + // local variable. + const argNames = customBlockArgNameMap.get(script)!; + timesVar = uniqueNameFactory(Object.values(argNames))("times"); + } else { + timesVar = "times"; + } + + blockSource = `for (let i = 0, ${timesVar} = ${times}; i < ${timesVar}; i++) { ${substack}; ${warp ? "" : "yield;"} }`; From 3a3ab7d4409e53230ffaf4676f506037d9a64bd0 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 22:52:12 -0300 Subject: [PATCH 5/8] toLeoaprd: don't overshadow "i" variable, either --- src/io/leopard/toLeopard.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 505f7d1..281c742 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -1327,23 +1327,32 @@ export default function toLeopard( const times = inputToJS(block.inputs.TIMES, InputShape.Number); const substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack); + // Note that although this is a useful-seeming utility function, it can't be + // factored out for general `blockToJS` usage. The variables in a `for` construct + // are only unique, disregarding all other blocks, *because* they are defined and + // accessed in a brand new scope, and deeper scopes can overshadow the name + // without causing any trouble. + let uniqueLocalVarName: (name: string) => string; + if (script && customBlockArgNameMap.has(script)) { + // Avoid overshadowing the name of a custom block input, which is just a normal + // local variable. + const argNames = customBlockArgNameMap.get(script)!; + uniqueLocalVarName = uniqueNameFactory(Object.values(argNames)); + } else { + // Nothing to overshadow, the name provided will be unique. (Since we're only + // going to pass two names, right as part of this block!) + uniqueLocalVarName = (name: string) => name; + } + + const iVar = uniqueLocalVarName("i"); if (block.inputs.TIMES.type === "number") { - blockSource = `for (let i = 0; i < ${times}; i++) { + blockSource = `for (let ${iVar} = 0; ${iVar} < ${times}; ${iVar}++) { ${substack}; ${warp ? "" : "yield;"} }`; } else { - let timesVar: string; - if (script && customBlockArgNameMap.has(script)) { - // Avoid overshadowing the name of a custom block input, which is just a normal - // local variable. - const argNames = customBlockArgNameMap.get(script)!; - timesVar = uniqueNameFactory(Object.values(argNames))("times"); - } else { - timesVar = "times"; - } - - blockSource = `for (let i = 0, ${timesVar} = ${times}; i < ${timesVar}; i++) { + const timesVar = uniqueLocalVarName("times"); + blockSource = `for (let ${iVar} = 0, ${timesVar} = ${times}; ${iVar} < ${timesVar}; ${iVar}++) { ${substack}; ${warp ? "" : "yield;"} }`; From 51cff13db5395337345190287ce3ce7b802b0417 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 23:02:43 -0300 Subject: [PATCH 6/8] toLeopard: enter expected i2 -> i3 dynamic-repeat snapshot --- .../__snapshots__/dynamic-repeat.test.ts.snap | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap index f5f97dc..25cc664 100644 --- a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap +++ b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap @@ -28,9 +28,9 @@ export default class Tests extends Sprite { *avoidAbscuring(times, i) { for (let i2 = 0; i2 < 1; i2++) { for ( - let i2 = 0, times2 = this.x + 6 * this.toNumber(i); - i2 < times2; - i2++ + let i3 = 0, times2 = this.x + 6 * this.toNumber(i); + i3 < times2; + i3++ ) { this.x += this.toNumber(times); yield; @@ -64,11 +64,11 @@ export default class Tests extends Sprite { this.x += 1; yield; } - for (let i = 0, times = 2 + 2 * 0.5; i < times; i++) { + for (let i2 = 0, times = 2 + 2 * 0.5; i2 < times; i2++) { this.x += 1; yield; } - for (let i = 0, times = this.x + 4; i < times; i++) { + for (let i3 = 0, times2 = this.x + 4; i3 < times2; i3++) { this.x += 1; yield; } From 9a4b59f8d8059fa6b34f3daba2f518b57142643b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 23:20:03 -0300 Subject: [PATCH 7/8] toLeopard: factor out uniqueLocalVarName, reuse across script --- src/io/leopard/toLeopard.ts | 49 ++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 281c742..fc88aec 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -433,6 +433,12 @@ export default function toLeopard( // Leopard names (JS function arguments, which are identifiers). let customBlockArgNameMap: Map = new Map(); + // Maps scripts to a function (from uniqueNameFactory) which produces unique + // names based on the provided default names. This is for "unqualified" + // identifiers, which basically means ordinary variables. That namespace is + // shared with custom block arguments! + let uniqueLocalVarNameMap: Map string> = new Map(); + // Maps variables and lists' Scratch IDs to corresponding Leopard names // (JS properties on `this.vars`). This is shared across all sprites, so // that global (stage) variables' IDs map to the same name regardless what @@ -482,6 +488,8 @@ export default function toLeopard( } } } + + uniqueLocalVarNameMap.set(script, uniqueNameFactory(Object.values(argNameMap))); } } @@ -590,6 +598,17 @@ export default function toLeopard( } function blockToJSWithContext(block: Block, target: Target, script?: Script): string { + // This will be shared by all contained blockToJS calls, which should include + // all following/descendant relative to the provided one. Officially local names + // should be unique to the script, but if we don't have a script, they will still + // be unique to these "nearby" blocks (part of the same blockToJSWithContext call). + let uniqueLocalVarName: (name: string) => string; + if (script && uniqueLocalVarNameMap.has(script)) { + uniqueLocalVarName = uniqueLocalVarNameMap.get(script)!; + } else { + uniqueLocalVarName = uniqueNameFactory(); + } + return blockToJS(block); function increase(leftSide: string, input: BlockInput.Any, allowIncrementDecrement: boolean): string { @@ -1324,34 +1343,24 @@ export default function toLeopard( case OpCode.control_repeat: { satisfiesInputShape = InputShape.Stack; + const timesIsStatic = block.inputs.TIMES.type === "number"; + + // Of course we convert blocks in a descending recursive hierarchy, + // but we still need to make sure we get the relevant local var names + // *before* processing the substack - which might include more "repeat" + // blocks! + const iVar = uniqueLocalVarName("i"); + const timesVar = timesIsStatic ? null : uniqueLocalVarName("times"); + const times = inputToJS(block.inputs.TIMES, InputShape.Number); const substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack); - // Note that although this is a useful-seeming utility function, it can't be - // factored out for general `blockToJS` usage. The variables in a `for` construct - // are only unique, disregarding all other blocks, *because* they are defined and - // accessed in a brand new scope, and deeper scopes can overshadow the name - // without causing any trouble. - let uniqueLocalVarName: (name: string) => string; - if (script && customBlockArgNameMap.has(script)) { - // Avoid overshadowing the name of a custom block input, which is just a normal - // local variable. - const argNames = customBlockArgNameMap.get(script)!; - uniqueLocalVarName = uniqueNameFactory(Object.values(argNames)); - } else { - // Nothing to overshadow, the name provided will be unique. (Since we're only - // going to pass two names, right as part of this block!) - uniqueLocalVarName = (name: string) => name; - } - - const iVar = uniqueLocalVarName("i"); - if (block.inputs.TIMES.type === "number") { + if (timesIsStatic) { blockSource = `for (let ${iVar} = 0; ${iVar} < ${times}; ${iVar}++) { ${substack}; ${warp ? "" : "yield;"} }`; } else { - const timesVar = uniqueLocalVarName("times"); blockSource = `for (let ${iVar} = 0, ${timesVar} = ${times}; ${iVar} < ${timesVar}; ${iVar}++) { ${substack}; ${warp ? "" : "yield;"} From 8e26c36367fa18f391df7332629477278ad82226 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 24 Jun 2024 23:23:15 -0300 Subject: [PATCH 8/8] toLeopard: test repeat nesting behavior more explicitly --- .../__snapshots__/dynamic-repeat.test.ts.snap | 9 ++++----- src/__tests__/dynamic-repeat.sb3 | Bin 7511 -> 7353 bytes 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap index 25cc664..1048760 100644 --- a/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap +++ b/src/__tests__/__snapshots__/dynamic-repeat.test.ts.snap @@ -61,11 +61,10 @@ export default class Tests extends Sprite { *whenGreenFlagClicked() { this.x = 0; for (let i = 0; i < 2; i++) { - this.x += 1; - yield; - } - for (let i2 = 0, times = 2 + 2 * 0.5; i2 < times; i2++) { - this.x += 1; + for (let i2 = 0, times = 2 + 2; i2 < times; i2++) { + this.x += 1; + yield; + } yield; } for (let i3 = 0, times2 = this.x + 4; i3 < times2; i3++) { diff --git a/src/__tests__/dynamic-repeat.sb3 b/src/__tests__/dynamic-repeat.sb3 index c9844ac6c14b28b2f59ef3b33827be5d7f3c412b..117cb500a9e2ec75941f1ebe02194a1c12cf7274 100644 GIT binary patch delta 3642 zcmV-A4#n};I=ML*P)h>@3IG5A2msI$*;v^50o0KV008yf_mK{FG<}wOV`oX3-v)B5>lk%M zl^_yu{qHbqvIsv=Jvf;vDDp|L5dDgt~HGo=Bl z7FB2{|3Qs-Lm2ANAq)*u9XNPd75WZIv1oqYVxSV6Vd_YKqr9#HQ2@4D@oy}#dPW!r zP+8ZY&UEzaLN&}Gwh@g5tY_PhE6edf=!?3lF<2*(IRIJ#8mc@r5ko4Af@U9 zfOJe5qYjcohJa>TM8BCJ42GbK~ zK{kZpy`whJd^d2Fu_c zWiW-h?XsAs2?v@=MMLOuB#BTh=|C|wfb1wtsVKL9E=JLYQBlZ3xXr{YsYMM!p_>6p zXJ|#IGL+?|cOzWeKsMgu)rz~{;+5)66D?PvBstq>x@YjzZTk!v#})GTp^W{u4J4Ur zZp8Ix4oRg-lFuU+au4!xH90BHno~N6%T-NKMax>cDnJqWywMV2TT&#;=MY9)qpo?Q zt}?xUt5hRaPM!wKX(pewrUC5MHbXD9iVf7Q9WpIze1sZ#?rEV@c&clz_AJ^<)i^O4 z%)eOh+Y*$;6<(tYM0NbCA5+yba0KP`{5GMYCrmi(rI7v^F0$Ox>lRw*(NxW;247j( zsMj3GI@)9<9}8V?_?{8K88CXD)SNz3ZFc8>?j!e!=3d+sy*}-zz^8hhD24p%VBBuy zbC9j(%%f>GJg#xGsNLe;xUDp186#dS`X_Op|0#2Rx>X-G{Wo>2^fMURk%+M&$GW%O z>9rKiFQdSWXs8iHkcV9zfo6J<9TRPpW=Z_EvA}lJj1Td_QE@Jc)yD@>Dg5CU#YC0a4t> zlBldrmiOf>W0TBvuA+}_Isr4O+^_gNm`Qb6L^9~5EQ&}lU<=mnPCLGOI~`+xBe`Wa zE#&S8eOoYOFYi0iBo4btbD-j&Y)v%Pct5xPMwM(Nvh#fh7}1IZU_uwIS8&2l_qM@h z%~ovG6K|By%PqfDiFo)$-IEzaY{fErDAsioJQ?2i{fes|ch)MJZWhP8tXx-7o_0fz z%3c=EAI|7(JdvHeyq&RV%RCa~Pn>%J=&QAeAs zrtT16n!VfOhmw3=KJ2-2`Mk~UOdOVj!KA;h>gQal*<*5rIbOL+7qdlFu5wKK%FPuD zv(gAQ?H2L=xY*C;drvog-LGT-TmWmWL#g`cNf^l5=plbN(6b|cxUgG)xS#Hto*TJ# zb|8-~w6fx)z4y4qQ87O0de75b%;f{}Z7wDEU3QD25xW&)Cs&=DYBOHV#?I?Q9k(cm z*YhQt_EmeyVxh|QXSGyjH3pq{hR+H9KxxzEyRgq+tb}n3@yfXCm{*HM&b)B>_@yiJ zqS!621EJ3K^@}%j5{BV_1?*kS;~H-9i@Ut2Tum58T-=@|nR@1gKD1kaXYF9&HY+s$ zz%Is(2=8X^a0~BQ+N?JD78C3k)mk814xGeSzH_rk$Ep)|I_4Zcr)l57o9O=VD*y41 zwCPnY-UxEOk-7$Ebw_Y!dUHptjECHBm4Gfrz>`W zWQ$15ALTG#WHZD_{0rF(ai;lRHp6^BogvN&KZ`!_ee}c`>Sxij-$zfJEq@mM_rVj_ z=RNY^ua>}#JQ$3FC*EeP(H|e{y>7@QA6*@Pk_S&&_R%RS(aW5F>71X`Pd(xe3Ba1? z^d=vDfWw!` zMS24xSqGb{uUYvBRx!PYiiyL2BStCH)0ib-GJeeqp8DD4jK7U-Cg0n1|1oMAD?9^) z=)Ag2$K~hP{nMG^YQolizVtlXJvxcK>^=)^u@i9?I!bGkc(LbC9B{>g)bwyK&D3fZ zBG4R0UH$QYaKS&yM`|%w^-hn{%I-9YWA6GgPSh3QW!(0+z~*cBThXq_iTwAPrM^Oc z`4%htOh-hQ|4yttDaViT?jm&R0Ek+zteJ&m{f zmT(svb{C1xt=Fi;Og4H@)BL;p7IDm7eQV2vLE7kl8L%&+rxQ@LUh(Dd(Ybh@%wium zdTc`m_b?=}|9uv0vN8T(M~7steL~?&W&dF)5IuU!9)|#A`7IXoWvu8wc285B<~=Ki z7}Xassa=q7-KuXe^g0N(ZqC2T>_qr~bwB>YizZRI-!aQ??z3gW`o^x4rC{2}aF!jm zZZ)lc#8^ve>-N%0DqwfEbx-MCXIuA?Hr!+D&e4X<*1aN2#$Nii?haWpJI_r2-w^t< z4WSR=Kztk7b=@MqM3L7mziQ~T*L!svWUkak`ux{Rc}P5 z-rcL<&Z{%2vt3z|U)+c+>+VctWbpH``W%aYMeCj6ZSI1|lHuxwL?@Jnv3m&|l#Vaa zi~Qa!iSNcc=eJ+I$G)*7zSNUghB|p(xio9nzaku42vkP#)08cEx(7|73;m16tUkp& z!N>s@{Uy}`7kOIuK8Qq`KsJx=%x-0RA>NFbS*^6_?QcxtsDEKwiyVOaZisI8MUpOm zeilAqM|3NEVgUb*O-16Yw(pkcR`|qe4}$iEMqn2hY2K74fyv5`Jp8(C0^ zCJO9DF_CKY`=NZ+PfQ(l%JJUm1F^GRS(5WMB1^sc%Qqe^ z({wy)yf|aQ(`o*h$TGXU?q(#mKAw55j|wm2(}!tiZpgNQKLn_Ps9^KZs4@%!4{4+g*dw*37;C+U=i`2FRMmRRn2G38q2)e}JN zb1OxR&0*h0ioUI>Elb*udAfCA0own;2K_4WeAkl5$&H2S?x#yp&q4a)@h<+Ja+O*Y6vyVSi$ChWpCu#JftbR$qM|`N8n(>rU^CUP%2Z za;+s_$$jR&49oc#Ci3s|K!!%%gCVNQYFZpX6JXsNa4^ENA;tP=AB|OvGF2+zV|{q3 z0v&rL%c?U;mVux1(GGVO2$Eu|MmOe2qY#!IsKAjcI%*DvZZTXdeB?-InhdLuzmrUV zh(;`Z4Cip)-ITH{8833kK6G3|p|2h}Dp1Fu{7l%#If|f-+~c(YwOsxeP)h>@3IG5A z2msI$vtv2@cQ_*;v^50o0KV008Blk5;ClkXf10?-nZ{Tv(uk`a?k5+)Wi_@% delta 3809 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