Files
quixos-protocol/src/capability-language/parser.ts
T

2873 lines
104 KiB
TypeScript

import {
BaseErrorListener,
CharStream,
CommonTokenStream,
type ATNSimulator,
type ParserRuleContext,
type RecognitionException,
type Recognizer,
type Token,
} from "antlr4ng";
import {
capabilityId,
cardinalityValueType,
compileWorkspaceRevision,
valueType,
type ExecutionContract,
type AtomId,
type BoundDependency,
type CompiledWorkspaceRevision,
type DependencyPort,
type EdgeCardinality,
type EdgeDefinition,
type EdgeEndpoint,
type EdgeEndpointConstraint,
type EdgePrimitive,
type EdgeTraversal,
type InterfaceMember,
type InterfaceOperation,
type InterfaceOperationMode,
type InterfaceRevision,
type AtomDefinition,
type PackageExport,
type PackageReceiverRequirement,
type PackageRevision,
type PersistentAttachment,
type SourceRevision,
type StatePrimitive,
type StateSlotDefinition,
type ValueType,
type WorkspaceRevision,
GenericTypeError,
TypeSubstitution,
type ValueTypeExpression,
type ObjectTypeExpression,
type GenericPackageExport,
type GenericDependencyPort,
specializePackageExport,
} from "../capability-model/index.js";
import { GenericSourceTypes } from "./generic-types.js";
import {
defaultQueryBudgets,
type QueryDeclaration,
type QueryBudgets,
type QueryUse,
type QueryTemplate,
} from "../query/types.js";
import { specializeQueryTemplate } from "../query/templates.js";
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
import {
QuixosCapabilityParser,
type ExecutionClauseContext,
type AttachmentDeclContext,
type ConformanceDeclContext,
type DependencyBindingBlockContext,
type DependencyPortContext,
type DocumentContext,
type EdgeDeclContext,
type EdgeEndpointContext,
type ExternalAtomDeclContext,
type ExternalInterfaceDeclContext,
type InterfaceResourceDeclContext,
type PackageConstructorExportContext,
type PackageResourceDeclContext,
type PackageFunctionExportContext,
type PackageOperationExportContext,
type OperationMemberContext,
type ResourceImportDeclContext,
type ResourcePreambleContext,
type StateDeclContext,
type TargetConstraintContext,
type ValueMemberContext,
type ValueTypeContext,
type RelationshipMemberContext,
type RelationshipMaterializationDeclContext,
type WorkspaceDeclContext,
} from "./generated/QuixosCapabilityParser.js";
export type CapabilityResourceKind = "interface" | "package";
const genericLocations = new WeakMap<GenericTypeError, { line: number; column: number }>();
const atGenericSource = <T>(context: ParserRuleContext, lower: () => T): T => {
try {
return lower();
} catch (error) {
if (error instanceof GenericTypeError && !genericLocations.has(error))
genericLocations.set(error, { line: context.start?.line ?? 0, column: context.start?.column ?? 0 });
throw error;
}
};
export type CapabilityResourceImport = {
kind: CapabilityResourceKind;
binding: string;
};
export type CapabilityExternalInterface = {
binding: string;
revisionId: InterfaceRevision["revisionId"];
};
export type CapabilityImportEnvironment = {
interfaces?: ReadonlyMap<string, InterfaceRevision>;
packages?: ReadonlyMap<string, PackageRevision>;
/** Complete resolved closure; direct maps above provide local authoring names. */
interfaceClosure?: readonly InterfaceRevision[];
packageClosure?: readonly PackageRevision[];
/** External nominal atoms required by imported resources. */
externalAtoms?: readonly AtomDefinition[];
/** External nominal interfaces supplied by the importing workspace. */
externalInterfaces?: readonly CapabilityExternalInterface[];
};
export type CapabilityResource =
| {
kind: "interface";
imports: CapabilityResourceImport[];
externalAtoms: AtomDefinition[];
externalInterfaces: CapabilityExternalInterface[];
revision: InterfaceRevision;
specializations?: InterfaceRevision[];
}
| {
kind: "package";
imports: CapabilityResourceImport[];
externalAtoms: AtomDefinition[];
externalInterfaces: CapabilityExternalInterface[];
revision: PackageRevision;
specializations?: InterfaceRevision[];
};
export type CapabilityResourceCompileResult =
| { ok: true; resource: CapabilityResource; diagnostics: [] }
| { ok: false; diagnostics: CapabilitySourceDiagnostic[] };
export type CapabilitySourceDiagnosticPhase = "syntax" | "lowering" | "validation";
export interface CapabilitySourceDiagnostic {
phase: CapabilitySourceDiagnosticPhase;
code: string;
message: string;
fileName: string;
line: number;
column: number;
path?: string;
}
export type CapabilitySourceCompileResult =
| {
ok: true;
workspace: WorkspaceRevision;
plan: CompiledWorkspaceRevision;
imports: CapabilityResourceImport[];
diagnostics: [];
}
| {
ok: false;
diagnostics: CapabilitySourceDiagnostic[];
};
interface InterfaceSymbol {
definition?: InterfaceRevision;
revisionId: InterfaceRevision["revisionId"];
contractAvailable: boolean;
members: Map<
string,
{
memberId: InterfaceMember["id"];
operations: Map<string, InterfaceOperation["id"]>;
}
>;
}
interface PackageExportSymbol {
exportId: PackageExport["id"];
ports: Map<string, DependencyPort["id"]>;
}
interface PackageSymbol {
queries?: Map<string, string>;
definition?: PackageRevision;
revisionId: PackageRevision["revisionId"];
exports: Map<string, PackageExportSymbol>;
}
interface AttachmentSymbol {
attachment: PersistentAttachment;
projections: Map<string, EdgeEndpoint["projectionId"]>;
}
interface LoweringState {
types: GenericSourceTypes;
fileName: string;
diagnostics: CapabilitySourceDiagnostic[];
atoms: Map<string, AtomId>;
interfaces: Map<string, InterfaceSymbol>;
packages: Map<string, PackageSymbol>;
attachments: Map<string, AttachmentSymbol>;
}
class SyntaxErrorListener extends BaseErrorListener {
constructor(
private readonly fileName: string,
private readonly diagnostics: CapabilitySourceDiagnostic[],
) {
super();
}
override syntaxError<S extends Token, T extends ATNSimulator>(
_recognizer: Recognizer<T>,
_offendingSymbol: S | null,
line: number,
column: number,
message: string,
_error: RecognitionException | null,
) {
this.diagnostics.push({
phase: "syntax",
code: "syntax-error",
message,
fileName: this.fileName,
line,
column,
});
}
}
const contextPosition = (context: ParserRuleContext) => ({
line: context.start?.line ?? 0,
column: context.start?.column ?? 0,
});
const loweringIssue = (state: LoweringState, context: ParserRuleContext, code: string, message: string) => {
state.diagnostics.push({
phase: "lowering",
code,
message,
fileName: state.fileName,
...contextPosition(context),
});
};
const text = (context: { getText(): string } | null): string => {
if (!context) {
throw new Error("ANTLR returned a missing context after a successful parse");
}
return context.getText();
};
const identifier = text;
const stringValue = (context: { getText(): string } | null) => JSON.parse(text(context)) as string;
const declareSymbol = <Value>(
state: LoweringState,
table: Map<string, Value>,
name: string,
value: Value,
context: ParserRuleContext,
kind: string,
) => {
if (table.has(name)) {
loweringIssue(state, context, "duplicate-symbol", `Duplicate ${kind} authoring name ${name}`);
return;
}
table.set(name, value);
};
const requireSymbol = <Value>(
state: LoweringState,
table: ReadonlyMap<string, Value>,
name: string,
context: ParserRuleContext,
kind: string,
): Value | undefined => {
const value = table.get(name);
if (value === undefined) {
loweringIssue(state, context, "unknown-symbol", `Unknown ${kind} ${name}`);
}
return value;
};
const lowerCardinality = (context: { getText(): string } | null): EdgeCardinality => text(context) as EdgeCardinality;
const lowerConstraint = (
state: LoweringState,
context: TargetConstraintContext | null,
): EdgeEndpointConstraint | undefined => {
if (!context) {
throw new Error("Missing target constraint after a successful parse");
}
const name = identifier(context.identifier());
if (context.OBJECT() || context.typeArguments() || name === "Self" || state.types.interfaces.get(name)?.template)
return state.types.target(context);
if (context.ATOM()) {
const atomId = requireSymbol(state, state.atoms, name, context, "atom");
return atomId ? { kind: "atom", atomId } : undefined;
}
const symbol = requireSymbol(state, state.interfaces, name, context, "interface");
return symbol ? { kind: "interface", interfaceRevisionId: symbol.revisionId } : undefined;
};
const lowerValueType = (state: LoweringState, context: ValueTypeContext | null): ValueType => {
if (!context) {
throw new Error("Missing value type after a successful parse");
}
if (
context.REF() ||
context.typeArguments() ||
(context.identifier() && !context.ATOM_REF() && !context.INTERFACE_REF())
)
return state.types.value(context);
if (context.INTERFACE_REF() && state.types.interfaces.get(context.identifier()!.getText())?.template)
return state.types.value(context);
if ((context.ATOM_REF() || context.INTERFACE_REF()) && context.identifier()?.getText() === "Self")
return state.types.value(context);
const scalar = context.scalarType();
if (scalar) {
return { kind: "scalar", name: text(scalar) as never };
}
if (context.UNIT()) {
return valueType.unit;
}
if (context.WATCH_HANDLE()) {
return valueType.watchHandle;
}
if (context.MESSAGE()) {
return valueType.message(stringValue(context.stringLiteral()));
}
if (context.RECORD()) {
const fields = context
.recordField()
.map((field) => [identifier(field.identifier()), lowerValueType(state, field.valueType())] as const);
if (new Set(fields.map(([name]) => name)).size !== fields.length)
loweringIssue(state, context, "invalid-type", "Duplicate record field");
return { kind: "record", fields: Object.fromEntries(fields) };
}
if (context.ATOM_REF()) {
const name = identifier(context.identifier());
const atomId = requireSymbol(state, state.atoms, name, context, "atom");
return valueType.atomRef(atomId ?? capabilityId.atom(`unresolved:${name}`));
}
if (context.INTERFACE_REF()) {
const name = identifier(context.identifier());
const symbol = requireSymbol(state, state.interfaces, name, context, "interface");
return valueType.interfaceRef(symbol?.revisionId ?? capabilityId.interfaceRevision(`unresolved:${name}`));
}
if (context.OPTIONAL()) {
return valueType.optional(lowerValueType(state, context.valueType()));
}
if (context.LIST()) {
return valueType.list(lowerValueType(state, context.valueType()));
}
loweringIssue(state, context, "invalid-type", "Unrecognized value type");
return valueType.unit;
};
const lowerSelectedOperations = (
state: LoweringState,
context: DependencyPortContext,
target: InterfaceSymbol | undefined,
): { operations?: InterfaceOperation["id"][] } => {
const selected = context.selectedOperations();
if (!selected) return {};
const operations = selected.memberOperationRef().flatMap((reference) => {
const name = identifier(reference.identifier());
const member = target && requireSymbol(state, target.members, name, reference, "interface member");
const operation =
member &&
requireSymbol(state, member.operations, text(reference.operationName()), reference, "interface operation");
return operation ? [operation] : [];
});
if (new Set(operations).size !== operations.length)
loweringIssue(state, context, "duplicate-operation-selection", "Interface operation selection contains duplicates");
return { operations };
};
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => {
if (!context) return {};
return {
execution: context.REMOTE()
? { kind: "remote" }
: {
kind: context.SNAPSHOT() ? "snapshot" : "transaction",
targets: context.identifierList()!.identifier().map(identifier),
},
};
};
const valueOperation = (displayName: string, id: InterfaceOperation["id"], value: ValueType): InterfaceOperation => {
switch (displayName) {
case "get":
return {
id,
displayName,
inputType: valueType.unit,
outputType: value,
mode: "call",
};
case "edit":
return { id, displayName, inputType: { kind: "change", value }, outputType: valueType.unit, mode: "call" };
case "set":
return {
id,
displayName,
inputType: value,
outputType: valueType.unit,
mode: "call",
};
case "watch-start":
return {
id,
displayName,
inputType: valueType.unit,
outputType: valueType.watchHandle,
eventType: value,
mode: "watch-start",
};
case "watch-stop":
return {
id,
displayName,
inputType: valueType.watchHandle,
outputType: valueType.unit,
mode: "watch-stop",
};
default:
throw new Error(`Unknown value operation ${displayName}`);
}
};
const relationshipOperation = (
displayName: string,
id: InterfaceOperation["id"],
target: EdgeEndpointConstraint,
cardinality: EdgeCardinality,
): InterfaceOperation => {
const targetType =
target.kind === "atom" ? valueType.atomRef(target.atomId) : valueType.interfaceRef(target.interfaceRevisionId);
const resolvedType = cardinalityValueType(target, cardinality);
switch (displayName) {
case "resolve":
return {
id,
displayName,
inputType: valueType.unit,
outputType: resolvedType,
mode: "call",
};
case "connect":
case "disconnect":
return {
id,
displayName,
inputType: targetType,
outputType: valueType.unit,
mode: "call",
};
case "watch-start":
return {
id,
displayName,
inputType: valueType.unit,
outputType: valueType.watchHandle,
eventType: resolvedType,
mode: "watch-start",
};
case "watch-stop":
return {
id,
displayName,
inputType: valueType.watchHandle,
outputType: valueType.unit,
mode: "watch-stop",
};
default:
throw new Error(`Unknown relationship operation ${displayName}`);
}
};
const lowerValueMember = (state: LoweringState, context: ValueMemberContext): InterfaceMember => {
const memberName = identifier(context.identifier());
const memberValueType = lowerValueType(state, context.valueType());
const operations: InterfaceOperation[] = [];
for (const operation of context.valueMemberOperation()) {
if (operation.GET()) {
operations.push({
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
});
} else if (operation.EDIT()) {
operations.push({
...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
});
} else if (operation.SET()) {
operations.push({
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
});
} else {
operations.push(
valueOperation("watch-start", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
valueOperation("watch-stop", capabilityId.operation(stringValue(operation.stringLiteral(1))), memberValueType),
);
}
}
return {
kind: "value",
...(context.queryReadContract()
? {
queryRead: {
execution: context.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: context.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
id: capabilityId.member(stringValue(context.stringLiteral())),
displayName: memberName,
valueType: memberValueType,
operations,
};
};
const lowerRelationshipMember = (
state: LoweringState,
context: RelationshipMemberContext,
): InterfaceMember | undefined => {
const target = lowerConstraint(state, context.targetConstraint());
if (!target) {
return undefined;
}
const cardinality = lowerCardinality(context.cardinality());
const operations: InterfaceOperation[] = [];
for (const operation of context.relationshipOperation()) {
if (operation.RESOLVE()) {
operations.push(
relationshipOperation(
"resolve",
capabilityId.operation(stringValue(operation.stringLiteral(0))),
target,
cardinality,
),
);
} else if (operation.CONNECT()) {
operations.push(
relationshipOperation(
"connect",
capabilityId.operation(stringValue(operation.stringLiteral(0))),
target,
cardinality,
),
);
} else if (operation.DISCONNECT()) {
operations.push(
relationshipOperation(
"disconnect",
capabilityId.operation(stringValue(operation.stringLiteral(0))),
target,
cardinality,
),
);
} else {
operations.push(
relationshipOperation(
"watch-start",
capabilityId.operation(stringValue(operation.stringLiteral(0))),
target,
cardinality,
),
relationshipOperation(
"watch-stop",
capabilityId.operation(stringValue(operation.stringLiteral(1))),
target,
cardinality,
),
);
}
if (operation.executionClause())
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause()));
}
return {
kind: "relationship",
...(context.KEYED() ? { keyType: stringValue(context.stringLiteral(1)) as "string" | "boolean" | "int64" } : {}),
...(context.queryReadContract()
? {
queryRead: {
execution: context.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: context.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
id: capabilityId.member(stringValue(context.stringLiteral(0))),
displayName: identifier(context.identifier()),
target,
cardinality,
ordered: Boolean(context.ORDERED()),
operations,
};
};
const lowerOperationMember = (state: LoweringState, context: OperationMemberContext): InterfaceMember => {
const inputType = lowerValueType(state, context.valueType(0));
const outputType = lowerValueType(state, context.valueType(1));
return {
kind: "operation",
id: capabilityId.member(stringValue(context.stringLiteral(0))),
displayName: identifier(context.identifier()),
inputType,
outputType,
operations: [
{
id: capabilityId.operation(stringValue(context.stringLiteral(1))),
displayName: "call",
inputType,
outputType,
mode: "call",
...(context.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(context.executionClause()),
},
],
};
};
const lowerInterface = (
state: LoweringState,
context: InterfaceResourceDeclContext,
source: SourceRevision,
): InterfaceRevision => {
const alias = identifier(context.identifier());
const members = context.interfaceMember().flatMap((entry) => {
const value = entry.valueMember();
if (value) {
return [lowerValueMember(state, value)];
}
const operation = entry.operationMember();
if (operation) {
return [lowerOperationMember(state, operation)];
}
const relationship = lowerRelationshipMember(state, entry.relationshipMember()!);
return relationship ? [relationship] : [];
});
const symbol = state.interfaces.get(alias)!;
for (const member of members) {
declareSymbol(
state,
symbol.members,
member.displayName,
{
memberId: member.id,
operations: new Map(member.operations.map((operation) => [operation.displayName, operation.id])),
},
context,
"interface member",
);
}
return {
interfaceId: capabilityId.interface(stringValue(context.stringLiteral(0))),
revisionId: symbol.revisionId,
displayName: alias,
source,
members,
...(context.interfaceType().length
? {
requiredInterfaces: context
.interfaceType()
.map((entry) => new TypeSubstitution(state.types.environment()).application(state.types.interface(entry))),
}
: {}),
};
};
const hasSelfIdentifier = (context: ParserRuleContext, state: LoweringState): boolean => {
if (context.ruleIndex === QuixosCapabilityParser.RULE_identifier && context.getText() === "Self") return true;
if (context.ruleIndex === QuixosCapabilityParser.RULE_valueType) {
const aliasName = context.children
.find((child) => "ruleIndex" in child && child.ruleIndex === QuixosCapabilityParser.RULE_identifier)
?.getText();
const seen = new Set<string>();
const containsSelf = (value: unknown): boolean => {
if (!value || typeof value !== "object") return false;
if ("kind" in value && value.kind === "self") return true;
if ("kind" in value && value.kind === "alias" && "definitionId" in value) {
const id = String(value.definitionId);
if (!seen.has(id)) {
seen.add(id);
if (containsSelf(state.types.aliases.get(id)?.body)) return true;
}
}
return Object.values(value).some(containsSelf);
};
if (aliasName && containsSelf(state.types.aliases.get(aliasName)?.body)) return true;
}
if (
context.ruleIndex === QuixosCapabilityParser.RULE_interfaceType ||
((context.ruleIndex === QuixosCapabilityParser.RULE_valueType ||
context.ruleIndex === QuixosCapabilityParser.RULE_targetConstraint) &&
["interface", "interface-ref"].includes(context.getChild(0)?.getText() ?? ""))
) {
const name = context.children
.find((child) => "ruleIndex" in child && child.ruleIndex === QuixosCapabilityParser.RULE_identifier)
?.getText();
if (name && state.types.interfaces.get(name)?.template?.usesSelf) return true;
}
return context.children.some((child) => "ruleIndex" in child && hasSelfIdentifier(child as ParserRuleContext, state));
};
const lowerInterfaceTemplate = (
state: LoweringState,
context: InterfaceResourceDeclContext,
source: SourceRevision,
): InterfaceRevision => {
const types = state.types;
const revisionId = capabilityId.interfaceRevision(stringValue(context.stringLiteral(1)));
const parameters = types.declareParameters(context.typeParameters(), JSON.stringify(["interface", revisionId]));
const signature = (
name: string,
id: InterfaceOperation["id"],
input: ValueTypeExpression,
output: ValueTypeExpression,
mode: InterfaceOperationMode = "call",
eventType?: ValueTypeExpression,
): InterfaceOperation<ValueTypeExpression> => ({
id,
displayName: name,
inputType: input,
outputType: output,
mode,
...(eventType ? { eventType } : {}),
});
const members: InterfaceMember<ValueTypeExpression, ObjectTypeExpression>[] = context
.interfaceMember()
.map((entry) => {
const value = entry.valueMember();
if (value) {
const type = types.expression(value.valueType());
return {
kind: "value",
...(value.queryReadContract()
? {
queryRead: {
execution: value.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: value.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
id: capabilityId.member(stringValue(value.stringLiteral())),
displayName: identifier(value.identifier()),
valueType: type,
operations: value.valueMemberOperation().flatMap((operation) => {
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.GET())
return [
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
];
if (operation.EDIT())
return [
{
...signature("edit", id, { kind: "change", value: type }, valueType.unit),
...lowerExecution(operation.executionClause()),
},
];
if (operation.SET())
return [
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
];
return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
signature(
"watch-stop",
capabilityId.operation(stringValue(operation.stringLiteral(1))),
valueType.watchHandle,
valueType.unit,
"watch-stop",
),
];
}),
};
}
const operation = entry.operationMember();
if (operation) {
const inputType = types.expression(operation.valueType(0)!);
const outputType = types.expression(operation.valueType(1)!);
return {
kind: "operation",
id: capabilityId.member(stringValue(operation.stringLiteral(0))),
displayName: identifier(operation.identifier()),
inputType,
outputType,
operations: [
{
...signature(
"call",
capabilityId.operation(stringValue(operation.stringLiteral(1))),
inputType,
outputType,
),
...(operation.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(operation.executionClause()),
},
],
};
}
const relationship = entry.relationshipMember()!;
const target = types.targetExpression(relationship.targetConstraint());
const targetType: ValueTypeExpression = { kind: "object-ref", expectation: target };
const cardinality = lowerCardinality(relationship.cardinality());
const resolved: ValueTypeExpression =
cardinality === "exactly-one"
? targetType
: { kind: cardinality === "optional-one" ? "optional" : "list", value: targetType };
return {
kind: "relationship",
...(relationship.KEYED()
? { keyType: stringValue(relationship.stringLiteral(1)) as "string" | "boolean" | "int64" }
: {}),
...(relationship.queryReadContract()
? {
queryRead: {
execution: relationship.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: relationship.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
id: capabilityId.member(stringValue(relationship.stringLiteral(0))),
displayName: identifier(relationship.identifier()),
target,
cardinality,
ordered: Boolean(relationship.ORDERED()),
operations: relationship.relationshipOperation().flatMap((operation) => {
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.RESOLVE())
return [
{ ...signature("resolve", id, valueType.unit, resolved), ...lowerExecution(operation.executionClause()) },
];
if (operation.CONNECT() || operation.DISCONNECT())
return [
{
...signature(operation.CONNECT() ? "connect" : "disconnect", id, targetType, valueType.unit),
...lowerExecution(operation.executionClause()),
},
];
return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", resolved),
signature(
"watch-stop",
capabilityId.operation(stringValue(operation.stringLiteral(1))),
valueType.watchHandle,
valueType.unit,
"watch-stop",
),
];
}),
};
});
const requires = context.interfaceType().map((requirement) => types.interface(requirement));
const memberIds = new Set<string>();
const memberNames = new Set<string>();
const operationIds = new Set<string>();
for (const member of members) {
if (!member.id || memberIds.has(member.id) || memberNames.has(member.displayName))
throw new GenericTypeError(
"duplicate-interface-member",
revisionId,
`Invalid or duplicate member ${member.displayName}`,
);
memberIds.add(member.id);
memberNames.add(member.displayName);
const operationNames = new Set<string>();
for (const operation of member.operations) {
if (!operation.id || operationIds.has(operation.id) || operationNames.has(operation.displayName))
throw new GenericTypeError(
"duplicate-interface-operation",
revisionId,
`Invalid or duplicate operation ${member.displayName}.${operation.displayName}`,
);
operationIds.add(operation.id);
operationNames.add(operation.displayName);
}
}
const hasSelf = (value: unknown): boolean =>
value !== null &&
typeof value === "object" &&
(("kind" in value && value.kind === "self") || Object.values(value).some(hasSelf));
const aliases = [...types.aliases.values()];
types.parameters = new Map();
return {
interfaceId: capabilityId.interface(stringValue(context.stringLiteral(0))),
revisionId,
displayName: identifier(context.identifier()),
source,
members: [],
template: { parameters, members, requires, aliases, usesSelf: hasSelf([parameters, members, requires, aliases]) },
};
};
const lowerQueryReference = (
state: LoweringState,
packageName: string,
queryName: string,
context: ParserRuleContext,
) => {
const pkg = requireSymbol(state, state.packages, packageName, context, "package");
const queryId =
pkg?.queries?.get(queryName) ?? pkg?.definition?.queries?.find((query) => query.displayName === queryName)?.id;
if (!pkg || !queryId) {
loweringIssue(state, context, "unknown-query", `Unknown query export ${packageName}.${queryName}`);
return undefined;
}
return { packageRevisionId: pkg.revisionId, queryId };
};
const lowerDependencyPort = (state: LoweringState, context: DependencyPortContext): DependencyPort | undefined => {
const name = identifier(context.identifier(0));
const id = capabilityId.dependencyPort(stringValue(context.stringLiteral()));
if (context.REFLECTION())
return {
id,
displayName: name,
requirement: { kind: "reflection", selectors: context.identifierList()!.identifier().map(identifier) },
};
if (context.QUERY()) {
const query = lowerQueryReference(
state,
identifier(context.identifier(1)),
identifier(context.identifier(2)),
context,
);
return query ? { id, displayName: name, requirement: { kind: "query", ...query } } : undefined;
}
if (context.STATE()) {
const primitives = context
.primitiveList()!
.primitive()
.map((entry) => text(entry));
const invalid = primitives.filter(
(entry) => !["read", "write", "edit", "watch-start", "watch-stop"].includes(entry),
);
if (invalid.length > 0) {
loweringIssue(state, context, "invalid-port-primitive", `State port cannot request ${invalid.join(", ")}`);
}
return {
id,
displayName: name,
requirement: {
kind: "state",
valueType: lowerValueType(state, context.valueType()),
primitives: primitives.filter((entry) => !invalid.includes(entry)) as StatePrimitive[],
},
};
}
if (context.EDGE()) {
const target = lowerConstraint(state, context.targetConstraint());
if (!target) {
return undefined;
}
const primitives = context
.primitiveList()!
.primitive()
.map((entry) => text(entry));
const invalid = primitives.filter(
(entry) => !["resolve", "connect", "disconnect", "watch-start", "watch-stop"].includes(entry),
);
if (invalid.length > 0) {
loweringIssue(state, context, "invalid-port-primitive", `Edge port cannot request ${invalid.join(", ")}`);
}
return {
id,
displayName: name,
requirement: {
kind: "edge",
target,
cardinality: lowerCardinality(context.cardinality()),
primitives: primitives.filter((entry) => !invalid.includes(entry)) as EdgePrimitive[],
},
};
}
const targetName = identifier(context.identifier(1));
if (context.INTERFACE()) {
let target = requireSymbol(state, state.interfaces, targetName, context, "interface");
if (target && (context.typeArguments() || target.definition?.template)) {
const expression = state.types.interfaceByName(targetName, context.typeArguments());
const id = new TypeSubstitution(state.types.environment()).application(expression);
target = interfaceSymbolFor(state.types.definitions.get(id)!);
}
if (target && !target.contractAvailable) {
loweringIssue(
state,
context,
"nominal-interface-port",
`Interface port ${name} needs the full ${targetName} contract; use import interface ${targetName}`,
);
return undefined;
}
return target
? {
id,
displayName: name,
requirement: {
kind: "interface",
interfaceRevisionId: target.revisionId,
...lowerSelectedOperations(state, context, target),
...(context.FROM() ? { receiver: "input" as const } : {}),
},
}
: undefined;
}
const atomId = requireSymbol(state, state.atoms, targetName, context, "atom");
if (context.CREATION())
return atomId
? {
id,
displayName: name,
requirement: { kind: "creation", atomId, inputType: lowerValueType(state, context.valueType()) },
}
: undefined;
return atomId
? {
id,
displayName: name,
requirement: {
kind: "constructor",
atomId,
...(context.valueType() ? { inputType: lowerValueType(state, context.valueType()) } : {}),
},
}
: undefined;
};
const lowerDependencyPorts = (
state: LoweringState,
context: { dependencyBlock(): ReturnType<PackageOperationExportContext["dependencyBlock"]> },
) => {
const block = context.dependencyBlock();
if (!block) {
return [];
}
return block.dependencyPort().flatMap((entry) => {
const port = lowerDependencyPort(state, entry);
return port ? [port] : [];
});
};
const lowerReceiver = (
state: LoweringState,
context: PackageOperationExportContext["receiverRequirement"] extends () => infer Result ? Result : never,
): PackageReceiverRequirement => {
if (context.OBJECT())
throw new GenericTypeError(
"unbound-parameter",
context.getText(),
"An object-parameter receiver requires a generic operation declaration",
);
if (context.ANY()) {
return { kind: "any-object" };
}
if (context.ATOM()) {
const name = identifier(context.identifier());
return {
kind: "exact-atom",
atomId: requireSymbol(state, state.atoms, name, context, "atom") ?? capabilityId.atom(`unresolved:${name}`),
};
}
return {
kind: "all-interfaces",
interfaceRevisionIds: context.interfaceType().map((entry) => {
const name = identifier(entry.identifier());
if (entry.typeArguments() || state.types.interfaces.get(name)?.template)
return new TypeSubstitution(state.types.environment()).application(state.types.interface(entry));
return (
requireSymbol(state, state.interfaces, name, entry, "interface")?.revisionId ??
capabilityId.interfaceRevision(`unresolved:${name}`)
);
}),
};
};
const registerPackageExport = (
state: LoweringState,
packageAlias: string,
exportAlias: string,
entry: PackageExport,
context: ParserRuleContext,
) => {
const packageSymbol = state.packages.get(packageAlias)!;
const portSymbols = new Map<string, DependencyPort["id"]>();
for (const port of entry.dependencyPorts) {
declareSymbol(state, portSymbols, port.displayName, port.id, context, "dependency port");
}
declareSymbol(
state,
packageSymbol.exports,
exportAlias,
{ exportId: entry.id, ports: portSymbols },
context,
"package export",
);
};
const lowerPackageOperation = (
state: LoweringState,
packageAlias: string,
context: PackageOperationExportContext,
): PackageExport => {
const alias = identifier(context.identifier());
const ports = lowerDependencyPorts(state, context);
const event = context.eventClause();
const entry: PackageExport = {
kind: "operation",
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias,
inputType: lowerValueType(state, context.valueType(0)),
outputType: lowerValueType(state, context.valueType(1)),
mode: text(context.operationMode()) as InterfaceOperationMode,
...lowerExecution(context.executionClause()),
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
dependencyPorts: ports,
};
registerPackageExport(state, packageAlias, alias, entry, context);
return entry;
};
const lowerPackageFunction = (
state: LoweringState,
packageAlias: string,
context: PackageFunctionExportContext,
): PackageExport => {
const alias = identifier(context.identifier());
const entry: PackageExport = {
kind: "function",
...lowerExecution(context.executionClause()),
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias,
inputType: lowerValueType(state, context.valueType(0)),
outputType: lowerValueType(state, context.valueType(1)),
dependencyPorts: lowerDependencyPorts(state, context),
};
registerPackageExport(state, packageAlias, alias, entry, context);
return entry;
};
const lowerPackageConstructor = (
state: LoweringState,
packageAlias: string,
context: PackageConstructorExportContext,
): PackageExport => {
const alias = identifier(context.identifier(0));
const atomName = identifier(context.identifier(1));
const atomId =
requireSymbol(state, state.atoms, atomName, context, "atom") ?? capabilityId.atom(`unresolved:${atomName}`);
const entry: PackageExport = {
kind: "constructor",
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias,
inputType: lowerValueType(state, context.valueType()),
outputType: valueType.atomRef(atomId),
constructsAtom: atomId,
dependencyPorts: lowerDependencyPorts(state, context),
};
registerPackageExport(state, packageAlias, alias, entry, context);
return entry;
};
const lowerGenericPackageExport = (
state: LoweringState,
context: PackageOperationExportContext | PackageFunctionExportContext,
): GenericPackageExport => {
const previous = state.types.parameters;
state.types.parameters = new Map();
const id = capabilityId.packageExport(stringValue(context.stringLiteral()));
try {
const parameters = state.types.declareParameters(context.typeParameters(), JSON.stringify(["export", id]));
const dependencyPorts = (context.dependencyBlock()?.dependencyPort() ?? []).map((port): GenericDependencyPort => {
const base = {
id: capabilityId.dependencyPort(stringValue(port.stringLiteral())),
displayName: identifier(port.identifier(0)),
};
const primitives =
port
.primitiveList()
?.primitive()
.map((item) => text(item)) ?? [];
if (port.REFLECTION())
return {
...base,
requirement: { kind: "reflection", selectors: port.identifierList()!.identifier().map(identifier) },
};
if (port.QUERY()) {
const query = lowerQueryReference(state, identifier(port.identifier(1)), identifier(port.identifier(2)), port);
if (!query)
throw new GenericTypeError(
"unknown-query",
base.displayName,
"Query port requires an existing closed query export",
);
return { ...base, requirement: { kind: "query", ...query } };
}
if (port.STATE())
return {
...base,
requirement: {
kind: "state",
valueType: state.types.expression(port.valueType()!),
primitives: primitives as StatePrimitive[],
},
};
if (port.EDGE())
return {
...base,
requirement: {
kind: "edge",
target: state.types.targetExpression(port.targetConstraint()!),
cardinality: lowerCardinality(port.cardinality()!),
primitives: primitives as EdgePrimitive[],
},
};
if (port.INTERFACE())
return {
...base,
requirement: {
kind: "interface",
application: state.types.interfaceByName(identifier(port.identifier(1)), port.typeArguments()),
...lowerSelectedOperations(state, port, state.interfaces.get(identifier(port.identifier(1)))),
...(port.FROM() ? { receiver: "input" as const } : {}),
},
};
const name = identifier(port.identifier(1)),
parameter = state.types.parameters.get(name);
const target: ObjectTypeExpression =
parameter?.kind === "object"
? { kind: "parameter", parameterId: parameter.id }
: { kind: "atom", atomId: requireSymbol(state, state.atoms, name, port, "atom")! };
if (!port.valueType())
throw new GenericTypeError("missing-constructor-input", id, "Constructor ports require an explicit input type");
return {
...base,
requirement: {
kind: port.CREATION() ? "creation" : "constructor",
target,
inputType: state.types.expression(port.valueType()!),
},
};
});
const operation = "receiverRequirement" in context ? context : undefined;
const receiver = operation?.receiverRequirement();
let receiverRequirement: GenericPackageExport["receiverRequirement"] = { kind: "any-object" };
if (receiver?.ATOM())
receiverRequirement = {
kind: "target",
target: {
kind: "atom",
atomId: requireSymbol(state, state.atoms, identifier(receiver.identifier()), receiver, "atom")!,
},
};
if (receiver?.OBJECT()) {
const name = identifier(receiver.identifier());
const parameter = state.types.parameters.get(name);
if (parameter?.kind !== "object")
throw new GenericTypeError("parameter-kind", id, "Receiver needs an object parameter");
receiverRequirement = { kind: "target", target: { kind: "parameter", parameterId: parameter.id } };
}
if (receiver?.INTERFACES())
receiverRequirement = {
kind: "interfaces",
interfaces: receiver.interfaceType().map((item) => state.types.interface(item)),
};
const definition: GenericPackageExport = {
id,
displayName: identifier(context.identifier()),
parameters,
kind: operation ? "operation" : "function",
...lowerExecution(context.executionClause()),
inputType: state.types.expression(context.valueType(0)!),
outputType: state.types.expression(context.valueType(1)!),
dependencyPorts,
receiverRequirement,
...(operation ? { mode: text(operation.operationMode()) as InterfaceOperationMode } : {}),
...(operation?.eventClause() ? { eventType: state.types.expression(operation.eventClause()!.valueType()) } : {}),
aliases: [...state.types.aliases.values()],
};
const bindSelf = (value: unknown): unknown => {
if (!value || typeof value !== "object") return value;
if ("kind" in value && value.kind === "self") {
if (receiverRequirement.kind !== "target")
throw new GenericTypeError("unbound-self", id, "Generic Self requires an exact or object-parameter receiver");
return receiverRequirement.target;
}
if (Array.isArray(value)) return value.map(bindSelf);
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, bindSelf(child)]));
};
return bindSelf(definition) as GenericPackageExport;
} finally {
state.types.parameters = previous;
}
};
const lowerPackage = (
state: LoweringState,
context: PackageResourceDeclContext,
source: SourceRevision,
): PackageRevision => {
const alias = identifier(context.identifier());
state.packages.get(alias)!.queries = new Map(
context.packageExport().flatMap((entry) => {
const query = entry.packageQuery();
const specialization = entry.packageQuerySpecialization();
return query && !query.typeParameters()
? [[identifier(query.identifier()), stringValue(query.stringLiteral(0))] as const]
: specialization
? [[identifier(specialization.identifier(0)), stringValue(specialization.stringLiteral())] as const]
: [];
}),
);
const genericExports: GenericPackageExport[] = [];
const components: NonNullable<PackageRevision["components"]> = [];
const queries: QueryDeclaration[] = [];
const queryTemplates: QueryTemplate[] = [];
const exports = context.packageExport().flatMap((exportContext): PackageExport[] => {
const component = exportContext.componentDecl();
if (component) {
const runtime = identifier(component.identifier(1));
if (runtime !== "react")
loweringIssue(state, component, "unsupported-component-runtime", `Unsupported component runtime ${runtime}`);
components.push({
id: stringValue(component.stringLiteral()),
displayName: identifier(component.identifier(0)),
subject: new TypeSubstitution(state.types.environment()).application(
state.types.interface(component.interfaceType()),
),
runtime: "react",
});
return [];
}
if (exportContext.packageQuerySpecialization()) return [];
const query = exportContext.packageQuery();
if (query) {
const previousParameters = state.types.parameters;
state.types.parameters = new Map();
try {
const parameters = state.types.declareParameters(
query.typeParameters(),
`query:${stringValue(query.stringLiteral(0))}`,
);
const expressions = new Map<string, ReturnType<GenericSourceTypes["interface"]>>();
const templateViews: QueryTemplate["views"] = [];
const templateAllowances: QueryTemplate["allowances"] = [];
const close = (ctx: import("./generated/QuixosCapabilityParser.js").InterfaceTypeContext) =>
parameters.length
? (() => {
const id = capabilityId.interfaceRevision(`query-expression:${expressions.size}`);
expressions.set(id, state.types.interface(ctx));
return id;
})()
: new TypeSubstitution(state.types.environment()).application(state.types.interface(ctx));
const declaration: QueryDeclaration = {
id: stringValue(query.stringLiteral(0)),
displayName: identifier(query.identifier()),
root: { kind: query.WORKSPACE() ? "workspace-interface" : "object", interface: close(query.interfaceType()) },
document: stringValue(query.stringLiteral(1)),
operation: stringValue(query.stringLiteral(2)),
fragments: [],
views: [],
allowances: [],
budgets: { ...defaultQueryBudgets },
watch: false,
};
const budgetNames: Record<string, keyof QueryBudgets> = {
rows: "rows",
depth: "depth",
"result-bytes": "resultBytes",
candidates: "candidates",
"rpc-calls": "rpcCalls",
concurrency: "concurrency",
"deadline-ms": "deadlineMs",
};
const assigned = new Set<string>();
for (const clause of query.queryClause()) {
if (clause.REPLICA() || clause.LOCAL()) {
if (declaration.placement)
loweringIssue(state, clause, "duplicate-query-placement", "Declare query placement once");
else if (clause.REPLICA()) declaration.placement = { kind: "replica" };
else {
const name = identifier(clause.identifier(0));
const sourceQueryId = requireSymbol(
state,
state.packages.get(alias)!.queries!,
name,
clause,
"replica query",
);
if (sourceQueryId) declaration.placement = { kind: "local", sourceQueryId };
}
} else if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
else if (clause.VIEW()) {
const name = identifier(clause.identifier(0));
if (parameters.length) {
const parameter = state.types.parameters.get(name);
const atomId = state.atoms.get(name);
if (clause.OBJECT() && parameter?.kind === "object")
templateViews.push({
target: { kind: "parameter", parameterId: parameter.id },
interface: state.types.interface(clause.interfaceType()!),
});
else if (!clause.OBJECT() && atomId)
templateViews.push({
target: { kind: "atom", atomId },
interface: state.types.interface(clause.interfaceType()!),
});
else
loweringIssue(state, clause, "invalid-query-view", "Expected declared object parameter or atom view");
continue;
}
if (clause.OBJECT())
loweringIssue(state, clause, "invalid-query-view", "Object parameter views require a query template");
const atomId = requireSymbol(state, state.atoms, name, clause, "atom");
if (atomId) declaration.views.push({ atomId, interfaceRevisionId: close(clause.interfaceType()!) });
} else if (clause.MAX()) {
const name = identifier(clause.identifier(0));
const key = budgetNames[name],
n = Number(clause.INTEGER()!.getText());
if (!key || assigned.has(name) || !Number.isSafeInteger(n) || n < 1 || n > 0xffffffff)
loweringIssue(
state,
clause,
"invalid-query-budget",
`Invalid, duplicate, or out-of-range query budget ${name}`,
);
else {
declaration.budgets[key] = n;
assigned.add(name);
}
} else if (clause.ALLOW()) {
if (parameters.length) {
const use = identifier(clause.identifier(1));
const reason = stringValue(clause.stringLiteral()!);
if (!["select", "predicate", "order", "aggregate", "group", "distinct"].includes(use) || !reason.trim())
loweringIssue(
state,
clause,
"invalid-query-allowance",
"Expected select/predicate/order/aggregate/group/distinct and a nonempty reason",
);
templateAllowances.push({
interface: state.types.interface(clause.interfaceType()!),
memberName: identifier(clause.identifier(0)),
uses: [use as QueryUse],
reason,
});
continue;
}
const interfaceRevisionId = close(clause.interfaceType()!);
const contract = [...state.types.interfaces.values(), ...state.types.applications.values()].find(
(entry) => entry.revisionId === interfaceRevisionId,
);
const member = contract?.members.find((entry) => entry.displayName === identifier(clause.identifier(0)));
const use = identifier(clause.identifier(1));
const reason = stringValue(clause.stringLiteral()!);
if (
!member ||
!["select", "predicate", "order", "aggregate", "group", "distinct"].includes(use) ||
!reason.trim()
)
loweringIssue(
state,
clause,
"invalid-query-allowance",
"Query allowances name an exact member, use (select/predicate/order/aggregate/group/distinct), and nonempty reason",
);
else
declaration.allowances.push({
interfaceRevisionId,
memberId: member.id,
uses: [use as QueryUse],
reason,
});
} else if (clause.WATCH()) declaration.watch = true;
else if (clause.POLL()) {
const intervalMs = Number(clause.INTEGER()!.getText()),
reason = stringValue(clause.stringLiteral()!);
if (declaration.polling || !Number.isSafeInteger(intervalMs) || intervalMs < 1000 || !reason.trim())
loweringIssue(
state,
clause,
"invalid-query-polling",
"Polling needs one interval of at least 1000ms and a reason",
);
else declaration.polling = { intervalMs, reason };
}
}
if (
[...queries, ...queryTemplates.map((entry) => entry.declaration)].some(
(entry) => entry.id === declaration.id || entry.displayName === declaration.displayName,
)
)
loweringIssue(state, query, "duplicate-query", "Duplicate query name or ID");
if (parameters.length) {
const { root, views: _views, allowances: _allowances, ...common } = declaration;
queryTemplates.push({
declaration: common,
parameters,
root: { kind: root.kind, interface: expressions.get(root.interface)! },
views: templateViews,
allowances: templateAllowances,
});
} else queries.push(declaration);
return [];
} finally {
state.types.parameters = previousParameters;
}
}
const operation = exportContext.packageOperationExport();
const generic = operation ?? exportContext.packageFunctionExport();
if (generic?.typeParameters()) {
if (generic.acceptanceClause())
loweringIssue(
state,
generic,
"unsupported-generic-acceptance",
"Acceptance hooks currently require a closed export",
);
genericExports.push(atGenericSource(generic, () => lowerGenericPackageExport(state, generic)));
return [];
}
const constructor = exportContext.packageConstructorExport();
const receiver = operation?.receiverRequirement();
const selfName = constructor
? identifier(constructor.identifier(1))
: receiver?.ATOM()
? identifier(receiver.identifier())
: undefined;
const previousSelf = state.types.self;
state.types.self = selfName ? state.atoms.get(selfName) : undefined;
try {
if (operation) return [lowerPackageOperation(state, alias, operation)];
const fn = exportContext.packageFunctionExport();
if (fn) return [lowerPackageFunction(state, alias, fn)];
return [lowerPackageConstructor(state, alias, constructor!)];
} finally {
state.types.self = previousSelf;
}
});
// Resolve after all exports have been declared, so checks can follow bodies.
for (const source of context.packageExport()) {
const declaration = source.packageOperationExport() ?? source.packageFunctionExport();
const clause = declaration?.acceptanceClause();
if (!declaration || !clause || declaration.typeParameters()) continue;
const entry = exports.find((entry) => entry.displayName === identifier(declaration.identifier()));
const check = exports.find((entry) => entry.displayName === identifier(clause.identifier()));
if (!check)
loweringIssue(
state,
clause,
"unknown-acceptance-export",
"Acceptance hook must name a closed export in this package",
);
else if (entry) entry.acceptanceExportId = check.id;
}
for (const entry of context.packageExport()) {
const specialized = entry.packageQuerySpecialization();
if (!specialized) continue;
const templateName = identifier(specialized.identifier(1));
const template = queryTemplates.find((entry) => entry.declaration.displayName === templateName);
if (!template) {
loweringIssue(state, specialized, "unknown-query-template", `Unknown local query template ${templateName}`);
continue;
}
const substitution = new TypeSubstitution(state.types.environment());
const arguments_ = state.types
.arguments(specialized.typeArguments())
.map((argument) => substitution.argument(argument));
const declaration = specializeQueryTemplate(
template,
arguments_,
state.types.environment(),
() => [...state.types.definitions.values()],
{ id: stringValue(specialized.stringLiteral()), displayName: identifier(specialized.identifier(0)) },
);
if (
[...queries, ...queryTemplates.map((entry) => entry.declaration)].some(
(entry) => entry.id === declaration.id || entry.displayName === declaration.displayName,
)
)
loweringIssue(state, specialized, "duplicate-query", "Duplicate query name or ID");
queries.push(declaration);
}
return {
packageId: capabilityId.package(stringValue(context.stringLiteral(0))),
revisionId: state.packages.get(alias)!.revisionId,
...(context.INTEGER() ? { semanticMajor: Number(context.INTEGER()!.getText()) } : {}),
displayName: alias,
source,
exports,
...(components.length ? { components } : {}),
...(queries.length ? { queries } : {}),
...(queryTemplates.length ? { queryTemplates } : {}),
...(queries.some((query) => query.argumentRequirements?.length)
? { argumentRequirements: queries.flatMap((query) => query.argumentRequirements ?? []) }
: {}),
...(genericExports.length ? { genericExports } : {}),
};
};
const lowerState = (state: LoweringState, context: StateDeclContext): StateSlotDefinition => {
const atomName = identifier(context.identifier(1));
const atomId =
requireSymbol(state, state.atoms, atomName, context, "atom") ?? capabilityId.atom(`unresolved:${atomName}`);
const policy = context.storagePolicy();
const defaultContext = context.jsonLiteral();
let defaultValue: unknown;
if (defaultContext) {
defaultValue = JSON.parse(text(defaultContext)) as unknown;
}
return {
kind: "state",
id: capabilityId.slot(stringValue(context.stringLiteral())),
attachedTo: atomId,
displayName: identifier(context.identifier(0)),
valueType: lowerValueType(state, context.valueType()),
storagePolicy: policy.REGISTER()
? { kind: "register" }
: {
kind: "crdt-document",
updateType: lowerValueType(state, policy.valueType()),
},
defaultValue,
};
};
const lowerEdgeEndpoint = (state: LoweringState, context: EdgeEndpointContext): EdgeEndpoint | undefined => {
const constraint = lowerConstraint(state, context.targetConstraint());
return constraint
? {
projectionId: capabilityId.edgeProjection(stringValue(context.stringLiteral(0))),
displayName: identifier(context.identifier()),
constraint,
cardinality: lowerCardinality(context.cardinality()),
ordered: Boolean(context.ORDERED()),
...(context.ON_DELETE() ? { onDelete: stringValue(context.stringLiteral(1)) as EdgeEndpoint["onDelete"] } : {}),
...(context.RETAIN_OTHER() ? { retainOther: true } : {}),
...(context.KEYED()
? { keyType: stringValue(context.stringLiteral(context.ON_DELETE() ? 2 : 1)) as EdgeEndpoint["keyType"] }
: {}),
...(context.PUBLIC_TRAVERSAL() ? { publicTraversal: true } : {}),
}
: undefined;
};
const lowerEdge = (state: LoweringState, context: EdgeDeclContext): EdgeDefinition | undefined => {
const endpoints = context.edgeEndpoint().flatMap((entry) => {
const endpoint = lowerEdgeEndpoint(state, entry);
return endpoint ? [endpoint] : [];
});
if (endpoints.length !== 2) {
return undefined;
}
return {
kind: "edge",
id: capabilityId.edgeType(stringValue(context.stringLiteral())),
displayName: identifier(context.identifier()),
endpoints: [endpoints[0]!, endpoints[1]!],
};
};
const lowerAttachment = (state: LoweringState, context: AttachmentDeclContext): PersistentAttachment | undefined => {
const stateContext = context.stateDecl();
return stateContext ? lowerState(state, stateContext) : lowerEdge(state, context.edgeDecl()!);
};
const registerAttachment = (state: LoweringState, context: AttachmentDeclContext, attachment: PersistentAttachment) => {
const stateContext = context.stateDecl();
const name = identifier(stateContext ? stateContext.identifier(0) : context.edgeDecl()!.identifier());
const projections = new Map<string, EdgeEndpoint["projectionId"]>();
if (attachment.kind === "edge") {
for (const endpoint of attachment.endpoints) {
declareSymbol(state, projections, endpoint.displayName, endpoint.projectionId, context, "edge projection");
}
}
declareSymbol(state, state.attachments, name, { attachment, projections }, context, "attachment");
};
const findOperationId = (
state: LoweringState,
interfaceAlias: string,
memberName: string,
operationName: string,
context: ParserRuleContext,
) => {
const interfaceSymbol = requireSymbol(state, state.interfaces, interfaceAlias, context, "interface");
const member = interfaceSymbol
? requireSymbol(state, interfaceSymbol.members, memberName, context, `member on ${interfaceAlias}`)
: undefined;
return member
? requireSymbol(state, member.operations, operationName, context, `operation on ${interfaceAlias}.${memberName}`)
: undefined;
};
const lowerBoundDependencies = (
state: LoweringState,
context: DependencyBindingBlockContext | undefined,
exportSymbol: PackageExportSymbol,
): BoundDependency[] => {
if (!context) {
return [];
}
return context.dependencyBinding().flatMap<BoundDependency>((entry) => {
const portName = identifier(entry.identifier(0));
const portId = requireSymbol(state, exportSymbol.ports, portName, entry, "dependency port");
if (!portId) {
return [];
}
const traversal = (edgeName: string, projectionName: string): EdgeTraversal | undefined => {
const attachment = requireSymbol(state, state.attachments, edgeName, entry, "attachment");
if (!attachment || attachment.attachment.kind !== "edge") {
if (attachment) {
loweringIssue(state, entry, "wrong-attachment-kind", `${edgeName} is state, not an edge`);
}
return undefined;
}
const projectionId = requireSymbol(
state,
attachment.projections,
projectionName,
entry,
`projection on ${edgeName}`,
);
return projectionId ? { edgeTypeId: attachment.attachment.id, projectionId } : undefined;
};
if (entry.REFLECTION())
return [
{ portId, binding: { kind: "reflection", selectors: entry.identifierList()!.identifier().map(identifier) } },
];
if (entry.QUERY()) {
const query = lowerQueryReference(state, identifier(entry.identifier(1)), identifier(entry.identifier(2)), entry);
const via = entry.VIA() ? traversal(identifier(entry.identifier(3)), identifier(entry.identifier(4))) : undefined;
return query && (!entry.VIA() || via)
? [
{
portId,
binding: {
kind: "query",
...query,
scope: entry.WORKSPACE() ? "workspace" : "object",
...(via ? { via } : {}),
},
},
]
: [];
}
if (entry.STATE()) {
const attachmentName = identifier(entry.identifier(1));
const attachment = requireSymbol(state, state.attachments, attachmentName, entry, "attachment");
if (!attachment || attachment.attachment.kind !== "state") {
if (attachment) {
loweringIssue(state, entry, "wrong-attachment-kind", `${attachmentName} is an edge, not state`);
}
return [];
}
const via = entry.VIA() ? traversal(identifier(entry.identifier(2)), identifier(entry.identifier(3))) : undefined;
if (entry.VIA() && !via) return [];
return [
{
portId,
binding: { kind: "state", slotId: attachment.attachment.id, ...(via ? { via } : {}) },
},
];
}
if (entry.EDGE(0) && !entry.INTERFACE()) {
const attachmentName = identifier(entry.identifier(1));
const projectionName = identifier(entry.identifier(2));
const attachment = requireSymbol(state, state.attachments, attachmentName, entry, "attachment");
if (!attachment || attachment.attachment.kind !== "edge") {
if (attachment) {
loweringIssue(state, entry, "wrong-attachment-kind", `${attachmentName} is state, not an edge`);
}
return [];
}
const projectionId = requireSymbol(
state,
attachment.projections,
projectionName,
entry,
`projection on ${attachmentName}`,
);
const via = entry.VIA() ? traversal(identifier(entry.identifier(3)), identifier(entry.identifier(4))) : undefined;
if (entry.VIA() && !via) return [];
return projectionId
? [
{
portId,
binding: {
kind: "edge",
edgeTypeId: attachment.attachment.id,
projectionId,
...(via ? { via } : {}),
},
},
]
: [];
}
if (entry.INTERFACE()) {
const interfaceName = identifier(entry.identifier(1));
let interfaceSymbol = requireSymbol(state, state.interfaces, interfaceName, entry, "interface");
if (interfaceSymbol && (entry.typeArguments() || interfaceSymbol.definition?.template)) {
const id = new TypeSubstitution(state.types.environment()).application(
state.types.interfaceByName(interfaceName, entry.typeArguments()),
);
interfaceSymbol = interfaceSymbolFor(state.types.definitions.get(id)!);
}
const via = entry.VIA() ? traversal(identifier(entry.identifier(2)), identifier(entry.identifier(3))) : undefined;
if (entry.VIA() && !via) return [];
return interfaceSymbol
? [
{
portId,
binding: {
kind: "interface",
interfaceRevisionId: interfaceSymbol.revisionId,
...(entry.FROM() ? { receiver: "input" as const } : {}),
...(via ? { via } : {}),
},
},
]
: [];
}
const atomName = identifier(entry.identifier(1));
const atomId = requireSymbol(state, state.atoms, atomName, entry, "atom");
if (entry.CREATION())
return atomId
? [
{
portId,
binding: {
kind: "creation",
atomId,
owners: entry.creationOwnerMapping().map((owner) => ({
interfaceRevisionId: new TypeSubstitution(state.types.environment()).application(
state.types.interface(owner.interfaceType()),
),
inputPath: owner.identifier().map(identifier),
})),
},
},
]
: [];
return atomId ? [{ portId, binding: { kind: "constructor", atomId } }] : [];
});
};
const lowerRelationshipMaterialization = (
state: LoweringState,
interfaceName: string,
context: RelationshipMaterializationDeclContext,
): WorkspaceRevision["conformances"][number]["relationshipMaterializations"][number] | undefined => {
const memberName = identifier(context.identifier(0));
const member = requireSymbol(
state,
state.interfaces.get(interfaceName)?.members ?? new Map(),
memberName,
context,
`member on ${interfaceName}`,
);
const constructorAtom = requireSymbol(
state,
state.atoms,
identifier(context.identifier(1)),
context,
"constructor atom",
);
const edgeName = identifier(context.identifier(2));
const edge = requireSymbol(state, state.attachments, edgeName, context, "attachment");
if (edge && edge.attachment.kind !== "edge") {
loweringIssue(state, context, "wrong-attachment-kind", `${edgeName} is state, not an edge`);
}
const projectionId =
edge?.attachment.kind === "edge"
? requireSymbol(state, edge.projections, identifier(context.identifier(3)), context, `projection on ${edgeName}`)
: undefined;
return member && constructorAtom && edge?.attachment.kind === "edge" && projectionId
? {
memberId: member.memberId,
constructorAtomId: constructorAtom,
edgeTypeId: edge.attachment.id,
constructedProjectionId: projectionId,
}
: undefined;
};
const lowerConformance = (
state: LoweringState,
context: ConformanceDeclContext,
privateAttachments: PersistentAttachment[],
): WorkspaceRevision["conformances"][number] | undefined => {
const atomName = identifier(context.identifier(0));
const interfaceName = identifier(context.identifier(1));
const atomId = requireSymbol(state, state.atoms, atomName, context, "atom");
let interfaceSymbol = requireSymbol(state, state.interfaces, interfaceName, context, "interface");
if (!atomId || !interfaceSymbol) {
return undefined;
}
const originalSymbol = interfaceSymbol;
if (context.typeArguments() || interfaceSymbol.definition?.template) {
const previousSelf = state.types.self;
state.types.self = atomId;
try {
const expression = state.types.interfaceByName(interfaceName, context.typeArguments());
const id = new TypeSubstitution(state.types.environment()).application(expression);
interfaceSymbol = interfaceSymbolFor(state.types.definitions.get(id)!);
state.interfaces.set(interfaceName, interfaceSymbol);
} finally {
state.types.self = previousSelf;
}
}
const operationBindings = context
.conformanceItem()
.flatMap<WorkspaceRevision["conformances"][number]["operationBindings"][number]>((item) => {
const field = item.stateFieldBindingDecl();
if (field) {
const memberName = identifier(field.identifier(0));
const member = interfaceSymbol.definition?.members.find((entry) => entry.displayName === memberName);
const attachment = requireSymbol(
state,
state.attachments,
identifier(field.identifier(1)),
field,
"attachment",
);
if (!member || member.kind !== "value" || attachment?.attachment.kind !== "state") {
loweringIssue(
state,
field,
"invalid-state-field-binding",
"Field shorthand requires a value member and a state slot",
);
return [];
}
const primitives = {
get: "read",
set: "write",
edit: "edit",
"watch-start": "watch-start",
"watch-stop": "watch-stop",
} as const;
const slotId = attachment.attachment.id;
return member.operations.map((operation) => ({
operationId: operation.id,
binding: {
kind: "state" as const,
slotId,
primitive: primitives[operation.displayName as keyof typeof primitives],
},
}));
}
const bindingContext = item.operationBindingDecl();
if (!bindingContext) {
return [];
}
const memberName = identifier(bindingContext.memberOperationRef().identifier());
const operationName = text(bindingContext.memberOperationRef()?.operationName() ?? null);
const operationId = findOperationId(state, interfaceName, memberName, operationName, bindingContext);
if (!operationId) {
return [];
}
const provider = bindingContext.operationProvider();
if (provider.LIFECYCLE()) {
if (identifier(provider.identifier(0)) !== "delete") {
loweringIssue(state, provider, "invalid-lifecycle-binding", "Only lifecycle.delete is supported");
return [];
}
return [{ operationId, binding: { kind: "lifecycle", primitive: "delete" } }];
}
if (provider.INTERFACE()) {
const targetName = identifier(provider.identifier(0));
let target = requireSymbol(state, state.interfaces, targetName, provider, "interface");
if (!target) return [];
if (provider.typeArguments() || target.definition?.template) {
const id = new TypeSubstitution({ ...state.types.environment(), self: atomId }).application(
state.types.interfaceByName(targetName, provider.typeArguments()),
);
target = interfaceSymbolFor(state.types.definitions.get(id)!);
}
const ref = provider.memberOperationRef()!;
const member = target.members.get(identifier(ref.identifier()));
const targetOperation = member?.operations.get(text(ref.operationName()));
if (!targetOperation) {
loweringIssue(
state,
provider,
"unknown-operation",
"Forwarding target must name an interface member operation",
);
return [];
}
return [
{
operationId,
binding: { kind: "interface", interfaceRevisionId: target.revisionId, operationId: targetOperation },
...(bindingContext.stringLiteral() ? { queryReason: stringValue(bindingContext.stringLiteral()!) } : {}),
},
];
}
if (provider.STATE()) {
const attachmentName = identifier(provider.identifier(0));
const attachment = requireSymbol(state, state.attachments, attachmentName, provider, "attachment");
if (!attachment || attachment.attachment.kind !== "state") {
if (attachment) {
loweringIssue(state, provider, "wrong-attachment-kind", `${attachmentName} is not state`);
}
return [];
}
return [
{
operationId,
binding: {
kind: "state" as const,
slotId: attachment.attachment.id,
primitive: text(provider.statePrimitive()) as StatePrimitive,
},
},
];
}
if (provider.EDGE()) {
const edgeName = identifier(provider.identifier(0));
const projectionName = identifier(provider.identifier(1));
const attachment = requireSymbol(state, state.attachments, edgeName, provider, "attachment");
if (!attachment || attachment.attachment.kind !== "edge") {
if (attachment) {
loweringIssue(state, provider, "wrong-attachment-kind", `${edgeName} is not an edge`);
}
return [];
}
const projectionId = requireSymbol(
state,
attachment.projections,
projectionName,
provider,
`projection on ${edgeName}`,
);
return projectionId
? [
{
operationId,
binding: {
kind: "edge" as const,
edgeTypeId: attachment.attachment.id,
projectionId,
primitive: text(provider.edgePrimitive()) as EdgePrimitive,
},
},
]
: [];
}
const packageName = identifier(provider.identifier(0));
const exportName = identifier(provider.identifier(1));
const packageSymbol = requireSymbol(state, state.packages, packageName, provider, "package");
let exportSymbol = packageSymbol
? requireSymbol(state, packageSymbol.exports, exportName, provider, `export on ${packageName}`)
: undefined;
const generic = packageSymbol?.definition?.genericExports?.find((entry) => entry.displayName === exportName);
if (packageSymbol?.definition && generic) {
const arguments_ = state.types
.arguments(provider.typeArguments())
.map((entry) => new TypeSubstitution({ ...state.types.environment(), self: atomId }).argument(entry));
const pkg = packageSymbol.definition;
const specialized = specializePackageExport(pkg, generic, arguments_, {
...state.types.environment(),
implementsInterface: (target, required) => {
(pkg.argumentRequirements ??= []).push({ target, required });
return true;
},
});
if (!pkg.exports.some((entry) => entry.id === specialized.id)) pkg.exports.push(specialized);
exportSymbol = {
exportId: specialized.id,
ports: new Map(specialized.dependencyPorts.map((entry) => [entry.displayName, entry.id])),
};
} else if (provider.typeArguments()) {
throw new GenericTypeError("type-arity", exportName, "Non-generic export takes no type arguments");
}
const packageExport = packageSymbol?.definition?.exports.find((entry) => entry.id === exportSymbol?.exportId);
const check = packageSymbol?.definition?.exports.find((entry) => entry.id === packageExport?.acceptanceExportId);
const acceptanceBlock = provider.acceptanceBinding()?.dependencyBindingBlock();
if (acceptanceBlock && !check)
loweringIssue(
state,
acceptanceBlock,
"unexpected-acceptance-binding",
"Export does not declare an acceptance hook",
);
const acceptanceDependencies = check
? lowerBoundDependencies(state, acceptanceBlock ?? undefined, {
exportId: check.id,
ports: new Map(check.dependencyPorts.map((port) => [port.displayName, port.id])),
})
: undefined;
return packageSymbol && exportSymbol
? [
{
operationId,
...(bindingContext.stringLiteral() ? { queryReason: stringValue(bindingContext.stringLiteral()!) } : {}),
binding: {
kind: "package" as const,
packageRevisionId: packageSymbol.revisionId,
exportId: exportSymbol.exportId,
...(acceptanceDependencies ? { acceptanceDependencies } : {}),
dependencies: lowerBoundDependencies(
state,
provider.dependencyBindingBlock() ?? undefined,
exportSymbol,
),
},
},
]
: [];
});
const relationshipMaterializations = context.conformanceItem().flatMap((item) => {
const materialization = item.relationshipMaterializationDecl();
if (!materialization) return [];
const lowered = lowerRelationshipMaterialization(state, interfaceName, materialization);
return lowered ? [lowered] : [];
});
const initializers = context
.conformanceItem()
.flatMap((item) => (item.initializationDecl() ? [item.initializationDecl()!] : []));
if (initializers.length > 1)
loweringIssue(state, context, "duplicate-initialization", "Declare one initialization mapping per owner");
const initialization = initializers.flatMap((initializer) =>
initializer.initializationField().flatMap((field): import("../capability-model/types.js").InitializationField[] => {
const input = identifier(field.identifier(0));
const attachment = requireSymbol(
state,
state.attachments,
identifier(field.identifier(1)),
field,
"initialization attachment",
);
if (!attachment) return [];
if (field.STATE() && attachment.attachment.kind === "state")
return [{ input, binding: { kind: "state", slotId: attachment.attachment.id } }];
if (field.EDGE() && attachment.attachment.kind === "edge") {
const projectionId = requireSymbol(
state,
attachment.projections,
identifier(field.identifier(2)),
field,
"initialization projection",
);
return projectionId
? [{ input, binding: { kind: "edge", edgeTypeId: attachment.attachment.id, projectionId } }]
: [];
}
loweringIssue(state, field, "invalid-initialization", "Initialization attachment kind does not match");
return [];
}),
);
state.interfaces.set(interfaceName, originalSymbol);
return {
...(initializers.length ? { initialization } : {}),
atomId,
interfaceRevisionId: interfaceSymbol.revisionId,
...(context.stringLiteral() ? { id: capabilityId.conformance(stringValue(context.stringLiteral()!)) } : {}),
...(context.INTEGER() ? { semanticMajor: Number(context.INTEGER()!.getText()) } : {}),
privateAttachments,
operationBindings,
relationshipMaterializations,
};
};
const interfaceSymbolFor = (revision: InterfaceRevision): InterfaceSymbol => ({
definition: revision,
revisionId: revision.revisionId,
contractAvailable: true,
members: new Map(
(revision.template?.members ?? revision.members).map((member) => [
member.displayName,
{
memberId: member.id,
operations: new Map(member.operations.map((operation) => [operation.displayName, operation.id])),
},
]),
),
});
const packageSymbolFor = (revision: PackageRevision): PackageSymbol => ({
definition: structuredClone(revision),
revisionId: revision.revisionId,
exports: new Map(
[...revision.exports, ...(revision.genericExports ?? [])].map((entry) => [
entry.displayName,
{
exportId: entry.id,
ports: new Map(entry.dependencyPorts.map((port) => [port.displayName, port.id])),
},
]),
),
});
const resourceImport = (context: ResourceImportDeclContext): CapabilityResourceImport => ({
kind: context.INTERFACE() ? "interface" : "package",
binding: identifier(context.identifier()),
});
const registerImports = (
state: LoweringState,
contexts: readonly ResourceImportDeclContext[],
environment: CapabilityImportEnvironment,
) => {
for (const definition of environment.interfaceClosure ?? [])
state.types.definitions.set(definition.revisionId, definition);
return contexts.map((context) => {
const imported = resourceImport(context);
if (imported.kind === "interface") {
const revision = environment.interfaces?.get(imported.binding);
if (!revision) {
loweringIssue(
state,
context,
"missing-import",
`No resolved interface is available for lock binding ${imported.binding}`,
);
} else {
declareSymbol(state, state.interfaces, imported.binding, interfaceSymbolFor(revision), context, "interface");
state.types.register(imported.binding, revision);
}
} else {
const revision = environment.packages?.get(imported.binding);
if (!revision) {
loweringIssue(
state,
context,
"missing-import",
`No resolved package is available for lock binding ${imported.binding}`,
);
} else {
declareSymbol(state, state.packages, imported.binding, packageSymbolFor(revision), context, "package");
}
}
return imported;
});
};
const uniqueExactRevisions = <
Revision extends {
revisionId: string;
source: SourceRevision;
},
>(
revisions: readonly Revision[],
): Revision[] => {
const seen = new Set<string>();
return revisions.filter((revision) => {
const key = `${revision.revisionId}\0${revision.source.repository}\0${revision.source.commit}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
};
const uniqueAtoms = (atoms: readonly AtomDefinition[]): AtomDefinition[] => {
const seen = new Set<string>();
return atoms.filter((atom) => {
if (seen.has(atom.id)) return false;
seen.add(atom.id);
return true;
});
};
const lowerWorkspace = (
state: LoweringState,
context: WorkspaceDeclContext,
environment: CapabilityImportEnvironment,
): { workspace: WorkspaceRevision; imports: CapabilityResourceImport[] } => {
const items = context.workspaceItem();
for (const item of items) {
const atom = item.atomDecl();
if (atom) {
declareSymbol(
state,
state.atoms,
identifier(atom.identifier()),
capabilityId.atom(stringValue(atom.stringLiteral(0))),
atom,
"atom",
);
continue;
}
}
const imports = registerImports(
state,
items.flatMap((item) => item.resourceImportDecl() ?? []),
environment,
);
const atoms = items.flatMap((item) => {
const atom = item.atomDecl();
if (!atom) {
return [];
}
const atomId = state.atoms.get(identifier(atom.identifier()));
return atomId
? [
{
id: atomId,
displayName: identifier(atom.identifier()),
documentation: atom.DOC() ? stringValue(atom.stringLiteral(1)) : undefined,
},
]
: [];
});
state.types.declareAliases(items.flatMap((item) => item.typeAliasDecl() ?? []));
const interfaceImports = uniqueExactRevisions([
...(environment.interfaceClosure ?? []),
...[...(environment.interfaces?.values() ?? [])],
]);
const packageImports = uniqueExactRevisions([
...(environment.packageClosure ?? []),
...[...(environment.packages?.values() ?? [])],
]);
const sharedAttachments: PersistentAttachment[] = [];
const privateAttachments = new Map<ConformanceDeclContext, PersistentAttachment[]>();
for (const item of items) {
const shared = item.sharedAttachmentDecl();
if (shared) {
const declaration = shared.attachmentDecl();
const attachment = lowerAttachment(state, declaration);
if (attachment) {
sharedAttachments.push(attachment);
registerAttachment(state, declaration, attachment);
}
}
const conformance = item.conformanceDecl();
if (conformance) {
state.types.self = state.atoms.get(identifier(conformance.identifier(0)));
const attachments = conformance.conformanceItem().flatMap((entry) => {
const declaration = entry.attachmentDecl();
if (!declaration) {
return [];
}
const attachment = lowerAttachment(state, declaration);
if (!attachment) {
return [];
}
registerAttachment(state, declaration, attachment);
return [attachment];
});
privateAttachments.set(conformance, attachments);
state.types.self = undefined;
}
}
const conformances = items.flatMap((item) => {
const context = item.conformanceDecl();
if (!context) {
return [];
}
const conformance = atGenericSource(context, () =>
lowerConformance(state, context, privateAttachments.get(context) ?? []),
);
return conformance ? [conformance] : [];
});
const constructors = items.flatMap((item) => {
const constructor = item.constructorBindingDecl();
if (!constructor) {
return [];
}
const atomName = identifier(constructor.identifier(0));
const packageName = identifier(constructor.identifier(1));
const exportName = identifier(constructor.identifier(2));
const atomId = requireSymbol(state, state.atoms, atomName, constructor, "atom");
const packageSymbol = requireSymbol(state, state.packages, packageName, constructor, "package");
const exportSymbol = packageSymbol
? requireSymbol(state, packageSymbol.exports, exportName, constructor, `export on ${packageName}`)
: undefined;
return atomId && packageSymbol && exportSymbol
? [
{
atomId,
packageRevisionId: packageSymbol.revisionId,
exportId: exportSymbol.exportId,
dependencies: lowerBoundDependencies(
state,
constructor.dependencyBindingBlock() ?? undefined,
exportSymbol,
),
},
]
: [];
});
const presentations: NonNullable<WorkspaceRevision["presentations"]> = items.flatMap((item) => {
const presentation = item.presentationDecl();
if (!presentation) return [];
const packageName = identifier(presentation.identifier(1));
const componentName = identifier(presentation.identifier(2));
const pkg = requireSymbol(state, state.packages, packageName, presentation, "package");
const component = pkg?.definition?.components?.find((entry) => entry.displayName === componentName);
if (!component) {
loweringIssue(state, presentation, "unknown-component", `Unknown component ${packageName}.${componentName}`);
return [];
}
return [
{
id: stringValue(presentation.stringLiteral()),
displayName: identifier(presentation.identifier(0)),
subject: new TypeSubstitution(state.types.environment()).application(
state.types.interface(presentation.interfaceType()),
),
packageRevisionId: pkg!.revisionId,
componentId: component.id,
},
];
});
return {
imports,
workspace: {
id: capabilityId.workspaceRevision(stringValue(context.stringLiteral(1))),
workspaceId: capabilityId.workspace(stringValue(context.stringLiteral(0))),
parentRevisionIds: [],
sourceRootCommit: stringValue(context.stringLiteral(2)),
atoms,
sharedAttachments,
interfaceImports: uniqueExactRevisions([
...interfaceImports.filter((entry) => !entry.template),
...state.types.applications.values(),
]),
packageImports: packageImports.map(
(revision) =>
[...state.packages.values()].find((entry) => entry.revisionId === revision.revisionId)?.definition ??
revision,
),
conformances,
constructors,
...(presentations.length ? { presentations } : {}),
},
};
};
export const parseDocument = (
source: string,
fileName: string,
): { tree: DocumentContext; tokens: CommonTokenStream; diagnostics: CapabilitySourceDiagnostic[] } => {
const diagnostics: CapabilitySourceDiagnostic[] = [];
const listener = new SyntaxErrorListener(fileName, diagnostics);
const lexer = new QuixosCapabilityLexer(CharStream.fromString(source));
lexer.removeErrorListeners();
lexer.addErrorListener(listener);
const tokens = new CommonTokenStream(lexer);
const parser = new QuixosCapabilityParser(tokens);
parser.removeErrorListeners();
parser.addErrorListener(listener);
const tree = parser.document();
tokens.fill();
return { tree, tokens, diagnostics };
};
const newLoweringState = (fileName: string, diagnostics: CapabilitySourceDiagnostic[]): LoweringState => {
const atoms = new Map<string, AtomId>();
return {
fileName,
diagnostics,
atoms,
types: new GenericSourceTypes(atoms),
interfaces: new Map(),
packages: new Map(),
attachments: new Map(),
};
};
const validationDiagnostics = (
fileName: string,
issues: ReturnType<typeof compileWorkspaceRevision> extends infer _Result
? Array<{ code: string; message: string; path: string }>
: never,
): CapabilitySourceDiagnostic[] =>
issues.map((entry) => ({
phase: "validation",
code: entry.code,
message: entry.message,
fileName,
line: 0,
column: 0,
path: entry.path,
}));
const externalAtomsFrom = (state: LoweringState, contexts: readonly ExternalAtomDeclContext[]): AtomDefinition[] =>
contexts.map((context) => {
const atom: AtomDefinition = {
id: capabilityId.atom(stringValue(context.stringLiteral())),
displayName: identifier(context.identifier()),
};
declareSymbol(state, state.atoms, atom.displayName, atom.id, context, "external atom");
return atom;
});
const externalInterfacesFrom = (
state: LoweringState,
contexts: readonly ExternalInterfaceDeclContext[],
): CapabilityExternalInterface[] =>
contexts.map((context) => {
const requirement: CapabilityExternalInterface = {
binding: identifier(context.identifier()),
revisionId: capabilityId.interfaceRevision(stringValue(context.stringLiteral())),
};
declareSymbol(
state,
state.interfaces,
requirement.binding,
{
revisionId: requirement.revisionId,
contractAvailable: false,
members: new Map(),
},
context,
"external interface",
);
return requirement;
});
const resourcePreambleParts = (contexts: readonly ResourcePreambleContext[]) => ({
imports: contexts.flatMap((context) => context.resourceImportDecl() ?? []),
atoms: contexts.flatMap((context) => context.externalAtomDecl() ?? []),
interfaces: contexts.flatMap((context) => context.externalInterfaceDecl() ?? []),
aliases: contexts.flatMap((context) => context.typeAliasDecl() ?? []),
});
const resourceValidationWorkspace = (
resource: CapabilityResource,
environment: CapabilityImportEnvironment,
): WorkspaceRevision => {
const resolvedInterfaceIds = new Set([
...(environment.interfaceClosure ?? []).map((revision) => revision.revisionId),
...[...(environment.interfaces?.values() ?? [])].map((revision) => revision.revisionId),
...(resource.kind === "interface" ? [resource.revision.revisionId] : []),
]);
return {
id: capabilityId.workspaceRevision("workspace-revision:resource-validation"),
workspaceId: capabilityId.workspace("workspace:resource-validation"),
parentRevisionIds: [],
sourceRootCommit: resource.revision.source.commit,
atoms: uniqueAtoms([...(environment.externalAtoms ?? []), ...resource.externalAtoms]),
sharedAttachments: [],
interfaceImports: uniqueExactRevisions([
...(environment.interfaceClosure ?? []),
...[...(environment.interfaces?.values() ?? [])],
...(resource.kind === "interface" ? [resource.revision] : []),
...(resource.specializations ?? []),
...resource.externalInterfaces
.filter((requirement) => !resolvedInterfaceIds.has(requirement.revisionId))
.map(
(requirement): InterfaceRevision => ({
interfaceId: capabilityId.interface(`external:${requirement.revisionId}`),
revisionId: requirement.revisionId,
displayName: requirement.binding,
source: {
repository: `https://external.invalid/${encodeURIComponent(requirement.revisionId)}.git`,
commit: "0".repeat(40),
},
members: [],
}),
),
])
.filter((entry) => !entry.template)
.map((entry) => ({ ...entry, argumentRequirements: [] })),
packageImports: uniqueExactRevisions([
...(environment.packageClosure ?? []),
...[...(environment.packages?.values() ?? [])],
...(resource.kind === "package" ? [resource.revision] : []),
]),
conformances: [],
constructors: [],
};
};
const compileCapabilityResourceSourceInternal = (
sourceText: string,
options: {
source: SourceRevision;
fileName?: string;
environment?: CapabilityImportEnvironment;
},
): CapabilityResourceCompileResult => {
const fileName = options.fileName ?? "<memory>";
const environment = options.environment ?? {};
const { tree, diagnostics } = parseDocument(sourceText, fileName);
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const interfaceContext = tree.interfaceResourceDecl();
const packageContext = tree.packageResourceDecl();
if (!interfaceContext && !packageContext) {
return {
ok: false,
diagnostics: [
{
phase: "lowering",
code: "expected-resource",
message: "Expected a standalone interface or package resource document",
fileName,
line: 1,
column: 0,
},
],
};
}
const state = newLoweringState(fileName, diagnostics);
const preamble = resourcePreambleParts((interfaceContext ?? packageContext)!.resourcePreamble());
const externalAtoms = externalAtomsFrom(state, preamble.atoms);
const imports = registerImports(state, preamble.imports, environment);
const externalInterfaces = externalInterfacesFrom(state, preamble.interfaces);
state.types.declareAliases(preamble.aliases);
let resource: CapabilityResource;
if (interfaceContext) {
const alias = identifier(interfaceContext.identifier());
state.types.register(alias, {
interfaceId: capabilityId.interface(stringValue(interfaceContext.stringLiteral(0))),
revisionId: capabilityId.interfaceRevision(stringValue(interfaceContext.stringLiteral(1))),
displayName: alias,
source: options.source,
members: [],
});
declareSymbol(
state,
state.interfaces,
alias,
{
revisionId: capabilityId.interfaceRevision(stringValue(interfaceContext.stringLiteral(1))),
contractAvailable: true,
members: new Map(),
},
interfaceContext,
"interface",
);
resource = {
kind: "interface",
imports,
externalAtoms,
externalInterfaces,
revision:
interfaceContext.typeParameters() || hasSelfIdentifier(interfaceContext, state)
? atGenericSource(interfaceContext, () => lowerInterfaceTemplate(state, interfaceContext, options.source))
: lowerInterface(state, interfaceContext, options.source),
};
} else {
const context = packageContext!;
const alias = identifier(context.identifier());
declareSymbol(
state,
state.packages,
alias,
{
revisionId: capabilityId.packageRevision(stringValue(context.stringLiteral(1))),
exports: new Map(),
},
context,
"package",
);
resource = {
kind: "package",
imports,
externalAtoms,
externalInterfaces,
revision: lowerPackage(state, context, options.source),
};
if (resource.revision.components?.length) {
resource.revision.clientInterfaces = Object.fromEntries(
imports.flatMap((entry) => {
if (entry.kind !== "interface") return [];
const contract = options.environment?.interfaces?.get(entry.binding);
return contract && !contract.template ? [[entry.binding, contract.revisionId]] : [];
}),
);
}
}
if (resource.kind === "interface") {
state.types.register(identifier(interfaceContext!.identifier()), resource.revision);
atGenericSource(interfaceContext!, () => state.types.validateTemplate(resource.revision));
} else {
const names = new Set(resource.revision.exports.map((entry) => entry.displayName));
const ids = new Set(resource.revision.exports.map((entry) => entry.id));
for (const entry of resource.revision.genericExports ?? []) {
if (names.has(entry.displayName) || ids.has(entry.id))
throw new GenericTypeError("duplicate-package-export", entry.displayName, "Duplicate generic export");
names.add(entry.displayName);
ids.add(entry.id);
if (
entry.kind === "operation" &&
(entry.mode === "watch-start" || entry.mode === "subscribe") !== Boolean(entry.eventType)
)
throw new GenericTypeError(
"invalid-operation",
entry.displayName,
"Only watch-start/subscribe operations must declare an event type",
);
const portIds = new Set<string>(),
portNames = new Set<string>();
const types: ValueTypeExpression[] = [
entry.inputType,
entry.outputType,
...(entry.eventType ? [entry.eventType] : []),
];
const requires = entry.parameters.flatMap((parameter) =>
parameter.kind === "object" ? parameter.implements : [],
);
for (const port of entry.dependencyPorts) {
if (portIds.has(port.id) || portNames.has(port.displayName))
throw new GenericTypeError("duplicate-dependency-port", entry.displayName, "Duplicate port");
portIds.add(port.id);
portNames.add(port.displayName);
const requirement = port.requirement;
if (requirement.kind === "state") {
if (requirement.primitives.some((p) => !["read", "write", "edit", "watch-start", "watch-stop"].includes(p)))
throw new GenericTypeError("invalid-port-primitive", port.displayName, "Invalid state primitive");
types.push(requirement.valueType);
}
if (requirement.kind === "edge") types.push({ kind: "object-ref", expectation: requirement.target });
if (requirement.kind === "constructor" || requirement.kind === "creation")
types.push(requirement.inputType, { kind: "object-ref", expectation: requirement.target });
if (requirement.kind === "interface") requires.push(requirement.application);
}
if (entry.receiverRequirement.kind === "interfaces") requires.push(...entry.receiverRequirement.interfaces);
state.types.validateTemplate({
interfaceId: capabilityId.interface(entry.id),
revisionId: capabilityId.interfaceRevision(entry.id),
displayName: entry.displayName,
source: resource.revision.source,
members: [],
template: {
parameters: entry.parameters,
aliases: entry.aliases,
usesSelf: false,
requires,
members: types.map((type, index) => ({
kind: "value",
id: capabilityId.member(String(index)),
displayName: String(index),
valueType: type,
operations: [],
})),
},
});
}
}
resource.specializations = [...state.types.applications.values()];
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const compiled = compileWorkspaceRevision(resourceValidationWorkspace(resource, environment));
if (!compiled.ok) {
return {
ok: false,
diagnostics: validationDiagnostics(fileName, compiled.issues),
};
}
return { ok: true, resource, diagnostics: [] };
};
const compileCapabilitySourceInternal = (
source: string,
fileName = "<memory>",
environment: CapabilityImportEnvironment = {},
): CapabilitySourceCompileResult => {
const { tree, diagnostics } = parseDocument(source, fileName);
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const workspaceContext = tree.workspaceDecl();
if (!workspaceContext) {
return {
ok: false,
diagnostics: [
{
phase: "lowering",
code: "expected-workspace",
message: "Expected a workspace document",
fileName,
line: 1,
column: 0,
},
],
};
}
const state = newLoweringState(fileName, diagnostics);
for (const item of workspaceContext.workspaceItem()) {
if (item.sourceImportDecl())
loweringIssue(state, item, "unresolved-source-import", "Local imports require the workspace repository compiler");
}
if (diagnostics.length) return { ok: false, diagnostics };
const lowered = lowerWorkspace(state, workspaceContext, environment);
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const compiled = compileWorkspaceRevision(lowered.workspace);
if (!compiled.ok) {
return {
ok: false,
diagnostics: validationDiagnostics(fileName, compiled.issues),
};
}
return {
ok: true,
workspace: lowered.workspace,
plan: compiled.plan,
imports: lowered.imports,
diagnostics: [],
};
};
const genericDiagnostic = (error: GenericTypeError, fileName: string): CapabilitySourceDiagnostic => ({
phase: "lowering",
code: error.code,
message: error.message,
fileName,
line: genericLocations.get(error)?.line ?? 0,
column: genericLocations.get(error)?.column ?? 0,
path: error.path,
});
export const compileCapabilityResourceSource = (
...args: Parameters<typeof compileCapabilityResourceSourceInternal>
): CapabilityResourceCompileResult => {
try {
return compileCapabilityResourceSourceInternal(...args);
} catch (error) {
if (!(error instanceof GenericTypeError)) throw error;
return { ok: false, diagnostics: [genericDiagnostic(error, args[1].fileName ?? "<memory>")] };
}
};
export const compileCapabilitySource = (
...args: Parameters<typeof compileCapabilitySourceInternal>
): CapabilitySourceCompileResult => {
try {
return compileCapabilitySourceInternal(...args);
} catch (error) {
if (!(error instanceof GenericTypeError)) throw error;
return { ok: false, diagnostics: [genericDiagnostic(error, args[1] ?? "<memory>")] };
}
};