@silverbulletmd/silverbullet 2.4.1 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +20 -4
  2. package/client/markdown_parser/constants.ts +2 -2
  3. package/client/plugos/hooks/code_widget.ts +0 -3
  4. package/client/plugos/hooks/document_editor.ts +0 -3
  5. package/client/plugos/hooks/event.ts +1 -1
  6. package/client/plugos/hooks/mq.ts +1 -1
  7. package/client/plugos/hooks/plug_namespace.ts +0 -3
  8. package/client/plugos/hooks/slash_command.ts +2 -2
  9. package/client/plugos/plug.ts +0 -1
  10. package/client/plugos/plug_compile.ts +28 -29
  11. package/client/plugos/proxy_fetch.ts +1 -1
  12. package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
  13. package/client/plugos/sandboxes/worker_sandbox.ts +2 -3
  14. package/client/plugos/syscalls/editor.ts +12 -12
  15. package/client/plugos/syscalls/fetch.ts +1 -1
  16. package/client/plugos/syscalls/jsonschema.ts +1 -1
  17. package/client/plugos/syscalls/mq.ts +1 -1
  18. package/client/plugos/syscalls/space.ts +1 -1
  19. package/client/plugos/system.ts +2 -2
  20. package/client/plugos/worker_runtime.ts +8 -29
  21. package/client/space_lua/aggregates.ts +209 -0
  22. package/client/space_lua/ast.ts +24 -2
  23. package/client/space_lua/eval.ts +58 -53
  24. package/client/space_lua/labels.ts +1 -1
  25. package/client/space_lua/parse.ts +117 -12
  26. package/client/space_lua/query_collection.ts +850 -70
  27. package/client/space_lua/query_env.ts +26 -0
  28. package/client/space_lua/runtime.ts +47 -17
  29. package/client/space_lua/stdlib/format.ts +19 -19
  30. package/client/space_lua/stdlib/math.ts +73 -48
  31. package/client/space_lua/stdlib/net.ts +2 -2
  32. package/client/space_lua/stdlib/os.ts +5 -0
  33. package/client/space_lua/stdlib/pattern.ts +702 -0
  34. package/client/space_lua/stdlib/prng.ts +145 -0
  35. package/client/space_lua/stdlib/space_lua.ts +3 -8
  36. package/client/space_lua/stdlib/string.ts +103 -181
  37. package/client/space_lua/stdlib/string_pack.ts +486 -0
  38. package/client/space_lua/stdlib/table.ts +73 -9
  39. package/client/space_lua/stdlib.ts +38 -14
  40. package/client/space_lua/tonumber.ts +3 -2
  41. package/client/space_lua/util.ts +43 -9
  42. package/dist/plug-compile.js +23 -69
  43. package/dist/worker_runtime_bundle.js +233 -0
  44. package/package.json +16 -11
  45. package/plug-api/constants.ts +0 -32
  46. package/plug-api/lib/async.ts +2 -2
  47. package/plug-api/lib/crypto.ts +11 -11
  48. package/plug-api/lib/json.ts +1 -1
  49. package/plug-api/lib/limited_map.ts +1 -1
  50. package/plug-api/lib/native_fetch.ts +2 -0
  51. package/plug-api/lib/ref.ts +5 -5
  52. package/plug-api/lib/transclusion.ts +5 -5
  53. package/plug-api/lib/tree.ts +50 -2
  54. package/plug-api/lib/yaml.ts +10 -10
  55. package/plug-api/syscalls/editor.ts +1 -1
  56. package/plug-api/system_mock.ts +0 -1
  57. package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Aggregate function definitions and execution for LIQ.
3
+ *
4
+ * Built-in aggregates (sum, count, min, max, avg, array_agg) are
5
+ * implemented in TypeScript for speed. Users can override any builtin
6
+ * via `aggregate.define` or `aggregate.update`.
7
+ *
8
+ * Builtins implement ILuaFunction via plain objects rather than
9
+ * LuaBuiltinFunction instances. This avoids ES module TDZ issues:
10
+ * `class` exports are not available during circular module init,
11
+ * but `interface`/`type` imports are.
12
+ */
13
+
14
+ import type { ILuaFunction, LuaStackFrame } from "./runtime.ts";
15
+ import {
16
+ luaCall,
17
+ type LuaEnv,
18
+ LuaTable,
19
+ luaTruthy,
20
+ type LuaValue,
21
+ } from "./runtime.ts";
22
+ import type { LuaExpression } from "./ast.ts";
23
+ import { buildItemEnv } from "./query_env.ts";
24
+
25
+ export interface AggregateSpec {
26
+ name: string;
27
+ description?: string;
28
+ initialize: LuaValue; // ILuaFunction
29
+ iterate: LuaValue; // ILuaFunction
30
+ finish?: LuaValue; // ILuaFunction | undefined
31
+ }
32
+
33
+ // Helper to build an ILuaFunction from a plain function. Equivalent to
34
+ // LuaBuiltinFunction but without referencing the class.
35
+ function aggFn(
36
+ fn: (sf: LuaStackFrame, ...args: LuaValue[]) => LuaValue,
37
+ ): ILuaFunction {
38
+ return {
39
+ call(sf: LuaStackFrame, ...args: LuaValue[]) {
40
+ return fn(sf, ...args);
41
+ },
42
+ asString() {
43
+ return "<builtin aggregate>";
44
+ },
45
+ };
46
+ }
47
+
48
+ // Built-in aggregate specs
49
+ const builtinAggregates: Record<string, AggregateSpec> = {
50
+ sum: {
51
+ name: "sum",
52
+ description: "Sum of numeric values",
53
+ initialize: aggFn((_sf) => 0),
54
+ iterate: aggFn((_sf, state: any, value: any) => {
55
+ if (value === null || value === undefined) return state;
56
+ return (state as number) + (value as number);
57
+ }),
58
+ },
59
+ count: {
60
+ name: "count",
61
+ description: "Count of values; count() with no argument counts all rows",
62
+ initialize: aggFn((_sf) => 0),
63
+ iterate: aggFn((_sf, state: any, value: any) => {
64
+ if (value === null || value === undefined) return state;
65
+ return (state as number) + 1;
66
+ }),
67
+ },
68
+ min: {
69
+ name: "min",
70
+ description: "Minimum value",
71
+ initialize: aggFn((_sf) => null),
72
+ iterate: aggFn((_sf, state: any, value: any) => {
73
+ if (value === null || value === undefined) return state;
74
+ if (state === null || value < state) return value;
75
+ return state;
76
+ }),
77
+ },
78
+ max: {
79
+ name: "max",
80
+ description: "Maximum value",
81
+ initialize: aggFn((_sf) => null),
82
+ iterate: aggFn((_sf, state: any, value: any) => {
83
+ if (value === null || value === undefined) return state;
84
+ if (state === null || value > state) return value;
85
+ return state;
86
+ }),
87
+ },
88
+ avg: {
89
+ name: "avg",
90
+ description: "Average of numeric values",
91
+ initialize: aggFn((_sf) => ({ sum: 0, count: 0 })),
92
+ iterate: aggFn((_sf, state: any, value: any) => {
93
+ if (value === null || value === undefined) return state;
94
+ state.sum += value as number;
95
+ state.count += 1;
96
+ return state;
97
+ }),
98
+ finish: aggFn((_sf, state: any) => {
99
+ if (state.count === 0) return null;
100
+ return state.sum / state.count;
101
+ }),
102
+ },
103
+ array_agg: {
104
+ name: "array_agg",
105
+ description: "Collect values into an array",
106
+ initialize: aggFn((_sf) => new LuaTable()),
107
+ iterate: aggFn((_sf, state: any, value: any) => {
108
+ (state as LuaTable).rawSetArrayIndex(
109
+ (state as LuaTable).length + 1,
110
+ value,
111
+ );
112
+ return state;
113
+ }),
114
+ },
115
+ };
116
+
117
+ const noCtx = {};
118
+
119
+ function buildAggCtx(name: string): LuaTable {
120
+ const ctx = new LuaTable();
121
+ ctx.rawSet("name", name);
122
+ const clientConfig = globalThis.client?.config;
123
+ const aggConfig = clientConfig
124
+ ? clientConfig.get(`aggregateConfig.${name}`, {})
125
+ : {};
126
+ ctx.rawSet("config", aggConfig);
127
+ return ctx;
128
+ }
129
+
130
+ export function getAggregateSpec(name: string): AggregateSpec | null {
131
+ const clientConfig = globalThis.client?.config;
132
+ if (clientConfig) {
133
+ const spec: any = clientConfig.get(`aggregates.${name}`, null);
134
+ if (spec) {
135
+ let candidate: AggregateSpec | null = null;
136
+ if (spec instanceof LuaTable) {
137
+ const init = spec.rawGet("initialize");
138
+ const iter = spec.rawGet("iterate");
139
+ if (init && iter) {
140
+ candidate = {
141
+ name: spec.rawGet("name") ?? name,
142
+ description: spec.rawGet("description"),
143
+ initialize: init,
144
+ iterate: iter,
145
+ finish: spec.rawGet("finish"),
146
+ };
147
+ }
148
+ } else if (spec.initialize && spec.iterate) {
149
+ candidate = spec as AggregateSpec;
150
+ }
151
+ if (candidate) return candidate;
152
+ }
153
+ }
154
+ return builtinAggregates[name] ?? null;
155
+ }
156
+
157
+ /**
158
+ * Execute an aggregate function over a group of items.
159
+ */
160
+ export async function executeAggregate(
161
+ spec: AggregateSpec,
162
+ items: LuaTable,
163
+ valueExpr: LuaExpression | null,
164
+ objectVariable: string | undefined,
165
+ env: LuaEnv,
166
+ sf: LuaStackFrame,
167
+ evalExprFn: (
168
+ e: LuaExpression,
169
+ env: LuaEnv,
170
+ sf: LuaStackFrame,
171
+ ) => Promise<LuaValue> | LuaValue,
172
+ filterExpr?: LuaExpression,
173
+ ): Promise<LuaValue> {
174
+ const ctx = buildAggCtx(spec.name);
175
+
176
+ // Initialize
177
+ let state = await luaCall(spec.initialize, [ctx], noCtx, sf);
178
+
179
+ // Iterate
180
+ const len = items.length;
181
+ for (let i = 1; i <= len; i++) {
182
+ const item = items.rawGet(i);
183
+
184
+ // Filter
185
+ if (filterExpr) {
186
+ const filterEnv = buildItemEnv(objectVariable, item, env, sf);
187
+ const filterResult = await evalExprFn(filterExpr, filterEnv, sf);
188
+ if (!luaTruthy(filterResult)) {
189
+ continue;
190
+ }
191
+ }
192
+
193
+ let value: LuaValue;
194
+ if (valueExpr === null) {
195
+ value = item;
196
+ } else {
197
+ const itemEnv = buildItemEnv(objectVariable, item, env, sf);
198
+ value = await evalExprFn(valueExpr, itemEnv, sf);
199
+ }
200
+ state = await luaCall(spec.iterate, [state, value, ctx], noCtx, sf);
201
+ }
202
+
203
+ // Finish
204
+ if (spec.finish) {
205
+ state = await luaCall(spec.finish, [state, ctx], noCtx, sf);
206
+ }
207
+
208
+ return state;
209
+ }
@@ -175,7 +175,8 @@ export type LuaExpression =
175
175
  | LuaUnaryExpression
176
176
  | LuaTableConstructor
177
177
  | LuaFunctionDefinition
178
- | LuaQueryExpression;
178
+ | LuaQueryExpression
179
+ | LuaFilteredCallExpression;
179
180
 
180
181
  export type LuaNilLiteral = {
181
182
  type: "Nil";
@@ -281,6 +282,13 @@ export type LuaFunctionDefinition = {
281
282
  body: LuaFunctionBody;
282
283
  } & ASTContext;
283
284
 
285
+ // Aggregate with per-row filter
286
+ export type LuaFilteredCallExpression = {
287
+ type: "FilteredCall";
288
+ call: LuaFunctionCallExpression;
289
+ filter: LuaExpression;
290
+ } & ASTContext;
291
+
284
292
  // Query stuff
285
293
  export type LuaQueryExpression = {
286
294
  type: "Query";
@@ -292,7 +300,9 @@ export type LuaQueryClause =
292
300
  | LuaWhereClause
293
301
  | LuaLimitClause
294
302
  | LuaOrderByClause
295
- | LuaSelectClause;
303
+ | LuaSelectClause
304
+ | LuaGroupByClause
305
+ | LuaHavingClause;
296
306
 
297
307
  export type LuaFromClause = {
298
308
  type: "From";
@@ -320,9 +330,21 @@ export type LuaOrderBy = {
320
330
  type: "Order";
321
331
  expression: LuaExpression;
322
332
  direction: "asc" | "desc";
333
+ nulls?: "first" | "last";
334
+ using?: string | LuaFunctionBody;
323
335
  } & ASTContext;
324
336
 
325
337
  export type LuaSelectClause = {
326
338
  type: "Select";
327
339
  expression: LuaExpression;
328
340
  } & ASTContext;
341
+
342
+ export type LuaGroupByClause = {
343
+ type: "GroupBy";
344
+ expressions: LuaExpression[];
345
+ } & ASTContext;
346
+
347
+ export type LuaHavingClause = {
348
+ type: "Having";
349
+ expression: LuaExpression;
350
+ } & ASTContext;
@@ -19,6 +19,7 @@ import {
19
19
  luaEnsureCloseStack,
20
20
  LuaEnv,
21
21
  luaEquals,
22
+ luaFormatNumber,
22
23
  LuaFunction,
23
24
  luaGet,
24
25
  luaIndexValue,
@@ -37,10 +38,7 @@ import {
37
38
  luaValueToJS,
38
39
  singleResult,
39
40
  } from "./runtime.ts";
40
- import {
41
- ArrayQueryCollection,
42
- type LuaCollectionQuery,
43
- } from "./query_collection.ts";
41
+ import { type LuaCollectionQuery, toCollection } from "./query_collection.ts";
44
42
  import {
45
43
  coerceNumericPair,
46
44
  coerceToNumber,
@@ -178,17 +176,6 @@ function blockMetaOrThrow(
178
176
  }
179
177
  }
180
178
 
181
- // Queryable guard to avoid `(collection as any).query` usage
182
- type Queryable = {
183
- query: (
184
- q: LuaCollectionQuery,
185
- env: LuaEnv,
186
- sf: LuaStackFrame,
187
- ) => Promise<any>;
188
- };
189
- function isQueryable(x: unknown): x is Queryable {
190
- return !!x && typeof (x as any).query === "function";
191
- }
192
179
 
193
180
  function arithVerbFromOperator(op: string): string | null {
194
181
  switch (op) {
@@ -250,7 +237,7 @@ function arithCoercionErrorOrThrow(
250
237
  throw e;
251
238
  }
252
239
 
253
- function luaOp(
240
+ export function luaOp(
254
241
  op: string,
255
242
  left: any,
256
243
  right: any,
@@ -333,10 +320,10 @@ function luaOp(
333
320
  return v as string;
334
321
  }
335
322
  if (typeof v === "number") {
336
- return String(v);
323
+ return luaFormatNumber(v);
337
324
  }
338
325
  if (isTaggedFloat(v)) {
339
- return String(v.value);
326
+ return luaFormatNumber(v.value, "float");
340
327
  }
341
328
  const t = luaTypeName(v);
342
329
  throw new LuaRuntimeError(
@@ -853,22 +840,35 @@ export function evalExpression(
853
840
  sf.withCtx(q.ctx),
854
841
  );
855
842
  }
856
- if (collection instanceof LuaTable && collection.empty()) {
857
- // Make sure we're converting an empty result to an array to "query"
858
- collection = [];
859
- } else {
860
- collection = luaValueToJS(collection, sf);
861
- }
862
- // Check if collection is a queryable collection
863
- if (!isQueryable(collection)) {
864
- if (!Array.isArray(collection)) {
865
- throw new LuaRuntimeError(
866
- "Collection does not support query",
867
- sf.withCtx(q.ctx),
868
- );
843
+
844
+ // If already a queryable collection (e.g. DataStoreQueryCollection),
845
+ // use directly - skip all LuaTable/JS conversion.
846
+ if (
847
+ typeof collection === "object" &&
848
+ collection !== null &&
849
+ "query" in collection &&
850
+ typeof (collection as any).query === "function"
851
+ ) {
852
+ // Already queryable, use as-is
853
+ } else if (collection instanceof LuaTable && collection.empty()) {
854
+ // Empty table → empty array
855
+ collection = toCollection([]);
856
+ } else if (collection instanceof LuaTable) {
857
+ if (collection.length > 0) {
858
+ // Array-like table: extract array items, keep as LuaTables
859
+ const arr: any[] = [];
860
+ for (let i = 1; i <= collection.length; i++) {
861
+ arr.push(collection.rawGet(i));
862
+ }
863
+ collection = toCollection(arr);
864
+ } else {
865
+ // Record-like table (no array part): treat as singleton
866
+ collection = toCollection([collection]);
869
867
  }
870
- collection = new ArrayQueryCollection(collection);
868
+ } else {
869
+ collection = toCollection(luaValueToJS(collection, sf));
871
870
  }
871
+
872
872
  // Build up query object
873
873
  const query: LuaCollectionQuery = {
874
874
  objectVariable,
@@ -886,6 +886,8 @@ export function evalExpression(
886
886
  query.orderBy = clause.orderBy.map((o) => ({
887
887
  expr: o.expression,
888
888
  desc: o.direction === "desc",
889
+ nulls: o.nulls,
890
+ using: o.using,
889
891
  }));
890
892
  break;
891
893
  }
@@ -906,12 +908,19 @@ export function evalExpression(
906
908
  }
907
909
  break;
908
910
  }
911
+ case "GroupBy": {
912
+ query.groupBy = clause.expressions;
913
+ break;
914
+ }
915
+ case "Having": {
916
+ query.having = clause.expression;
917
+ break;
918
+ }
909
919
  }
910
920
  }
911
921
 
912
- return (collection as Queryable).query(query, env, sf).then(
913
- jsToLuaValue,
914
- );
922
+ // Always use the possibly-wrapped collection
923
+ return (collection as any).query(query, env, sf).then(jsToLuaValue);
915
924
  },
916
925
  );
917
926
  }
@@ -1410,9 +1419,9 @@ function evalExpressions(
1410
1419
  }
1411
1420
 
1412
1421
  type EvalBlockResult =
1413
- | void
1422
+ | undefined
1414
1423
  | ControlSignal
1415
- | Promise<void | ControlSignal>;
1424
+ | Promise<undefined | ControlSignal>;
1416
1425
 
1417
1426
  function runStatementsNoGoto(
1418
1427
  stmts: LuaStatement[],
@@ -1420,10 +1429,10 @@ function runStatementsNoGoto(
1420
1429
  sf: LuaStackFrame,
1421
1430
  returnOnReturn: boolean,
1422
1431
  startIdx: number,
1423
- ): void | ControlSignal | Promise<void | ControlSignal> {
1432
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> {
1424
1433
  const processFrom = (
1425
1434
  idx: number,
1426
- ): void | ControlSignal | Promise<void | ControlSignal> => {
1435
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
1427
1436
  for (let i = idx; i < stmts.length; i++) {
1428
1437
  const result = evalStatement(
1429
1438
  stmts[i],
@@ -1621,7 +1630,7 @@ export function evalStatement(
1621
1630
  env: LuaEnv,
1622
1631
  sf: LuaStackFrame,
1623
1632
  returnOnReturn = false,
1624
- ): void | ControlSignal | Promise<void | ControlSignal> {
1633
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> {
1625
1634
  switch (s.type) {
1626
1635
  case "Assignment": {
1627
1636
  const a = asAssignment(s);
@@ -1773,7 +1782,7 @@ export function evalStatement(
1773
1782
  return rpThen(rp, onValue) as any;
1774
1783
  };
1775
1784
 
1776
- return runFrom(0);
1785
+ return runFrom(0) as undefined | Promise<undefined>;
1777
1786
  }
1778
1787
  case "Semicolon": {
1779
1788
  return;
@@ -1826,9 +1835,9 @@ export function evalStatement(
1826
1835
  const runFrom = (
1827
1836
  i: number,
1828
1837
  ):
1829
- | void
1838
+ | undefined
1830
1839
  | ControlSignal
1831
- | Promise<void | ControlSignal> => {
1840
+ | Promise<undefined | ControlSignal> => {
1832
1841
  if (i >= conds.length) {
1833
1842
  if (iff.elseBlock) {
1834
1843
  return evalStatement(iff.elseBlock, env, sf, returnOnReturn);
@@ -1855,7 +1864,7 @@ export function evalStatement(
1855
1864
  case "While": {
1856
1865
  const w = asWhile(s);
1857
1866
 
1858
- const runAsync = async (): Promise<void | ControlSignal> => {
1867
+ const runAsync = async (): Promise<undefined | ControlSignal> => {
1859
1868
  while (true) {
1860
1869
  const c = await evalExpression(w.condition, env, sf);
1861
1870
  if (!luaTruthy(c)) {
@@ -1880,7 +1889,6 @@ export function evalStatement(
1880
1889
  if (!luaTruthy(cv)) {
1881
1890
  return;
1882
1891
  }
1883
- try {
1884
1892
  const r = evalStatement(w.block, env, sf, returnOnReturn);
1885
1893
  if (isPromise(r)) {
1886
1894
  return (r as Promise<any>).then((res) => {
@@ -1900,9 +1908,6 @@ export function evalStatement(
1900
1908
  return r;
1901
1909
  }
1902
1910
  return runAsync();
1903
- } catch (e: any) {
1904
- throw e;
1905
- }
1906
1911
  });
1907
1912
  }
1908
1913
  if (!luaTruthy(c)) {
@@ -1932,7 +1937,7 @@ export function evalStatement(
1932
1937
  case "Repeat": {
1933
1938
  const r = asRepeat(s);
1934
1939
 
1935
- const runAsync = async (): Promise<void | ControlSignal> => {
1940
+ const runAsync = async (): Promise<undefined | ControlSignal> => {
1936
1941
  while (true) {
1937
1942
  const rr = evalStatement(r.block, env, sf, returnOnReturn);
1938
1943
  const res = isPromise(rr) ? await rr : rr;
@@ -2101,7 +2106,7 @@ export function evalStatement(
2101
2106
  loopEnv: LuaEnv,
2102
2107
  i: number,
2103
2108
  loopType: NumericType,
2104
- ): void | ControlSignal | Promise<void | ControlSignal> => {
2109
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2105
2110
  loopEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
2106
2111
  return evalStatement(fr.block, loopEnv, sf, returnOnReturn);
2107
2112
  }
@@ -2109,7 +2114,7 @@ export function evalStatement(
2109
2114
  _loopEnv: LuaEnv,
2110
2115
  i: number,
2111
2116
  loopType: NumericType,
2112
- ): void | ControlSignal | Promise<void | ControlSignal> => {
2117
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2113
2118
  const localEnv = new LuaEnv(env);
2114
2119
  localEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
2115
2120
  return evalStatement(fr.block, localEnv, sf, returnOnReturn);
@@ -2148,9 +2153,9 @@ export function evalStatement(
2148
2153
  step: number,
2149
2154
  loopType: NumericType,
2150
2155
  ):
2151
- | void
2156
+ | undefined
2152
2157
  | ControlSignal
2153
- | Promise<void | ControlSignal> => {
2158
+ | Promise<undefined | ControlSignal> => {
2154
2159
  if (step === 0) {
2155
2160
  throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx));
2156
2161
  }
@@ -122,7 +122,7 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
122
122
 
123
123
  while (searchBlock) {
124
124
  const meta = blockMeta.get(searchBlock);
125
- if (meta && meta.labels.has(target)) {
125
+ if (meta?.labels.has(target)) {
126
126
  labelIndex = meta.labels.get(target);
127
127
  labelDefBlock = searchBlock;
128
128
  break;