refactor: enforce mandatory brackets for composite values

Implements Phase 1 and 2 of the syntax rework plan:
- Brackets `[]` are now mandatory for all tuple and array values.
- Removed the `[Type][]` array syntax; arrays now only use `Type[]`.
- `[single]` is now strictly a 1-tuple rather than an array.
- Updated documentation and test fixtures to reflect these breaking
  changes.
This commit is contained in:
2026-08-06 09:19:39 +08:00
parent 7db0742f50
commit c969c7f6fc
11 changed files with 89 additions and 164 deletions
+1
View File
@@ -28,6 +28,7 @@ Build produces separate bundles per entry point (see `tsup.config.ts`). The csv-
- Schema syntax uses **semicolons** (`;`) as separators, not commas - Schema syntax uses **semicolons** (`;`) as separators, not commas
- Unknown identifiers throw a `ParseError` — only recognized keywords (`string`, `number`, `int`, `float`, `boolean`) and string literals (`"on"`, `'off'`) are valid types - Unknown identifiers throw a `ParseError` — only recognized keywords (`string`, `number`, `int`, `float`, `boolean`) and string literals (`"on"`, `'off'`) are valid types
- Tuple and array **values** must be wrapped in brackets `[]` (e.g. `[a; b]`); `[single]` is a 1-tuple, not an array — use `Type[]` for arrays
- `@tablename` / `@tablename[]` are reference schemas resolved at CSV load time - `@tablename` / `@tablename[]` are reference schemas resolved at CSV load time
- `csvToModule()` emits accessor functions (`getData()`) for tables with references, and static JSON for tables without; bundler loaders all use `csvToModule` - `csvToModule()` emits accessor functions (`getData()`) for tables with references, and static JSON for tables without; bundler loaders all use `csvToModule`
- `parseCsv({ resolveReferences: false })` stores reference IDs instead of resolved objects — used by `csvToModule` to emit import-based lazy resolution - `parseCsv({ resolveReferences: false })` stores reference IDs instead of resolved objects — used by `csvToModule` to emit import-based lazy resolution
+9 -23
View File
@@ -35,14 +35,9 @@ numberSchema.validator(name); // false
```typescript ```typescript
const tupleSchema = defineSchema('[string; number; boolean]'); const tupleSchema = defineSchema('[string; number; boolean]');
// With brackets
const value1 = tupleSchema.parse('[hello; 42; true]'); const value1 = tupleSchema.parse('[hello; 42; true]');
// ["hello", 42, true] // ["hello", 42, true]
// Without brackets (outermost brackets are optional)
const value2 = tupleSchema.parse('hello; 42; true');
// ["hello", 42, true]
tupleSchema.validator(value1); // true tupleSchema.validator(value1); // true
tupleSchema.validator(['a', 'b', true]); // false (second element should be number) tupleSchema.validator(['a', 'b', true]); // false (second element should be number)
``` ```
@@ -50,16 +45,11 @@ tupleSchema.validator(['a', 'b', true]); // false (second element should be num
### Arrays ### Arrays
```typescript ```typescript
// Array syntax: Type[] or [Type][] // Array syntax: Type[]
const stringArray = defineSchema('string[]'); const stringArray = defineSchema('string[]');
const numberArray = defineSchema('[number][]'); const numberArray = defineSchema('number[]');
// With brackets const names = stringArray.parse('[alice; bob; charlie]');
const names1 = stringArray.parse('[alice; bob; charlie]');
// ["alice", "bob", "charlie"]
// Without brackets (outermost brackets are optional)
const names2 = stringArray.parse('alice; bob; charlie');
// ["alice", "bob", "charlie"] // ["alice", "bob", "charlie"]
const numbers = numberArray.parse('[1; 2; 3; 4; 5]'); const numbers = numberArray.parse('[1; 2; 3; 4; 5]');
@@ -71,12 +61,7 @@ const numbers = numberArray.parse('[1; 2; 3; 4; 5]');
```typescript ```typescript
const schema = defineSchema('[string; number][]'); const schema = defineSchema('[string; number][]');
// With outer brackets const data = schema.parse('[[a; 1]; [b; 2]; [c; 3]]');
const data1 = schema.parse('[[a; 1]; [b; 2]; [c; 3]]');
// [["a", 1], ["b", 2], ["c", 3]]
// Without outer brackets
const data2 = schema.parse('[a; 1]; [b; 2]; [c; 3]');
// [["a", 1], ["b", 2], ["c", 3]] // [["a", 1], ["b", 2], ["c", 3]]
``` ```
@@ -155,9 +140,9 @@ The following TypeScript types are exported:
| Float | `float` | `3.14` | | Float | `float` | `3.14` |
| Number | `number` | `42` or `3.14` | | Number | `number` | `42` or `3.14` |
| Boolean | `boolean` | `true` or `false` | | Boolean | `boolean` | `true` or `false` |
| Tuple | `[Type1; Type2; ...]` | `[hello; 42; true]` or `hello; 42; true` | | Tuple | `[Type1; Type2; ...]` | `[hello; 42; true]` |
| Array | `Type[]` or `[Type][]` | `[1; 2; 3]` or `1; 2; 3` | | Array | `Type[]` | `[1; 2; 3]` |
| Array of Tuples | `[Type1; Type2][]` | `[[a; 1]; [b; 2]]` or `[a; 1]; [b; 2]` | | Array of Tuples | `[Type1; Type2][]` | `[[a; 1]; [b; 2]]` |
| Union | `Type1 \| Type2` | `hello` or `42` (matches first valid member) | | Union | `Type1 \| Type2` | `hello` or `42` (matches first valid member) |
| String Literal | `'on' \| 'off'` or `"red"` | `on` or `off` | | String Literal | `'on' \| 'off'` or `"red"` | `on` or `off` |
| Reference | `@tablename` or `@tablename[]` | (resolved at CSV load time) | | Reference | `@tablename` or `@tablename[]` | (resolved at CSV load time) |
@@ -166,7 +151,8 @@ The following TypeScript types are exported:
## Notes ## Notes
- Semicolons `;` are used as separators instead of commas `,` - Semicolons `;` are used as separators instead of commas `,`
- Outermost brackets `[]` are optional for tuple and array values - Tuple and array values **must** be wrapped in brackets `[]` (e.g. `[a; b]`)
- `[single]` is a 1-tuple, not an array — use `Type[]` for arrays
- Special characters can be escaped with backslash: `\;`, `\[`, `\]`, `\\` - Special characters can be escaped with backslash: `\;`, `\[`, `\]`, `\\`
- Empty arrays/tuples are not allowed - Empty arrays/tuples are not allowed
- For CSV loading with reference resolution, see [csv-loader.md](./csv-loader.md) - For CSV loading with reference resolution, see [csv-loader.md](./csv-loader.md)
+1 -1
View File
@@ -146,7 +146,7 @@ Uses [typed-csv](https://github.com/your-repo/typed-csv) syntax:
| String | `string` | `hello` | | String | `string` | `hello` |
| Number | `number` | `42` | | Number | `number` | `42` |
| Boolean | `boolean` | `true` | | Boolean | `boolean` | `true` |
| Array | `string[]` or `[string][]` | `[a; b; c]` | | Array | `string[]` | `[a; b; c]` |
| Tuple | `[string; number]` | `[hello; 42]` | | Tuple | `[string; number]` | `[hello; 42]` |
## License ## License
+13 -18
View File
@@ -1,6 +1,6 @@
# Implementation Plan: Syntax Clarity & Parser Robustness # Implementation Plan: Syntax Clarity & Parser Robustness
Status: **Draft — not yet applied** Status: **In progress — Phases 1 & 2 applied** (Phases 36 not yet done)
## Goals ## Goals
@@ -14,34 +14,29 @@ All changes are **breaking** to the DSL → bump to `2.0.0`, update README + `cs
--- ---
## Phase 1 — Mandatory brackets for composite values ## Phase 1 — Mandatory brackets for composite values ✅ Applied
**Files:** `src/value-parser.ts`, `src/index.test.ts`, `src/csv-loader/reference-resolver.ts` **Files:** `src/value-parser.ts`, `src/index.test.ts`, `src/csv-loader/reference-resolver.ts`
- Remove the `allowOmitBrackets` parameter from `parseValue`, `parseTupleValue`, `parseArrayValue`. - Removed the `allowOmitBrackets` parameter from `parseValue`, `parseTupleValue`, `parseArrayValue`.
- Delete the `elementIsTupleOrArray` disambiguation block and all `savedPos` restore logic in `parseArrayValue`. - Deleted the `elementIsTupleOrArray` disambiguation block and all `savedPos` restore logic in `parseArrayValue`.
- In `parseValue` (top-level), drop the `allowOmitBrackets = schema.type === "tuple" || "array"` special case — brackets are always required. - Dropped the `allowOmitBrackets = schema.type === "tuple" || "array"` special case in top-level `parseValue` — brackets are always required.
- Values now must be fully bracketed: `[a; 1]; [b; 2]` (no more `[a; 1]; [b; 2]` without outer brackets). - Array references (`@table[]` values) now also require brackets, for consistency.
- Values must now be fully bracketed: `[a; 1]; [b; 2]` (no more `[a; 1]; [b; 2]` without outer brackets).
**Tests to update** (currently assert bracket-optional behavior): **Decision:** Full mandatory brackets (no carve-out).
- `index.test.ts`: "should parse tuple without brackets" [L177], "should parse array without brackets" [L215], "should parse array of tuples without outer brackets" [L249].
- `reference-resolver.ts` / `index.test.ts` reference tests: "should parse array reference IDs without brackets" [L685].
**Decision point:** Full mandatory brackets, or a *top-level-only* carve-out (brackets optional only when the value is the whole cell and the element is non-composite)? Recommend **full mandatory** for a clean grammar; note the carve-out as a fallback if ergonomics matter more.
--- ---
## Phase 2 — Single array form `Type[]` ## Phase 2 — Single array form `Type[]` ✅ Applied
**Files:** `src/parser.ts`, `src/index.test.ts` **Files:** `src/parser.ts`, `src/index.test.ts`
- Remove the `[Type][]` array syntax from `parseSchemaInternal`. Keep only `Type[]`. - Removed the `[Type][]` array syntax from `parseSchemaInternal`. Kept only `Type[]`.
- This makes `[string]` a **1-tuple** (currently it collapses to an array). Update the tuple branch: `[t]` `{ type: "tuple", elements: [t] }`; the special case `elements.length === 1 && !elements[0].name` becomes a real tuple. - `[string]` is now a **1-tuple** (previously it collapsed to an array). The tuple branch always returns `{ type: "tuple", elements }`.
- Update `schemaToTypeString` in `src/type-utils.ts` — the `array` case currently special-cases tuple elements; with `[Type][]` gone, arrays are always `elementType[]` (keep the `(union)[]` paren wrapping). - Updated `schemaToTypeString` in `src/type-utils.ts` — the `array` case no longer special-cases tuple elements; arrays are always `elementType[]` (kept the `(union)[]` paren wrapping).
**Tests to update:** **Tests updated:** `index.test.ts` bracket-optional tests, `encounter.csv` / `enemy_intents.csv` fixtures, and `parseCsv-typeDeclarations.test.ts` array-of-tuple values.
- `index.test.ts`: "should parse array of tuples" [L239] uses `[string; number][]` → becomes `[string; number][]` (unchanged) but "array of tuples without outer brackets" [L249] changes per Phase 1.
- Any test using `[number][]`, `[string][]` schema strings (e.g. README examples, `csv-loader.md`).
--- ---
+2 -2
View File
@@ -3,5 +3,5 @@
id,type,name,description,enemies,dialogue id,type,name,description,enemies,dialogue
string,EncounterType,string,string,EnemyList,string string,EncounterType,string,string,EnemyList,string
cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]], cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[[仙人掌怪;12;[]];[仙人掌怪;12;[]]],
snake_pair,minion,,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;10;[[poison;2];[quick;1]]], snake_pair,minion,,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[[蛇;10;[[poison;2];[quick;1]]]],
1 # type EncounterType = 'minion'|'elite'|'event'|'shop'|'camp'|'curio'
3 id,type,name,description,enemies,dialogue
4 string,EncounterType,string,string,EnemyList,string
5 cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]], cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[[仙人掌怪;12;[]];[仙人掌怪;12;[]]],
6 snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;10;[[poison;2];[quick;1]]], snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[[蛇;10;[[poison;2];[quick;1]]]],
7
+1 -1
View File
@@ -4,4 +4,4 @@
id,enemy,initialIntent,nextIntents,brokenIntent,effects id,enemy,initialIntent,nextIntents,brokenIntent,effects
string,string,boolean,string[],string[],IntentEffects string,string,boolean,string[],string[],IntentEffects
仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[user;spike;1];[user;defend;4] 仙人掌怪-boost,仙人掌怪,true,[仙人掌怪-boost;仙人掌怪-defend],,[[user;spike;1];[user;defend;4]]
1 # type IntentEffectTarget = 'user' | 'eachEnemy' | 'randomEnemy' | 'player'
4 id,enemy,initialIntent,nextIntents,brokenIntent,effects
5 string,string,boolean,string[],string[],IntentEffects
6 仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[user;spike;1];[user;defend;4] 仙人掌怪-boost,仙人掌怪,true,[仙人掌怪-boost;仙人掌怪-defend],,[[user;spike;1];[user;defend;4]]
7
@@ -209,7 +209,7 @@ describe("parseCsv - type declarations", () => {
"# type IntentEffects = IntentEffect[]", "# type IntentEffects = IntentEffect[]",
"id,effects", "id,effects",
"string,IntentEffects", "string,IntentEffects",
"boost,[user;spike;1];[user;defend;4]", "boost,[[user;spike;1];[user;defend;4]]",
].join("\n"); ].join("\n");
const result = parseCsv(csv, { emitTypes: false }); const result = parseCsv(csv, { emitTypes: false });
@@ -233,7 +233,7 @@ describe("parseCsv - type declarations", () => {
"# type IntentEffects = IntentEffect[]", "# type IntentEffects = IntentEffect[]",
"id,effects", "id,effects",
"string,IntentEffects", "string,IntentEffects",
"boost,[user;spike;1];[user;defend;4]", "boost,[[user;spike;1];[user;defend;4]]",
].join("\n"); ].join("\n");
const result = parseCsv(csv, { const result = parseCsv(csv, {
@@ -260,7 +260,7 @@ describe("parseCsv - type declarations", () => {
'# type Type = "apple" | "orange"', '# type Type = "apple" | "orange"',
"id,items", "id,items",
"string,[Type; int][]", "string,[Type; int][]",
"001,[apple;2];[orange;3]", "001,[[apple;2];[orange;3]]",
].join("\n"); ].join("\n");
const result = parseCsv(csv, { emitTypes: false }); const result = parseCsv(csv, { emitTypes: false });
@@ -292,7 +292,7 @@ describe("parseCsv - type declarations", () => {
"# type Entry = [@user]", "# type Entry = [@user]",
"id,entry", "id,entry",
"string,Entry", "string,Entry",
"1,1", "1,[1]",
].join("\n"); ].join("\n");
const result = parseCsv(csv, { const result = parseCsv(csv, {
+13 -9
View File
@@ -174,10 +174,11 @@ describe("Tuples", () => {
expect(schema.validator(["hello", "42"])).toBe(false); expect(schema.validator(["hello", "42"])).toBe(false);
}); });
it("should parse tuple without brackets", () => { it("should require brackets for tuple values", () => {
const schema = defineSchema("[string; number]"); const schema = defineSchema("[string; number]");
const value = schema.parse("hello; 42"); const value = schema.parse("[hello; 42]");
expect(value).toEqual(["hello", 42]); expect(value).toEqual(["hello", 42]);
expect(() => schema.parse("hello; 42")).toThrow(ParseError);
}); });
it("should parse named tuple", () => { it("should parse named tuple", () => {
@@ -212,10 +213,11 @@ describe("Arrays", () => {
expect(schema.validator(["hello", 42])).toBe(false); expect(schema.validator(["hello", 42])).toBe(false);
}); });
it("should parse array without brackets", () => { it("should require brackets for array values", () => {
const schema = defineSchema("string[]"); const schema = defineSchema("string[]");
const value = schema.parse("hello; world; test"); const value = schema.parse("[hello; world; test]");
expect(value).toEqual(["hello", "world", "test"]); expect(value).toEqual(["hello", "world", "test"]);
expect(() => schema.parse("hello; world; test")).toThrow(ParseError);
}); });
it("should parse array of numbers", () => { it("should parse array of numbers", () => {
@@ -246,14 +248,15 @@ describe("Arrays", () => {
]); ]);
}); });
it("should parse array of tuples without outer brackets", () => { it("should require outer brackets for array of tuples", () => {
const schema = defineSchema("[string; number][]"); const schema = defineSchema("[string; number][]");
const value = schema.parse("[a; 1]; [b; 2]; [c; 3]"); const value = schema.parse("[[a; 1]; [b; 2]; [c; 3]]");
expect(value).toEqual([ expect(value).toEqual([
["a", 1], ["a", 1],
["b", 2], ["b", 2],
["c", 3], ["c", 3],
]); ]);
expect(() => schema.parse("[a; 1]; [b; 2]; [c; 3]")).toThrow(ParseError);
}); });
it("should parse array of unions", () => { it("should parse array of unions", () => {
@@ -283,7 +286,7 @@ describe("Escaping", () => {
it("should handle escaped semicolon in tuple", () => { it("should handle escaped semicolon in tuple", () => {
const schema = defineSchema("[string; string]"); const schema = defineSchema("[string; string]");
const value = schema.parse("hello\\;world; test"); const value = schema.parse("[hello\\;world; test]");
expect(value).toEqual(["hello;world", "test"]); expect(value).toEqual(["hello;world", "test"]);
}); });
}); });
@@ -682,14 +685,15 @@ describe("Reference value parsing (parseValue)", () => {
expect(result).toEqual(["1", "2", "3"]); expect(result).toEqual(["1", "2", "3"]);
}); });
it("should parse array reference IDs without brackets", () => { it("should require brackets for array reference IDs", () => {
const schema: import("./types").ReferenceSchema = { const schema: import("./types").ReferenceSchema = {
type: "reference", type: "reference",
tableName: "users", tableName: "users",
isArray: true, isArray: true,
}; };
const result = parseValue(schema, "1; 2; 3"); const result = parseValue(schema, "[1; 2; 3]");
expect(result).toEqual(["1", "2", "3"]); expect(result).toEqual(["1", "2", "3"]);
expect(() => parseValue(schema, "1; 2; 3")).toThrow(ParseError);
}); });
it("should parse empty array reference", () => { it("should parse empty array reference", () => {
+1 -14
View File
@@ -250,20 +250,7 @@ class Parser {
throw new ParseError("Expected ]", this.pos); throw new ParseError("Expected ]", this.pos);
} }
if (this.consumeStr("[")) { // [t] is always a 1-tuple (the [Type][] array form was removed)
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "array", element: { type: "tuple", elements } };
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "tuple", elements }; return { type: "tuple", elements };
} }
-7
View File
@@ -39,13 +39,6 @@ export function schemaToTypeString(
return schema.isOptional ? `${baseType} | null` : baseType; return schema.isOptional ? `${baseType} | null` : baseType;
} }
case "array": case "array":
if (schema.element.type === "tuple") {
const tupleElements = schema.element.elements.map((el) => {
const typeStr = schemaToTypeString(el.schema, resourceNames);
return el.name ? `${el.name}: ${typeStr}` : typeStr;
});
return `[${tupleElements.join(", ")}][]`;
}
const elementType = schemaToTypeString(schema.element, resourceNames); const elementType = schemaToTypeString(schema.element, resourceNames);
if (schema.element.type === "union") { if (schema.element.type === "union") {
return `(${elementType})[]`; return `(${elementType})[]`;
+44 -85
View File
@@ -41,7 +41,7 @@ class ValueParser {
return false; return false;
} }
parseValue(schema: Schema, allowOmitBrackets: boolean = false): unknown { parseValue(schema: Schema): unknown {
this.skipWhitespace(); this.skipWhitespace();
switch (schema.type) { switch (schema.type) {
@@ -60,9 +60,9 @@ class ValueParser {
case "union": case "union":
return this.parseUnionValue(schema); return this.parseUnionValue(schema);
case "tuple": case "tuple":
return this.parseTupleValue(schema, allowOmitBrackets); return this.parseTupleValue(schema);
case "array": case "array":
return this.parseArrayValue(schema, allowOmitBrackets); return this.parseArrayValue(schema);
case "reference": case "reference":
// Reference values are parsed as strings (IDs) initially, resolved later // Reference values are parsed as strings (IDs) initially, resolved later
return this.parseReferenceValue(schema); return this.parseReferenceValue(schema);
@@ -250,7 +250,7 @@ class ValueParser {
for (let i = 0; i < schema.members.length; i++) { for (let i = 0; i < schema.members.length; i++) {
this.pos = savedPos; this.pos = savedPos;
try { try {
return this.parseValue(schema.members[i], false); return this.parseValue(schema.members[i]);
} catch (e) { } catch (e) {
errors.push(e as Error); errors.push(e as Error);
// Continue to next member // Continue to next member
@@ -266,16 +266,8 @@ class ValueParser {
); );
} }
private parseTupleValue( private parseTupleValue(schema: TupleSchema): unknown[] {
schema: TupleSchema, if (!this.consumeStr("[")) {
allowOmitBrackets: boolean,
): unknown[] {
let hasOpenBracket = false;
if (this.peek() === "[") {
this.consume();
hasOpenBracket = true;
} else if (!allowOmitBrackets) {
throw new ParseError( throw new ParseError(
"Expected [", "Expected [",
this.pos, this.pos,
@@ -286,7 +278,7 @@ class ValueParser {
this.skipWhitespace(); this.skipWhitespace();
if (this.peek() === "]" && hasOpenBracket) { if (this.peek() === "]") {
this.consume(); this.consume();
return []; return [];
} }
@@ -308,7 +300,7 @@ class ValueParser {
} }
} }
result.push(this.parseValue(elementSchema.schema, false)); result.push(this.parseValue(elementSchema.schema));
this.skipWhitespace(); this.skipWhitespace();
if (i < schema.elements.length - 1) { if (i < schema.elements.length - 1) {
@@ -325,57 +317,24 @@ class ValueParser {
this.skipWhitespace(); this.skipWhitespace();
if (hasOpenBracket) { if (!this.consumeStr("]")) {
if (!this.consumeStr("]")) { throw new ParseError(
throw new ParseError( "Expected ]",
"Expected ]", this.pos,
this.pos, this.schemaString,
this.schemaString, this.input,
this.input, );
);
}
} }
return result; return result;
} }
private parseArrayValue( private parseArrayValue(schema: ArraySchema): unknown[] {
schema: ArraySchema,
allowOmitBrackets: boolean,
): unknown[] {
let hasOpenBracket = false;
const elementIsTupleOrArray =
schema.element.type === "tuple" || schema.element.type === "array";
if (this.pos >= this.input.length || !this.input.trim()) { if (this.pos >= this.input.length || !this.input.trim()) {
return []; return [];
} }
if (this.peek() === "[") { if (!this.consumeStr("[")) {
if (!elementIsTupleOrArray) {
this.consume();
hasOpenBracket = true;
} else {
// Element is tuple or array - need to disambiguate
// Save position to check if this is an empty array
const savedPos = this.pos;
this.consume();
this.skipWhitespace();
if (this.peek() === "]") {
// Empty array []
this.consume();
return [];
} else if (this.peek() === "[") {
// Nested brackets [[ - this is the array opener
hasOpenBracket = true;
} else {
// [ belongs to the first element, restore position
this.pos = savedPos;
}
}
}
if (!hasOpenBracket && !allowOmitBrackets && !elementIsTupleOrArray) {
throw new ParseError( throw new ParseError(
"Expected [", "Expected [",
this.pos, this.pos,
@@ -386,7 +345,7 @@ class ValueParser {
this.skipWhitespace(); this.skipWhitespace();
if (this.peek() === "]" && hasOpenBracket) { if (this.peek() === "]") {
this.consume(); this.consume();
return []; return [];
} }
@@ -395,7 +354,7 @@ class ValueParser {
while (true) { while (true) {
this.skipWhitespace(); this.skipWhitespace();
result.push(this.parseValue(schema.element, elementIsTupleOrArray)); result.push(this.parseValue(schema.element));
this.skipWhitespace(); this.skipWhitespace();
if (!this.consumeStr(";")) { if (!this.consumeStr(";")) {
@@ -405,15 +364,13 @@ class ValueParser {
this.skipWhitespace(); this.skipWhitespace();
if (hasOpenBracket) { if (!this.consumeStr("]")) {
if (!this.consumeStr("]")) { throw new ParseError(
throw new ParseError( "Expected ]",
"Expected ]", this.pos,
this.pos, this.schemaString,
this.schemaString, this.input,
this.input, );
);
}
} }
return result; return result;
@@ -431,15 +388,18 @@ class ValueParser {
if (schema.isArray) { if (schema.isArray) {
// Parse array of IDs: [id1; id2; id3] // Parse array of IDs: [id1; id2; id3]
let hasOpenBracket = false; if (!this.consumeStr("[")) {
if (this.peek() === "[") { throw new ParseError(
this.consume(); "Expected [",
hasOpenBracket = true; this.pos,
this.schemaString,
this.input,
);
} }
this.skipWhitespace(); this.skipWhitespace();
if (this.peek() === "]" && hasOpenBracket) { if (this.peek() === "]") {
this.consume(); this.consume();
return []; return [];
} }
@@ -464,15 +424,15 @@ class ValueParser {
} }
} }
if (hasOpenBracket) { this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError( if (!this.consumeStr("]")) {
"Expected ]", throw new ParseError(
this.pos, "Expected ]",
this.schemaString, this.pos,
this.input, this.schemaString,
); this.input,
} );
} }
return ids; return ids;
@@ -506,8 +466,7 @@ export function parseValue(
): unknown { ): unknown {
const sStr = schemaString || schemaToTypeString(schema); const sStr = schemaString || schemaToTypeString(schema);
const parser = new ValueParser(valueString.trim(), sStr); const parser = new ValueParser(valueString.trim(), sStr);
const allowOmitBrackets = schema.type === "tuple" || schema.type === "array"; const value = parser.parseValue(schema);
const value = parser.parseValue(schema, allowOmitBrackets);
if (parser.getPosition() < parser.getInputLength()) { if (parser.getPosition() < parser.getInputLength()) {
throw new ParseError( throw new ParseError(