From e95d09f082dda15d4421fc3b3a23663889eb87f8 Mon Sep 17 00:00:00 2001 From: Joachim Fenkes <33034020+fenkes-ibm@users.noreply.github.com> Date: Fri, 22 Jan 2021 19:44:27 +0100 Subject: [PATCH] Fix for #43 (#44) * UnitTests: Fix typename case expectations * UnitTests: Fix two beautify3 false fails * UnitTests: Add test for issue#43 * Introduce notion of Code Blocks to track state of indentation code The previous approach meant that in nested indentation calls, if code further down the stack split an input line, code further up the stack would not update its iteration variables, leading to duplication of some lines and removal of others. To fix this, introduce a CodeBlock class that keeps track of nested sub-blocks and updates all iteration variables in the call chain if a line is being split. This also removes the need for a bunch of parameters and return variables, so the function signatures get a little shorter. This fixes issue #43. --- VHDLFormatter.js | 293 +++++++++++++++--------------- VHDLFormatter.ts | 308 +++++++++++++++++--------------- tests/VHDLFormatterUnitTests.ts | 68 +++++-- 3 files changed, 362 insertions(+), 307 deletions(-) diff --git a/VHDLFormatter.js b/VHDLFormatter.js index 4c2307c..6ed10cd 100644 --- a/VHDLFormatter.js +++ b/VHDLFormatter.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RemoveAsserts = exports.ApplyNoNewLineAfter = exports.beautify3 = exports.beautifySemicolonBlock = exports.beautifyVariableInitialiseBlock = exports.beautifyPackageIsNewBlock = exports.beautifyComponentBlock = exports.beautifyCaseBlock = exports.AlignSign = exports.AlignSigns = exports.beautifyPortGenericBlock = exports.FormattedLineToString = exports.FormattedLine = exports.beautify = exports.BeautifierSettings = exports.signAlignSettings = exports.SetNewLinesAfterSymbols = exports.NewLineSettings = void 0; +exports.RemoveAsserts = exports.ApplyNoNewLineAfter = exports.beautify3 = exports.beautifySemicolonBlock = exports.beautifyVariableInitialiseBlock = exports.beautifyPackageIsNewBlock = exports.beautifyComponentBlock = exports.beautifyCaseBlock = exports.AlignSign = exports.AlignSigns = exports.beautifyPortGenericBlock = exports.FormattedLineToString = exports.CodeBlock = exports.FormattedLine = exports.beautify = exports.BeautifierSettings = exports.signAlignSettings = exports.SetNewLinesAfterSymbols = exports.NewLineSettings = void 0; let isTesting = false; const ILEscape = "@@"; const ILCommentPrefix = ILEscape + "comments"; @@ -335,7 +335,8 @@ function beautify(input, settings) { input = input.replace(/(\() +([+\-]) +(\w)/g, '$1$2$3'); // `( - 2)` -> `(-2)` arr = input.split("\r\n"); let result = []; - beautify3(arr, result, settings, 0, 0); + let block = new CodeBlock(arr); + beautify3(block, result, settings, 0); var alignSettings = settings.SignAlignSettings; if (alignSettings != null && alignSettings.isAll) { AlignSigns(result, 0, result.length - 1, alignSettings.mode, alignSettings.alignComments); @@ -386,6 +387,36 @@ class FormattedLine { } } exports.FormattedLine = FormattedLine; +class CodeBlock { + constructor(lines, start = 0, end = lines.length - 1) { + this.lines = lines; + this.start = start; + this.end = end; + this.parent = null; + this.cursor = start; + } + _notifySplit(atLine) { + if (this.start > atLine) + this.start++; + if (this.end >= atLine) + this.end++; + if (this.cursor >= atLine) + this.cursor++; + if (this.parent) + this.parent._notifySplit(atLine); + } + splitLine(atLine, firstText, secondText) { + this.lines[atLine] = firstText; + this.lines.splice(atLine + 1, 0, secondText); + this._notifySplit(atLine); + } + subBlock(start, end) { + let newBlock = new CodeBlock(this.lines, start, end); + newBlock.parent = this; + return newBlock; + } +} +exports.CodeBlock = CodeBlock; function FormattedLineToString(arr, indentation) { let result = []; if (arr == null) { @@ -410,82 +441,75 @@ function FormattedLineToString(arr, indentation) { return result; } exports.FormattedLineToString = FormattedLineToString; -function GetCloseparentheseEndIndex(inputs, startIndex) { +function GetCloseparentheseEndIndex(block) { let openParentheseCount = 0; let closeParentheseCount = 0; - for (let i = startIndex; i < inputs.length; i++) { - let input = inputs[i]; + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + let input = block.lines[block.cursor]; openParentheseCount += input.count("("); closeParentheseCount += input.count(")"); if (openParentheseCount > 0 && openParentheseCount <= closeParentheseCount) { - return i; + return; } } - return startIndex; + block.cursor = startIndex; } -function beautifyPortGenericBlock(inputs, result, settings, startIndex, parentEndIndex, indent, mode) { - let firstLine = inputs[startIndex]; +function beautifyPortGenericBlock(block, result, settings, indent, mode) { + let startIndex = block.cursor; + let firstLine = block.lines[startIndex]; let regex = new RegExp("[\\w\\s:]*(" + mode + ")([\\s]|$)"); if (!firstLine.regexStartsWith(regex)) { - return [startIndex, parentEndIndex]; + return; } let firstLineHasParenthese = firstLine.indexOf("(") >= 0; - let hasParenthese = firstLineHasParenthese; - let blockBodyStartIndex = startIndex; - let secondLineHasParenthese = startIndex + 1 < inputs.length && inputs[startIndex + 1].startsWith("("); - if (secondLineHasParenthese) { - hasParenthese = true; - blockBodyStartIndex++; - } - let endIndex = hasParenthese ? GetCloseparentheseEndIndex(inputs, startIndex) : startIndex; + let secondLineHasParenthese = startIndex + 1 <= block.end && block.lines[startIndex + 1].startsWith("("); + let hasParenthese = firstLineHasParenthese || secondLineHasParenthese; + let blockBodyStartIndex = startIndex + (secondLineHasParenthese ? 1 : 0); + if (hasParenthese) { + GetCloseparentheseEndIndex(block); + } + let endIndex = block.cursor; + let bodyBlock = block.subBlock(blockBodyStartIndex, endIndex); if (endIndex != startIndex && firstLineHasParenthese) { - inputs[startIndex] = inputs[startIndex].replace(/\b(PORT|GENERIC|PROCEDURE)\b([\w ]+)\(([\w\(\) ]+)/, '$1$2(\r\n$3'); - let newInputs = inputs[startIndex].split("\r\n"); + block.lines[startIndex] = block.lines[startIndex].replace(/\b(PORT|GENERIC|PROCEDURE)\b([\w ]+)\(([\w\(\) ]+)/, '$1$2(\r\n$3'); + let newInputs = block.lines[startIndex].split("\r\n"); if (newInputs.length == 2) { - inputs[startIndex] = newInputs[0]; - inputs.splice(startIndex + 1, 0, newInputs[1]); - endIndex++; - parentEndIndex++; + bodyBlock.splitLine(startIndex, newInputs[0], newInputs[1]); } } else if (endIndex > startIndex + 1 && secondLineHasParenthese) { - inputs[startIndex + 1] = inputs[startIndex + 1].replace(/\(([\w\(\) ]+)/, '(\r\n$1'); - let newInputs = inputs[startIndex + 1].split("\r\n"); + block.lines[startIndex + 1] = block.lines[startIndex + 1].replace(/\(([\w\(\) ]+)/, '(\r\n$1'); + let newInputs = block.lines[startIndex + 1].split("\r\n"); if (newInputs.length == 2) { - inputs[startIndex + 1] = newInputs[0]; - inputs.splice(startIndex + 2, 0, newInputs[1]); - endIndex++; - parentEndIndex++; + bodyBlock.splitLine(startIndex + 1, newInputs[0], newInputs[1]); } } - if (firstLineHasParenthese && inputs[startIndex].indexOf("MAP") > 0) { - inputs[startIndex] = inputs[startIndex].replace(/([^\w])(MAP)\s+\(/g, '$1$2('); + if (firstLineHasParenthese && block.lines[startIndex].indexOf("MAP") > 0) { + block.lines[startIndex] = block.lines[startIndex].replace(/([^\w])(MAP)\s+\(/g, '$1$2('); } - result.push(new FormattedLine(inputs[startIndex], indent)); + result.push(new FormattedLine(block.lines[startIndex], indent)); if (secondLineHasParenthese) { let secondLineIndent = indent; if (endIndex == startIndex + 1) { secondLineIndent++; } - result.push(new FormattedLine(inputs[startIndex + 1], secondLineIndent)); + result.push(new FormattedLine(block.lines[startIndex + 1], secondLineIndent)); } - let blockBodyEndIndex = endIndex; - let i = beautify3(inputs, result, settings, blockBodyStartIndex + 1, indent + 1, endIndex); - if (inputs[i].startsWith(")")) { - result[i].Indent--; - blockBodyEndIndex--; + beautify3(bodyBlock.subBlock(bodyBlock.start + 1, bodyBlock.end), result, settings, indent + 1); + if (block.lines[block.cursor].startsWith(")")) { + result[block.cursor].Indent--; + bodyBlock.end--; } var alignSettings = settings.SignAlignSettings; if (alignSettings != null) { if (alignSettings.isRegional && !alignSettings.isAll && alignSettings.keyWords != null && alignSettings.keyWords.indexOf(mode) >= 0) { - blockBodyStartIndex++; - AlignSigns(result, blockBodyStartIndex, blockBodyEndIndex, alignSettings.mode, alignSettings.alignComments); + AlignSigns(result, bodyBlock.start + 1, bodyBlock.end, alignSettings.mode, alignSettings.alignComments); } } - return [i, parentEndIndex]; } exports.beautifyPortGenericBlock = beautifyPortGenericBlock; function AlignSigns(result, startIndex, endIndex, mode, alignComments = false) { @@ -580,22 +604,22 @@ function AlignSign(result, startIndex, endIndex, symbol, maxSymbolIndex = -1, sy } } exports.AlignSign = AlignSign; -function beautifyCaseBlock(inputs, result, settings, startIndex, indent) { - if (!inputs[startIndex].regexStartsWith(/(.+:\s*)?(CASE)([\s]|$)/)) { - return startIndex; +function beautifyCaseBlock(block, result, settings, indent) { + if (!block.lines[block.cursor].regexStartsWith(/(.+:\s*)?(CASE)([\s]|$)/)) { + return; } - result.push(new FormattedLine(inputs[startIndex], indent)); - let i = beautify3(inputs, result, settings, startIndex + 1, indent + 2); - result[i].Indent = indent; - return i; + result.push(new FormattedLine(block.lines[block.cursor], indent)); + block.cursor++; + beautify3(block, result, settings, indent + 2); + result[block.cursor].Indent = indent; } exports.beautifyCaseBlock = beautifyCaseBlock; -function getSemicolonBlockEndIndex(inputs, settings, startIndex, parentEndIndex) { - let endIndex = 0; +function getSemicolonBlockEndIndex(block, settings) { + let endIndex = block.cursor; let openBracketsCount = 0; let closeBracketsCount = 0; - for (let i = startIndex; i < inputs.length; i++) { - let input = inputs[i]; + for (; block.cursor <= block.end; block.cursor++) { + let input = block.lines[block.cursor]; let indexOfSemicolon = input.indexOf(";"); let splitIndex = indexOfSemicolon < 0 ? input.length : indexOfSemicolon + 1; let stringBeforeSemicolon = input.substring(0, splitIndex); @@ -607,80 +631,62 @@ function getSemicolonBlockEndIndex(inputs, settings, startIndex, parentEndIndex) continue; } if (openBracketsCount == closeBracketsCount) { - endIndex = i; + endIndex = block.cursor; if (stringAfterSemicolon.trim().length > 0 && settings.NewLineSettings.newLineAfter.indexOf(";") >= 0) { - inputs[i] = stringBeforeSemicolon; - inputs.splice(i, 0, stringAfterSemicolon); - parentEndIndex++; + block.splitLine(block.cursor, stringBeforeSemicolon, stringAfterSemicolon); } break; } } - return [endIndex, parentEndIndex]; + block.cursor = endIndex; } -function beautifyComponentBlock(inputs, result, settings, startIndex, parentEndIndex, indent) { - let endIndex = startIndex; - for (let i = startIndex; i < inputs.length; i++) { - if (inputs[i].regexStartsWith(/END(\s|$)/)) { - endIndex = i; +function beautifyComponentBlock(block, result, settings, indent) { + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + if (block.lines[block.cursor].regexStartsWith(/END(\s|$)/)) { break; } } - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - let actualEndIndex = beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - let incremental = actualEndIndex - endIndex; - endIndex += incremental; - parentEndIndex += incremental; + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); } - return [endIndex, parentEndIndex]; } exports.beautifyComponentBlock = beautifyComponentBlock; -function beautifyPackageIsNewBlock(inputs, result, settings, startIndex, parentEndIndex, indent) { - let endIndex = startIndex; - for (let i = startIndex; i < inputs.length; i++) { - if (inputs[i].regexIndexOf(/;(\s|$)/) >= 0) { - endIndex = i; +function beautifyPackageIsNewBlock(block, result, settings, indent) { + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + if (block.lines[block.cursor].regexIndexOf(/;(\s|$)/) >= 0) { break; } } - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - let actualEndIndex = beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - let incremental = actualEndIndex - endIndex; - endIndex += incremental; - parentEndIndex += incremental; + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); } - return [endIndex, parentEndIndex]; } exports.beautifyPackageIsNewBlock = beautifyPackageIsNewBlock; -function beautifyVariableInitialiseBlock(inputs, result, settings, startIndex, parentEndIndex, indent) { - let endIndex = startIndex; - for (let i = startIndex; i < inputs.length; i++) { - if (inputs[i].regexIndexOf(/;(\s|$)/) >= 0) { - endIndex = i; +function beautifyVariableInitialiseBlock(block, result, settings, indent) { + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + if (block.lines[block.cursor].regexIndexOf(/;(\s|$)/) >= 0) { break; } } - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - let actualEndIndex = beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - let incremental = actualEndIndex - endIndex; - endIndex += incremental; - parentEndIndex += incremental; + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); } - return [endIndex, parentEndIndex]; } exports.beautifyVariableInitialiseBlock = beautifyVariableInitialiseBlock; -function beautifySemicolonBlock(inputs, result, settings, startIndex, parentEndIndex, indent) { - let endIndex = startIndex; - [endIndex, parentEndIndex] = getSemicolonBlockEndIndex(inputs, settings, startIndex, parentEndIndex); - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - alignSignalAssignmentBlock(settings, inputs, startIndex, endIndex, result); +function beautifySemicolonBlock(block, result, settings, indent) { + let startIndex = block.cursor; + getSemicolonBlockEndIndex(block, settings); + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); + alignSignalAssignmentBlock(settings, block.lines, startIndex, block.cursor, result); } - return [endIndex, parentEndIndex]; } exports.beautifySemicolonBlock = beautifySemicolonBlock; function alignSignalAssignmentBlock(settings, inputs, startIndex, endIndex, result) { @@ -698,8 +704,7 @@ function alignSignalAssignmentBlock(settings, inputs, startIndex, endIndex, resu } } } -function beautify3(inputs, result, settings, startIndex, indent, endIndex) { - let i; +function beautify3(block, result, settings, indent) { let regexOneLineBlockKeyWords = new RegExp(/(PROCEDURE)[^\w](?!.+[^\w]IS([^\w]|$))/); //match PROCEDURE..; but not PROCEDURE .. IS; let regexFunctionMultiLineBlockKeyWords = new RegExp(/(FUNCTION|IMPURE FUNCTION)[^\w](?=.+[^\w]IS([^\w]|$))/); //match FUNCTION .. IS; but not FUNCTION let blockMidKeyWords = ["BEGIN"]; @@ -741,29 +746,26 @@ function beautify3(inputs, result, settings, startIndex, indent, endIndex) { let regexblockEndsWithSemicolon = blockEndsWithSemicolon.convertToRegexBlockWords(); let regexMidKeyWhen = "WHEN".convertToRegexBlockWords(); let regexMidKeyElse = "ELSE|ELSIF".convertToRegexBlockWords(); - if (endIndex == null) { - endIndex = inputs.length - 1; - } - for (i = startIndex; i <= endIndex; i++) { + for (; block.cursor <= block.end; block.cursor++) { if (indent < 0) { indent = 0; } - let input = inputs[i].trim(); + let input = block.lines[block.cursor].trim(); if (input.regexStartsWith(regexBlockIndentedEndsKeyWords)) { result.push(new FormattedLine(input, indent)); - return i; + return; } if (input.regexStartsWith(/COMPONENT\s/)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifyComponentBlock(inputs, result, settings, i, endIndex, indent); + beautifyComponentBlock(block, result, settings, indent); Mode = modeCache; continue; } if (input.regexStartsWith(/PACKAGE[\s\w]+IS\s+NEW/)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifyPackageIsNewBlock(inputs, result, settings, i, endIndex, indent); + beautifyPackageIsNewBlock(block, result, settings, indent); Mode = modeCache; continue; } @@ -771,10 +773,10 @@ function beautify3(inputs, result, settings, startIndex, indent, endIndex) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; let endsWithBracket = input.regexIndexOf(/:\s*=\s*\(/) > 0; - let startIndex = i; - [i, endIndex] = beautifySemicolonBlock(inputs, result, settings, i, endIndex, indent); - if (endsWithBracket && startIndex != i) { - let fl = result[endIndex]; + let startIndex = block.cursor; + beautifySemicolonBlock(block, result, settings, indent); + if (endsWithBracket && startIndex != block.cursor) { + let fl = result[block.end]; if (fl.Line.regexStartsWith(/\);$/)) { fl.Indent--; } @@ -785,96 +787,99 @@ function beautify3(inputs, result, settings, startIndex, indent, endIndex) { if (input.regexStartsWith(/\w+\s*:\s*ENTITY/)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifySemicolonBlock(inputs, result, settings, i, endIndex, indent); + beautifySemicolonBlock(block, result, settings, indent); Mode = modeCache; continue; } if (Mode != FormatMode.EndsWithSemicolon && input.regexStartsWith(regexblockEndsWithSemicolon)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifySemicolonBlock(inputs, result, settings, i, endIndex, indent); + beautifySemicolonBlock(block, result, settings, indent); Mode = modeCache; continue; } if (input.regexStartsWith(/(.+:\s*)?(CASE)([\s]|$)/)) { let modeCache = Mode; Mode = FormatMode.CaseWhen; - i = beautifyCaseBlock(inputs, result, settings, i, indent); + beautifyCaseBlock(block, result, settings, indent); Mode = modeCache; continue; } if (input.regexStartsWith(/[\w\s:]*(:=)([\s]|$)/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, ":="); + beautifyPortGenericBlock(block, result, settings, indent, ":="); continue; } if (input.regexStartsWith(/[\w\s:]*\bPORT\b([\s]|$)/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "PORT"); + beautifyPortGenericBlock(block, result, settings, indent, "PORT"); continue; } if (input.regexStartsWith(/TYPE\s+\w+\s+IS\s+\(/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "IS"); + beautifyPortGenericBlock(block, result, settings, indent, "IS"); continue; } if (input.regexStartsWith(/[\w\s:]*GENERIC([\s]|$)/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "GENERIC"); + beautifyPortGenericBlock(block, result, settings, indent, "GENERIC"); continue; } if (input.regexStartsWith(/[\w\s:]*PROCEDURE[\s\w]+\($/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "PROCEDURE"); - if (inputs[i].regexStartsWith(/.*\)[\s]*IS/)) { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + beautifyPortGenericBlock(block, result, settings, indent, "PROCEDURE"); + if (block.lines[block.cursor].regexStartsWith(/.*\)[\s]*IS/)) { + block.cursor++; + beautify3(block, result, settings, indent + 1); } continue; } if (input.regexStartsWith(/FUNCTION[^\w]/) && input.regexIndexOf(/[^\w]RETURN[^\w]/) < 0) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "FUNCTION"); - if (!inputs[i].regexStartsWith(regexBlockEndsKeyWords)) { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + beautifyPortGenericBlock(block, result, settings, indent, "FUNCTION"); + if (!block.lines[block.cursor].regexStartsWith(regexBlockEndsKeyWords)) { + block.cursor++; + beautify3(block, result, settings, indent + 1); } else { - result[i].Indent++; + result[block.cursor].Indent++; } continue; } if (input.regexStartsWith(/IMPURE FUNCTION[^\w]/) && input.regexIndexOf(/[^\w]RETURN[^\w]/) < 0) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "IMPURE FUNCTION"); - if (!inputs[i].regexStartsWith(regexBlockEndsKeyWords)) { - if (inputs[i].regexStartsWith(regexBlockIndentedEndsKeyWords)) { - result[i].Indent++; + beautifyPortGenericBlock(block, result, settings, indent, "IMPURE FUNCTION"); + if (!block.lines[block.cursor].regexStartsWith(regexBlockEndsKeyWords)) { + if (block.lines[block.cursor].regexStartsWith(regexBlockIndentedEndsKeyWords)) { + result[block.cursor].Indent++; } else { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + block.cursor++; + beautify3(block, result, settings, indent + 1); } } else { - result[i].Indent++; + result[block.cursor].Indent++; } continue; } result.push(new FormattedLine(input, indent)); - if (startIndex != 0 + if (indent > 0 && (input.regexStartsWith(regexBlockMidKeyWords) || (Mode != FormatMode.EndsWithSemicolon && input.regexStartsWith(regexMidKeyElse)) || (Mode == FormatMode.CaseWhen && input.regexStartsWith(regexMidKeyWhen)))) { - result[i].Indent--; + result[block.cursor].Indent--; } - else if (startIndex != 0 + else if (indent > 0 && (input.regexStartsWith(regexBlockEndsKeyWords))) { - result[i].Indent--; - return i; + result[block.cursor].Indent--; + return; } if (input.regexStartsWith(regexOneLineBlockKeyWords)) { continue; } if (input.regexStartsWith(regexFunctionMultiLineBlockKeyWords) || input.regexStartsWith(regexBlockStartsKeywords)) { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + block.cursor++; + beautify3(block, result, settings, indent + 1); } } - i--; - return i; + block.cursor--; } exports.beautify3 = beautify3; function ReserveSemicolonInKeywords(arr) { diff --git a/VHDLFormatter.ts b/VHDLFormatter.ts index 816815d..51906da 100644 --- a/VHDLFormatter.ts +++ b/VHDLFormatter.ts @@ -390,7 +390,8 @@ export function beautify(input: string, settings: BeautifierSettings) { input = input.replace(/(\() +([+\-]) +(\w)/g, '$1$2$3');// `( - 2)` -> `(-2)` arr = input.split("\r\n"); let result: (FormattedLine | FormattedLine[])[] = []; - beautify3(arr, result, settings, 0, 0); + let block = new CodeBlock(arr); + beautify3(block, result, settings, 0); var alignSettings = settings.SignAlignSettings; if (alignSettings != null && alignSettings.isAll) { AlignSigns(result, 0, result.length - 1, alignSettings.mode, alignSettings.alignComments); @@ -448,6 +449,45 @@ export class FormattedLine { } } +export class CodeBlock { + lines: Array; + start: number; // index of first line of range + end: number; // index of last line of range + cursor: number; // line currently being processed + parent: CodeBlock; + + constructor(lines: Array, start = 0, end = lines.length - 1) { + this.lines = lines; + this.start = start; + this.end = end; + this.parent = null; + this.cursor = start; + } + + _notifySplit(atLine: number) { + if (this.start > atLine) + this.start++; + if (this.end >= atLine) + this.end++; + if (this.cursor >= atLine) + this.cursor++; + if (this.parent) + this.parent._notifySplit(atLine); + } + + splitLine(atLine: number, firstText: string, secondText: string) { + this.lines[atLine] = firstText; + this.lines.splice(atLine + 1, 0, secondText); + this._notifySplit(atLine); + } + + subBlock(start: number, end: number): CodeBlock { + let newBlock = new CodeBlock(this.lines, start, end); + newBlock.parent = this; + return newBlock; + } +} + export function FormattedLineToString(arr: (FormattedLine | FormattedLine[])[], indentation: string): Array { let result: Array = []; if (arr == null) { @@ -472,83 +512,81 @@ export function FormattedLineToString(arr: (FormattedLine | FormattedLine[])[], return result; } -function GetCloseparentheseEndIndex(inputs: Array, startIndex: number): number { +function GetCloseparentheseEndIndex(block: CodeBlock) { let openParentheseCount: number = 0; let closeParentheseCount: number = 0; - for (let i = startIndex; i < inputs.length; i++) { - let input = inputs[i]; + let startIndex = block.cursor; + for (;block.cursor <= block.end; block.cursor++) { + let input = block.lines[block.cursor]; openParentheseCount += input.count("("); closeParentheseCount += input.count(")"); if (openParentheseCount > 0 && openParentheseCount <= closeParentheseCount) { - return i; + return; } } - return startIndex; + block.cursor = startIndex; } -export function beautifyPortGenericBlock(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, parentEndIndex: number, indent: number, mode: string): [number, number] { - let firstLine: string = inputs[startIndex]; +export function beautifyPortGenericBlock(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number, mode: string) { + let startIndex = block.cursor; + let firstLine: string = block.lines[startIndex]; let regex: RegExp = new RegExp("[\\w\\s:]*(" + mode + ")([\\s]|$)"); if (!firstLine.regexStartsWith(regex)) { - return [startIndex, parentEndIndex]; + return; } + let firstLineHasParenthese: boolean = firstLine.indexOf("(") >= 0; - let hasParenthese: boolean = firstLineHasParenthese; - let blockBodyStartIndex = startIndex; - let secondLineHasParenthese: boolean = startIndex + 1 < inputs.length && inputs[startIndex + 1].startsWith("("); - if (secondLineHasParenthese) { - hasParenthese = true; - blockBodyStartIndex++; + let secondLineHasParenthese: boolean = startIndex + 1 <= block.end && block.lines[startIndex + 1].startsWith("("); + let hasParenthese: boolean = firstLineHasParenthese || secondLineHasParenthese; + let blockBodyStartIndex = startIndex + (secondLineHasParenthese ? 1 : 0); + if (hasParenthese) { + GetCloseparentheseEndIndex(block); } - let endIndex: number = hasParenthese ? GetCloseparentheseEndIndex(inputs, startIndex) : startIndex; + let endIndex: number = block.cursor; + let bodyBlock = block.subBlock(blockBodyStartIndex, endIndex); + if (endIndex != startIndex && firstLineHasParenthese) { - inputs[startIndex] = inputs[startIndex].replace(/\b(PORT|GENERIC|PROCEDURE)\b([\w ]+)\(([\w\(\) ]+)/, '$1$2(\r\n$3'); - let newInputs = inputs[startIndex].split("\r\n"); + block.lines[startIndex] = block.lines[startIndex].replace(/\b(PORT|GENERIC|PROCEDURE)\b([\w ]+)\(([\w\(\) ]+)/, '$1$2(\r\n$3'); + let newInputs = block.lines[startIndex].split("\r\n"); if (newInputs.length == 2) { - inputs[startIndex] = newInputs[0]; - inputs.splice(startIndex + 1, 0, newInputs[1]); - endIndex++; - parentEndIndex++; + bodyBlock.splitLine(startIndex, newInputs[0], newInputs[1]); } } else if (endIndex > startIndex + 1 && secondLineHasParenthese) { - inputs[startIndex + 1] = inputs[startIndex + 1].replace(/\(([\w\(\) ]+)/, '(\r\n$1'); - let newInputs = inputs[startIndex + 1].split("\r\n"); + block.lines[startIndex + 1] = block.lines[startIndex + 1].replace(/\(([\w\(\) ]+)/, '(\r\n$1'); + let newInputs = block.lines[startIndex + 1].split("\r\n"); if (newInputs.length == 2) { - inputs[startIndex + 1] = newInputs[0]; - inputs.splice(startIndex + 2, 0, newInputs[1]); - endIndex++; - parentEndIndex++; + bodyBlock.splitLine(startIndex + 1, newInputs[0], newInputs[1]); } } - if (firstLineHasParenthese && inputs[startIndex].indexOf("MAP") > 0) { - inputs[startIndex] = inputs[startIndex].replace(/([^\w])(MAP)\s+\(/g, '$1$2('); + + if (firstLineHasParenthese && block.lines[startIndex].indexOf("MAP") > 0) { + block.lines[startIndex] = block.lines[startIndex].replace(/([^\w])(MAP)\s+\(/g, '$1$2('); } - result.push(new FormattedLine(inputs[startIndex], indent)); + + result.push(new FormattedLine(block.lines[startIndex], indent)); if (secondLineHasParenthese) { let secondLineIndent = indent; if (endIndex == startIndex + 1) { secondLineIndent++; } - result.push(new FormattedLine(inputs[startIndex + 1], secondLineIndent)); + result.push(new FormattedLine(block.lines[startIndex + 1], secondLineIndent)); } - let blockBodyEndIndex = endIndex; - let i = beautify3(inputs, result, settings, blockBodyStartIndex + 1, indent + 1, endIndex); - if (inputs[i].startsWith(")")) { - (result[i]).Indent--; - blockBodyEndIndex--; + + beautify3(bodyBlock.subBlock(bodyBlock.start + 1, bodyBlock.end), result, settings, indent + 1); + if (block.lines[block.cursor].startsWith(")")) { + (result[block.cursor]).Indent--; + bodyBlock.end--; } var alignSettings = settings.SignAlignSettings; if (alignSettings != null) { if (alignSettings.isRegional && !alignSettings.isAll && alignSettings.keyWords != null && alignSettings.keyWords.indexOf(mode) >= 0) { - blockBodyStartIndex++; - AlignSigns(result, blockBodyStartIndex, blockBodyEndIndex, alignSettings.mode, alignSettings.alignComments); + AlignSigns(result, bodyBlock.start + 1, bodyBlock.end, alignSettings.mode, alignSettings.alignComments); } } - return [i, parentEndIndex]; } export function AlignSigns(result: (FormattedLine | FormattedLine[])[], startIndex: number, endIndex: number, mode: string, alignComments: boolean = false) { @@ -646,23 +684,22 @@ export function AlignSign(result: (FormattedLine | FormattedLine[])[], startInde } } -export function beautifyCaseBlock(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, indent: number): number { - if (!inputs[startIndex].regexStartsWith(/(.+:\s*)?(CASE)([\s]|$)/)) { - return startIndex; +export function beautifyCaseBlock(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) { + if (!block.lines[block.cursor].regexStartsWith(/(.+:\s*)?(CASE)([\s]|$)/)) { + return; } - result.push(new FormattedLine(inputs[startIndex], indent)); - - let i = beautify3(inputs, result, settings, startIndex + 1, indent + 2); - (result[i]).Indent = indent; - return i; + result.push(new FormattedLine(block.lines[block.cursor], indent)); + block.cursor++; + beautify3(block, result, settings, indent + 2); + (result[block.cursor]).Indent = indent; } -function getSemicolonBlockEndIndex(inputs: Array, settings: BeautifierSettings, startIndex: number, parentEndIndex: number): [number, number] { - let endIndex = 0; +function getSemicolonBlockEndIndex(block: CodeBlock, settings: BeautifierSettings) { + let endIndex = block.cursor; let openBracketsCount = 0; let closeBracketsCount = 0; - for (let i = startIndex; i < inputs.length; i++) { - let input = inputs[i]; + for (; block.cursor <= block.end; block.cursor++) { + let input = block.lines[block.cursor]; let indexOfSemicolon = input.indexOf(";"); let splitIndex = indexOfSemicolon < 0 ? input.length : indexOfSemicolon + 1; let stringBeforeSemicolon = input.substring(0, splitIndex); @@ -674,85 +711,63 @@ function getSemicolonBlockEndIndex(inputs: Array, settings: BeautifierSe continue; } if (openBracketsCount == closeBracketsCount) { - endIndex = i; + endIndex = block.cursor; if (stringAfterSemicolon.trim().length > 0 && settings.NewLineSettings.newLineAfter.indexOf(";") >= 0) { - inputs[i] = stringBeforeSemicolon; - inputs.splice(i, 0, stringAfterSemicolon); - parentEndIndex++; + block.splitLine(block.cursor, stringBeforeSemicolon, stringAfterSemicolon); } break; } } - return [endIndex, parentEndIndex]; + block.cursor = endIndex; } -export function beautifyComponentBlock(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, parentEndIndex: number, indent: number): [number, number] { - let endIndex = startIndex; - for (let i = startIndex; i < inputs.length; i++) { - if (inputs[i].regexStartsWith(/END(\s|$)/)) { - endIndex = i; +export function beautifyComponentBlock(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) { + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + if (block.lines[block.cursor].regexStartsWith(/END(\s|$)/)) { break; } } - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - let actualEndIndex = beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - let incremental = actualEndIndex - endIndex; - endIndex += incremental; - parentEndIndex += incremental; + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); } - - return [endIndex, parentEndIndex]; } -export function beautifyPackageIsNewBlock(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, parentEndIndex: number, indent: number): [number, number] { - let endIndex = startIndex; - for (let i = startIndex; i < inputs.length; i++) { - if (inputs[i].regexIndexOf(/;(\s|$)/) >= 0) { - endIndex = i; +export function beautifyPackageIsNewBlock(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) { + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + if (block.lines[block.cursor].regexIndexOf(/;(\s|$)/) >= 0) { break; } } - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - let actualEndIndex = beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - let incremental = actualEndIndex - endIndex; - endIndex += incremental; - parentEndIndex += incremental; + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); } - - return [endIndex, parentEndIndex]; } -export function beautifyVariableInitialiseBlock(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, parentEndIndex: number, indent: number): [number, number] { - let endIndex = startIndex; - for (let i = startIndex; i < inputs.length; i++) { - if (inputs[i].regexIndexOf(/;(\s|$)/) >= 0) { - endIndex = i; +export function beautifyVariableInitialiseBlock(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) { + let startIndex = block.cursor; + for (; block.cursor <= block.end; block.cursor++) { + if (block.lines[block.cursor].regexIndexOf(/;(\s|$)/) >= 0) { break; } } - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - let actualEndIndex = beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - let incremental = actualEndIndex - endIndex; - endIndex += incremental; - parentEndIndex += incremental; + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); } - - return [endIndex, parentEndIndex]; } -export function beautifySemicolonBlock(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, parentEndIndex: number, indent: number): [number, number] { - let endIndex = startIndex; - [endIndex, parentEndIndex] = getSemicolonBlockEndIndex(inputs, settings, startIndex, parentEndIndex); - result.push(new FormattedLine(inputs[startIndex], indent)); - if (endIndex != startIndex) { - beautify3(inputs, result, settings, startIndex + 1, indent + 1, endIndex); - alignSignalAssignmentBlock(settings, inputs, startIndex, endIndex, result); +export function beautifySemicolonBlock(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) { + let startIndex = block.cursor; + getSemicolonBlockEndIndex(block, settings); + result.push(new FormattedLine(block.lines[startIndex], indent)); + if (block.cursor != startIndex) { + beautify3(block.subBlock(startIndex + 1, block.cursor), result, settings, indent + 1); + alignSignalAssignmentBlock(settings, block.lines, startIndex, block.cursor, result); } - - return [endIndex, parentEndIndex]; } function alignSignalAssignmentBlock(settings: BeautifierSettings, inputs: string[], startIndex: number, endIndex: number, result: (FormattedLine | FormattedLine[])[]) { @@ -771,8 +786,7 @@ function alignSignalAssignmentBlock(settings: BeautifierSettings, inputs: string } } -export function beautify3(inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, indent: number, endIndex?: number): number { - let i: number; +export function beautify3(block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) { let regexOneLineBlockKeyWords: RegExp = new RegExp(/(PROCEDURE)[^\w](?!.+[^\w]IS([^\w]|$))/);//match PROCEDURE..; but not PROCEDURE .. IS; let regexFunctionMultiLineBlockKeyWords: RegExp = new RegExp(/(FUNCTION|IMPURE FUNCTION)[^\w](?=.+[^\w]IS([^\w]|$))/);//match FUNCTION .. IS; but not FUNCTION let blockMidKeyWords: Array = ["BEGIN"]; @@ -814,29 +828,26 @@ export function beautify3(inputs: Array, result: (FormattedLine | Format let regexblockEndsWithSemicolon: RegExp = blockEndsWithSemicolon.convertToRegexBlockWords(); let regexMidKeyWhen: RegExp = "WHEN".convertToRegexBlockWords(); let regexMidKeyElse: RegExp = "ELSE|ELSIF".convertToRegexBlockWords(); - if (endIndex == null) { - endIndex = inputs.length - 1; - } - for (i = startIndex; i <= endIndex; i++) { + for (; block.cursor <= block.end; block.cursor++) { if (indent < 0) { indent = 0; } - let input: string = inputs[i].trim(); + let input: string = block.lines[block.cursor].trim(); if (input.regexStartsWith(regexBlockIndentedEndsKeyWords)) { result.push(new FormattedLine(input, indent)); - return i; + return; } if (input.regexStartsWith(/COMPONENT\s/)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifyComponentBlock(inputs, result, settings, i, endIndex, indent); + beautifyComponentBlock(block, result, settings, indent); Mode = modeCache; continue; } if (input.regexStartsWith(/PACKAGE[\s\w]+IS\s+NEW/)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifyPackageIsNewBlock(inputs, result, settings, i, endIndex, indent); + beautifyPackageIsNewBlock(block, result, settings, indent); Mode = modeCache; continue; } @@ -844,10 +855,10 @@ export function beautify3(inputs: Array, result: (FormattedLine | Format let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; let endsWithBracket = input.regexIndexOf(/:\s*=\s*\(/) > 0; - let startIndex = i; - [i, endIndex] = beautifySemicolonBlock(inputs, result, settings, i, endIndex, indent); - if (endsWithBracket && startIndex != i) { - let fl = result[endIndex] as FormattedLine; + let startIndex = block.cursor; + beautifySemicolonBlock(block, result, settings, indent); + if (endsWithBracket && startIndex != block.cursor) { + let fl = result[block.end] as FormattedLine; if (fl.Line.regexStartsWith(/\);$/)) { fl.Indent--; } @@ -858,93 +869,96 @@ export function beautify3(inputs: Array, result: (FormattedLine | Format if (input.regexStartsWith(/\w+\s*:\s*ENTITY/)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifySemicolonBlock(inputs, result, settings, i, endIndex, indent); + beautifySemicolonBlock(block, result, settings, indent); Mode = modeCache; continue; } if (Mode != FormatMode.EndsWithSemicolon && input.regexStartsWith(regexblockEndsWithSemicolon)) { let modeCache = Mode; Mode = FormatMode.EndsWithSemicolon; - [i, endIndex] = beautifySemicolonBlock(inputs, result, settings, i, endIndex, indent); + beautifySemicolonBlock(block, result, settings, indent); Mode = modeCache; continue; } if (input.regexStartsWith(/(.+:\s*)?(CASE)([\s]|$)/)) { let modeCache = Mode; Mode = FormatMode.CaseWhen; - i = beautifyCaseBlock(inputs, result, settings, i, indent); + beautifyCaseBlock(block, result, settings, indent); Mode = modeCache; continue; } if (input.regexStartsWith(/[\w\s:]*(:=)([\s]|$)/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, ":="); + beautifyPortGenericBlock(block, result, settings, indent, ":="); continue; } if (input.regexStartsWith(/[\w\s:]*\bPORT\b([\s]|$)/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "PORT"); + beautifyPortGenericBlock(block, result, settings, indent, "PORT"); continue; } if (input.regexStartsWith(/TYPE\s+\w+\s+IS\s+\(/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "IS"); + beautifyPortGenericBlock(block, result, settings, indent, "IS"); continue; } if (input.regexStartsWith(/[\w\s:]*GENERIC([\s]|$)/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "GENERIC"); + beautifyPortGenericBlock(block, result, settings, indent, "GENERIC"); continue; } if (input.regexStartsWith(/[\w\s:]*PROCEDURE[\s\w]+\($/)) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "PROCEDURE"); - if (inputs[i].regexStartsWith(/.*\)[\s]*IS/)) { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + beautifyPortGenericBlock(block, result, settings, indent, "PROCEDURE"); + if (block.lines[block.cursor].regexStartsWith(/.*\)[\s]*IS/)) { + block.cursor++; + beautify3(block, result, settings, indent + 1); } continue; } if (input.regexStartsWith(/FUNCTION[^\w]/) && input.regexIndexOf(/[^\w]RETURN[^\w]/) < 0) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "FUNCTION"); - if (!inputs[i].regexStartsWith(regexBlockEndsKeyWords)) { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + beautifyPortGenericBlock(block, result, settings, indent, "FUNCTION"); + if (!block.lines[block.cursor].regexStartsWith(regexBlockEndsKeyWords)) { + block.cursor++; + beautify3(block, result, settings, indent + 1); } else { - (result[i]).Indent++; + (result[block.cursor]).Indent++; } continue; } if (input.regexStartsWith(/IMPURE FUNCTION[^\w]/) && input.regexIndexOf(/[^\w]RETURN[^\w]/) < 0) { - [i, endIndex] = beautifyPortGenericBlock(inputs, result, settings, i, endIndex, indent, "IMPURE FUNCTION"); - if (!inputs[i].regexStartsWith(regexBlockEndsKeyWords)) { - if (inputs[i].regexStartsWith(regexBlockIndentedEndsKeyWords)) { - (result[i]).Indent++; + beautifyPortGenericBlock(block, result, settings, indent, "IMPURE FUNCTION"); + if (!block.lines[block.cursor].regexStartsWith(regexBlockEndsKeyWords)) { + if (block.lines[block.cursor].regexStartsWith(regexBlockIndentedEndsKeyWords)) { + (result[block.cursor]).Indent++; } else { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + block.cursor++; + beautify3(block, result, settings, indent + 1); } } else { - (result[i]).Indent++; + (result[block.cursor]).Indent++; } continue; } result.push(new FormattedLine(input, indent)); - if (startIndex != 0 + if (indent > 0 && (input.regexStartsWith(regexBlockMidKeyWords) || (Mode != FormatMode.EndsWithSemicolon && input.regexStartsWith(regexMidKeyElse)) || (Mode == FormatMode.CaseWhen && input.regexStartsWith(regexMidKeyWhen)))) { - (result[i]).Indent--; + (result[block.cursor]).Indent--; } - else if (startIndex != 0 + else if (indent > 0 && (input.regexStartsWith(regexBlockEndsKeyWords))) { - (result[i]).Indent--; - return i; + (result[block.cursor]).Indent--; + return; } if (input.regexStartsWith(regexOneLineBlockKeyWords)) { continue; } if (input.regexStartsWith(regexFunctionMultiLineBlockKeyWords) || input.regexStartsWith(regexBlockStartsKeywords)) { - i = beautify3(inputs, result, settings, i + 1, indent + 1); + block.cursor++; + beautify3(block, result, settings, indent + 1); } } - i--; - return i; + block.cursor--; } function ReserveSemicolonInKeywords(arr: Array) { diff --git a/tests/VHDLFormatterUnitTests.ts b/tests/VHDLFormatterUnitTests.ts index 4c0cf48..dd21b7a 100644 --- a/tests/VHDLFormatterUnitTests.ts +++ b/tests/VHDLFormatterUnitTests.ts @@ -4,7 +4,7 @@ import { BeautifierSettings } from "../VHDLFormatter"; import { RemoveAsserts } from "../VHDLFormatter"; import { ApplyNoNewLineAfter } from "../VHDLFormatter"; import { SetNewLinesAfterSymbols } from "../VHDLFormatter"; -import { beautify3 } from "../VHDLFormatter"; +import { CodeBlock, beautify3 } from "../VHDLFormatter"; import { FormattedLine } from "../VHDLFormatter"; import { FormattedLineToString } from "../VHDLFormatter"; import { CompareString } from "./assert"; @@ -75,6 +75,7 @@ function UnitTestbeautify3() { Beautify3Case19(); Beautify3Case20(); Beautify3Case21(); + Beautify3Case22(); } function Beautify3Case1() { @@ -471,8 +472,8 @@ function Beautify3Case16() { ]; let expected: (FormattedLine | FormattedLine[])[] = [ new FormattedLine("x <= 1 WHEN foo", 0), - new FormattedLine("ELSE 2 WHEN bar", 1), - new FormattedLine("ELSE 3;", 1), + new FormattedLine(" ELSE 2 WHEN bar", 1), + new FormattedLine(" ELSE 3;", 1), new FormattedLine("y <= 2;", 0) ]; UnitTest6(beautify3, "one line ends with ;", settings, inputs, expected, 0, expected.length - 1, 0); @@ -562,7 +563,7 @@ function Beautify3Case20() { ]; let expected: (FormattedLine | FormattedLine[])[] = [ new FormattedLine("m <= ((1, 2, 3, 4)", 0), - new FormattedLine("(5, 6, 7, 8));", 1), + new FormattedLine(" (5, 6, 7, 8));", 1), new FormattedLine("y <= 2;", 0) ]; UnitTest6(beautify3, "function", settings, inputs, expected, 0, expected.length - 1, 0); @@ -590,6 +591,39 @@ function Beautify3Case21() { UnitTest6(beautify3, "function", settings, inputs, expected, 0, expected.length - 1, 0); } +function Beautify3Case22() { + let new_line_after_symbols: NewLineSettings = new NewLineSettings(); + new_line_after_symbols.newLineAfter = ["then", ";"]; + let settings = getDefaultBeautifierSettings(new_line_after_symbols); + let inputs: Array = [ + "test: ENTITY work.testing", + "GENERIC MAP( x => 1,", + "y => 2", + ")", + "PORT MAP( z => OPEN,", + "w => '1'", + ");", + "", + "foo <= '1';", + "bar <= '1';", + ]; + let expected: (FormattedLine | FormattedLine[])[] = [ + new FormattedLine("test: ENTITY work.testing", 0), + new FormattedLine("GENERIC MAP(", 1), + new FormattedLine("x => 1,", 2), + new FormattedLine("y => 2", 2), + new FormattedLine(")", 1), + new FormattedLine("PORT MAP(", 1), + new FormattedLine("z => OPEN,", 2), + new FormattedLine("w => '1'", 2), + new FormattedLine(");", 1), + new FormattedLine("", 0), + new FormattedLine("foo <= '1';", 0), + new FormattedLine("bar <= '1';", 0), + ]; + UnitTest6(beautify3, "inserted line breaks in generic maps (issue #43)", settings, inputs, expected, 0, expected.length - 1, 0); +} + function UnitTestSetNewLinesAfterSymbols() { console.log("=== SetNewLinesAfterSymbols ==="); let input = "a; @@comments1\r\nb;" @@ -712,7 +746,7 @@ type Array2Callback = (arr: Array, parameters: Array) => void; type String2Callback = (text: string, parameters: NewLineSettings) => string; -type BeautifyCallback = (inputs: Array, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, startIndex: number, indent: number) => number; +type BeautifyCallback = (block: CodeBlock, result: (FormattedLine | FormattedLine[])[], settings: BeautifierSettings, indent: number) => void; type FormattedLinesCallback = (inputs: (FormattedLine | FormattedLine[])[], indentation: string) => Array; @@ -723,9 +757,11 @@ function UnitTest7(func: FormattedLinesCallback, testName: string, indentation: function UnitTest6(func: BeautifyCallback, testName: string, parameters: BeautifierSettings, inputs: Array, expected: (FormattedLine | FormattedLine[])[], startIndex: number, expectedEndIndex: number, indent: number) { let actual: (FormattedLine | FormattedLine[])[] = [] - let endIndex: number = func(inputs, actual, parameters, startIndex, indent); - if (endIndex != expectedEndIndex) { - console.log(testName + " failed;\nend index, actual: " + endIndex + "; expected: " + expectedEndIndex) + let block = new CodeBlock(inputs); + block.cursor = startIndex; + func(block, actual, parameters, indent); + if (block.cursor != expectedEndIndex) { + console.log(testName + " failed;\nend index, actual: " + block.cursor + "; expected: " + expectedEndIndex) } assertFormattedLines(testName, expected, actual); } @@ -759,7 +795,7 @@ function IntegrationTest() { new_line_after_symbols.noNewLineAfter = ["port", "generic"]; let settings = getDefaultBeautifierSettings(new_line_after_symbols); let input = "architecture TB of TB_CPU is\r\n component CPU_IF\r\n port -- port list\r\n end component;\r\n signal CPU_DATA_VALID: std_ulogic;\r\n signal CLK, RESET: std_ulogic := '0';\r\n constant PERIOD : time := 10 ns;\r\n constant MAX_SIM: time := 50 * PERIOD;\r\n begin\r\n -- concurrent statements\r\n end TB;" - let expected = "ARCHITECTURE TB OF TB_CPU IS\r\n COMPONENT CPU_IF\r\n PORT -- port list\r\n END COMPONENT;\r\n SIGNAL CPU_DATA_VALID : std_ulogic;\r\n SIGNAL CLK, RESET : std_ulogic := '0';\r\n CONSTANT PERIOD : TIME := 10 ns;\r\n CONSTANT MAX_SIM : TIME := 50 * PERIOD;\r\nBEGIN\r\n -- concurrent statements\r\nEND TB;"; + let expected = "ARCHITECTURE TB OF TB_CPU IS\r\n COMPONENT CPU_IF\r\n PORT -- port list\r\n END COMPONENT;\r\n SIGNAL CPU_DATA_VALID : STD_ULOGIC;\r\n SIGNAL CLK, RESET : STD_ULOGIC := '0';\r\n CONSTANT PERIOD : TIME := 10 ns;\r\n CONSTANT MAX_SIM : TIME := 50 * PERIOD;\r\nBEGIN\r\n -- concurrent statements\r\nEND TB;"; let actual = beautify(input, settings); assertAndCountTest("General", expected, actual); @@ -818,17 +854,17 @@ function IntegrationTest() { assertAndCountTest("WHEN CASE & IF", expected, actual); input = "entity aa is\r\n port (a : in std_logic;\r\n b : in std_logic;\r\n );\r\nend aa;\r\narchitecture bb of aa is\r\n component cc\r\n port(\r\n a : in std_logic;\r\n b : in std_logic;\r\n );\r\n end cc;\r\n\r\nbegin\r\n C : cc port map (\r\n long => a,\r\n b => b\r\n );\r\nend;"; - expected = "ENTITY aa IS\r\n PORT (\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n );\r\nEND aa;\r\nARCHITECTURE bb OF aa IS\r\n COMPONENT cc\r\n PORT (\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n );\r\n END cc;\r\n\r\nBEGIN\r\n C : cc PORT MAP(\r\n long => a,\r\n b => b\r\n );\r\nEND;"; + expected = "ENTITY aa IS\r\n PORT (\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n );\r\nEND aa;\r\nARCHITECTURE bb OF aa IS\r\n COMPONENT cc\r\n PORT (\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n );\r\n END cc;\r\n\r\nBEGIN\r\n C : cc PORT MAP(\r\n long => a,\r\n b => b\r\n );\r\nEND;"; actual = beautify(input, settings); assertAndCountTest("PORT MAP", expected, actual); input = "entity aa is\r\n port (a : in std_logic;\r\n b : in std_logic;\r\n );\r\n port (a : in std_logic;\r\n b : in std_logic;\r\n );\r\nend aa;\r\narchitecture bb of aa is\r\n component cc\r\n port(\r\n a : in std_logic;\r\n b : in std_logic;\r\n );\r\n port(\r\n a : in std_logic;\r\n b : in std_logic;\r\n );\r\n end cc;\r\n\r\nbegin\r\n C : cc port map (\r\n long => a,\r\n b => b\r\n );\r\n D : cc port map (\r\n long => a,\r\n b => b\r\n );\r\nend;"; - expected = "ENTITY aa IS\r\n PORT (\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n );\r\n PORT (\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n );\r\nEND aa;\r\nARCHITECTURE bb OF aa IS\r\n COMPONENT cc\r\n PORT (\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n );\r\n PORT (\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n );\r\n END cc;\r\n\r\nBEGIN\r\n C : cc PORT MAP(\r\n long => a,\r\n b => b\r\n );\r\n D : cc PORT MAP(\r\n long => a,\r\n b => b\r\n );\r\nEND;"; + expected = "ENTITY aa IS\r\n PORT (\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n );\r\n PORT (\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n );\r\nEND aa;\r\nARCHITECTURE bb OF aa IS\r\n COMPONENT cc\r\n PORT (\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n );\r\n PORT (\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n );\r\n END cc;\r\n\r\nBEGIN\r\n C : cc PORT MAP(\r\n long => a,\r\n b => b\r\n );\r\n D : cc PORT MAP(\r\n long => a,\r\n b => b\r\n );\r\nEND;"; actual = beautify(input, settings); assertAndCountTest("Multiple PORT MAPs", expected, actual); input = "port (a : in std_logic;\r\n b : in std_logic;\r\n);"; - expected = "PORT\r\n(\r\n a : IN std_logic;\r\n b : IN std_logic;\r\n);"; + expected = "PORT\r\n(\r\n a : IN STD_LOGIC;\r\n b : IN STD_LOGIC;\r\n);"; new_line_after_symbols_2 = new NewLineSettings(); new_line_after_symbols_2.newLineAfter = ["then", ";", "generic", "port"]; newSettings = deepCopy(settings); @@ -839,14 +875,14 @@ function IntegrationTest() { newSettings = deepCopy(settings); newSettings.NewLineSettings.newLineAfter = []; input = "component a is\r\nport( Data : inout Std_Logic_Vector(7 downto 0););\r\nend component a;"; - expected = "COMPONENT a IS\r\n PORT (Data : INOUT Std_Logic_Vector(7 DOWNTO 0););\r\nEND COMPONENT a;"; + expected = "COMPONENT a IS\r\n PORT (Data : INOUT STD_LOGIC_VECTOR(7 DOWNTO 0););\r\nEND COMPONENT a;"; actual = beautify(input, newSettings); assertAndCountTest("New line after PORT (single line)", expected, actual); //IntegrationTest20(); input = "architecture a of b is\r\nbegin\r\n process (w)\r\n variable t : std_logic_vector (4 downto 0) ;\r\nbegin\r\n a := (others => '0') ;\r\nend process ;\r\nend a;"; - expected = "ARCHITECTURE a OF b IS\r\nBEGIN\r\n PROCESS (w)\r\n VARIABLE t : std_logic_vector (4 DOWNTO 0);\r\n BEGIN\r\n a := (OTHERS => '0');\r\n END PROCESS;\r\nEND a;"; + expected = "ARCHITECTURE a OF b IS\r\nBEGIN\r\n PROCESS (w)\r\n VARIABLE t : STD_LOGIC_VECTOR (4 DOWNTO 0);\r\n BEGIN\r\n a := (OTHERS => '0');\r\n END PROCESS;\r\nEND a;"; actual = beautify(input, newSettings); assertAndCountTest("Double BEGIN", expected, actual); @@ -1459,7 +1495,7 @@ function IntegrationTest2() { let settings = getDefaultBeautifierSettings(new_line_after_symbols); settings.RemoveComments = true; let input = "architecture TB of TB_CPU is\r\n component CPU_IF\r\n port -- port list\r\n end component;\r\n signal CPU_DATA_VALID: std_ulogic;\r\n signal CLK, RESET: std_ulogic := '0';\r\n constant PERIOD : time := 10 ns;\r\n constant MAX_SIM: time := 50 * PERIOD;\r\n begin\r\n -- concurrent statements\r\n end TB;" - let expected = "ARCHITECTURE TB OF TB_CPU IS\r\n COMPONENT CPU_IF\r\n PORT\r\n END COMPONENT;\r\n SIGNAL CPU_DATA_VALID : std_ulogic;\r\n SIGNAL CLK, RESET : std_ulogic := '0';\r\n CONSTANT PERIOD : TIME := 10 ns;\r\n CONSTANT MAX_SIM : TIME := 50 * PERIOD;\r\nBEGIN\r\nEND TB;"; + let expected = "ARCHITECTURE TB OF TB_CPU IS\r\n COMPONENT CPU_IF\r\n PORT\r\n END COMPONENT;\r\n SIGNAL CPU_DATA_VALID : STD_ULOGIC;\r\n SIGNAL CLK, RESET : STD_ULOGIC := '0';\r\n CONSTANT PERIOD : TIME := 10 ns;\r\n CONSTANT MAX_SIM : TIME := 50 * PERIOD;\r\nBEGIN\r\nEND TB;"; let actual = beautify(input, settings); assertAndCountTest("Remove comments", expected, actual); } @@ -1490,4 +1526,4 @@ function CompareArray(actual: Array, expected: Array) { } } return true; -} \ No newline at end of file +}