Skip to content

Commit

Permalink
feat: rework the service registration, rework the exposed methods
Browse files Browse the repository at this point in the history
  • Loading branch information
GideonKoenig committed Jan 20, 2025
1 parent 674cda8 commit 6f02bf7
Show file tree
Hide file tree
Showing 16 changed files with 1,185 additions and 1 deletion.
40 changes: 39 additions & 1 deletion packages/safe-ds-lang/src/language/communication/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { MessageDirection, NotificationType0, RequestType0 } from 'vscode-languageserver';
import { NotificationType } from 'vscode-languageserver-protocol';
import { NotificationType, RequestType } from 'vscode-languageserver-protocol';
import { UUID } from 'node:crypto';
import { Buildin, Collection } from '../graphical-editor/global.js';
import { Uri } from 'vscode';

export namespace InstallRunnerNotification {
export const method = 'runner/install' as const;
Expand Down Expand Up @@ -91,3 +93,39 @@ export namespace IsRunnerReadyRequest {
export const messageDirection = MessageDirection.clientToServer;
export const type = new RequestType0(method);
}

export namespace GraphicalEditorSyncEventNotification {
export const method = 'graphical-editor/sync-event' as const;
export const messageDirection = MessageDirection.serverToClient;
export const type = new NotificationType<Collection>(method);
}

export namespace GraphicalEditorOpenSyncChannelRequest {
export const method = 'graphical-editor/openSyncChannel' as const;
export const messageDirection = MessageDirection.clientToServer;
export const type = new RequestType<Uri, void, void>(method);
}

export namespace GraphicalEditorCloseSyncChannelRequest {
export const method = 'graphical-editor/closeSyncChannel' as const;
export const messageDirection = MessageDirection.clientToServer;
export const type = new RequestType<Uri, void, void>(method);
}

export namespace GraphicalEditorGetDocumentationRequest {
export const method = 'graphical-editor/getDocumentation' as const;
export const messageDirection = MessageDirection.clientToServer;
export const type = new RequestType<{ uri: Uri; uniquePath: string }, string | undefined, void>(method);
}

export namespace GraphicalEditorGetBuildinsRequest {
export const method = 'graphical-editor/getBuildins' as const;
export const messageDirection = MessageDirection.clientToServer;
export const type = new RequestType<void, Buildin[], void>(method);
}

export namespace GraphicalEditorParseDocumentRequest {
export const method = 'graphical-editor/parseDocument' as const;
export const messageDirection = MessageDirection.clientToServer;
export const type = new RequestType<Uri, Collection, void>(method);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { isSdsLiteral, SdsArgument } from '../../generated/ast.js';
import { CustomError } from '../global.js';
import { Call } from './call.js';
import { Placeholder } from './placeholder.js';
import { Expression, GenericExpression } from './expression.js';
import { Parameter } from './parameter.js';
import { Parser } from './parser.js';

export class Argument {
constructor(
public readonly text: string,
public readonly reference: GenericExpression | Call | Placeholder | Parameter | undefined,
public readonly parameterName?: string,
) {}

public static parse(node: SdsArgument, parser: Parser) {
if (!node.value.$cstNode) return parser.pushError('CstNode missing', node.value);
const text = node.value.$cstNode.text;

let expression;
if (!isSdsLiteral(node.value)) expression = Expression.parse(node.value, parser);
if (expression instanceof CustomError) return expression;

if (node.parameter && !node.parameter.ref) return parser.pushError('Missing Parameterreference', node);
const parameterName = node.parameter?.ref?.name;

return new Argument(text, expression, parameterName);
}
}
236 changes: 236 additions & 0 deletions packages/safe-ds-lang/src/language/graphical-editor/ast-parser/call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import {
SdsCall,
SdsClass,
SdsExpression,
SdsFunction,
SdsMemberAccess,
SdsPlaceholder,
SdsReference,
SdsSegment,
isSdsCall,
isSdsClass,
isSdsFunction,
isSdsMemberAccess,
isSdsPlaceholder,
isSdsReference,
isSdsSegment,
} from '../../generated/ast.js';
import { CustomError } from '../global.js';
import { Argument } from './argument.js';
import { Edge, Port } from './edge.js';
import { GenericExpression } from './expression.js';
import { Parameter } from './parameter.js';
import { Placeholder } from './placeholder.js';
import { Result } from './result.js';
import { filterErrors } from './utils.js';
import { Parser } from './parser.js';

export class Call {
private constructor(
public readonly id: number,
public readonly name: string,
public readonly self: string | undefined,
public readonly parameterList: Parameter[],
public readonly resultList: Result[],
public readonly category: string,
public readonly uniquePath: string,
) {}

public static parse(node: SdsCall, parser: Parser): Call | CustomError {
const id = parser.getNewId();

if (!isValidCallReceiver(node.receiver)) {
return parser.pushError(`Invalid Call receiver: ${debugInvalidCallReceiver(node.receiver)}`, node.receiver);
}

let name = '';
let self: string | undefined = undefined;
let category = '';
let argumentList: Argument[] = [];
let parameterList: Parameter[] = [];
let resultList: Result[] = [];

argumentList = filterErrors(node.argumentList.arguments.map((argument) => Argument.parse(argument, parser)));

if (isSdsMemberAccess(node.receiver)) {
const tmp = Call.parseSelf(node.receiver, id, parser);
if (tmp instanceof CustomError) return tmp;
self = tmp;

const functionDeclaration = node.receiver.member.target.ref;
name = functionDeclaration.name;
category = parser.getCategory(functionDeclaration)?.name ?? '';

resultList = filterErrors(
(functionDeclaration.resultList?.results ?? []).map((result) => Result.parse(result, parser)),
);
parameterList = filterErrors(
(functionDeclaration.parameterList?.parameters ?? []).map((parameter) =>
Parameter.parse(parameter, parser),
),
);
}

if (isSdsReference(node.receiver) && isSdsClass(node.receiver.target.ref)) {
const classDeclaration = node.receiver.target.ref;

name = 'new';
self = classDeclaration.name;
category = 'Modeling';

if (!classDeclaration.parameterList)
return parser.pushError('Missing constructor parameters', classDeclaration);
parameterList = filterErrors(
classDeclaration.parameterList.parameters.map((parameter) => Parameter.parse(parameter, parser)),
);
resultList = [new Result('new', classDeclaration.name)];
}

if (isSdsReference(node.receiver) && isSdsSegment(node.receiver.target.ref)) {
const segmentDeclaration = node.receiver.target.ref;

self = '';
name = segmentDeclaration.name;
category = 'Segment';

resultList = filterErrors(
(segmentDeclaration.resultList?.results ?? []).map((result) => Result.parse(result, parser)),
);
parameterList = filterErrors(
(segmentDeclaration.parameterList?.parameters ?? []).map((parameter) =>
Parameter.parse(parameter, parser),
),
);
}

const parameterListCompleted = matchArgumentsToParameter(parameterList, argumentList, node, id, parser);
if (parameterListCompleted instanceof CustomError) return parameterListCompleted;

const call = new Call(id, name, self, parameterListCompleted, resultList, category, parser.getUniquePath(node));
parser.graph.callList.push(call);
return call;
}

private static parseSelf(node: CallReceiver, id: number, parser: Parser) {
if (isSdsMemberAccess(node)) {
if (isSdsCall(node.receiver)) {
const call = Call.parse(node.receiver, parser);
if (call instanceof CustomError) return call;

if (call.resultList.length > 1) return parser.pushError('To many result', node.receiver);
if (call.resultList.length < 1) return parser.pushError('Missing result', node.receiver);

Edge.create(Port.fromResult(call.resultList[0]!, call.id), Port.fromName(id, 'self'), parser);
} else if (isSdsReference(node.receiver)) {
const receiver = node.receiver.target.ref;

if (isSdsClass(receiver)) {
return receiver.name;
} else if (isSdsPlaceholder(receiver)) {
const placeholder = Placeholder.parse(receiver, parser);
Edge.create(Port.fromPlaceholder(placeholder, false), Port.fromName(id, 'self'), parser);
}
}
}
return '';
}
}

const matchArgumentsToParameter = (
parameterList: Parameter[],
argumentList: Argument[],
callNode: SdsCall,
id: number,
parser: Parser,
): Parameter[] | CustomError => {
for (const [i, parameter] of parameterList.entries()) {
const argumentIndexMatched = argumentList[i];
if (argumentIndexMatched instanceof CustomError) return argumentIndexMatched;

const argumentNameMatched = argumentList.find(
(argument) => !(argument instanceof CustomError) && argument.parameterName === parameter.name,
) as Argument | undefined;

if (argumentIndexMatched && argumentNameMatched && argumentIndexMatched !== argumentNameMatched)
return parser.pushError(`To many matches for ${parameter.name}`, callNode.argumentList);
const argument = argumentIndexMatched ?? argumentNameMatched;

if (argument) {
parameter.argumentText = argument.text;
if (argument.reference instanceof Call) {
const call = argument.reference;
if (call.resultList.length !== 1) return parser.pushError('Type missmatch', callNode.argumentList);
Edge.create(Port.fromResult(call.resultList[0]!, call.id), Port.fromParameter(parameter, id), parser);
}
if (argument.reference instanceof GenericExpression) {
const experession = argument.reference;
Edge.create(Port.fromGenericExpression(experession, false), Port.fromParameter(parameter, id), parser);
}
if (argument.reference instanceof Placeholder) {
const placeholder = argument.reference;
Edge.create(Port.fromPlaceholder(placeholder, false), Port.fromParameter(parameter, id), parser);
}
if (argument.reference instanceof Parameter) {
const segmentParameter = argument.reference;
Edge.create(Port.fromParameter(segmentParameter, -1), Port.fromParameter(parameter, id), parser);
}
continue;
}

if (!argument && parameter.defaultValue) {
continue;
}

if (!argument && !parameter.defaultValue) {
return parser.pushError(`Missing Argument for ${parameter.name}`, callNode);
}
}
return parameterList;
};

type CallReceiver =
| (SdsReference & { target: { ref: SdsClass | SdsSegment } })
| (SdsMemberAccess & {
member: {
target: { ref: SdsFunction };
};
receiver: SdsCall | { target: { ref: SdsPlaceholder | SdsClass } };
});

const isValidCallReceiver = (receiver: SdsExpression): receiver is CallReceiver => {
/* eslint-disable no-implicit-coercion */
return (
(isSdsMemberAccess(receiver) &&
!!receiver.member &&
!!receiver.member.target.ref &&
isSdsFunction(receiver.member.target.ref) &&
((isSdsReference(receiver.receiver) &&
(isSdsClass(receiver.receiver.target.ref) || isSdsPlaceholder(receiver.receiver.target.ref))) ||
isSdsCall(receiver.receiver))) ||
(isSdsReference(receiver) && (isSdsClass(receiver.target.ref) || isSdsSegment(receiver.target.ref)))
);
};

const debugInvalidCallReceiver = (receiver: SdsExpression): string => {
/* eslint-disable no-implicit-coercion */
if (isSdsMemberAccess(receiver)) {
if (!receiver.member) return 'MemberAccess: Missing member';
if (!receiver.member.target.ref) return 'MemberAccess: Missing member declaration';
if (!isSdsFunction(receiver.member.target.ref)) return 'MemberAccess: Member is not a function';
if (!isSdsCall(receiver.receiver) && !isSdsReference(receiver.receiver))
return `MemberAccess: Receiver is not a Reference or Call but - ${receiver.receiver.$type}`;
if (
isSdsReference(receiver.receiver) &&
!isSdsClass(receiver.receiver.target.ref) &&
isSdsReference(receiver.receiver) &&
!isSdsPlaceholder(receiver.receiver.target.ref)
)
return 'MemberAccess: Reference Receiver is not Class of Placeholder';
}
if (isSdsReference(receiver)) {
if (!isSdsClass(receiver.target.ref) && !isSdsSegment(receiver.target.ref))
return 'Reference: Not a class or segment';
}

return receiver.$type;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { GenericExpression } from './expression.js';
import { Parameter } from './parameter.js';
import { Parser } from './parser.js';
import { Placeholder } from './placeholder.js';
import { Result } from './result.js';
import { SegmentGroupId } from './segment.js';

export class Edge {
public constructor(
public readonly from: Port,
public readonly to: Port,
) {}

public static create(from: Port, to: Port, parser: Parser) {
parser.graph.edgeList.push(new Edge(from, to));
}
}

export class Port {
private constructor(
public readonly nodeId: string,
public readonly portIdentifier: string,
) {}

public static fromName = (nodeId: number, name: string): Port => {
return new Port(nodeId.toString(), name);
};

public static fromPlaceholder = (placeholder: Placeholder, input: boolean): Port => {
return new Port(placeholder.name, input ? 'target' : 'source');
};

public static fromResult = (result: Result, nodeId: number): Port => {
return new Port(nodeId.toString(), result.name);
};

public static fromParameter = (parameter: Parameter, nodeId: number): Port => {
return new Port(nodeId.toString(), parameter.name);
};

public static fromGenericExpression(node: GenericExpression, input: boolean) {
return new Port(node.id.toString(), input ? 'target' : 'source');
}

public static fromAssignee = (node: Placeholder | Result, input: boolean): Port => {
if (node instanceof Placeholder) {
return new Port(node.name, input ? 'target' : 'source');
}
return new Port(SegmentGroupId.toString(), node.name);
};

public static isPortList(object: any): object is Port[] {
return Array.isArray(object) && object.every((element) => element instanceof Port);
}
}
Loading

0 comments on commit 6f02bf7

Please sign in to comment.