bonescript-compiler 0.5.1 → 0.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.
- package/dist/commands/diff.js +3 -5
- package/dist/commands/diff.js.map +1 -1
- package/dist/emit_capability.js +6 -8
- package/dist/emit_capability.js.map +1 -1
- package/dist/emit_database.js +2 -4
- package/dist/emit_database.js.map +1 -1
- package/dist/emit_deploy.js +6 -8
- package/dist/emit_deploy.js.map +1 -1
- package/dist/emit_events.js +2 -19
- package/dist/emit_events.js.map +1 -1
- package/dist/emit_extras.js +3 -5
- package/dist/emit_extras.js.map +1 -1
- package/dist/emit_full.js +9 -11
- package/dist/emit_full.js.map +1 -1
- package/dist/emit_openapi.js +2 -4
- package/dist/emit_openapi.js.map +1 -1
- package/dist/emit_package.js +2 -4
- package/dist/emit_package.js.map +1 -1
- package/dist/emit_router.js +3 -4
- package/dist/emit_router.js.map +1 -1
- package/dist/emit_tests.js +16 -18
- package/dist/emit_tests.js.map +1 -1
- package/dist/emit_websocket.js +0 -3
- package/dist/emit_websocket.js.map +1 -1
- package/dist/emitter.js +27 -48
- package/dist/emitter.js.map +1 -1
- package/dist/extension_manager.js +3 -17
- package/dist/extension_manager.js.map +1 -1
- package/dist/lowering.js +19 -0
- package/dist/lowering.js.map +1 -1
- package/dist/optimizer.js +6 -3
- package/dist/optimizer.js.map +1 -1
- package/dist/solver.js +1 -1
- package/dist/solver.js.map +1 -1
- package/dist/typechecker.d.ts +2 -0
- package/dist/typechecker.js +35 -3
- package/dist/typechecker.js.map +1 -1
- package/dist/verifier.js +2 -3
- package/dist/verifier.js.map +1 -1
- package/package.json +3 -3
- package/src/commands/compile.ts +191 -191
- package/src/commands/diff.ts +1 -4
- package/src/emit_capability.ts +1 -5
- package/src/emit_database.ts +1 -4
- package/src/emit_deploy.ts +1 -4
- package/src/emit_events.ts +1 -16
- package/src/emit_extras.ts +1 -4
- package/src/emit_full.ts +1 -4
- package/src/emit_openapi.ts +1 -4
- package/src/emit_package.ts +1 -4
- package/src/emit_router.ts +1 -2
- package/src/emit_tests.ts +1 -4
- package/src/emit_websocket.ts +1 -4
- package/src/emitter.ts +12 -28
- package/src/extension_manager.ts +1 -13
- package/src/lowering.ts +27 -1
- package/src/optimizer.ts +6 -3
- package/src/solver.ts +1 -1
- package/src/typechecker.ts +47 -4
- package/src/verifier.ts +4 -5
package/src/emitter.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* BoneScript Code Emitter — Stage 6 of the compilation pipeline.
|
|
3
3
|
* Implements spec/09_CODEGEN.md.
|
|
4
4
|
*
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as IR from "./ir";
|
|
10
|
+
import { emitCapabilityBody } from "./emit_capability";
|
|
11
|
+
import { emitPipelineBody, emitAlgorithmBody } from "./emit_composition";
|
|
12
|
+
import { toSnakeCase } from "./lowering_helpers";
|
|
13
|
+
import { toTsType } from "./emit_router";
|
|
10
14
|
|
|
11
15
|
export interface EmittedFile {
|
|
12
16
|
path: string;
|
|
@@ -41,19 +45,6 @@ const SQL_TYPE_MAP: Record<string, string> = {
|
|
|
41
45
|
json: "JSONB",
|
|
42
46
|
};
|
|
43
47
|
|
|
44
|
-
function toTsType(irType: string): string {
|
|
45
|
-
if (TS_TYPE_MAP[irType]) return TS_TYPE_MAP[irType];
|
|
46
|
-
const listMatch = irType.match(/^list<(.+)>$/);
|
|
47
|
-
if (listMatch) return `${toTsType(listMatch[1])}[]`;
|
|
48
|
-
const setMatch = irType.match(/^set<(.+)>$/);
|
|
49
|
-
if (setMatch) return `Set<${toTsType(setMatch[1])}>`;
|
|
50
|
-
const mapMatch = irType.match(/^map<(.+),\s*(.+)>$/);
|
|
51
|
-
if (mapMatch) return `Map<${toTsType(mapMatch[1])}, ${toTsType(mapMatch[2])}>`;
|
|
52
|
-
const optMatch = irType.match(/^optional<(.+)>$/);
|
|
53
|
-
if (optMatch) return `${toTsType(optMatch[1])} | null`;
|
|
54
|
-
// Entity reference or unknown
|
|
55
|
-
return irType;
|
|
56
|
-
}
|
|
57
48
|
|
|
58
49
|
function toSqlType(irType: string): string {
|
|
59
50
|
if (SQL_TYPE_MAP[irType]) return SQL_TYPE_MAP[irType];
|
|
@@ -68,9 +59,6 @@ function sqlCheckConstraint(irType: string): string {
|
|
|
68
59
|
return "";
|
|
69
60
|
}
|
|
70
61
|
|
|
71
|
-
function toSnakeCase(s: string): string {
|
|
72
|
-
return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
73
|
-
}
|
|
74
62
|
|
|
75
63
|
// ─── Emitter ─────────────────────────────────────────────────────────────────
|
|
76
64
|
|
|
@@ -167,12 +155,6 @@ export class Emitter {
|
|
|
167
155
|
// Add cardinality CHECK constraints from relations
|
|
168
156
|
// has_one: enforce at most 1 child row via a partial unique index (emitted below)
|
|
169
157
|
// has_many with explicit max: enforce via CHECK on count (done via trigger — see below)
|
|
170
|
-
for (const rel of (mod as any).relations_with_cardinality || []) {
|
|
171
|
-
if (rel.cardinality && rel.cardinality.max !== "*" && typeof rel.cardinality.max === "number") {
|
|
172
|
-
// Will be enforced via trigger — placeholder comment
|
|
173
|
-
fieldLines.push(` -- cardinality: ${rel.name} max ${rel.cardinality.max} (enforced by trigger)`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
158
|
|
|
177
159
|
lines.push(fieldLines.join(",\n"));
|
|
178
160
|
lines.push(`);`);
|
|
@@ -462,8 +444,6 @@ export class Emitter {
|
|
|
462
444
|
|
|
463
445
|
// Real implementation — delegate to emitCapabilityBody for capabilities,
|
|
464
446
|
// or generate CRUD SQL for standard methods
|
|
465
|
-
const { emitCapabilityBody } = require("./emit_capability");
|
|
466
|
-
const { emitPipelineBody, emitAlgorithmBody } = require("./emit_composition");
|
|
467
447
|
|
|
468
448
|
if (method.pipeline) {
|
|
469
449
|
lines.push(emitPipelineBody(method, " "));
|
|
@@ -473,10 +453,14 @@ export class Emitter {
|
|
|
473
453
|
// Capability with effects/preconditions — use the full capability body emitter
|
|
474
454
|
try {
|
|
475
455
|
lines.push(emitCapabilityBody(method, mod, system, " "));
|
|
476
|
-
} catch {
|
|
477
|
-
//
|
|
456
|
+
} catch (err: unknown) {
|
|
457
|
+
// Body generation failed — emit a visible compile-time warning in the generated
|
|
458
|
+
// file so the developer knows this method needs attention, rather than silently
|
|
459
|
+
// producing a broken stub.
|
|
460
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
461
|
+
lines.push(` // ⚠ COMPILER WARNING: emitCapabilityBody failed for '${method.name}': ${msg.replace(/`/g, "'")}`);
|
|
478
462
|
lines.push(` // Effects: ${method.effects.map((e: any) => e.target + " " + e.op + " " + e.value).join("; ")}`);
|
|
479
|
-
lines.push(` return { ok: false, error: { code: "
|
|
463
|
+
lines.push(` return { ok: false, error: { code: "CODEGEN_FAILED", message: "Body generation failed for '${method.name}': " + ${JSON.stringify(msg)} } };`);
|
|
480
464
|
}
|
|
481
465
|
} else {
|
|
482
466
|
// CRUD or simple method — emit a typed not-implemented stub
|
package/src/extension_manager.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import * as fs from "fs";
|
|
20
20
|
import * as path from "path";
|
|
21
21
|
import * as AST from "./ast";
|
|
22
|
+
import { toTsType } from "./emit_router";
|
|
22
23
|
|
|
23
24
|
// ─── Sentinel Helpers ────────────────────────────────────────────────────────
|
|
24
25
|
|
|
@@ -36,19 +37,6 @@ export function isStubImplementation(code: string): boolean {
|
|
|
36
37
|
|
|
37
38
|
// ─── Stub Generator ──────────────────────────────────────────────────────────
|
|
38
39
|
|
|
39
|
-
function toTsType(irType: string): string {
|
|
40
|
-
const map: Record<string, string> = {
|
|
41
|
-
string: "string", uint: "number", int: "number", float: "number",
|
|
42
|
-
bool: "boolean", timestamp: "Date", uuid: "string", bytes: "Buffer", json: "unknown",
|
|
43
|
-
};
|
|
44
|
-
if (map[irType]) return map[irType];
|
|
45
|
-
const m = irType.match(/^(list|set)<(.+)>$/);
|
|
46
|
-
if (m) return `${toTsType(m[2])}[]`;
|
|
47
|
-
const om = irType.match(/^optional<(.+)>$/);
|
|
48
|
-
if (om) return `${toTsType(om[1])} | null`;
|
|
49
|
-
return irType;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
40
|
function serializeType(t: AST.TypeExprNode): string {
|
|
53
41
|
switch (t.kind) {
|
|
54
42
|
case "PrimitiveType": return t.name;
|
package/src/lowering.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import * as AST from "./ast";
|
|
12
12
|
import * as IR from "./ir";
|
|
13
13
|
import { makeId, serializeExpr, serializeType } from "./lowering_helpers";
|
|
14
|
-
import { lowerEntity } from "./lowering_entities";
|
|
14
|
+
import { lowerEntity, lowerCapability } from "./lowering_entities";
|
|
15
15
|
import { lowerStore, lowerChannel, lowerEvent, lowerFlow } from "./lowering_channels";
|
|
16
16
|
|
|
17
17
|
export class Lowering {
|
|
@@ -53,6 +53,32 @@ export class Lowering {
|
|
|
53
53
|
modules.push(lowerEntity(this.systemName, entity, relatedCaps, stores));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// Standalone capabilities (no entity params) → utility api_service module
|
|
57
|
+
// These include algorithm capabilities, pure-function capabilities, etc.
|
|
58
|
+
const attachedCaps = new Set(
|
|
59
|
+
entities.flatMap(entity =>
|
|
60
|
+
capabilities.filter(c =>
|
|
61
|
+
c.params.some(p => p.type.kind === "EntityRefType" && p.type.name === entity.name)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
const standaloneCaps = capabilities.filter(c => !attachedCaps.has(c));
|
|
66
|
+
if (standaloneCaps.length > 0) {
|
|
67
|
+
const utilMethods = standaloneCaps.map(cap => lowerCapability(cap));
|
|
68
|
+
modules.push({
|
|
69
|
+
id: makeId(this.systemName, "api_service", "UtilityService"),
|
|
70
|
+
kind: "api_service",
|
|
71
|
+
name: "UtilityService",
|
|
72
|
+
interfaces: [{ name: "IUtilityService", methods: utilMethods }],
|
|
73
|
+
models: [],
|
|
74
|
+
events: [],
|
|
75
|
+
state_machines: [],
|
|
76
|
+
relations: [],
|
|
77
|
+
dependencies: [],
|
|
78
|
+
config: { authenticated: false, auth_method: "none" },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
56
82
|
// Channels → realtime_service modules
|
|
57
83
|
for (const channel of channels) {
|
|
58
84
|
modules.push(lowerChannel(this.systemName, channel));
|
package/src/optimizer.ts
CHANGED
|
@@ -50,14 +50,17 @@ export function optimize(system: IR.IRSystem): OptimizationResult {
|
|
|
50
50
|
function deadModuleElimination(s: IR.IRSystem, log: OptimizationLog[]): IR.IRSystem {
|
|
51
51
|
const reachable = new Set<string>();
|
|
52
52
|
|
|
53
|
-
// Seed: always
|
|
53
|
+
// Seed: entry-point kinds that are always reachable by definition.
|
|
54
|
+
// worker_service, event_bus, and cache are infrastructure modules that are
|
|
55
|
+
// only reachable if something depends on them — they are NOT seeded here so
|
|
56
|
+
// that truly orphaned infrastructure modules can be eliminated.
|
|
54
57
|
for (const m of s.modules) {
|
|
55
|
-
if (["gateway", "frontend", "auth_service", "api_service", "realtime_service", "data_store"
|
|
58
|
+
if (["gateway", "frontend", "auth_service", "api_service", "realtime_service", "data_store"].includes(m.kind)) {
|
|
56
59
|
reachable.add(m.id);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
// Propagate reachability
|
|
63
|
+
// Propagate reachability through the dependency graph
|
|
61
64
|
let changed = true;
|
|
62
65
|
while (changed) {
|
|
63
66
|
changed = false;
|
package/src/solver.ts
CHANGED
|
@@ -59,7 +59,7 @@ const DOMAIN_DEFAULTS: Record<string, DomainDefaults> = {
|
|
|
59
59
|
},
|
|
60
60
|
iot_system: {
|
|
61
61
|
auth: "apikey",
|
|
62
|
-
engine: "
|
|
62
|
+
engine: "postgresql", // dynamodb is not yet supported by the emitter (T014); use postgresql
|
|
63
63
|
session_engine: "redis",
|
|
64
64
|
sync: "eventual",
|
|
65
65
|
channel_transport: "grpc_stream",
|
package/src/typechecker.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* BoneScript Type Checker — Stage 3 of the compilation pipeline.
|
|
3
3
|
* Implements spec/04_TYPE_SYSTEM.md.
|
|
4
4
|
*
|
|
@@ -180,6 +180,7 @@ export class TypeChecker {
|
|
|
180
180
|
case "ExtensionPointDecl": this.checkExtensionPoint(decl); break;
|
|
181
181
|
case "StoreDecl": this.checkStore(decl); break;
|
|
182
182
|
case "PolicyDecl": this.checkPolicy(decl); break;
|
|
183
|
+
case "EventDecl": this.checkEvent(decl); break;
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -524,8 +525,22 @@ export class TypeChecker {
|
|
|
524
525
|
if (condType && !this.isBoolish(condType)) {
|
|
525
526
|
this.addError("T005", "Ternary condition must be bool", expr.loc);
|
|
526
527
|
}
|
|
527
|
-
//
|
|
528
|
-
|
|
528
|
+
// Both branches must have compatible types
|
|
529
|
+
const consequentType = this.inferExprType(expr.consequent, ctx);
|
|
530
|
+
const alternateType = this.inferExprType(expr.alternate, ctx);
|
|
531
|
+
if (
|
|
532
|
+
consequentType && alternateType &&
|
|
533
|
+
!typeEquals(consequentType, alternateType) &&
|
|
534
|
+
!this.isAssignable(consequentType, alternateType) &&
|
|
535
|
+
!this.isAssignable(alternateType, consequentType)
|
|
536
|
+
) {
|
|
537
|
+
this.addError(
|
|
538
|
+
"T017",
|
|
539
|
+
`Ternary branches have incompatible types: consequent is ${typeToString(consequentType)}, alternate is ${typeToString(alternateType)}`,
|
|
540
|
+
expr.loc,
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
return consequentType;
|
|
529
544
|
}
|
|
530
545
|
|
|
531
546
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
@@ -635,10 +650,38 @@ export class TypeChecker {
|
|
|
635
650
|
}
|
|
636
651
|
}
|
|
637
652
|
|
|
653
|
+
// ─── Event Checking ───────────────────────────────────────────────────────────
|
|
638
654
|
|
|
639
|
-
|
|
655
|
+
private static readonly VALID_DELIVERY_MODES = new Set(["at_least_once", "at_most_once", "exactly_once"]);
|
|
656
|
+
|
|
657
|
+
private checkEvent(decl: AST.EventDeclNode): void {
|
|
658
|
+
// Check payload field types resolve and have no duplicates
|
|
659
|
+
const seen = new Set<string>();
|
|
660
|
+
for (const field of decl.payload) {
|
|
661
|
+
if (seen.has(field.name)) {
|
|
662
|
+
this.addError("T009", `Duplicate field name '${field.name}' in event '${decl.name}'`, field.loc);
|
|
663
|
+
}
|
|
664
|
+
seen.add(field.name);
|
|
665
|
+
|
|
666
|
+
const resolved = this.resolveTypeExpr(field.type);
|
|
667
|
+
if (!resolved) {
|
|
668
|
+
this.addError("T001", `Event '${decl.name}' payload field '${field.name}' references undefined type`, field.loc);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Check delivery mode is valid
|
|
673
|
+
if (decl.delivery && !TypeChecker.VALID_DELIVERY_MODES.has(decl.delivery)) {
|
|
674
|
+
this.addError(
|
|
675
|
+
"T016",
|
|
676
|
+
`Event '${decl.name}' has invalid delivery mode '${decl.delivery}'. ` +
|
|
677
|
+
`Valid values: ${[...TypeChecker.VALID_DELIVERY_MODES].join(", ")}.`,
|
|
678
|
+
decl.loc,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
640
682
|
// ─── Type Context ────────────────────────────────────────────────────────────
|
|
641
683
|
|
|
684
|
+
}
|
|
642
685
|
class TypeContext {
|
|
643
686
|
private locals: Map<string, CVType>;
|
|
644
687
|
private symbols: SymbolTable;
|
package/src/verifier.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* BoneScript Verifier — Stage 7 of the compilation pipeline.
|
|
3
3
|
* Implements spec/07_IR_SPEC.md §5 (IR Validation Rules).
|
|
4
4
|
*
|
|
@@ -197,7 +197,7 @@ export class Verifier {
|
|
|
197
197
|
const visited = new Set<string>();
|
|
198
198
|
const inStack = new Set<string>();
|
|
199
199
|
|
|
200
|
-
const dfs = (node: string, path: string[]):
|
|
200
|
+
const dfs = (node: string, path: string[]): void => {
|
|
201
201
|
if (inStack.has(node)) {
|
|
202
202
|
const cycle = [...path.slice(path.indexOf(node)), node];
|
|
203
203
|
const names = cycle.map(id => system.modules.find(m => m.id === id)?.name || id);
|
|
@@ -207,9 +207,9 @@ export class Verifier {
|
|
|
207
207
|
message: `Circular dependency: ${names.join(" → ")}`,
|
|
208
208
|
location: node,
|
|
209
209
|
});
|
|
210
|
-
return
|
|
210
|
+
return;
|
|
211
211
|
}
|
|
212
|
-
if (visited.has(node)) return
|
|
212
|
+
if (visited.has(node)) return;
|
|
213
213
|
|
|
214
214
|
visited.add(node);
|
|
215
215
|
inStack.add(node);
|
|
@@ -221,7 +221,6 @@ export class Verifier {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
inStack.delete(node);
|
|
224
|
-
return false;
|
|
225
224
|
};
|
|
226
225
|
|
|
227
226
|
for (const [id] of graph) {
|