型レベルの JSON パーサー作った


## 完成品

https://www.typescriptlang.org/play?#code/C4TwDgpgBA6gFgS2BAymAhgY2gXigcgKgB8CAdYQ0-MgOyvICd8AoF0SKABUYmGAQAzEAB4AKlAgAPZLQAmAZygBvAL4A+KHmUsoUANoBpKAlpQA1hBAB7QVDEBdAFz2jDgNwtVUAGQrVbBzQADIQgsAAQoxYEGLWlmbaFqZyLvgANmHAALQARtHYhAFBUABKCADmcJEFsfEQiSrJ8mmMldV5tUXs4CFZUViWwHEJWk3mKWmZ4Z2DfN0l5VU1c8P1jcrNqQRty7OYQwu9UADC1gC25+gjDWObEy0EmBdXR5xn6da0Nxtbac+fehQYrHAByAFd0ukfnc-gRaJD0m9oBFrNZMuhvutxJIZA1FFBcmiMbRNEkHtt8ET0RBMfgADRQABu6HS4IgLgkIM4KGAbVoFR+OOksgJCj5pgqZPGkwI4v5FQZzNZ7M5wJ6nAh51yEEYQokIvxSgR2t10vusvwJp1zEZLLZHPs6o10BheF0UFC4QG2BhxA9S2qPrqo39ei9KwOfD9AfakaGMb0Z0u13WJA9Hy+iagEKh2dRNMxQupJM0YagvIVQvlkrLHq1NqF1rNgWOYkYIC46EYCggDd1wrx8iUNYFjIAairoIbh1BRxUxvglQAJdAKABinwEAq41lMwFxoqUJdpjUErN7ZI9+knDsZjkPRoMS8ZAANsgASZSmQS6soQcVVFfBwoAAfnsDsux7PtwVNRgRFKADgEZfBsnwdQPRcG8p0ZVcNy3SVd33e8QJnAl9FfL9m0YIDGXPdJezfAA6L8fz-AARR9Z2o1RWNoX9GH-QDgLAiDO27Xt+3gxDxTfL9b3ZVQWOUdigIwvQ1TIpRKO-fiOK4gkeL4gShOAIDRPbcToKkhCkLk5QFIgXiVLUzCoEcgztOyAAGV9RP0bySLcjytKgHS2MEkKh0M2CbXM8DsLvewHDc2gICZXVW04SyoN7StJUHI85wlMd3KnTzioVRclQASQUXlu2QOQKpPTExnoy8tGvOqGsYJqSIq-QOogN8ACJjL-GSzJE8Ccok1ASoqWzZLKpK+XZdSoCwnrgEaiA5AG0L9HWkawvG3STKmoCQISxzGSmlKNIMHa9oO5LBpOt8yDIc6ItM66LMg+b8oFZbkLC+Sp1UH7X0ZF6+v2zbtvq3aEbeh8js+iGLr-E44G7Zy-qumaxNyhaFTB+zHOcvGCdhqB4aapGoDSjLGCy111gQAAvCBCqfed73WJRQp+fQQLwcWrz0A1oqUJcLOFtzZaKnT4CQVAMGwQm9ME4nFYSHm+amoWEgUZmVafV9lAmvWkPi+wud5ymDCYt2fgURkI2DH4HAtirXx1y77b82aneNuzXfd4X7rjH31j95WA-0W3-tDx3Dedk2o6Yj2vf6AoEwT-3QuA1P9bDzOI5W-Q3dzmOyjjwvo2LpPS-pcuQ4NhojZd2vo7NxlkyuX2S7lsKnE74Tu9oXvs-7+vB9OdEs1bp7LdnV8EShKfppnufI4XvOc0RUe2-H18Tt3h2fgPmu6+PgsSSFE71ET9eA+G6-09vrPD4fhuT9TxCmGm-Zm+gxD3iBtBEGS0xBv0Gq+c685aIGD+lFIqgsTC61MhVecDgboZx7n-e+A8GiewrItIUjkwHK2gZJWKA54EVVZn+cCrD2br3oTBOC4hNBHXQeVUK1FGREyQngxahDf7V3BkfBuUlqFTloU9DhHMoAAEFGDRCsnlXayBFzSEgJgHI4pGr4GIPgUxfVzH4HtOyGxzwUysBKJo7RZN+azjFg4CcU4Rbj3BLQcwtBrAAHdaDizGOLRkDV9GhVcegHRC10D6LwPgQxEBjHZCsZQaWBgYmnQxuPfQaSpBGJMajSgjJ9AWkeBkLI+xDjAkZHXMR4oKpeIIaJeJiS+7eLlBU9Cbl9D5MOkUyxAyLF2IgEqfQgZ4wtwSM0t2rSDyi3WOLTpt1fH3SQo9LaeS9GnUcu4+BpExnZIcS8dAMy-oAFVRE4Kmu09ZBDCHdPcfPOujkKG3L6bYqcgynrDMOaMoqxSpkzOHqmRZUAWmPPEWshIGy3laISR8yO3yUKONeMzVRLooAAHlcgACsMnADJvkgxpSyVZImeMsxxBlCWBAC4FBkyAUWOxdc-FRLSXGJOc8pFfSMFPjUHcVQ0TDkVV5WSilUrUnpMyRc3JwLkkFLOWCkpZTaVmKqd7WoPwllMRWYKhoyLRIyv5fNF2agULKqGSM96R16XWPZQ6GZcz44wrhcHNpiKzWvP8jwPgAhhAiBoTs8UeysKOsKZqi5nKrkzNgUKP6hgrDqCHivLE3rlnwr9ePDphDLXkutdnO6KhmUuHTSADQDqQWrXZCchBR0mVWFZYtCVLN0q6kIaooFjrjnWtOYNNtLLsEmRrRIhUXb9B3Ieb61ZhaXmbMJSS2VZaMXlT8MofQzLsFQBrc4KAtyu3-PdeA2NGqnzgoBVUqFhrYV5sXaa8JgbwIlvRStCt+AuWAv2XikoQ7oIeIJF43JG9yK5mhOsI1KzCH6G3ukSNwBo1OiOkAos2JBEOkzU+41+bUP+QrQ9c+YKU3YZwRG-D8HiM4VMmhyDSh9AKMoyZajPrJq7Lo0lUjH8jr6tWI+gBQqUVuOHWPMFgnfSwfwx0i166rUgfgalHt7N8UACkFBfAFaFecEHw58Iqn9D2r7xYej0OBYDvZxDC34ePDhol+16H2dZvmHt7Ngr+mTN6GzRI+YA2pizgW2ZqPYhAXI4IFx4CgFpnT1rXzVNGrQdA5wICjRcKNMQ3ZrCjUZKNdAFR0suAAMyBSgKNdImIKjgkKwBDLBhRoafQBgNKvY8sVYAKICnSAgBQcBRp9IK8YhAGUGsnS7coZLqXisVfUQgcwuX8t1YayVkr+WqsClq0VhQDX9BNZa5iAC6WhtYAEGNlww1JvTbSw10aq4UuLY6wVorDWABMABODb1Xtv1awqNbrFRev9ee+uXgtBMADdOyNi7UAJsOFfBhAA9EjlzAA9UCLAgA

## モチベーション

- 深夜のノリ
- 初めて字句解析、構文解析したので忘れないように

## 全体的な流れ

1. 字句解析
2. 構文解析

## 字句解析

### トークン定義

各種トークンを定義します。
論理値、文字列、数値はジェネリクスを使って値も持っておけるようにします

```typescript
type LeftBraceToken = { kind: "left-brace" };
type RightBraceToken = { kind: "right-brace" };
type LeftBracketToken = { kind: "left-bracket" };
type RightBracketToken = { kind: "right-bracket" };
type CommaToken = { kind: "comma" };
type ColonToken = { kind: "colon" };
type NullToken = { kind: "null" };
type BooleanToken<T extends boolean> = { kind: "boolean"; value: T };
type StringToken<T extends string> = { kind: "string"; value: T };
type NumberToken<T extends number> = { kind: "number"; value: T };

type Token =
  | LeftBraceToken
  | RightBraceToken
  | LeftBracketToken
  | RightBracketToken
  | CommaToken
  | ColonToken
  | NullToken
  | BooleanToken<boolean>
  | StringToken<string>
  | NumberToken<number>;
```

### トークナイザー

トークナイザーの基本的な形は以下の通りです。
左側に出てきたトークンを `Tokens` に詰めて、再帰的に呼び出し、空文字列になるまで続けます。
空文字列になったら `Tokens` を返します
いずれのトークンにもマッチしなかったら `never` を返すことで失敗を表現します

```typescript
type Tokenize<T extends string, Tokens extends Token[] = []> =
  T extends '' ? Tokens
  : T extends `トークン1${infer Rest}` ? Tokenize<Rest, [...Tokens, トークン1]>
  : T extends `トークン2${infer Rest}` ? Tokenize<Rest, [...Tokens, トークン2]>
  : never
```

例えば `LeftBraceToken` 、 `RightBraceToken` の場合はこんな感じです。

```diff_typescript
type Tokenize<T extends string, Tokens extends Token[] = []> =
  T extends '' ? Tokens
+ : T extends `{${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBraceToken]>
+ : T extends `}${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBraceToken]>
  : never
```

```typescript
type Tokenized = Tokenize<'{}'>
//   ^? [LeftBraceToken, RightBraceToken]
```

`LeftBraceToken` 、 `RightBraceToken` 、 `LeftBracketToken` 、 `RightBracketToken` 、 `CommaToken` 、 `ColonToken` 、 `NullToken` まではこの方法でトークナイズできます


#### 空白文字

JSON では空白文字は特に意味を持たないので今回は無視することにします。

```diff_typescript
+ type WhiteSpace = ' ' | '\t' | '\n' | '\r'

type Tokenize<T extends string, Tokens extends Token[] = []> =
  T extends '' ? Tokens
+ : T extends `${WhiteSpace}${infer Rest}` ? Tokenize<Rest, Tokens>
  : T extends `{${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBraceToken]>
  : T extends `}${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBraceToken]>
  : T extends `[${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBracketToken]>
  : T extends `]${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBracketToken]>
  : T extends `,${infer Rest}` ? Tokenize<Rest, [...Tokens, CommaToken]>
  : T extends `:${infer Rest}` ? Tokenize<Rest, [...Tokens, ColonToken]>
  : T extends `null${infer Rest}` ? Tokenize<Rest, [...Tokens, NullToken]>
  : never
```

#### 論理値

論理値は `BooleanToken<T>` にそれぞれ値を詰めてあげます。
`true` の場合は `BooleanToken<true>`
`false` の場合は `BooleanToken<false>`

```diff_typescript
type Tokenize<T extends string, Tokens extends Token[] = []> =
  T extends '' ? Tokens
  : T extends `${WhiteSpace}${infer Rest}` ? Tokenize<Rest, Tokens>
  : T extends `{${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBraceToken]>
  : T extends `}${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBraceToken]>
  : T extends `[${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBracketToken]>
  : T extends `]${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBracketToken]>
  : T extends `,${infer Rest}` ? Tokenize<Rest, [...Tokens, CommaToken]>
  : T extends `:${infer Rest}` ? Tokenize<Rest, [...Tokens, ColonToken]>
  : T extends `null${infer Rest}` ? Tokenize<Rest, [...Tokens, NullToken]>
+ : T extends `true${infer Rest}` ? Tokenize<Rest, [...Tokens, BooleanToken<true>]>
+ : T extends `false${infer Rest}` ? Tokenize<Rest, [...Tokens, BooleanToken<false>]>
  : never
```

#### 文字列

エスケープされた `"` があると `"${infer Value extends string}"${string}` で正しく判定できないので、別な手段を使います

```typescript
type Tokenized = '"aa\\"a"' extends `"${infer Value extends string}"${string}` ? Value : never
//   ^? "aa\\"
```

見つけた値を持っておく `Value` と状態を保持しておく `IsStarted` を使って、与えられた文字列の左側にある文字列を取ってきます。
成功した場合は、見つけた値と残りの文字列をタプルで返します。
失敗した場合は、`never` を返します。

状態遷移は以下の通りです。

```mermaid
flowchart TD

開始していない -- 左側にダブルクォートがある --> 開始中
開始中 -- 左側にダブルクォートがある --> 終了
開始中 -- エスケープされたダブルクォートがある --> 開始中
開始中 -- それ以外の文字がある --> 開始中
```

```typescript
type TryParseString<T extends string, Value extends string = '', IsStarted extends boolean = false> =
  [IsStarted, T] extends [false, `"${infer Rest}`] ? TryParseString<Rest, Value, true>
  : [IsStarted, T] extends [true, `"${infer Rest}`] ? [Value, Rest]
  : [IsStarted, T] extends [true, `\\"${infer Rest}`] ? TryParseString<Rest, `${Value}\"`, IsStarted>
  : [IsStarted, T] extends [true, `${infer Char}${infer Rest}`] ? TryParseString<Rest, `${Value}${Char}`, IsStarted>
  : never
```

タプルをうまく使って `T` が `"${string}` に型推論できて、かつ `TryParseString<T>` が文字列の値と残りの文字列に型推論できたら、トークン列に追加するようにします。

```diff_typescript
type Tokenize<T extends string, Tokens extends Token[] = []> =
  T extends '' ? Tokens
  : T extends `${WhiteSpace}${infer Rest}` ? Tokenize<Rest, Tokens>
  : T extends `{${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBraceToken]>
  : T extends `}${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBraceToken]>
  : T extends `[${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBracketToken]>
  : T extends `]${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBracketToken]>
  : T extends `,${infer Rest}` ? Tokenize<Rest, [...Tokens, CommaToken]>
  : T extends `:${infer Rest}` ? Tokenize<Rest, [...Tokens, ColonToken]>
  : T extends `null${infer Rest}` ? Tokenize<Rest, [...Tokens, NullToken]>
  : T extends `true${infer Rest}` ? Tokenize<Rest, [...Tokens, BooleanToken<true>]>
  : T extends `false${infer Rest}` ? Tokenize<Rest, [...Tokens, BooleanToken<false>]>
+ : [T, TryParseString<T>] extends [`"${string}`, [infer Value extends string, infer Rest extends string]] ? Tokenize<Rest, [...Tokens, StringToken<Value>]>
  : never
```

#### 数値

数値も文字列と同じようなことをします。
見つけた値を持っておく `Value` と状態を保持しておく `HasFlotingPoint` を使って、与えられた文字列の左側にある数値を取ってきます。
成功した場合は、見つけた値と残りの文字列をタプルで返します。
失敗した場合は、`never` を返します。

状態遷移は以下の通りです。

```mermaid
flowchart TD

開始 -- 値が空で左側に - がある --> 浮動小数点なし
開始 -- 左側に 数値がある --> 浮動小数点なし
浮動小数点なし -- 左側に . と数値がある --> 浮動小数点あり
浮動小数点なし -- 左側に 数値がある --> 浮動小数点なし
浮動小数点あり -- 左側に 数値がある --> 浮動小数点あり
浮動小数点なし -- 左側に 数値がない --> 終了
浮動小数点あり -- 左側に 数値がない --> 終了
```

```typescript
type TryParseNumber<T extends string, Value extends string = '', HasFlotingPoint extends boolean = false> =
  [Value, T] extends ['', `-${infer Rest}`] ? TryParseNumber<Rest, '-'>
  : [Value, HasFlotingPoint, T] extends [`${number}`, false, `.${infer D extends number}${infer Rest}`] ? TryParseNumber<Rest, `${Value}.${D}`>
  : T extends `${infer D extends number}${infer Rest}` ? TryParseNumber<Rest, `${Value}${D}`>
  : Value extends `-0` ? [0, T]
  : Value extends `${infer Value extends number}` ? [Value, T]
  : never
```

最後に `-0` の場合は `0` として、値が `number` に型推論できたら返します。
値が空文字列や `-` のみのときは `never` を返します。 

今回はマイナス符号と浮動小数点数のみサポートしました。
自然対数もサポートする場合はもう少し改良が必要です。


```diff_typescript
type Tokenize<T extends string, Tokens extends Token[] = []> =
  T extends '' ? Tokens
  : T extends `${WhiteSpace}${infer Rest}` ? Tokenize<Rest, Tokens>
  : T extends `{${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBraceToken]>
  : T extends `}${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBraceToken]>
  : T extends `[${infer Rest}` ? Tokenize<Rest, [...Tokens, LeftBracketToken]>
  : T extends `]${infer Rest}` ? Tokenize<Rest, [...Tokens, RightBracketToken]>
  : T extends `,${infer Rest}` ? Tokenize<Rest, [...Tokens, CommaToken]>
  : T extends `:${infer Rest}` ? Tokenize<Rest, [...Tokens, ColonToken]>
  : T extends `null${infer Rest}` ? Tokenize<Rest, [...Tokens, NullToken]>
  : T extends `true${infer Rest}` ? Tokenize<Rest, [...Tokens, BooleanToken<true>]>
  : T extends `false${infer Rest}` ? Tokenize<Rest, [...Tokens, BooleanToken<false>]>
  : [T, ParseLeftString<T>] extends [`"${string}`, [infer Value extends string, infer Rest extends string]] ? Tokenize<Rest, [...Tokens, StringToken<Value>]>
+ : TryParseNumber<T> extends never ? never
+ : TryParseNumber<T> extends [infer Value extends number, infer Rest extends string] ? Tokenize<Rest, [...Tokens, NumberToken<Value>]>
  : never
```

:::note warn
今回は雑に `TryParseNumber<T>` が `never` に推論されたとき、`never` を返すようにしました。
下の方なので問題ないですが、本当だったら `T` の左側に `-` か `0` ~ `9` のいずれかがあるときのみ `TryParseNumber<T>` をしたほうがよいです。
:::

## 構文解析

構文解析器の基本的な形は以下のとおりです。
`Tokens[]` を受け取って、解析済みの値と残りのトークン列のタプルを返します。

```typescript
type ValueParse<T extends Token[]> =
  T extends [トークン1, ...infer Rest] ? [値1, Rest]
  : T extends [トークン2, ...infer Rest] ? [値2, Rest]
  : never
```

プリミティブ型は以下のように書けます。

```diff_typescript
type ValueParse<T extends Token[]> =
+ T extends [NullToken, ...infer Rest] ? [null, Rest]
+ : T extends [BooleanToken<infer Value>, ...infer Rest] ? [Value, Rest]
+ : T extends [StringToken<infer Value>, ...infer Rest] ? [Value, Rest]
+ : T extends [NumberToken<infer Value>, ...infer Rest] ? [Value, Rest]
  : never
```

### 配列型

トークン配列を受け取り、解析済みの値を持つ `Values` と状態の `State` を使って構文解析します。
成功した場合は、配列の値と残りのトークン列を返します。
失敗した場合は、never を返します。


```mermaid
flowchart TD

開始前 -- "左側のトークンが [ トークンだったら" --> 開始
開始 -- "左側のトークンがValueParseできたら" --> 値が追加された状態
カンマ状態 -- "左側のトークンがValueParseできたら" --> 値が追加された状態
値が追加された状態 -- "左側のトークンが , トークンだったら" --> カンマ状態
開始 -- "左側のトークンが ] トークンだったら" --> 終了
値が追加された状態 -- "左側のトークンが ] トークンだったら" --> 終了
```

```typescript
type ArrayParseState = 'expect-start'|'start'|'value'|'comma'
type ArrayParse<T extends Token[], Values extends unknown[] = [], State extends ArrayParseState = 'expect-start'> =
  [State, T] extends ['expect-start', [{ kind: 'left-bracket' }, ...infer Rest extends Token[]]] ? ArrayParse<Rest, [], 'start'>
  : [State, T] extends ['start'|'value', [RightBracketToken, ...infer Rest extends Token[]]] ? [Values, Rest]
  : [State, ValueParse<T>] extends ['start'|'comma', [infer U, infer Rest extends Token[]]] ? ArrayParse<Rest, [...Values, U], 'value'>
  : [State, T] extends ['value', [CommaToken, ...infer Rest extends Token[]]] ? ArrayParse<Rest, Values, 'comma'>
  : never
```

```diff_typescript
type ValueParse<T extends Token[]> =
  T extends [NullToken, ...infer Rest] ? [null, Rest]
  : T extends [BooleanToken<infer Value>, ...infer Rest] ? [Value, Rest]
  : T extends [StringToken<infer Value>, ...infer Rest] ? [Value, Rest]
  : T extends [NumberToken<infer Value>, ...infer Rest] ? [Value, Rest]
+ : T extends [LeftBracketToken, ...Token[]] ? ArrayParse<T>
  : never
```

### オブジェクト型

配列型と同じように構文解析します。
オブジェクトのキーが見つかった状態は `{key: string}` として、キー名を持っておけるようにします

```mermaid
flowchart TD

開始前 -- "左側のトークンが { トークンだったら" --> 開始
開始 -- "左側のトークンが文字列トークンと : トークンだったら" --> キーが見つかった状態
キーが見つかった状態 -- "左側のトークンがValueParseできたら" --> 値が追加された状態
値が追加された状態 -- "左側のトークンが , トークンだったら" --> カンマ状態
カンマ状態 -- "左側のトークンが文字列トークンと : トークンだったら" --> キーが見つかった状態
開始 -- "左側のトークンが } トークンだったら" --> 終了
値が追加された状態 -- "左側のトークンが } トークンだったら" --> 終了
```

```typescript
type ObjectParseState = 'expect-start'|'start'|{key: string}|'value'|'comma'
type ObjectParse<T extends Token[], Value extends {} = {}, State extends ObjectParseState = 'expect-start'> =
  [State, T] extends ['expect-start', [LeftBraceToken, ...infer Rest extends Token[]]] ? ObjectParse<Rest, {}, 'start'>
  : [State, T] extends ['start'|'value', [RightBraceToken, ...infer Rest extends Token[]]] ? [Prettify<Value>, Rest]
  : [State, T] extends ['start'|'comma', [StringToken<infer Key>, ColonToken, ...infer Rest extends Token[]]] ? ObjectParse<Rest, Value, {key: Key}>
  : [State, ValueParse<T>] extends [{key: string}, never] ? never
  : [State, ValueParse<T>] extends [{key: infer Key extends string}, [infer U, infer Rest extends Token[]]] ? ObjectParse<Rest, Value & {[key in Key]: U}, 'value'>
  : [State, T] extends ['value', [CommaToken, ...infer Rest extends Token[]]] ? ObjectParse<Rest, Value, 'comma'>
  : never
```

構文解析できたオブジェクトは `Prettify<T>` を通すことでオブジェクトの交差型から一つのオブジェクトにまとめます

```typescript
type Prettify<T extends {}> = {
  [K in keyof T]: T[K];
} & {}
```

```diff_typescript
type ValueParse<T extends Token[]> =
  T extends [NullToken, ...infer Rest] ? [null, Rest]
  : T extends [BooleanToken<infer Value>, ...infer Rest] ? [Value, Rest]
  : T extends [StringToken<infer Value>, ...infer Rest] ? [Value, Rest]
  : T extends [NumberToken<infer Value>, ...infer Rest] ? [Value, Rest]
  : T extends [LeftBracketToken, ...Token[]] ? ArrayParse<T>
+ : T extends [LeftBraceToken, ...Token[]] ? ObjectParse<T>
  : never
```

## 最終結果

```typescript
type JsonParse<T extends string> =
  Tokenize<T> extends infer Tokens extends Token[]
    ? ValueParse<Tokens> extends never ? never
    : ValueParse<Tokens> extends [infer Parsed, []] ? Parsed : never
    : never
```

まず字句解析を行い、その後構文解析を行います。
構文解析の結果、残りのトークン列が空配列になったら構文解析完了とします。

https://www.typescriptlang.org/play?#code/C4TwDgpgBA6gFgS2BAymAhgY2gXigcgKgB8CAdYQ0-MgOyvICd8AoF0SKABUYmGAQAzEAB4AKlAgAPZLQAmAZygBvAL4A+KHmUsoUANoBpKAlpQA1hBAB7QVDEBdAFz2jDgNwtVUAGQrVbBzQADIQgsAAQoxYEGLWlmbaFqZyLvgANmHAALQARtHYhAFBUABKCADmcJEFsfEQiSrJ8mmMldV5tUXs4CFZUViWwHEJWk3mKWmZ4Z2DfN0l5VU1c8P1jcrNqQRty7OYQwu9UADC1gC25+gjDWObEy0EmBdXR5xn6da0Nxtbac+fehQYrHAByAFd0ukfnc-gRaJD0m9oBFrNZMuhvutxJIZA1FFBcmiMbRNEkHtt8ET0RBMfgADRQABu6HS4IgLgkIM4KGAbVoFR+OOksgJCj5pgqZPGkwI4v5FQZzNZ7M5wJ6nAh51yEEYQokIvxSgR2t10vusvwJp1zEZLLZHPs6o10BheF0UFC4QG2BhxA9S2qPrqo39ei9KwOfD9AfakaGMb0Z0u13WJA9Hy+iagEKh2dRNMxQupJM0YagvIVQvlkrLHq1NqF1rNgWOYkYIC46EYCggDd1wrx8iUNYFjIAairoIbh1BRxUxvglQAJdAKABinwEAq41lMwFxoqUJdpjUErN7ZI9+knDsZjkPRoMS8ZAANsgASZSmQS6soQcVVFfBwoAAfnsDsux7PtwVNRgRFKADgEZfBsnwdQPRcG8p0ZVcNy3SVd33e8QJnAl9FfL9m0YIDGXPdJezfAA6L8fz-AARR9Z2o1RWNoX9GH-QDgLAiDO27Xt+3gxDxTfL9b3ZVQWOUdigIwvQ1TIpRKO-fiOK4gkeL4gShOAIDRPbcToKkhCkLk5QFIgXiVLUzCoEcgztOyAAGV9RP0bySLcjytKgHS2MEkKh0M2CbXM8DsLvewHDc2gICZXVW04SyoN7StJUHI85wlMd3KnTzioVRclQASQUXlu2QOQKpPTExnoy8tGvOqGsYJqSIq-QOogN8ACJjL-GSzJE8Ccok1ASoqWzZLKpK+XZdSoCwnrgEaiA5AG0L9HWkawvG3STKmoCQISxzGSmlKNIMHa9oO5LBpOt8yDIc6ItM66LMg+b8oFZbkLC+Sp1UH7X0ZF6+v2zbtvq3aEbeh8js+iGLr-E44G7Zy-qumaxNyhaFTB+zHOcvGCdhqB4aapGoDSjLGCy111gQAAvCBCqfed73WJRQp+fQQLwcWrz0A1oqUJcLOFtzZaKnT4CQVAMGwQm9ME4nFYSHm+amoWEgUZmVafV9lAmvWkPi+wud5ymDCYt2fgURkI2DH4HAtirXx1y77b82aneNuzXfd4X7rjH31j95WA-0W3-tDx3Dedk2o6Yj2vf6AoEwT-3QuA1P9bDzOI5W-Q3dzmOyjjwvo2LpPS-pcuQ4NhojZd2vo7NxlkyuX2S7lsKnE74Tu9oXvs-7+vB9OdEs1bp7LdnV8EShKfppnufI4XvOc0RUe2-H18Tt3h2fgPmu6+PgsSSFE71ET9eA+G6-09vrPD4fhuT9TxCmGm-Zm+gxD3iBtBEGS0xBv0Gq+c685aIGD+lFIqgsTC61MhVecDgboZx7n-e+A8GiewrItIUjkwHK2gZJWKA54EVVZn+cCrD2br3oTBOC4hNBHXQeVUK1FGREyQngxahDf7V3BkfBuUlqFTloU9DhHMoAAEFGDRCsnlXayBFzSEgJgHI4pGr4GIPgUxfVzH4HtOyGxzwUysBKJo7RZN+azjFg4CcU4Rbj3BLQcwtBrAAHdaDizGOLRkDV9GhVcegHRC10D6LwPgQxEBjHZCsZQaWBgYmnQxuPfQaSpBGJMajSgjJ9AWkeBkLI+xDjAkZHXMR4oKpeIIaJeJiS+7eLlBU9Cbl9D5MOkUyxAyLF2IgEqfQgZ4wtwSM0t2rSDyi3WOLTpt1fH3SQo9LaeS9GnUcu4+BpExnZIcS8dAMy-oAFVRE4Kmu09ZBDCHdPcfPOujkKG3L6bYqcgynrDMOaMoqxSpkzOHqmRZUAWmPPEWshIGy3laISR8yO3yUKONeMzVRLooAAHlcgACsMnADJvkgxpSyVZImeMsxxBlCWBAC4FBkyAUWOxdc-FRLSXGJOc8pFfSMFPjUHcVQ0TDkVV5WSilUrUnpMyRc3JwLkkFLOWCkpZTaVmKqd7WoPwllMRWYKhoyLRIyv5fNF2agULKqGSM96R16XWPZQ6GZcz44wrhcHNpiKzWvP8jwPgAhhAiBoTs8UeysKOsKZqi5nKrkzNgUKP6hgrDqCHivLE3rlnwr9ePDphDLXkutdnO6KhmUuHTSADQDqQWrXZCchBR0mVWFZYtCVLN0q6kIaooFjrjnWtOYNNtLLsEmRrRIhUXb9B3Ieb61ZhaXmbMJSS2VZaMXlT8MofQzLsFQBrc4KAtyu3-PdeA2NGqnzgoBVUqFhrYV5sXaa8JgbwIlvRStCt+AuWAv2XikoQ7oIeIJF43JG9yK5mhOsI1KzCH6G3ukSNwBo1OiOkAos2JBEOkzU+41+bUP+QrQ9c+YKU3YZwRG-D8HiM4VMmhyDSh9AKMoyZajPrJq7Lo0lUjH8jr6tWI+gBQqUVuOHWPMFgnfSwfwx0i166rUgfgalHt7N8UACkFBfAFaFecEHw58Iqn9D2r7xYej0OBYDvZxDC34ePDhol+16H2dZvmHt7Ngr+mTN6GzRI+YA2pizgW2ZqPYhAXI4IFx4CgFpnT1rXzVNGrQdA5wICjRcKNMQ3ZrCjUZKNdAFR0suAAMyBSgKNdImIKjgkKwBDLBhRoafQBgNKvY8sVYAKICnSAgBQcBRp9IK8YhAGUGsnS7coZLqXisVfUQgcwuX8t1YayVkr+WqsClq0VhQDX9BNZa5iAC6WhtYAEGNlww1JvTbSw10aq4UuLY6wVorDWABMABODb1Xtv1awqNbrFRev9ee+uXgtBMADdOyNi7UAJsOFfBhAA9EjlzAA9UCLAgA