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:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 3–6 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`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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]]]],
|
||||||
|
|||||||
|
@@ -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]]
|
||||||
|
|||||||
|
@@ -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
@@ -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
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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})[]`;
|
||||||
|
|||||||
+23
-64
@@ -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,7 +317,6 @@ class ValueParser {
|
|||||||
|
|
||||||
this.skipWhitespace();
|
this.skipWhitespace();
|
||||||
|
|
||||||
if (hasOpenBracket) {
|
|
||||||
if (!this.consumeStr("]")) {
|
if (!this.consumeStr("]")) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected ]",
|
"Expected ]",
|
||||||
@@ -334,48 +325,16 @@ class ValueParser {
|
|||||||
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,7 +364,6 @@ class ValueParser {
|
|||||||
|
|
||||||
this.skipWhitespace();
|
this.skipWhitespace();
|
||||||
|
|
||||||
if (hasOpenBracket) {
|
|
||||||
if (!this.consumeStr("]")) {
|
if (!this.consumeStr("]")) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected ]",
|
"Expected ]",
|
||||||
@@ -414,7 +372,6 @@ class ValueParser {
|
|||||||
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,7 +424,8 @@ class ValueParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasOpenBracket) {
|
this.skipWhitespace();
|
||||||
|
|
||||||
if (!this.consumeStr("]")) {
|
if (!this.consumeStr("]")) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected ]",
|
"Expected ]",
|
||||||
@@ -473,7 +434,6 @@ class ValueParser {
|
|||||||
this.input,
|
this.input,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
return ids;
|
||||||
} else {
|
} else {
|
||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user