1489 lines
39 KiB
TypeScript
1489 lines
39 KiB
TypeScript
import {
|
|||
|
|
BaseErrorListener,
|
||
|
|
CharStream,
|
||
|
|
CommonTokenStream,
|
||
|
|
type ATNSimulator,
|
||
|
|
type ParserRuleContext,
|
||
|
|
type RecognitionException,
|
||
|
|
type Recognizer,
|
||
|
|
type Token,
|
||
|
|
} from "antlr4ng";
|
||
|
|
import {
|
||
|
|
capabilityId,
|
||
|
|
cardinalityValueType,
|
||
|
|
compileWorkspaceRevision,
|
||
|
|
valueType,
|
||
|
|
type AtomId,
|
||
|
|
type BoundDependency,
|
||
|
|
type CompiledWorkspaceRevision,
|
||
|
|
type DependencyPort,
|
||
|
|
type EdgeCardinality,
|
||
|
|
type EdgeDefinition,
|
||
|
|
type EdgeEndpoint,
|
||
|
|
type EdgeEndpointConstraint,
|
||
|
|
type EdgePrimitive,
|
||
|
|
type InterfaceMember,
|
||
|
|
type InterfaceOperation,
|
||
|
|
type InterfaceOperationMode,
|
||
|
|
type InterfaceRevision,
|
||
|
|
type PackageExport,
|
||
|
|
type PackageReceiverRequirement,
|
||
|
|
type PackageRevision,
|
||
|
|
type PersistentAttachment,
|
||
|
|
type SourceRevision,
|
||
|
|
type StatePrimitive,
|
||
|
|
type StateSlotDefinition,
|
||
|
|
type ValueType,
|
||
|
|
type WorkspaceRevision,
|
||
|
|
} from "../capability-model/index.js";
|
||
|
|
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
|
||
|
|
import { QuixosCapabilityParser,
|
||
|
|
type AttachmentDeclContext,
|
||
|
|
type ConformanceDeclContext,
|
||
|
|
type DependencyBindingBlockContext,
|
||
|
|
type DependencyPortContext,
|
||
|
|
type EdgeDeclContext,
|
||
|
|
type EdgeEndpointContext,
|
||
|
|
type InterfaceDeclContext,
|
||
|
|
type PackageConstructorExportContext,
|
||
|
|
type PackageDeclContext,
|
||
|
|
type PackageFunctionExportContext,
|
||
|
|
type PackageOperationExportContext,
|
||
|
|
type OperationMemberContext,
|
||
|
|
type SourceBlockContext,
|
||
|
|
type StateDeclContext,
|
||
|
|
type TargetConstraintContext,
|
||
|
|
type ValueMemberContext,
|
||
|
|
type ValueTypeContext,
|
||
|
|
type RelationshipMemberContext,
|
||
|
|
type WorkspaceDeclContext,
|
||
|
|
} from "./generated/QuixosCapabilityParser.js";
|
||
|
|
|
||
|
|
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;
|
||
|
|
diagnostics: [];
|
||
|
|
}
|
||
|
|
| {
|
||
|
|
ok: false;
|
||
|
|
diagnostics: CapabilitySourceDiagnostic[];
|
||
|
|
};
|
||
|
|
|
||
|
|
interface InterfaceSymbol {
|
||
|
|
revisionId: InterfaceRevision["revisionId"];
|
||
|
|
members: Map<
|
||
|
|
string,
|
||
|
|
{
|
||
|
|
operations: Map<string, InterfaceOperation["id"]>;
|
||
|
|
}
|
||
|
|
>;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface PackageExportSymbol {
|
||
|
|
exportId: PackageExport["id"];
|
||
|
|
ports: Map<string, DependencyPort["id"]>;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface PackageSymbol {
|
||
|
|
revisionId: PackageRevision["revisionId"];
|
||
|
|
exports: Map<string, PackageExportSymbol>;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface AttachmentSymbol {
|
||
|
|
attachment: PersistentAttachment;
|
||
|
|
projections: Map<string, EdgeEndpoint["projectionId"]>;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface LoweringState {
|
||
|
|
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 lowerSource = (context: SourceBlockContext): SourceRevision => ({
|
||
|
|
repository: stringValue(context.stringLiteral(0)),
|
||
|
|
commit: stringValue(context.stringLiteral(1)),
|
||
|
|
});
|
||
|
|
|
||
|
|
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.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");
|
||
|
|
}
|
||
|
|
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.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 valueOperation = (
|
||
|
|
displayName: string,
|
||
|
|
id: InterfaceOperation["id"],
|
||
|
|
value: ValueType,
|
||
|
|
): InterfaceOperation => {
|
||
|
|
switch (displayName) {
|
||
|
|
case "get":
|
||
|
|
return {
|
||
|
|
id,
|
||
|
|
displayName,
|
||
|
|
inputType: valueType.unit,
|
||
|
|
outputType: value,
|
||
|
|
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,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
} else if (operation.SET()) {
|
||
|
|
operations.push(
|
||
|
|
valueOperation(
|
||
|
|
"set",
|
||
|
|
capabilityId.operation(stringValue(operation.stringLiteral(0))),
|
||
|
|
memberValueType,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
} 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",
|
||
|
|
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,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
kind: "relationship",
|
||
|
|
id: capabilityId.member(stringValue(context.stringLiteral())),
|
||
|
|
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",
|
||
|
|
}],
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
const lowerInterface = (
|
||
|
|
state: LoweringState,
|
||
|
|
context: InterfaceDeclContext,
|
||
|
|
): 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,
|
||
|
|
{
|
||
|
|
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: lowerSource(context.sourceBlock()),
|
||
|
|
members,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
const lowerDependencyPort = (
|
||
|
|
state: LoweringState,
|
||
|
|
context: DependencyPortContext,
|
||
|
|
): DependencyPort | undefined => {
|
||
|
|
const name = identifier(context.identifier(0));
|
||
|
|
const id = capabilityId.dependencyPort(stringValue(context.stringLiteral()));
|
||
|
|
if (context.STATE()) {
|
||
|
|
const primitives = context
|
||
|
|
.primitiveList()!
|
||
|
|
.primitive()
|
||
|
|
.map((entry) => text(entry));
|
||
|
|
const invalid = primitives.filter(
|
||
|
|
(entry) => !["read", "write", "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()) {
|
||
|
|
const target = requireSymbol(
|
||
|
|
state,
|
||
|
|
state.interfaces,
|
||
|
|
targetName,
|
||
|
|
context,
|
||
|
|
"interface",
|
||
|
|
);
|
||
|
|
return target
|
||
|
|
? {
|
||
|
|
id,
|
||
|
|
displayName: name,
|
||
|
|
requirement: {
|
||
|
|
kind: "receiver-interface",
|
||
|
|
interfaceRevisionId: target.revisionId,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
: undefined;
|
||
|
|
}
|
||
|
|
const atomId = requireSymbol(state, state.atoms, targetName, context, "atom");
|
||
|
|
return atomId
|
||
|
|
? {
|
||
|
|
id,
|
||
|
|
displayName: name,
|
||
|
|
requirement: { kind: "constructor", atomId },
|
||
|
|
}
|
||
|
|
: 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.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}`),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const list = context.identifierList();
|
||
|
|
return {
|
||
|
|
kind: "all-interfaces",
|
||
|
|
interfaceRevisionIds: (list?.identifier() ?? []).map((entry) => {
|
||
|
|
const name = identifier(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,
|
||
|
|
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",
|
||
|
|
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 lowerPackage = (
|
||
|
|
state: LoweringState,
|
||
|
|
context: PackageDeclContext,
|
||
|
|
): PackageRevision => {
|
||
|
|
const alias = identifier(context.identifier());
|
||
|
|
const exports = context.packageExport().map((exportContext) => {
|
||
|
|
const operation = exportContext.packageOperationExport();
|
||
|
|
if (operation) {
|
||
|
|
return lowerPackageOperation(state, alias, operation);
|
||
|
|
}
|
||
|
|
const fn = exportContext.packageFunctionExport();
|
||
|
|
if (fn) {
|
||
|
|
return lowerPackageFunction(state, alias, fn);
|
||
|
|
}
|
||
|
|
return lowerPackageConstructor(
|
||
|
|
state,
|
||
|
|
alias,
|
||
|
|
exportContext.packageConstructorExport()!,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
packageId: capabilityId.package(stringValue(context.stringLiteral(0))),
|
||
|
|
revisionId: state.packages.get(alias)!.revisionId,
|
||
|
|
displayName: alias,
|
||
|
|
source: lowerSource(context.sourceBlock()),
|
||
|
|
exports,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
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.OPTIMISTIC_REGISTER()
|
||
|
|
? { kind: "optimistic-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()),
|
||
|
|
),
|
||
|
|
displayName: identifier(context.identifier()),
|
||
|
|
constraint,
|
||
|
|
cardinality: lowerCardinality(context.cardinality()),
|
||
|
|
ordered: Boolean(context.ORDERED()),
|
||
|
|
}
|
||
|
|
: 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 [];
|
||
|
|
}
|
||
|
|
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 [];
|
||
|
|
}
|
||
|
|
return [{ portId, binding: { kind: "state", slotId: attachment.attachment.id } }];
|
||
|
|
}
|
||
|
|
if (entry.EDGE()) {
|
||
|
|
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}`,
|
||
|
|
);
|
||
|
|
return projectionId
|
||
|
|
? [
|
||
|
|
{
|
||
|
|
portId,
|
||
|
|
binding: {
|
||
|
|
kind: "edge",
|
||
|
|
edgeTypeId: attachment.attachment.id,
|
||
|
|
projectionId,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
}
|
||
|
|
if (entry.INTERFACE()) {
|
||
|
|
const interfaceName = identifier(entry.identifier(1));
|
||
|
|
const interfaceSymbol = requireSymbol(
|
||
|
|
state,
|
||
|
|
state.interfaces,
|
||
|
|
interfaceName,
|
||
|
|
entry,
|
||
|
|
"interface",
|
||
|
|
);
|
||
|
|
return interfaceSymbol
|
||
|
|
? [
|
||
|
|
{
|
||
|
|
portId,
|
||
|
|
binding: {
|
||
|
|
kind: "receiver-interface",
|
||
|
|
interfaceRevisionId: interfaceSymbol.revisionId,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
}
|
||
|
|
const atomName = identifier(entry.identifier(1));
|
||
|
|
const atomId = requireSymbol(state, state.atoms, atomName, entry, "atom");
|
||
|
|
return atomId
|
||
|
|
? [{ portId, binding: { kind: "constructor", atomId } }]
|
||
|
|
: [];
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
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");
|
||
|
|
const interfaceSymbol = requireSymbol(
|
||
|
|
state,
|
||
|
|
state.interfaces,
|
||
|
|
interfaceName,
|
||
|
|
context,
|
||
|
|
"interface",
|
||
|
|
);
|
||
|
|
if (!atomId || !interfaceSymbol) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
const operationBindings = context.conformanceItem().flatMap<
|
||
|
|
WorkspaceRevision["conformances"][number]["operationBindings"][number]
|
||
|
|
>((item) => {
|
||
|
|
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.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",
|
||
|
|
);
|
||
|
|
const exportSymbol = packageSymbol
|
||
|
|
? requireSymbol(
|
||
|
|
state,
|
||
|
|
packageSymbol.exports,
|
||
|
|
exportName,
|
||
|
|
provider,
|
||
|
|
`export on ${packageName}`,
|
||
|
|
)
|
||
|
|
: undefined;
|
||
|
|
return packageSymbol && exportSymbol
|
||
|
|
? [
|
||
|
|
{
|
||
|
|
operationId,
|
||
|
|
binding: {
|
||
|
|
kind: "package" as const,
|
||
|
|
packageRevisionId: packageSymbol.revisionId,
|
||
|
|
exportId: exportSymbol.exportId,
|
||
|
|
dependencies: lowerBoundDependencies(
|
||
|
|
state,
|
||
|
|
provider.dependencyBindingBlock() ?? undefined,
|
||
|
|
exportSymbol,
|
||
|
|
),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
atomId,
|
||
|
|
interfaceRevisionId: interfaceSymbol.revisionId,
|
||
|
|
privateAttachments,
|
||
|
|
operationBindings,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
const lowerWorkspace = (
|
||
|
|
state: LoweringState,
|
||
|
|
context: WorkspaceDeclContext,
|
||
|
|
): WorkspaceRevision => {
|
||
|
|
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 interfaceContext = item.interfaceDecl();
|
||
|
|
if (interfaceContext) {
|
||
|
|
declareSymbol(
|
||
|
|
state,
|
||
|
|
state.interfaces,
|
||
|
|
identifier(interfaceContext.identifier()),
|
||
|
|
{
|
||
|
|
revisionId: capabilityId.interfaceRevision(
|
||
|
|
stringValue(interfaceContext.stringLiteral(1)),
|
||
|
|
),
|
||
|
|
members: new Map(),
|
||
|
|
},
|
||
|
|
interfaceContext,
|
||
|
|
"interface",
|
||
|
|
);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const packageContext = item.packageDecl();
|
||
|
|
if (packageContext) {
|
||
|
|
declareSymbol(
|
||
|
|
state,
|
||
|
|
state.packages,
|
||
|
|
identifier(packageContext.identifier()),
|
||
|
|
{
|
||
|
|
revisionId: capabilityId.packageRevision(
|
||
|
|
stringValue(packageContext.stringLiteral(1)),
|
||
|
|
),
|
||
|
|
exports: new Map(),
|
||
|
|
},
|
||
|
|
packageContext,
|
||
|
|
"package",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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,
|
||
|
|
},
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
});
|
||
|
|
|
||
|
|
const interfaceImports = items.flatMap((item) => {
|
||
|
|
const interfaceContext = item.interfaceDecl();
|
||
|
|
return interfaceContext ? [lowerInterface(state, interfaceContext)] : [];
|
||
|
|
});
|
||
|
|
|
||
|
|
const packageImports = items.flatMap((item) => {
|
||
|
|
const packageContext = item.packageDecl();
|
||
|
|
return packageContext ? [lowerPackage(state, packageContext)] : [];
|
||
|
|
});
|
||
|
|
|
||
|
|
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) {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const conformances = items.flatMap((item) => {
|
||
|
|
const context = item.conformanceDecl();
|
||
|
|
if (!context) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
const conformance = 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,
|
||
|
|
),
|
||
|
|
},
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
id: capabilityId.workspaceRevision(stringValue(context.stringLiteral(1))),
|
||
|
|
workspaceId: capabilityId.workspace(stringValue(context.stringLiteral(0))),
|
||
|
|
parentRevisionIds: [],
|
||
|
|
sourceRootCommit: stringValue(context.stringLiteral(2)),
|
||
|
|
atoms,
|
||
|
|
sharedAttachments,
|
||
|
|
interfaceImports,
|
||
|
|
packageImports,
|
||
|
|
conformances,
|
||
|
|
constructors,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
export const compileCapabilitySource = (
|
||
|
|
source: string,
|
||
|
|
fileName = "<memory>",
|
||
|
|
): CapabilitySourceCompileResult => {
|
||
|
|
const diagnostics: CapabilitySourceDiagnostic[] = [];
|
||
|
|
const listener = new SyntaxErrorListener(fileName, diagnostics);
|
||
|
|
const lexer = new QuixosCapabilityLexer(CharStream.fromString(source));
|
||
|
|
lexer.removeErrorListeners();
|
||
|
|
lexer.addErrorListener(listener);
|
||
|
|
const parser = new QuixosCapabilityParser(
|
||
|
|
new CommonTokenStream(lexer),
|
||
|
|
);
|
||
|
|
parser.removeErrorListeners();
|
||
|
|
parser.addErrorListener(listener);
|
||
|
|
const tree = parser.document();
|
||
|
|
if (diagnostics.length > 0) {
|
||
|
|
return { ok: false, diagnostics };
|
||
|
|
}
|
||
|
|
|
||
|
|
const state: LoweringState = {
|
||
|
|
fileName,
|
||
|
|
diagnostics,
|
||
|
|
atoms: new Map(),
|
||
|
|
interfaces: new Map(),
|
||
|
|
packages: new Map(),
|
||
|
|
attachments: new Map(),
|
||
|
|
};
|
||
|
|
const workspace = lowerWorkspace(state, tree.workspaceDecl());
|
||
|
|
if (diagnostics.length > 0) {
|
||
|
|
return { ok: false, diagnostics };
|
||
|
|
}
|
||
|
|
const compiled = compileWorkspaceRevision(workspace);
|
||
|
|
if (!compiled.ok) {
|
||
|
|
return {
|
||
|
|
ok: false,
|
||
|
|
diagnostics: compiled.issues.map((entry) => ({
|
||
|
|
phase: "validation",
|
||
|
|
code: entry.code,
|
||
|
|
message: entry.message,
|
||
|
|
fileName,
|
||
|
|
line: 0,
|
||
|
|
column: 0,
|
||
|
|
path: entry.path,
|
||
|
|
})),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return { ok: true, workspace, plan: compiled.plan, diagnostics: [] };
|
||
|
|
};
|