From e47092f0114f159cc2255a1d328c5b9b16eaca10 Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 2 Oct 2024 16:51:48 +0200 Subject: [PATCH 01/16] Feat: Change port name delimiter from _ to ; --- src/features/dfdElements/outputPortBehaviorValidation.ts | 4 ++-- src/features/dfdElements/outputPortEditUi.ts | 2 +- src/features/dfdElements/ports.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index b2736e5..d59a749 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -23,11 +23,11 @@ export class PortBehaviorValidator { // Regex that validates a set statement. // Has the label type and label value that should be set as capturing groups. private static readonly SET_REGEX = - /^set +([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*) *= *(?: +|!|TRUE|FALSE|\|\||&&|\(|\)|[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*){2})+$/; + /^set +([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*) *= *(?: +|!|TRUE|FALSE|\|\||&&|\(|\)|[A-Za-z][A-Za-z0-9_;]*(?:\.[A-Za-z][A-Za-z0-9_;]*){2})+$/; // Regex that is used to extract all inputs, their label types and label values from a set statement. // Each input is a match with the input name, label type and label value as capturing groups. private static readonly SET_REGEX_EXPRESSION_INPUTS = - /([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*)/g; + /([A-Za-z][A-Za-z0-9_;]*)\.([A-Za-z][A-Za-z0-9_;]*)\.([A-Za-z][A-Za-z0-9_;]*)/g; // Regex matching alphanumeric characters. public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_]+/; diff --git a/src/features/dfdElements/outputPortEditUi.ts b/src/features/dfdElements/outputPortEditUi.ts index ee4c493..cb2552f 100644 --- a/src/features/dfdElements/outputPortEditUi.ts +++ b/src/features/dfdElements/outputPortEditUi.ts @@ -103,7 +103,7 @@ const dfdBehaviorLanguageMonarchDefinition: monaco.languages.IMonarchLanguage = root: [ // keywords and identifiers [ - /[a-zA-Z_$][\w$]*/, + /[a-zA-Z_;$][\w$]*/, { cases: { "@keywords": "keyword", diff --git a/src/features/dfdElements/ports.tsx b/src/features/dfdElements/ports.tsx index a61e740..f45f10c 100644 --- a/src/features/dfdElements/ports.tsx +++ b/src/features/dfdElements/ports.tsx @@ -54,7 +54,7 @@ export class DfdInputPortImpl extends SPortImpl { if (edgeNames.length === 0) { return undefined; } else { - return edgeNames.sort().join("_"); + return edgeNames.sort().join(";"); } } From f5be7ec11e81d5674a975b9cb69c00704d12df8d Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 2 Oct 2024 20:21:15 +0200 Subject: [PATCH 02/16] Feat: Change delimiter --- src/features/dfdElements/outputPortBehaviorValidation.ts | 6 +++--- src/features/dfdElements/outputPortEditUi.ts | 2 +- src/features/dfdElements/ports.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index d59a749..768ab29 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -23,13 +23,13 @@ export class PortBehaviorValidator { // Regex that validates a set statement. // Has the label type and label value that should be set as capturing groups. private static readonly SET_REGEX = - /^set +([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*) *= *(?: +|!|TRUE|FALSE|\|\||&&|\(|\)|[A-Za-z][A-Za-z0-9_;]*(?:\.[A-Za-z][A-Za-z0-9_;]*){2})+$/; + /^set +([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*) *= *(?: +|!|TRUE|FALSE|\|\||&&|\(|\)|[A-Za-z][A-Za-z0-9_\|]*(?:\.[A-Za-z][A-Za-z0-9_\|]*){2})+$/; // Regex that is used to extract all inputs, their label types and label values from a set statement. // Each input is a match with the input name, label type and label value as capturing groups. private static readonly SET_REGEX_EXPRESSION_INPUTS = - /([A-Za-z][A-Za-z0-9_;]*)\.([A-Za-z][A-Za-z0-9_;]*)\.([A-Za-z][A-Za-z0-9_;]*)/g; + /([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)/g; // Regex matching alphanumeric characters. - public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_]+/; + public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_\|]+/; constructor(@inject(LabelTypeRegistry) @optional() private readonly labelTypeRegistry?: LabelTypeRegistry) {} diff --git a/src/features/dfdElements/outputPortEditUi.ts b/src/features/dfdElements/outputPortEditUi.ts index cb2552f..6d7a28e 100644 --- a/src/features/dfdElements/outputPortEditUi.ts +++ b/src/features/dfdElements/outputPortEditUi.ts @@ -103,7 +103,7 @@ const dfdBehaviorLanguageMonarchDefinition: monaco.languages.IMonarchLanguage = root: [ // keywords and identifiers [ - /[a-zA-Z_;$][\w$]*/, + /[a-zA-Z_\|$][\w$]*/, { cases: { "@keywords": "keyword", diff --git a/src/features/dfdElements/ports.tsx b/src/features/dfdElements/ports.tsx index f45f10c..6d0f5e6 100644 --- a/src/features/dfdElements/ports.tsx +++ b/src/features/dfdElements/ports.tsx @@ -54,7 +54,7 @@ export class DfdInputPortImpl extends SPortImpl { if (edgeNames.length === 0) { return undefined; } else { - return edgeNames.sort().join(";"); + return edgeNames.sort().join("|"); } } From 3803b8722aa18f027ad4bbfbc399171fefbd2dbf Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 01:05:42 +0200 Subject: [PATCH 03/16] Feat: Changing Behaviour Definition, Validation and AutoCompletion --- .../outputPortBehaviorValidation.ts | 195 ++++++++++-------- src/features/dfdElements/outputPortEditUi.ts | 155 ++++++++++---- 2 files changed, 229 insertions(+), 121 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 768ab29..b3ac4ed 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -20,14 +20,17 @@ interface PortBehaviorValidationError { */ @injectable() export class PortBehaviorValidator { + private static readonly ASSIGNMENT_REGEX = + /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+;\{(((([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*))+(,\s*(([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)))*)?)\}\)+$/; + private static readonly FORWARDING_REGEX = /^Forwarding\(\{[A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*\}\)$/; + // Regex that validates a set statement. // Has the label type and label value that should be set as capturing groups. - private static readonly SET_REGEX = - /^set +([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*) *= *(?: +|!|TRUE|FALSE|\|\||&&|\(|\)|[A-Za-z][A-Za-z0-9_\|]*(?:\.[A-Za-z][A-Za-z0-9_\|]*){2})+$/; + private static readonly TERM_REGEX = + /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+$/; // Regex that is used to extract all inputs, their label types and label values from a set statement. // Each input is a match with the input name, label type and label value as capturing groups. - private static readonly SET_REGEX_EXPRESSION_INPUTS = - /([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)/g; + private static readonly SET_REGEX_EXPRESSION_INPUTS = /([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)/g; // Regex matching alphanumeric characters. public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_\|]+/; @@ -74,11 +77,11 @@ export class PortBehaviorValidator { return; } - if (line.startsWith("forward")) { + if (line.startsWith("Forwarding")) { return this.validateForwardStatement(line, lineNumber, port); } - if (line.startsWith("set")) { + if (line.startsWith("Assignment")) { return this.validateSetStatement(line, lineNumber, port); } @@ -95,7 +98,17 @@ export class PortBehaviorValidator { lineNumber: number, port: DfdOutputPortImpl, ): PortBehaviorValidationError[] | undefined { - const inputsString = line.substring("forward".length); + const match = line.match(PortBehaviorValidator.FORWARDING_REGEX); + if (!match) { + return [ + { + line: lineNumber, + message: "invalid forwarding(Template:Forwarding({in_ports})", + }, + ]; + } + + const inputsString = line.substring("Forwarding({".length, line.length - 2); const inputs = inputsString.split(",").map((input) => input.trim()); if (inputs.filter((input) => input !== "").length === 0) { return [ @@ -197,37 +210,12 @@ export class PortBehaviorValidator { lineNumber: number, port: DfdOutputPortImpl, ): PortBehaviorValidationError[] | undefined { - const match = line.match(PortBehaviorValidator.SET_REGEX); + const match = line.match(PortBehaviorValidator.ASSIGNMENT_REGEX); if (!match) { return [ { line: lineNumber, - message: "invalid set statement", - }, - ]; - } - - // Check that the label type and value that this statement tries to set are valid. - const setLabelType = match[1]; - const setLabelValue = match[2]; - const labelType = this.labelTypeRegistry?.getLabelTypes().find((type) => type.name === setLabelType); - if (!labelType) { - return [ - { - line: lineNumber, - message: `unknown label type: ${setLabelType}`, - colStart: line.indexOf(setLabelType), - colEnd: line.indexOf(setLabelType) + setLabelType.length, - }, - ]; - } - if (!labelType.values.find((value) => value.text === setLabelValue)) { - return [ - { - line: lineNumber, - message: `unknown label value of label type ${setLabelType}: ${setLabelValue}`, - colStart: line.indexOf(setLabelValue), - colEnd: line.indexOf(setLabelValue) + setLabelValue.length, + message: "invalid assignment(Template:Assignment({in_ports}; term; {out_label})", }, ]; } @@ -246,7 +234,7 @@ export class PortBehaviorValidator { return [ { line: lineNumber, - message: "invalid set statement: missing opening parenthesis", + message: "invalid assignment: missing opening parenthesis", colStart: strIdx, colEnd: strIdx + 1, }, @@ -258,23 +246,31 @@ export class PortBehaviorValidator { return [ { line: lineNumber, - message: "invalid set statement: missing closing parenthesis", + message: "invalid assignment: missing closing parenthesis", }, ]; } // Extract all used inputs, label types and the corresponding label values. - const expression = line.split("=")[1].trim(); // get everything after the = - if (expression.length === 0) { + const term = line.split(";")[1].trim(); // get everything after the = + if (term.length === 0) { return [ { line: lineNumber, - message: "invalid set statement: missing expression", + message: "invalid assignment: missing term", }, ]; } - const matches = [...expression.matchAll(PortBehaviorValidator.SET_REGEX_EXPRESSION_INPUTS)]; + const termMatch = term.match(PortBehaviorValidator.TERM_REGEX); + if (!termMatch) { + return [ + { + line: lineNumber, + message: "invalid term", + }, + ]; + } const node = port.parent; if (!(node instanceof DfdNodeImpl)) { @@ -282,37 +278,42 @@ export class PortBehaviorValidator { } const availableInputs = node.getAvailableInputs(); + const innerContent = line.substring("Assignment(".length, line.length - 1); + + // Step 2: Split by the semicolons to separate the blocks + const parts = innerContent.split(";").map((part) => part.trim()); + + const inPorts = parts[0] + .substring(1, parts[0].length - 1) + .split(",") + .map((variable) => variable.trim()); + const outLabel = parts[2] + .substring(1, parts[2].length - 1) + .split(",") + .map((variable) => variable.trim()); + // Check for each input access that the input exists and that the label type and value are valid. const inputAccessErrors = []; - for (const inputMatch of matches) { - const inputName = inputMatch[1]; - const inputLabelType = inputMatch[2]; - const inputLabelValue = inputMatch[3]; - if (!availableInputs.includes(inputName)) { + for (const inPortName of inPorts) { + if (!availableInputs.includes(inPortName) && inPortName !== "") { // Find all occurrences of the unavailable input. - let idx = line.indexOf(inputName); - while (idx !== -1) { - // Check that this is not a substring of another input. - if ( - // before must not be alphanumeric => start of this string must be the beginning of the input name - line[idx - 1]?.match(PortBehaviorValidator.REGEX_ALPHANUMERIC) && - line[idx + inputName.length] === "." // must be followed by a dot to access the label type of the input - ) { - inputAccessErrors.push({ - line: lineNumber, - message: `invalid/unknown input: ${inputName}`, - colStart: idx, - colEnd: idx + inputName.length, - }); - } - - idx = line.indexOf(inputName, idx + 1); - } + let idx = line.indexOf(inPortName); + inputAccessErrors.push({ + line: lineNumber, + message: `invalid/unknown input: ${inPortName}`, + colStart: idx, + colEnd: idx + inPortName.length, + }); continue; } + } + + for (const typeValuePair of outLabel) { + if (typeValuePair === "") continue; + const inputLabelType = typeValuePair.split(".")[0].trim(); const inputLabelTypeObject = this.labelTypeRegistry ?.getLabelTypes() .find((type) => type.name === inputLabelType); @@ -335,30 +336,58 @@ export class PortBehaviorValidator { idx = line.indexOf(inputLabelType, idx + 1); } - } else if (!inputLabelTypeObject.values.find((value) => value.text === inputLabelValue)) { - let idx = line.indexOf(inputLabelValue); - while (idx !== -1) { - // Check that this is not a substring of another label value. - if ( - // must start after a dot and end at the end of the alphanumeric text - line[idx - 1] === "." && - // Might be at the end of the line - (!line[idx + inputLabelValue.length] || - !line[idx + inputLabelValue.length].match(PortBehaviorValidator.REGEX_ALPHANUMERIC)) - ) { - inputAccessErrors.push({ - line: lineNumber, - message: `unknown label value of label type ${inputLabelType}: ${inputLabelValue}`, - colStart: idx, - colEnd: idx + inputLabelValue.length, - }); - } + } - idx = line.indexOf(inputLabelValue, idx + 1); + if (typeValuePair.indexOf(".") !== -1) { + const inputLabelValue = typeValuePair.split(".")[1].trim(); + + const inputLabelTypeObject = this.labelTypeRegistry + ?.getLabelTypes() + .find((type) => type.name === inputLabelType); + if (!inputLabelTypeObject) { + let idx = line.indexOf(inputLabelType); + while (idx !== -1) { + // Check that this is not a substring of another label type. + if ( + // must start after a dot and end before a dot + line[idx - 1] === "." && + line[idx + inputLabelType.length] === "." + ) { + inputAccessErrors.push({ + line: lineNumber, + message: `unknown label type: ${inputLabelType}`, + colStart: idx, + colEnd: idx + inputLabelType.length, + }); + } + + idx = line.indexOf(inputLabelType, idx + 1); + } + } else if (!inputLabelTypeObject.values.find((value) => value.text === inputLabelValue)) { + let idx = line.indexOf(inputLabelValue); + while (idx !== -1) { + // Check that this is not a substring of another label value. + if ( + // must start after a dot and end at the end of the alphanumeric text + line[idx - 1] === "." && + // Might be at the end of the line + (!line[idx + inputLabelValue.length] || + !line[idx + inputLabelValue.length].match(PortBehaviorValidator.REGEX_ALPHANUMERIC)) + ) { + inputAccessErrors.push({ + line: lineNumber, + message: `unknown label value of label type ${inputLabelType}: ${inputLabelValue}`, + colStart: idx, + colEnd: idx + inputLabelValue.length, + }); + } + + idx = line.indexOf(inputLabelValue, idx + 1); + } } } } - return inputAccessErrors.length > 0 ? inputAccessErrors : undefined; + return inputAccessErrors.length > 0 ? inputAccessErrors : []; } } diff --git a/src/features/dfdElements/outputPortEditUi.ts b/src/features/dfdElements/outputPortEditUi.ts index 6d7a28e..ee3bae1 100644 --- a/src/features/dfdElements/outputPortEditUi.ts +++ b/src/features/dfdElements/outputPortEditUi.ts @@ -82,7 +82,7 @@ export class OutputPortEditUIMouseListener extends MouseListener { } // More information and playground website for testing: https://microsoft.github.io/monaco-editor/monarch.html -const statementKeywords = ["forward", "set"]; +const statementKeywords = ["Forwarding({})", "Assignment({})"]; const constantsKeywords = ["TRUE", "FALSE"]; const dfdBehaviorLanguageMonarchDefinition: monaco.languages.IMonarchLanguage = { keywords: [...statementKeywords, ...constantsKeywords], @@ -140,7 +140,7 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp // Auto open completions after typing a dot. Useful for the set statement where // components are delimited by dots. - triggerCharacters = ["."]; + triggerCharacters = [".", ";", "{"]; provideCompletionItems( model: monaco.editor.ITextModel, @@ -159,7 +159,8 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp suggestions: statementKeywords.map((keyword) => ({ label: keyword, kind: monaco.languages.CompletionItemKind.Keyword, - insertText: keyword, + insertText: keyword.slice(0, -2) + "$0" + keyword.slice(-2), + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, // Treat insertText as a snippet // Replace full line with new statement start keyword range: new monaco.Range( position.lineNumber, @@ -180,15 +181,34 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp const availableInputs = parent.getAvailableInputs().filter((input) => input !== undefined) as string[]; + const curlyBracketCompletion = { + label: "{", + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: "$0}", // Automatically add closing curly bracket and position cursor inside + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), + }; + + // Add the curly bracket completion when the user types a curly bracket + if ( + model.getValueInRange( + new monaco.Range(position.lineNumber, position.column - 1, position.lineNumber, position.column), + ) === "{" + ) { + return { + suggestions: [curlyBracketCompletion], + }; + } + // Suggestions per statement type switch (statementType?.word) { - case "set": + case "Assignment": return { - suggestions: this.getSetStatementCompletions(model, position, availableInputs), + suggestions: this.getAssignmentCompletions(model, position, availableInputs), }; - case "forward": + case "Forwarding": return { - suggestions: this.getInputCompletions(model, position, availableInputs), + suggestions: this.getForwardingCompletions(model, position, availableInputs), }; } @@ -198,53 +218,112 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp }; } - private getSetStatementCompletions( + private getAssignmentCompletions( model: monaco.editor.ITextModel, position: monaco.Position, availableInputs: string[], ): monaco.languages.CompletionItem[] { const line = model.getLineContent(position.lineNumber); + const currentWord = model.getWordUntilPosition(position); + const column = position.column; + + // Check if we're inside the input list (i.e., inside first curly braces `{}`) + const openBraceIndex = line.indexOf("{"); + const firstSemicolonIndex = line.indexOf(";"); + + // If the first semicolon hasn't been typed yet, assume we're inside the input list + if (openBraceIndex !== -1 && (firstSemicolonIndex === -1 || column < firstSemicolonIndex)) { + // Inside `{List of available inputs}` section + return this.getInputCompletions(model, position, availableInputs); + } + + // If the second semicolon hasn't been typed yet, assume we're typing in the term section or outPorts list + const secondSemicolonIndex = line.indexOf(";", firstSemicolonIndex + 1); + const secondOpenBraceIndex = line.indexOf("{", openBraceIndex + 1); - // Find the start of the current expression - // -1 because the column is to the right of the last char => last filled column is -1 + if (secondSemicolonIndex !== -1 && column > secondSemicolonIndex) { + // If the second semicolon hasn't been typed but we're inside the second curly brace, assume it's outPorts + if (secondOpenBraceIndex !== -1 && column > secondOpenBraceIndex) { + // We're inside the `{List of outPorts}` section + return this.getOutLabelCompletions(model, position); + } else { + return [ + { + label: "{", + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: "{$0}", // Automatically add closing curly bracket and position cursor inside + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range: new monaco.Range( + position.lineNumber, + position.column, + position.lineNumber, + position.column, + ), + }, + ]; + } + } + + if (line.charAt(column - 2) === ".") { + // If the last character is a ".", return only getOutLabelCompletions + return this.getOutLabelCompletions(model, position); + } + + const constantsCompletions = this.getConstantsCompletions(model, position); + const outLabelCompletions = this.getOutLabelCompletions(model, position); + + // Return combined completions + return [...constantsCompletions, ...outLabelCompletions]; + } + + private getForwardingCompletions( + model: monaco.editor.ITextModel, + position: monaco.Position, + availableInputs: string[], + ): monaco.languages.CompletionItem[] { + const line = model.getLineContent(position.lineNumber); + const currentWord = model.getWordUntilPosition(position); + const column = position.column; + + // Check if we're inside the input list (i.e., inside first curly braces `{}`) + const openBraceIndex = line.indexOf("{"); + + // If the first semicolon hasn't been typed yet, assume we're inside the input list + if (openBraceIndex !== -1 && column > openBraceIndex) { + // Inside `{List of available inputs}` section + return this.getInputCompletions(model, position, availableInputs); + } else { + return []; + } + } + + private getOutLabelCompletions( + model: monaco.editor.ITextModel, + position: monaco.Position, + ): monaco.languages.CompletionItem[] { + const line = model.getLineContent(position.lineNumber); + + // Find the start of the current expression (Type or value) let currentExpressionStart = position.column - 1; while (currentExpressionStart > 0) { - const currentChar = line[currentExpressionStart - 1]; // column is 1-based but array is 0-based => -1 - - if (currentChar !== "." && !currentChar.match(PortBehaviorValidator.REGEX_ALPHANUMERIC)) { + const currentChar = line[currentExpressionStart - 1]; // column is 1-based, array is 0-based + if (currentChar !== "." && !currentChar.match(/[A-Za-z0-9_]/)) { break; } - currentExpressionStart--; } - const currentExpression = line.substring(currentExpressionStart - 1, position.column); + const currentExpression = line.substring(currentExpressionStart, position.column); const expressionParts = currentExpression.split("."); - // Check whether the position is the assignment target (aka the left side of the "=" or missing equals) - const equalsIdx = line.indexOf("="); - const isTargetLabel = equalsIdx == -1 || equalsIdx > currentExpressionStart; - if (isTargetLabel) { - // Left hand side: labelType.labelValue (is for the target node, so we don't need to specify) - if (expressionParts.length === 1) { + switch (expressionParts.length) { + case 1: + // If there's only one part, we're completing the `Type` return this.getLabelTypeCompletions(model, position); - } else { - return this.getLabelValueCompletions(model, position, expressionParts[0]); - } - } else { - // Right hand side: input.labelType.labelValue or constant - switch (expressionParts.length) { - case 1: - return [ - ...this.getInputCompletions(model, position, availableInputs), - ...this.getConstantsCompletions(model, position), - ]; - case 2: - return this.getLabelTypeCompletions(model, position); - case 3: - const labelTypeName = expressionParts[1]; - return this.getLabelValueCompletions(model, position, labelTypeName); - } + case 2: + // If there's already a dot, we complete the `value` for the specific `Type` + const labelTypeName = expressionParts[0]; + return this.getLabelValueCompletions(model, position, labelTypeName); } return []; From 7347a8ae0f030b1beb9e6ace0c6ed81cde7355a6 Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 01:09:45 +0200 Subject: [PATCH 04/16] Feat: Adjusting Default Diagram --- src/features/serialize/defaultDiagram.json | 279 +++++++++++---------- 1 file changed, 143 insertions(+), 136 deletions(-) diff --git a/src/features/serialize/defaultDiagram.json b/src/features/serialize/defaultDiagram.json index 340033b..58312fd 100644 --- a/src/features/serialize/defaultDiagram.json +++ b/src/features/serialize/defaultDiagram.json @@ -1,10 +1,16 @@ { "model": { - "scroll": { + "canvasBounds": { "x": 0, - "y": 0 + "y": 0, + "width": 1278, + "height": 1324 + }, + "scroll": { + "x": 181.68489464915504, + "y": -12.838536201820945 }, - "zoom": 3.854984894259819, + "zoom": 6.057478948161569, "position": { "x": 0, "y": 0 @@ -13,6 +19,7 @@ "width": -1, "height": -1 }, + "features": {}, "type": "graph", "id": "root", "children": [ @@ -39,8 +46,8 @@ "ports": [ { "position": { - "x": 31, - "y": 38.5 + "x": 58.5, + "y": 7 }, "size": { "width": -1, @@ -50,16 +57,15 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "set Sensitivity.Personal = TRUE", "features": {}, - "id": "4wbyft", - "type": "port:dfd-output", + "id": "nhcrad", + "type": "port:dfd-input", "children": [] }, { "position": { - "x": 58.5, - "y": 7 + "x": 31, + "y": 38.5 }, "size": { "width": -1, @@ -69,9 +75,10 @@ "selected": false, "hoverFeedback": false, "opacity": 1, + "behavior": "Assignment({};TRUE;{Sensitivity.Personal})", "features": {}, - "id": "nhcrad", - "type": "port:dfd-input", + "id": "4wbyft", + "type": "port:dfd-output", "children": [] }, { @@ -87,7 +94,7 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "set Sensitivity.Public = TRUE", + "behavior": "Assignment({};TRUE;{Sensitivity.Public})", "features": {}, "id": "wksxi8", "type": "port:dfd-output", @@ -101,8 +108,8 @@ }, { "position": { - "x": 422.5, - "y": 59 + "x": 249, + "y": 67 }, "size": { "width": -1, @@ -112,18 +119,13 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "text": "Database", - "labels": [ - { - "labelTypeId": "gvia09", - "labelTypeValueId": "5hnugm" - } - ], + "text": "view", + "labels": [], "ports": [ { "position": { "x": -3.5, - "y": 5 + "y": 13 }, "size": { "width": -1, @@ -133,40 +135,40 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "set Sensitivity.Public = TRUE", "features": {}, - "id": "1j7bn5", - "type": "port:dfd-output", + "id": "ti4ri7", + "type": "port:dfd-input", "children": [] }, { "position": { - "x": -3.5, - "y": 28 + "x": 58.5, + "y": 13 }, "size": { "width": -1, "height": -1 }, "strokeWidth": 0, - "selected": true, - "hoverFeedback": true, + "selected": false, + "hoverFeedback": false, "opacity": 1, + "behavior": "Forwarding({request})", "features": {}, - "id": "scljwi", - "type": "port:dfd-input", + "id": "bsqjm", + "type": "port:dfd-output", "children": [] } ], "features": {}, - "id": "8j2r1g", - "type": "node:storage", + "id": "0bh7yh", + "type": "node:function", "children": [] }, { "position": { "x": 249, - "y": 67 + "y": 22 }, "size": { "width": -1, @@ -176,13 +178,13 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "text": "view", + "text": "display", "labels": [], "ports": [ { "position": { "x": 58.5, - "y": 13 + "y": 15 }, "size": { "width": -1, @@ -192,40 +194,40 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "forward request", "features": {}, - "id": "bsqjm", - "type": "port:dfd-output", + "id": "0hfzu", + "type": "port:dfd-input", "children": [] }, { "position": { "x": -3.5, - "y": 13 + "y": 9 }, "size": { "width": -1, "height": -1 }, "strokeWidth": 0, - "selected": false, + "selected": true, "hoverFeedback": false, "opacity": 1, + "behavior": "Forwarding({items})", "features": {}, - "id": "ti4ri7", - "type": "port:dfd-input", + "id": "y1p7qq", + "type": "port:dfd-output", "children": [] } ], "features": {}, - "id": "0bh7yh", + "id": "4myuyr", "type": "node:function", "children": [] }, { "position": { - "x": 249, - "y": 22 + "x": 364, + "y": 152 }, "size": { "width": -1, @@ -235,13 +237,13 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "text": "display", + "text": "encrypt", "labels": [], "ports": [ { "position": { - "x": 58.5, - "y": 15 + "x": -3.5, + "y": 15.5 }, "size": { "width": -1, @@ -252,14 +254,14 @@ "hoverFeedback": false, "opacity": 1, "features": {}, - "id": "0hfzu", + "id": "kqjy4g", "type": "port:dfd-input", "children": [] }, { "position": { - "x": -3.5, - "y": 9 + "x": 29, + "y": -3.5 }, "size": { "width": -1, @@ -269,48 +271,22 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "forward items", + "behavior": "Forwarding({data})\nAssignment({};TRUE;{Encryption.Encrypted})", "features": {}, - "id": "y1p7qq", + "id": "3wntc", "type": "port:dfd-output", "children": [] } ], "features": {}, - "id": "4myuyr", + "id": "3n988k", "type": "node:function", "children": [] }, - { - "routingPoints": [], - "selected": false, - "hoverFeedback": false, - "opacity": 1, - "features": {}, - "id": "n81f3b", - "type": "edge:arrow", - "sourceId": "1j7bn5", - "targetId": "0hfzu", - "text": "items", - "children": [] - }, - { - "routingPoints": [], - "selected": false, - "hoverFeedback": false, - "opacity": 1, - "features": {}, - "id": "hi397b", - "type": "edge:arrow", - "sourceId": "y1p7qq", - "targetId": "nhcrad", - "text": "items", - "children": [] - }, { "position": { - "x": 364, - "y": 152 + "x": 104, + "y": 157 }, "size": { "width": -1, @@ -320,12 +296,12 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "text": "encrypt", + "text": "buy", "labels": [], "ports": [ { "position": { - "x": 29, + "x": 19, "y": -3.5 }, "size": { @@ -336,16 +312,15 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "forward data\nset Encryption.Encrypted = TRUE", "features": {}, - "id": "3wntc", - "type": "port:dfd-output", + "id": "2331e8", + "type": "port:dfd-input", "children": [] }, { "position": { - "x": -3.5, - "y": 15.5 + "x": 58.5, + "y": 10.5 }, "size": { "width": -1, @@ -355,20 +330,21 @@ "selected": false, "hoverFeedback": false, "opacity": 1, + "behavior": "Forwarding({data})", "features": {}, - "id": "kqjy4g", - "type": "port:dfd-input", + "id": "vnkg73", + "type": "port:dfd-output", "children": [] } ], "features": {}, - "id": "3n988k", + "id": "z9v1jp", "type": "node:function", "children": [] }, { "position": { - "x": 104, + "x": 233.5, "y": 157 }, "size": { @@ -379,13 +355,13 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "text": "buy", + "text": "process", "labels": [], "ports": [ { "position": { - "x": 19, - "y": -3.5 + "x": -3.5, + "y": 10.5 }, "size": { "width": -1, @@ -396,13 +372,13 @@ "hoverFeedback": false, "opacity": 1, "features": {}, - "id": "2331e8", + "id": "xyepdb", "type": "port:dfd-input", "children": [] }, { "position": { - "x": 58.5, + "x": 59.5, "y": 10.5 }, "size": { @@ -413,35 +389,22 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "forward data", + "behavior": "Forwarding({data})", "features": {}, - "id": "vnkg73", + "id": "eedb56", "type": "port:dfd-output", "children": [] } ], "features": {}, - "id": "z9v1jp", + "id": "js61f", "type": "node:function", "children": [] }, - { - "routingPoints": [], - "selected": false, - "hoverFeedback": false, - "opacity": 1, - "features": {}, - "id": "vq8g3l", - "type": "edge:arrow", - "sourceId": "4wbyft", - "targetId": "2331e8", - "text": "data", - "children": [] - }, { "position": { - "x": 233.5, - "y": 157 + "x": 422.5, + "y": 59 }, "size": { "width": -1, @@ -451,13 +414,18 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "text": "process", - "labels": [], + "text": "Database", + "labels": [ + { + "labelTypeId": "gvia09", + "labelTypeValueId": "5hnugm" + } + ], "ports": [ { "position": { "x": -3.5, - "y": 10.5 + "y": 23 }, "size": { "width": -1, @@ -468,14 +436,14 @@ "hoverFeedback": false, "opacity": 1, "features": {}, - "id": "xyepdb", + "id": "scljwi", "type": "port:dfd-input", "children": [] }, { "position": { - "x": 59.5, - "y": 10.5 + "x": -3.5, + "y": 0.5 }, "size": { "width": -1, @@ -485,16 +453,29 @@ "selected": false, "hoverFeedback": false, "opacity": 1, - "behavior": "forward data", + "behavior": "Assignment({};TRUE;{Sensitivity.Public})", "features": {}, - "id": "eedb56", + "id": "1j7bn5", "type": "port:dfd-output", "children": [] } ], "features": {}, - "id": "js61f", - "type": "node:function", + "id": "8j2r1g", + "type": "node:storage", + "children": [] + }, + { + "routingPoints": [], + "selected": false, + "hoverFeedback": false, + "opacity": 1, + "features": {}, + "id": "vq8g3l", + "type": "edge:arrow", + "sourceId": "4wbyft", + "targetId": "2331e8", + "text": "data", "children": [] }, { @@ -523,6 +504,32 @@ "text": "data", "children": [] }, + { + "routingPoints": [], + "selected": false, + "hoverFeedback": false, + "opacity": 1, + "features": {}, + "id": "ojjvtp", + "type": "edge:arrow", + "sourceId": "3wntc", + "targetId": "scljwi", + "text": "data", + "children": [] + }, + { + "routingPoints": [], + "selected": false, + "hoverFeedback": false, + "opacity": 1, + "features": {}, + "id": "c9n88l", + "type": "edge:arrow", + "sourceId": "bsqjm", + "targetId": "scljwi", + "text": "request", + "children": [] + }, { "routingPoints": [], "selected": false, @@ -543,11 +550,11 @@ "hoverFeedback": false, "opacity": 1, "features": {}, - "id": "ojjvtp", + "id": "n81f3b", "type": "edge:arrow", - "sourceId": "3wntc", - "targetId": "scljwi", - "text": "data", + "sourceId": "1j7bn5", + "targetId": "0hfzu", + "text": "items", "children": [] }, { @@ -556,11 +563,11 @@ "hoverFeedback": false, "opacity": 1, "features": {}, - "id": "c9n88l", + "id": "hi397b", "type": "edge:arrow", - "sourceId": "bsqjm", - "targetId": "scljwi", - "text": "request", + "sourceId": "y1p7qq", + "targetId": "nhcrad", + "text": "items", "children": [] } ] From 9edb7e1fc8eb6fee93b69ebb45a651ce665d43aa Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 01:12:47 +0200 Subject: [PATCH 05/16] Fix: Prod build errors --- src/features/dfdElements/outputPortBehaviorValidation.ts | 3 --- src/features/dfdElements/outputPortEditUi.ts | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index b3ac4ed..3525651 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -28,9 +28,6 @@ export class PortBehaviorValidator { // Has the label type and label value that should be set as capturing groups. private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+$/; - // Regex that is used to extract all inputs, their label types and label values from a set statement. - // Each input is a match with the input name, label type and label value as capturing groups. - private static readonly SET_REGEX_EXPRESSION_INPUTS = /([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)/g; // Regex matching alphanumeric characters. public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_\|]+/; diff --git a/src/features/dfdElements/outputPortEditUi.ts b/src/features/dfdElements/outputPortEditUi.ts index ee3bae1..185a76f 100644 --- a/src/features/dfdElements/outputPortEditUi.ts +++ b/src/features/dfdElements/outputPortEditUi.ts @@ -224,7 +224,6 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp availableInputs: string[], ): monaco.languages.CompletionItem[] { const line = model.getLineContent(position.lineNumber); - const currentWord = model.getWordUntilPosition(position); const column = position.column; // Check if we're inside the input list (i.e., inside first curly braces `{}`) @@ -282,7 +281,6 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp availableInputs: string[], ): monaco.languages.CompletionItem[] { const line = model.getLineContent(position.lineNumber); - const currentWord = model.getWordUntilPosition(position); const column = position.column; // Check if we're inside the input list (i.e., inside first curly braces `{}`) From 057a139f93482c41db8b4a3f9e58cb097fb399f7 Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 13:23:47 +0200 Subject: [PATCH 06/16] Doc: Added documentation for Regex in validation --- .../dfdElements/outputPortBehaviorValidation.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 3525651..46b5bc6 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -20,14 +20,20 @@ interface PortBehaviorValidationError { */ @injectable() export class PortBehaviorValidator { + // Regex that validates assignments + // Matches "Assignment({input_Pins};Term;{out_Label})" private static readonly ASSIGNMENT_REGEX = /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+;\{(((([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*))+(,\s*(([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)))*)?)\}\)+$/; + + // Regex that validates forwarding + // Matches "Forwarding({input_pins})" private static readonly FORWARDING_REGEX = /^Forwarding\(\{[A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*\}\)$/; - // Regex that validates a set statement. - // Has the label type and label value that should be set as capturing groups. + // Regex that validates a term + // Matches Constants, Operants and Label References private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+$/; + // Regex matching alphanumeric characters. public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_\|]+/; From cf8d5f0ca674381a8bdf1e4a31ec0fc711715d09 Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 14:03:48 +0200 Subject: [PATCH 07/16] Feat: reducing regex complexity, keeping whitespaces --- src/features/dfdElements/outputPortBehaviorValidation.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 46b5bc6..4769468 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -23,16 +23,15 @@ export class PortBehaviorValidator { // Regex that validates assignments // Matches "Assignment({input_Pins};Term;{out_Label})" private static readonly ASSIGNMENT_REGEX = - /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+;\{(((([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*))+(,\s*(([A-Za-z][A-Za-z0-9_\|]*)\.([A-Za-z][A-Za-z0-9_\|]*)))*)?)\}\)+$/; + /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_\|]*)\.[A-Za-z0-9_\|]*)+(,\s*([A-Za-z0-9_\|]*\.[A-Za-z0-9_\|]*))*)?)\}\)+$/; // Regex that validates forwarding // Matches "Forwarding({input_pins})" private static readonly FORWARDING_REGEX = /^Forwarding\(\{[A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*\}\)$/; // Regex that validates a term - // Matches Constants, Operants and Label References - private static readonly TERM_REGEX = - /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*))+$/; + // Has the label type and label value that should be set as capturing groups. + private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+$/; // Regex matching alphanumeric characters. public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_\|]+/; From c3fbce717d586d4d04a90e7a6f7debec1bfda0aa Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 14:08:18 +0200 Subject: [PATCH 08/16] Fix: Pipe in labels --- src/features/dfdElements/outputPortBehaviorValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 4769468..4db078a 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -23,7 +23,7 @@ export class PortBehaviorValidator { // Regex that validates assignments // Matches "Assignment({input_Pins};Term;{out_Label})" private static readonly ASSIGNMENT_REGEX = - /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_\|]*)\.[A-Za-z0-9_\|]*)+(,\s*([A-Za-z0-9_\|]*\.[A-Za-z0-9_\|]*))*)?)\}\)+$/; + /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_]*)\.[A-Za-z0-9_]*)+(,\s*([A-Za-z0-9_]*\.[A-Za-z0-9_]*))*)?)\}\)+$/; // Regex that validates forwarding // Matches "Forwarding({input_pins})" From dc717858c5e10995f188359491dfe0dab474c98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20H=C3=BCller?= <62952208+01Parzival10@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:28:47 +0200 Subject: [PATCH 09/16] Fix: Detect delimiter as first symbol for input port --- src/features/dfdElements/outputPortBehaviorValidation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 4db078a..6173e1b 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -21,13 +21,13 @@ interface PortBehaviorValidationError { @injectable() export class PortBehaviorValidator { // Regex that validates assignments - // Matches "Assignment({input_Pins};Term;{out_Label})" + // Matches "Assignment({input_Pins};TERM_REGEX;{out_Label})" private static readonly ASSIGNMENT_REGEX = - /^Assignment\(\{(([A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_]*)\.[A-Za-z0-9_]*)+(,\s*([A-Za-z0-9_]*\.[A-Za-z0-9_]*))*)?)\}\)+$/; + /^Assignment\(\{(([A-Za-z0-9_][A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_]*)\.[A-Za-z0-9_]*)+(,\s*([A-Za-z0-9_]*\.[A-Za-z0-9_]*))*)?)\}\)+$/; // Regex that validates forwarding // Matches "Forwarding({input_pins})" - private static readonly FORWARDING_REGEX = /^Forwarding\(\{[A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*\}\)$/; + private static readonly FORWARDING_REGEX = /^Forwarding\(\{[A-Za-z0-9_][A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_][A-Za-z0-9_\|]+)*\}\)$/; // Regex that validates a term // Has the label type and label value that should be set as capturing groups. From 07b249e73944fcb73dd2d76d8bd1d01fb6171eff Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 17:37:57 +0200 Subject: [PATCH 10/16] Fix: Edge case that caused validation to crash --- src/features/dfdElements/outputPortBehaviorValidation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 4db078a..db082f0 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -341,6 +341,7 @@ export class PortBehaviorValidator { } if (typeValuePair.indexOf(".") !== -1) { + if (typeValuePair.split(".")[1] === null || typeValuePair.split(".")[1] === "") continue; const inputLabelValue = typeValuePair.split(".")[1].trim(); const inputLabelTypeObject = this.labelTypeRegistry From 08e542a1c09505b3124fb49644e08b552c9554e4 Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 23:03:05 +0200 Subject: [PATCH 11/16] Fix: Severly improve autocompletion --- src/features/dfdElements/outputPortEditUi.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/features/dfdElements/outputPortEditUi.ts b/src/features/dfdElements/outputPortEditUi.ts index 185a76f..2d4735f 100644 --- a/src/features/dfdElements/outputPortEditUi.ts +++ b/src/features/dfdElements/outputPortEditUi.ts @@ -193,7 +193,10 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp if ( model.getValueInRange( new monaco.Range(position.lineNumber, position.column - 1, position.lineNumber, position.column), - ) === "{" + ) === "{" && + model.getValueInRange( + new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column + 1), + ) !== "}" ) { return { suggestions: [curlyBracketCompletion], @@ -228,19 +231,24 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp // Check if we're inside the input list (i.e., inside first curly braces `{}`) const openBraceIndex = line.indexOf("{"); - const firstSemicolonIndex = line.indexOf(";"); + const closingBraceIndex = line.indexOf("}"); + console.log("Moin" + column); + console.log("Moin" + openBraceIndex); + console.log("Moin" + closingBraceIndex); + console.log("Moin" + (openBraceIndex !== -1 && (closingBraceIndex === -1 || column <= closingBraceIndex))); // If the first semicolon hasn't been typed yet, assume we're inside the input list - if (openBraceIndex !== -1 && (firstSemicolonIndex === -1 || column < firstSemicolonIndex)) { + if (openBraceIndex !== -1 && (closingBraceIndex === -1 || column <= closingBraceIndex + 1)) { // Inside `{List of available inputs}` section return this.getInputCompletions(model, position, availableInputs); } // If the second semicolon hasn't been typed yet, assume we're typing in the term section or outPorts list + const firstSemicolonIndex = line.indexOf(";"); const secondSemicolonIndex = line.indexOf(";", firstSemicolonIndex + 1); const secondOpenBraceIndex = line.indexOf("{", openBraceIndex + 1); - if (secondSemicolonIndex !== -1 && column > secondSemicolonIndex) { + if (secondSemicolonIndex !== -1 && column > secondSemicolonIndex + 1) { // If the second semicolon hasn't been typed but we're inside the second curly brace, assume it's outPorts if (secondOpenBraceIndex !== -1 && column > secondOpenBraceIndex) { // We're inside the `{List of outPorts}` section From 329030117bf44647b00e2af7c8138fa9661874f5 Mon Sep 17 00:00:00 2001 From: Huell Date: Wed, 9 Oct 2024 23:04:03 +0200 Subject: [PATCH 12/16] Remove debugging logs --- src/features/dfdElements/outputPortEditUi.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/features/dfdElements/outputPortEditUi.ts b/src/features/dfdElements/outputPortEditUi.ts index 2d4735f..61dfb39 100644 --- a/src/features/dfdElements/outputPortEditUi.ts +++ b/src/features/dfdElements/outputPortEditUi.ts @@ -233,10 +233,6 @@ class MonacoEditorDfdBehaviorCompletionProvider implements monaco.languages.Comp const openBraceIndex = line.indexOf("{"); const closingBraceIndex = line.indexOf("}"); - console.log("Moin" + column); - console.log("Moin" + openBraceIndex); - console.log("Moin" + closingBraceIndex); - console.log("Moin" + (openBraceIndex !== -1 && (closingBraceIndex === -1 || column <= closingBraceIndex))); // If the first semicolon hasn't been typed yet, assume we're inside the input list if (openBraceIndex !== -1 && (closingBraceIndex === -1 || column <= closingBraceIndex + 1)) { // Inside `{List of available inputs}` section From 0f801f9153674a4f4b5e612740210334049ebc91 Mon Sep 17 00:00:00 2001 From: Huell Date: Fri, 11 Oct 2024 13:04:04 +0200 Subject: [PATCH 13/16] Fix: Add label verification in term --- .../outputPortBehaviorValidation.ts | 68 ++++++++++++++++++- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 157001d..6e5d02e 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -27,12 +27,15 @@ export class PortBehaviorValidator { // Regex that validates forwarding // Matches "Forwarding({input_pins})" - private static readonly FORWARDING_REGEX = /^Forwarding\(\{[A-Za-z0-9_][A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_][A-Za-z0-9_\|]+)*\}\)$/; + private static readonly FORWARDING_REGEX = + /^Forwarding\(\{[A-Za-z0-9_][A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_][A-Za-z0-9_\|]+)*\}\)$/; // Regex that validates a term // Has the label type and label value that should be set as capturing groups. private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+$/; + private static readonly LABEL_REGEX = /([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)/g; + // Regex matching alphanumeric characters. public static readonly REGEX_ALPHANUMERIC = /[A-Za-z0-9_\|]+/; @@ -254,7 +257,7 @@ export class PortBehaviorValidator { } // Extract all used inputs, label types and the corresponding label values. - const term = line.split(";")[1].trim(); // get everything after the = + var term = line.split(";")[1].trim(); // get everything after the ; if (term.length === 0) { return [ { @@ -263,6 +266,9 @@ export class PortBehaviorValidator { }, ]; } + if (term.indexOf(";") !== -1) { + term = term.split(";")[0]; + } const termMatch = term.match(PortBehaviorValidator.TERM_REGEX); if (!termMatch) { @@ -274,6 +280,63 @@ export class PortBehaviorValidator { ]; } + const matches = [...term.matchAll(PortBehaviorValidator.LABEL_REGEX)]; + const inputAccessErrors = []; + + for (const inputMatch of matches) { + const inputLabelType = inputMatch[1]; + const inputLabelValue = inputMatch[2]; + + const inputLabelTypeObject = this.labelTypeRegistry + ?.getLabelTypes() + .find((type) => type.name === inputLabelType); + if (!inputLabelTypeObject) { + let idx = line.indexOf(inputLabelType); + while (idx !== -1) { + // Check that this is not a substring of another label type. + if ( + // must start after a dot and end before a dot + line[idx - 1] === "." && + line[idx + inputLabelType.length] === "." + ) { + inputAccessErrors.push({ + line: lineNumber, + message: `unknown label type: ${inputLabelType}`, + colStart: idx, + colEnd: idx + inputLabelType.length, + }); + } + + idx = line.indexOf(inputLabelType, idx + 1); + } + } else if ( + inputLabelValue === undefined || + inputLabelValue === "" || + !inputLabelTypeObject.values.find((value) => value.text === inputLabelValue) + ) { + let idx = line.indexOf(inputLabelValue); + while (idx !== -1) { + // Check that this is not a substring of another label value. + if ( + // must start after a dot and end at the end of the alphanumeric text + line[idx - 1] === "." && + // Might be at the end of the line + (!line[idx + inputLabelValue.length] || + !line[idx + inputLabelValue.length].match(PortBehaviorValidator.REGEX_ALPHANUMERIC)) + ) { + inputAccessErrors.push({ + line: lineNumber, + message: `unknown label value of label type ${inputLabelType}: ${inputLabelValue}`, + colStart: idx, + colEnd: idx + inputLabelValue.length, + }); + } + + idx = line.indexOf(inputLabelValue, idx + 1); + } + } + } + const node = port.parent; if (!(node instanceof DfdNodeImpl)) { throw new Error("Expected port parent to be a DfdNodeImpl."); @@ -295,7 +358,6 @@ export class PortBehaviorValidator { .map((variable) => variable.trim()); // Check for each input access that the input exists and that the label type and value are valid. - const inputAccessErrors = []; for (const inPortName of inPorts) { if (!availableInputs.includes(inPortName) && inPortName !== "") { From 24af14fea643fc1bbd78df63334bc5d6f420cd8d Mon Sep 17 00:00:00 2001 From: Huell Date: Fri, 11 Oct 2024 14:18:10 +0200 Subject: [PATCH 14/16] Fix: Regex allowed empty labeltype/values --- src/features/dfdElements/outputPortBehaviorValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 6e5d02e..8b6f469 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -32,7 +32,7 @@ export class PortBehaviorValidator { // Regex that validates a term // Has the label type and label value that should be set as capturing groups. - private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+$/; + private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]+\.[A-Za-z0-9_]+))+$/; private static readonly LABEL_REGEX = /([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)/g; From 4cca5f6f80e97259db3dc9bd307348f08c91133b Mon Sep 17 00:00:00 2001 From: Huell Date: Fri, 11 Oct 2024 15:04:42 +0200 Subject: [PATCH 15/16] Fix: Regex allowed empty labeltype/values for outlabels --- src/features/dfdElements/outputPortBehaviorValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index 8b6f469..a8377c1 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -23,7 +23,7 @@ export class PortBehaviorValidator { // Regex that validates assignments // Matches "Assignment({input_Pins};TERM_REGEX;{out_Label})" private static readonly ASSIGNMENT_REGEX = - /^Assignment\(\{(([A-Za-z0-9_][A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_]*)\.[A-Za-z0-9_]*)+(,\s*([A-Za-z0-9_]*\.[A-Za-z0-9_]*))*)?)\}\)+$/; + /^Assignment\(\{(([A-Za-z0-9_][A-Za-z0-9_\|]+(,\s*[A-Za-z0-9_\|]+)*)?)\};(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]*\.[A-Za-z0-9_]*))+;\{(((([A-Za-z0-9_]+)\.[A-Za-z0-9_]+)+(,\s*([A-Za-z0-9_]+\.[A-Za-z0-9_]+))*)?)\}\)+$/; // Regex that validates forwarding // Matches "Forwarding({input_pins})" From 03127ceb6807f8a6eb37c44ef8f3da85ba755a34 Mon Sep 17 00:00:00 2001 From: Huell Date: Fri, 11 Oct 2024 15:49:35 +0200 Subject: [PATCH 16/16] Fix: prevent term and out labels from allowing a.b.c --- .../outputPortBehaviorValidation.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/features/dfdElements/outputPortBehaviorValidation.ts b/src/features/dfdElements/outputPortBehaviorValidation.ts index a8377c1..f46f7ba 100644 --- a/src/features/dfdElements/outputPortBehaviorValidation.ts +++ b/src/features/dfdElements/outputPortBehaviorValidation.ts @@ -32,7 +32,8 @@ export class PortBehaviorValidator { // Regex that validates a term // Has the label type and label value that should be set as capturing groups. - private static readonly TERM_REGEX = /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]+\.[A-Za-z0-9_]+))+$/; + private static readonly TERM_REGEX = + /^(\s*|!|TRUE|FALSE|\|\||&&|\(|\)|([A-Za-z0-9_]+\.[A-Za-z0-9_]+(?![A-Za-z0-9_]*\.[A-Za-z0-9_]*)))+$/g; private static readonly LABEL_REGEX = /([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)/g; @@ -335,6 +336,15 @@ export class PortBehaviorValidator { idx = line.indexOf(inputLabelValue, idx + 1); } } + + console.log(inputMatch); + + if (inputMatch[3] !== undefined) { + inputAccessErrors.push({ + line: lineNumber, + message: `invalid label definition`, + }); + } } const node = port.parent; @@ -451,6 +461,13 @@ export class PortBehaviorValidator { } } } + + if (typeValuePair.split(".")[2] !== undefined) { + inputAccessErrors.push({ + line: lineNumber, + message: `invalid label definition`, + }); + } } return inputAccessErrors.length > 0 ? inputAccessErrors : [];