bonescript-compiler 0.6.1 → 0.7.0
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.
- package/dist/cli.js +121 -4
- package/dist/cli.js.map +1 -1
- package/dist/emit_prisma.d.ts +20 -0
- package/dist/emit_prisma.js +314 -0
- package/dist/emit_prisma.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/cli.ts +137 -5
- package/src/emit_prisma.ts +346 -0
- package/src/index.ts +1 -0
- package/dist/source_map.js.map +0 -1
- package/dist/test.js.map +0 -1
- package/dist/test_typechecker.d.ts +0 -5
- package/dist/test_typechecker.js +0 -126
- package/dist/test_typechecker.js.map +0 -1
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript Prisma Schema Emitter
|
|
3
|
+
* Generates a Prisma schema file from the IR.
|
|
4
|
+
*
|
|
5
|
+
* Maps BoneScript entities, relations, and constraints to Prisma's
|
|
6
|
+
* schema language. Produces a single `schema.prisma` file that can
|
|
7
|
+
* be used with `npx prisma migrate dev` and `npx prisma generate`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as IR from "./ir";
|
|
11
|
+
import { EmittedFile } from "./emitter";
|
|
12
|
+
|
|
13
|
+
function toSnakeCase(s: string): string {
|
|
14
|
+
return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toPascalCase(s: string): string {
|
|
18
|
+
return s.replace(/(^|[-_\s])(\w)/g, (_, __, c: string) => c.toUpperCase());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─── Type Mapping ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const PRISMA_TYPE_MAP: Record<string, string> = {
|
|
24
|
+
string: "String",
|
|
25
|
+
uint: "Int",
|
|
26
|
+
int: "Int",
|
|
27
|
+
float: "Float",
|
|
28
|
+
bool: "Boolean",
|
|
29
|
+
timestamp: "DateTime",
|
|
30
|
+
uuid: "String",
|
|
31
|
+
bytes: "Bytes",
|
|
32
|
+
json: "Json",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function toPrismaType(irType: string): string {
|
|
36
|
+
if (PRISMA_TYPE_MAP[irType]) return PRISMA_TYPE_MAP[irType];
|
|
37
|
+
const listMatch = irType.match(/^(list|set)<(.+)>$/);
|
|
38
|
+
if (listMatch) return `${toPrismaType(listMatch[2])}[]`;
|
|
39
|
+
const optMatch = irType.match(/^optional<(.+)>$/);
|
|
40
|
+
if (optMatch) return `${toPrismaType(optMatch[1])}?`;
|
|
41
|
+
// Entity reference — will be handled as a relation
|
|
42
|
+
return "String";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toPrismaNativeType(irType: string): string | null {
|
|
46
|
+
switch (irType) {
|
|
47
|
+
case "uuid": return "@db.Uuid";
|
|
48
|
+
case "string": return null; // default is fine
|
|
49
|
+
case "json": return null; // Json maps directly
|
|
50
|
+
case "bytes": return null;
|
|
51
|
+
case "timestamp": return "@db.Timestamptz";
|
|
52
|
+
case "float": return "@db.DoublePrecision";
|
|
53
|
+
case "uint": return "@db.BigInt";
|
|
54
|
+
case "int": return "@db.BigInt";
|
|
55
|
+
default: return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Prisma Emitter ───────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export class PrismaEmitter {
|
|
62
|
+
emit(system: IR.IRSystem): EmittedFile[] {
|
|
63
|
+
const files: EmittedFile[] = [];
|
|
64
|
+
files.push({
|
|
65
|
+
path: "prisma/schema.prisma",
|
|
66
|
+
content: this.emitSchema(system),
|
|
67
|
+
language: "typescript", // closest match for syntax highlighting
|
|
68
|
+
source_module: "prisma",
|
|
69
|
+
});
|
|
70
|
+
return files;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private emitSchema(system: IR.IRSystem): string {
|
|
74
|
+
const lines: string[] = [];
|
|
75
|
+
|
|
76
|
+
// Header
|
|
77
|
+
lines.push(`// Generated by BoneScript compiler. DO NOT EDIT.`);
|
|
78
|
+
lines.push(`// Source: ${system.name} (${system.source_hash})`);
|
|
79
|
+
lines.push(`// Run: npx prisma migrate dev --name init`);
|
|
80
|
+
lines.push(`// npx prisma generate`);
|
|
81
|
+
lines.push(``);
|
|
82
|
+
|
|
83
|
+
// Datasource
|
|
84
|
+
lines.push(`datasource db {`);
|
|
85
|
+
lines.push(` provider = "postgresql"`);
|
|
86
|
+
lines.push(` url = env("DATABASE_URL")`);
|
|
87
|
+
lines.push(`}`);
|
|
88
|
+
lines.push(``);
|
|
89
|
+
|
|
90
|
+
// Generator
|
|
91
|
+
lines.push(`generator client {`);
|
|
92
|
+
lines.push(` provider = "prisma-client-js"`);
|
|
93
|
+
lines.push(`}`);
|
|
94
|
+
lines.push(``);
|
|
95
|
+
|
|
96
|
+
// Collect all models across modules, deduplicating by name
|
|
97
|
+
const modelMap = new Map<string, { model: IR.IRModel; mod: IR.IRModule }>();
|
|
98
|
+
const allRelations: IR.IRRelation[] = [];
|
|
99
|
+
|
|
100
|
+
for (const mod of system.modules) {
|
|
101
|
+
if (mod.kind === "data_store" || mod.kind === "api_service") {
|
|
102
|
+
for (const model of mod.models) {
|
|
103
|
+
if (!modelMap.has(model.name)) {
|
|
104
|
+
modelMap.set(model.name, { model, mod });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
allRelations.push(...mod.relations);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Deduplicate relations by a stable key
|
|
112
|
+
const seenRelations = new Set<string>();
|
|
113
|
+
const uniqueRelations: IR.IRRelation[] = [];
|
|
114
|
+
for (const rel of allRelations) {
|
|
115
|
+
const key = `${rel.from_entity}:${rel.to_entity}:${rel.kind}:${rel.foreign_key}`;
|
|
116
|
+
if (!seenRelations.has(key)) {
|
|
117
|
+
seenRelations.add(key);
|
|
118
|
+
uniqueRelations.push(rel);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build a lookup: entity name → relations where it's the "from" side
|
|
123
|
+
const relationsFrom = new Map<string, IR.IRRelation[]>();
|
|
124
|
+
const relationsTo = new Map<string, IR.IRRelation[]>();
|
|
125
|
+
for (const rel of uniqueRelations) {
|
|
126
|
+
if (!relationsFrom.has(rel.from_entity)) relationsFrom.set(rel.from_entity, []);
|
|
127
|
+
relationsFrom.get(rel.from_entity)!.push(rel);
|
|
128
|
+
if (!relationsTo.has(rel.to_entity)) relationsTo.set(rel.to_entity, []);
|
|
129
|
+
relationsTo.get(rel.to_entity)!.push(rel);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Emit each model
|
|
133
|
+
for (const [name, { model, mod }] of modelMap) {
|
|
134
|
+
lines.push(this.emitModel(name, model, mod, relationsFrom.get(name) || [], relationsTo.get(name) || [], modelMap));
|
|
135
|
+
lines.push(``);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Emit junction tables for many_to_many
|
|
139
|
+
for (const rel of uniqueRelations) {
|
|
140
|
+
if (rel.kind === "many_to_many" && rel.junction_table) {
|
|
141
|
+
lines.push(this.emitJunctionModel(rel));
|
|
142
|
+
lines.push(``);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Emit audit_log and event_outbox models (infrastructure)
|
|
147
|
+
lines.push(this.emitAuditLogModel());
|
|
148
|
+
lines.push(``);
|
|
149
|
+
lines.push(this.emitEventOutboxModel());
|
|
150
|
+
lines.push(``);
|
|
151
|
+
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private emitModel(
|
|
156
|
+
name: string,
|
|
157
|
+
model: IR.IRModel,
|
|
158
|
+
mod: IR.IRModule,
|
|
159
|
+
relationsFrom: IR.IRRelation[],
|
|
160
|
+
relationsTo: IR.IRRelation[],
|
|
161
|
+
allModels: Map<string, { model: IR.IRModel; mod: IR.IRModule }>
|
|
162
|
+
): string {
|
|
163
|
+
const lines: string[] = [];
|
|
164
|
+
const tableName = toSnakeCase(name) + "s";
|
|
165
|
+
|
|
166
|
+
lines.push(`model ${toPascalCase(name)} {`);
|
|
167
|
+
|
|
168
|
+
// Fields
|
|
169
|
+
for (const field of model.fields) {
|
|
170
|
+
lines.push(` ${this.emitField(field, model, name)}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// State field (if entity has a state machine)
|
|
174
|
+
const hasSM = mod.state_machines.some(sm => sm.entity === name);
|
|
175
|
+
const hasStateField = model.fields.some(f => f.name === "state");
|
|
176
|
+
if (hasSM && !hasStateField) {
|
|
177
|
+
lines.push(` state String`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Relation fields
|
|
181
|
+
for (const rel of relationsFrom) {
|
|
182
|
+
if (rel.kind === "belongs_to") {
|
|
183
|
+
// The FK field is already in the model fields. Add the relation object.
|
|
184
|
+
const targetModel = toPascalCase(rel.to_entity);
|
|
185
|
+
const fieldName = toSnakeCase(rel.to_entity);
|
|
186
|
+
lines.push(` ${fieldName} ${targetModel} @relation(fields: [${rel.foreign_key}], references: [id], onDelete: Cascade)`);
|
|
187
|
+
} else if (rel.kind === "has_many") {
|
|
188
|
+
const targetModel = toPascalCase(rel.to_entity);
|
|
189
|
+
const fieldName = toSnakeCase(rel.to_entity) + "s";
|
|
190
|
+
lines.push(` ${fieldName} ${targetModel}[]`);
|
|
191
|
+
} else if (rel.kind === "has_one") {
|
|
192
|
+
const targetModel = toPascalCase(rel.to_entity);
|
|
193
|
+
const fieldName = toSnakeCase(rel.to_entity);
|
|
194
|
+
lines.push(` ${fieldName} ${targetModel}?`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Inverse relations (where this model is the "to" side)
|
|
199
|
+
for (const rel of relationsTo) {
|
|
200
|
+
if (rel.kind === "belongs_to") {
|
|
201
|
+
// This model is the target of a belongs_to — add the inverse has_many
|
|
202
|
+
const sourceModel = toPascalCase(rel.from_entity);
|
|
203
|
+
const fieldName = toSnakeCase(rel.from_entity) + "s";
|
|
204
|
+
// Only add if not already covered by relationsFrom
|
|
205
|
+
const alreadyCovered = relationsFrom.some(r =>
|
|
206
|
+
r.to_entity === rel.from_entity && r.kind === "has_many"
|
|
207
|
+
);
|
|
208
|
+
if (!alreadyCovered) {
|
|
209
|
+
lines.push(` ${fieldName} ${sourceModel}[]`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Table mapping
|
|
215
|
+
lines.push(``);
|
|
216
|
+
lines.push(` @@map("${tableName}")`);
|
|
217
|
+
|
|
218
|
+
// Indexes
|
|
219
|
+
for (const idx of model.indexes) {
|
|
220
|
+
if (idx.unique) {
|
|
221
|
+
lines.push(` @@unique([${idx.fields.join(", ")}])`);
|
|
222
|
+
} else {
|
|
223
|
+
lines.push(` @@index([${idx.fields.join(", ")}])`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
lines.push(`}`);
|
|
228
|
+
return lines.join("\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private emitField(field: IR.IRField, model: IR.IRModel, entityName: string): string {
|
|
232
|
+
const parts: string[] = [];
|
|
233
|
+
|
|
234
|
+
// Field name (padded for alignment)
|
|
235
|
+
parts.push(field.name.padEnd(14));
|
|
236
|
+
|
|
237
|
+
// Type
|
|
238
|
+
let prismaType = toPrismaType(field.type);
|
|
239
|
+
if (field.nullable) prismaType += "?";
|
|
240
|
+
parts.push(prismaType.padEnd(12));
|
|
241
|
+
|
|
242
|
+
// Attributes
|
|
243
|
+
const attrs: string[] = [];
|
|
244
|
+
|
|
245
|
+
// Primary key
|
|
246
|
+
if (field.name === model.primary_key) {
|
|
247
|
+
attrs.push("@id");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Default values
|
|
251
|
+
if (field.name === "id" && field.type === "uuid") {
|
|
252
|
+
attrs.push("@default(uuid())");
|
|
253
|
+
} else if (field.name === "created_at" || (field.type === "timestamp" && field.name.includes("created"))) {
|
|
254
|
+
attrs.push("@default(now())");
|
|
255
|
+
} else if (field.name === "updated_at") {
|
|
256
|
+
attrs.push("@updatedAt");
|
|
257
|
+
} else if (field.default_value) {
|
|
258
|
+
const dv = this.mapDefaultValue(field.default_value, field.type);
|
|
259
|
+
if (dv) attrs.push(dv);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Unique constraint (skip if already primary key — @id implies uniqueness)
|
|
263
|
+
if (field.unique && field.name !== model.primary_key) {
|
|
264
|
+
attrs.push("@unique");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Native type annotation
|
|
268
|
+
const nativeType = toPrismaNativeType(field.type);
|
|
269
|
+
if (nativeType) {
|
|
270
|
+
attrs.push(nativeType);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (attrs.length > 0) {
|
|
274
|
+
parts.push(attrs.join(" "));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return parts.join(" ").trimEnd();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private mapDefaultValue(value: string, type: string): string | null {
|
|
281
|
+
if (value === "gen_random_uuid()" || value === "uuid()") return "@default(uuid())";
|
|
282
|
+
if (value === "now()") return "@default(now())";
|
|
283
|
+
if (value === "true" || value === "false") return `@default(${value})`;
|
|
284
|
+
if (/^\d+$/.test(value)) return `@default(${value})`;
|
|
285
|
+
if (/^\d+\.\d+$/.test(value)) return `@default(${value})`;
|
|
286
|
+
if (value.startsWith('"') && value.endsWith('"')) return `@default(${value})`;
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private emitJunctionModel(rel: IR.IRRelation): string {
|
|
291
|
+
const lines: string[] = [];
|
|
292
|
+
const fromCol = toSnakeCase(rel.from_entity) + "_id";
|
|
293
|
+
const toCol = toSnakeCase(rel.to_entity) + "_id";
|
|
294
|
+
|
|
295
|
+
lines.push(`model ${toPascalCase(rel.junction_table!)} {`);
|
|
296
|
+
lines.push(` ${fromCol.padEnd(14)} String @db.Uuid`);
|
|
297
|
+
lines.push(` ${toCol.padEnd(14)} String @db.Uuid`);
|
|
298
|
+
lines.push(` created_at DateTime @default(now()) @db.Timestamptz`);
|
|
299
|
+
lines.push(``);
|
|
300
|
+
lines.push(` ${toSnakeCase(rel.from_entity).padEnd(14)} ${toPascalCase(rel.from_entity)} @relation(fields: [${fromCol}], references: [id], onDelete: Cascade)`);
|
|
301
|
+
lines.push(` ${toSnakeCase(rel.to_entity).padEnd(14)} ${toPascalCase(rel.to_entity)} @relation(fields: [${toCol}], references: [id], onDelete: Cascade)`);
|
|
302
|
+
lines.push(``);
|
|
303
|
+
lines.push(` @@id([${fromCol}, ${toCol}])`);
|
|
304
|
+
lines.push(` @@index([${fromCol}])`);
|
|
305
|
+
lines.push(` @@index([${toCol}])`);
|
|
306
|
+
lines.push(` @@map("${rel.junction_table}")`);
|
|
307
|
+
lines.push(`}`);
|
|
308
|
+
return lines.join("\n");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private emitAuditLogModel(): string {
|
|
312
|
+
return `model AuditLog {
|
|
313
|
+
id String @id @default(uuid()) @db.Uuid
|
|
314
|
+
actor_id String? @db.Uuid
|
|
315
|
+
action String
|
|
316
|
+
entity_type String
|
|
317
|
+
entity_id String? @db.Uuid
|
|
318
|
+
payload Json?
|
|
319
|
+
ip String?
|
|
320
|
+
user_agent String?
|
|
321
|
+
trace_id String? @db.Uuid
|
|
322
|
+
created_at DateTime @default(now()) @db.Timestamptz
|
|
323
|
+
|
|
324
|
+
@@index([actor_id])
|
|
325
|
+
@@index([entity_type, entity_id])
|
|
326
|
+
@@index([created_at])
|
|
327
|
+
@@map("audit_log")
|
|
328
|
+
}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private emitEventOutboxModel(): string {
|
|
332
|
+
return `model EventOutbox {
|
|
333
|
+
id String @id @default(uuid()) @db.Uuid
|
|
334
|
+
event_type String
|
|
335
|
+
payload Json
|
|
336
|
+
status String @default("pending")
|
|
337
|
+
attempts Int @default(0)
|
|
338
|
+
created_at DateTime @default(now()) @db.Timestamptz
|
|
339
|
+
processed_at DateTime? @db.Timestamptz
|
|
340
|
+
error String?
|
|
341
|
+
|
|
342
|
+
@@index([status, created_at])
|
|
343
|
+
@@map("event_outbox")
|
|
344
|
+
}`;
|
|
345
|
+
}
|
|
346
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { ConstraintSolver } from "./solver";
|
|
|
16
16
|
export type { SolverResult } from "./solver";
|
|
17
17
|
export { FullEmitter } from "./emit_full";
|
|
18
18
|
export { NakamaEmitter } from "./emit_nakama";
|
|
19
|
+
export { PrismaEmitter } from "./emit_prisma";
|
|
19
20
|
export type { NakamaEmittedFile } from "./emit_nakama";
|
|
20
21
|
export type { EmittedFile } from "./emitter";
|
|
21
22
|
export { Verifier } from "./verifier";
|
package/dist/source_map.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"source_map.js","sourceRoot":"","sources":["../src/source_map.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAqBH,MAAa,gBAAgB;IAA7B;QACU,aAAQ,GAAoB,EAAE,CAAC;IA8DzC,CAAC;IA5DC,GAAG,CAAC,OAAsB;QACxB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,eAAe,CACb,aAAqB,EACrB,UAAkB,EAClB,UAAkB,EAClB,GAAgB;QAEhB,MAAM,QAAQ,GAAoB,EAAE,CAAC;QAErC,oCAAoC;QACpC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnC,QAAQ,CAAC,IAAI,CAAC;oBACZ,cAAc,EAAE,aAAa;oBAC7B,cAAc,EAAE,CAAC,EAAE,oCAAoC;oBACvD,WAAW,EAAE,UAAU;oBACvB,WAAW,EAAE,CAAC,EAAK,mCAAmC;oBACtD,aAAa,EAAE,CAAC;oBAChB,UAAU,EAAE,GAAG,CAAC,EAAE;oBAClB,WAAW,EAAE,WAAW,MAAM,CAAC,IAAI,QAAQ,GAAG,CAAC,IAAI,EAAE;iBACtD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,4CAA4C;QAC5C,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC;gBACZ,cAAc,EAAE,aAAa;gBAC7B,cAAc,EAAE,CAAC;gBACjB,WAAW,EAAE,UAAU;gBACvB,WAAW,EAAE,CAAC;gBACd,aAAa,EAAE,CAAC;gBAChB,UAAU,EAAE,GAAG,CAAC,EAAE;gBAClB,WAAW,EAAE,UAAU,KAAK,CAAC,IAAI,GAAG;aACrC,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,UAAU;YAClB,WAAW,EAAE,UAAU;YACvB,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAkB,EAAE,UAAkB;QAC1C,OAAO;YACL,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,UAAU;YAClB,WAAW,EAAE,UAAU;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;IACJ,CAAC;CACF;AA/DD,4CA+DC"}
|
package/dist/test.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AACzB,2CAA6B;AAC7B,mCAA4C;AAC5C,qCAAkC;AAClC,+CAA2C;AAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC;AAElF,SAAS,IAAI;IACX,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAEhD,gCAAgC;IAChC,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,aAAK,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,kBAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;IAED,iCAAiC;IACjC,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,aAAK,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjE,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,8BAA8B;IAC9B,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,OAAO,CAAC,MAAM,YAAY,CAAC,CAAC;QAC5D,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,IAAI,cAAc,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YAChE,OAAO,CAAC,GAAG,CAAC,uBAAuB,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACvD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACnC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,wBAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;IAED,kCAAkC;IAClC,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,eAAM,CAAC,IAAI,aAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9D,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,+BAA+B;IAC/B,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,aAAK,CAAC,EAAE,CAAC,CAAC;QACjC,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,eAAM,CAAC,WAAW,CAAC,CAAC;QAC5C,WAAW,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,wBAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,uBAAuB,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAI,aAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;QAClD,IAAI,eAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,wBAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,yHAAyH,CAAC,CAAC;IACvI,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,uHAAuH,CAAC,CAAC;AACvI,CAAC;AAED,IAAI,EAAE,CAAC"}
|
package/dist/test_typechecker.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* BoneScript Type Checker Tests
|
|
4
|
-
* Verifies the type checker catches errors per spec/04_TYPE_SYSTEM.md.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const lexer_1 = require("./lexer");
|
|
8
|
-
const parser_1 = require("./parser");
|
|
9
|
-
const typechecker_1 = require("./typechecker");
|
|
10
|
-
function compile(source) {
|
|
11
|
-
const tokens = new lexer_1.Lexer(source).tokenize();
|
|
12
|
-
const ast = new parser_1.Parser(tokens).parse();
|
|
13
|
-
const checker = new typechecker_1.TypeChecker();
|
|
14
|
-
return checker.check(ast);
|
|
15
|
-
}
|
|
16
|
-
let passed = 0;
|
|
17
|
-
let failed = 0;
|
|
18
|
-
function expect(name, source, expectedCode) {
|
|
19
|
-
const errors = compile(source);
|
|
20
|
-
if (expectedCode === null) {
|
|
21
|
-
if (errors.length === 0) {
|
|
22
|
-
console.log(` ✓ ${name}`);
|
|
23
|
-
passed++;
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
console.log(` ✗ ${name}: expected no errors, got ${errors.length}`);
|
|
27
|
-
for (const e of errors)
|
|
28
|
-
console.log(` ${e.code}: ${e.message}`);
|
|
29
|
-
failed++;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
const found = errors.find(e => e.code === expectedCode);
|
|
34
|
-
if (found) {
|
|
35
|
-
console.log(` ✓ ${name} (${expectedCode}: ${found.message})`);
|
|
36
|
-
passed++;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
console.log(` ✗ ${name}: expected ${expectedCode}, got [${errors.map(e => e.code).join(", ")}]`);
|
|
40
|
-
for (const e of errors)
|
|
41
|
-
console.log(` ${e.code}: ${e.message}`);
|
|
42
|
-
failed++;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
console.log("BoneScript Type Checker Tests\n");
|
|
47
|
-
// ─── Valid Programs ──────────────────────────────────────────────────────────
|
|
48
|
-
expect("Valid minimal system", `
|
|
49
|
-
system Minimal {
|
|
50
|
-
entity User {
|
|
51
|
-
owns: [name: string]
|
|
52
|
-
}
|
|
53
|
-
capability greet(u: User) {
|
|
54
|
-
requires: [u.name != ""]
|
|
55
|
-
effects: [u.name = "greeted"]
|
|
56
|
-
sync: eventual
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
`, null);
|
|
60
|
-
expect("Valid with set operations", `
|
|
61
|
-
system SetOps {
|
|
62
|
-
entity Bag {
|
|
63
|
-
owns: [items: set<string>]
|
|
64
|
-
}
|
|
65
|
-
capability add_item(b: Bag, item: string) {
|
|
66
|
-
requires: [b.items.size < 100]
|
|
67
|
-
effects: [b.items += item]
|
|
68
|
-
sync: transactional
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
`, null);
|
|
72
|
-
expect("Valid with numeric operations", `
|
|
73
|
-
system NumOps {
|
|
74
|
-
entity Counter {
|
|
75
|
-
owns: [value: uint]
|
|
76
|
-
constraints: [value >= 0, value <= 1000]
|
|
77
|
-
}
|
|
78
|
-
capability increment(c: Counter, amount: uint) {
|
|
79
|
-
requires: [amount > 0]
|
|
80
|
-
effects: [c.value += amount]
|
|
81
|
-
sync: eventual
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
`, null);
|
|
85
|
-
// ─── Type Errors ─────────────────────────────────────────────────────────────
|
|
86
|
-
expect("T009: Duplicate field name", `
|
|
87
|
-
system DupField {
|
|
88
|
-
entity Bad {
|
|
89
|
-
owns: [name: string, name: uint]
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
`, "T009");
|
|
93
|
-
expect("T010: Undefined state in transition", `
|
|
94
|
-
system BadState {
|
|
95
|
-
entity Thing {
|
|
96
|
-
owns: [x: uint]
|
|
97
|
-
states: active -> nonexistent
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
`, null); // states are just identifiers, both are "defined" by being in the graph
|
|
101
|
-
expect("T012: Flow with less than 2 steps", `
|
|
102
|
-
system BadFlow {
|
|
103
|
-
entity Item { owns: [x: uint] }
|
|
104
|
-
flow single_step {
|
|
105
|
-
step one: do_thing(x)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
`, "T012");
|
|
109
|
-
expect("T011: Emit undeclared event", `
|
|
110
|
-
system BadEmit {
|
|
111
|
-
entity User { owns: [name: string] }
|
|
112
|
-
capability greet(u: User) {
|
|
113
|
-
requires: [u.name != ""]
|
|
114
|
-
effects: [u.name = "hi"]
|
|
115
|
-
emits: NonExistentEvent
|
|
116
|
-
sync: eventual
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
`, "T011");
|
|
120
|
-
// ─── Summary ─────────────────────────────────────────────────────────────────
|
|
121
|
-
console.log(`\nâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•`);
|
|
122
|
-
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
123
|
-
console.log(`â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•`);
|
|
124
|
-
if (failed > 0)
|
|
125
|
-
process.exit(1);
|
|
126
|
-
//# sourceMappingURL=test_typechecker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test_typechecker.js","sourceRoot":"","sources":["../src/test_typechecker.ts"],"names":[],"mappings":"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"}
|