diff --git a/spin2/CHANGELOG.md b/spin2/CHANGELOG.md index 9539132..14365e4 100644 --- a/spin2/CHANGELOG.md +++ b/spin2/CHANGELOG.md @@ -18,6 +18,12 @@ Possible next additions: - Add new-file templates as Snippets - Add additional Snippets as the community identifies them +## [2.2.10] 2023-12-23 + +Update P1 and P2 + +- Complete the implementation of the spin/spin2 colde folding + ## [2.2.9] 2023-12-11 Update P2 Only diff --git a/spin2/TEST_LANG_SERVER/spin/fold_tests.spin b/spin2/TEST_LANG_SERVER/spin/fold_tests.spin new file mode 100644 index 0000000..fd91bbf --- /dev/null +++ b/spin2/TEST_LANG_SERVER/spin/fold_tests.spin @@ -0,0 +1,60 @@ +' flow control structures that need to be folded + +PUB main(valueParm) + +' -------------------------------------------------- + '' IF testing + ' keywords: IF IFNOT, ELSE, ELSEIF, ELSEIFNOT + if true + ' do something + elseif false + ifnot true + ' do something + elseifnot false + ' do other + else + ' yet more + else + ' do other + + ifnot true + ' do something + elseifnot false + ' do other + else + ' yet more + +' -------------------------------------------------- + '' CASE testing + ' keywords: CASE, CASE_FAST + case valueParm + 1: ' code + + 2: ' code + other: + ' code + +' -------------------------------------------------- + '' REPEAT testing + ' keywords: REPEAT, WHILE, UNTIL + repeat 10 + ' code + + repeat + ' code + until false + + repeat + 'code + while true + +' -------------------------------------------------- + +PRI dec(value) | flag, place, digit 'private method prints decimals, three local variables + flag~ 'reset digit-printed flag + place := 1_000_000_000 'start at the one-billion's place and work downward + + REPEAT + IF flag ||= (digit := value / place // 10) || place == 1 'print a digit? + IF LOOKDOWN(place : 1_000_000_000, 1_000_000, 1_000) 'also print a comma? + WHILE place /= 10 diff --git a/spin2/TEST_LANG_SERVER/spin2/231112-fixes.spin2 b/spin2/TEST_LANG_SERVER/spin2/231112-fixes.spin2 index b1f895e..3fa6359 100644 --- a/spin2/TEST_LANG_SERVER/spin2/231112-fixes.spin2 +++ b/spin2/TEST_LANG_SERVER/spin2/231112-fixes.spin2 @@ -62,11 +62,11 @@ PUB LEDon() DoSETUP(string("Text",13)) 'turn on LEDs DoSETUP(lstring("Hello",0,"Terve",0)) 'turn on LEDs - DoSETUP(bytes($21,$09,$00,$02,$00,$00,$01,$00)) 'turn on LEDs - DoSETUP(bytes($80,$09,$77,WORD $1234,LONG -1)) 'turn on LEDs - DoSETUP(words($21,$09,$00,$02,$00,$00,$01,$00)) 'turn on LEDs - DoSETUP(words(1_000,10_000,50_000,LONG $12345678)) 'turn on LEDs - DoSETUP(longs($21,$09,$00,$02,$00,$00,$01,$00)) 'turn on LEDs - DoSETUP(longs(1e-6,1e-3,1.0,1e3,1e6,-50,BYTE $FF)) 'turn on LEDs + DoSETUP(byte($21,$09,$00,$02,$00,$00,$01,$00)) 'turn on LEDs + DoSETUP(byte($80,$09,$77,WORD $1234,LONG -1)) 'turn on LEDs + DoSETUP(word($21,$09,$00,$02,$00,$00,$01,$00)) 'turn on LEDs + DoSETUP(word(1_000,10_000,50_000,LONG $12345678)) 'turn on LEDs + DoSETUP(long($21,$09,$00,$02,$00,$00,$01,$00)) 'turn on LEDs + DoSETUP(long(1e-6,1e-3,1.0,1e3,1e6,-50,BYTE $FF)) 'turn on LEDs PRI DoSETUP(pBytes) diff --git a/spin2/TEST_LANG_SERVER/spin2/fold_tests.spin2 b/spin2/TEST_LANG_SERVER/spin2/fold_tests.spin2 new file mode 100644 index 0000000..28a94c2 --- /dev/null +++ b/spin2/TEST_LANG_SERVER/spin2/fold_tests.spin2 @@ -0,0 +1,72 @@ +' flow control structures that need to be folded + +PUB main(valueParm) + +' -------------------------------------------------- + '' IF testing + ' keywords: IF IFNOT, ELSE, ELSEIF, ELSEIFNOT + if true + ' do something + elseif false + ifnot true + ' do something + elseifnot false + ' do other + else + ' yet more + else + ' do other + + ifnot true + ' do something + elseifnot false + ' do other + else + ' yet more + +' -------------------------------------------------- + '' CASE testing + ' keywords: CASE, CASE_FAST + case valueParm + 1: ' code + + 2: ' code + other: + ' code + + case_fast valueParm + 1: ' code + + 2: ' code + other: + ' code + +' -------------------------------------------------- + '' REPEAT testing + ' keywords: REPEAT, WHILE, UNTIL + repeat 10 + ' code + + repeat 20 with valueParm + ' code + + repeat + ' code + until false + + repeat + 'code + while true + +' -------------------------------------------------- + +PRI dec(value) | flag, place, digit 'private method prints decimals, three local variables + flag~ 'reset digit-printed flag + place := 1_000_000_000 'start at the one-billion's place and work downward + + REPEAT + IF flag ||= (digit := value / place // 10) || place == 1 'print a digit? + SEND("0" + digit) 'yes + IF LOOKDOWN(place : 1_000_000_000, 1_000_000, 1_000) 'also print a comma? + SEND(",") 'yes + WHILE place /= 10 diff --git a/spin2/server/src/context.ts b/spin2/server/src/context.ts index a86e651..7be7e65 100644 --- a/spin2/server/src/context.ts +++ b/spin2/server/src/context.ts @@ -16,6 +16,11 @@ export class ServerBehaviorConfiguration { public highlightFlexspinDirectives: boolean = false; } +export class EditorConfiguration { + public tabSize: number = 4; + public insertSpaces: boolean = true; +} + export interface Context { topDocsByFSpec: TopDocsByFSpec; docsByFSpec: ProcessedDocumentByFSpec; @@ -25,6 +30,7 @@ export interface Context { logger: lsp.Logger; connection: lsp.Connection; parserConfig: ServerBehaviorConfiguration; + editorConfig: EditorConfiguration; } let language: string = "spin2"; @@ -56,5 +62,6 @@ export async function createContext(workspaceFolders: lsp.WorkspaceFolder[], log logger, connection, parserConfig: new ServerBehaviorConfiguration(), + editorConfig: new EditorConfiguration(), }; } diff --git a/spin2/server/src/parser/spin.common.ts b/spin2/server/src/parser/spin.common.ts index aa820f7..1e15fc9 100644 --- a/spin2/server/src/parser/spin.common.ts +++ b/spin2/server/src/parser/spin.common.ts @@ -3,6 +3,8 @@ import { Position } from "vscode-languageserver-types"; import { Context } from "../context"; +import { timeStamp } from "console"; +//import { listenerCount } from "process"; export enum eDebugDisplayType { Unknown = 0, @@ -47,6 +49,25 @@ export enum eParseState { inNothing, } +export enum eControlFlowType { + Unknown = 0, + inCase, + inCaseFast, + inRepeat, + inIf, +} + +export interface ICurrControlStatement { + startLineIdx: number; + startLineCharOffset: number; + type: eControlFlowType; // [variable|method] +} + +export interface ICurrControlSpan { + startLineIdx: number; + endLineIdx: number; +} + export interface IBuiltinDescription { found: boolean; type: eBuiltInType; // [variable|method] @@ -63,6 +84,131 @@ export function haveDebugLine(line: string, startsWith: boolean = false): boolea return startsWith ? debugStatementOpenStartRegEx.test(line) : debugStatementOpenRegEx.test(line); } +export class SpinControlFlowTracker { + private flowStatementStack: ICurrControlStatement[] = []; // nested statement tracking + private flowLogEnabled: boolean = false; + private ctx: Context | undefined = undefined; + + constructor() {} + + public enableLogging(ctx: Context, doEnable: boolean = true): void { + this.flowLogEnabled = doEnable; + this.ctx = ctx; + } + + private _logMessage(message: string): void { + if (this.flowLogEnabled) { + //Write to output window. + if (this.ctx) { + this.ctx.logger.log(message); + } + } + } + + public reset() { + this.flowStatementStack = []; + } + + public startControlFlow(name: string, startLineCharOffset: number, startLineIdx: number) { + // record start of possible nest flow control statements + this._logMessage(`- SFlowCtrl: start([${name}], ofs=${startLineCharOffset}, Ln#${startLineIdx + 1})`); + const flowItem: ICurrControlStatement = { startLineIdx: startLineIdx, startLineCharOffset: startLineCharOffset, type: this.typeForControlFlowName(name) }; + this.flowStatementStack.push(flowItem); + } + + public isControlFlow(possibleName: string): boolean { + // return T/F where T means {possibleName} is start of spin control-flow statement + const possibleNesting: boolean = this.typeForControlFlowName(possibleName) != eControlFlowType.Unknown; + return possibleNesting; + } + + public finishControlFlow(endLineIdx: number): ICurrControlSpan[] { + const closedFlowSpans: ICurrControlSpan[] = []; + this._logMessage(`- SFlowCtrl: finish(Ln#${endLineIdx + 1})`); + if (this.flowStatementStack.length > 0) { + do { + const currStatement: ICurrControlStatement = this.flowStatementStack[this.flowStatementStack.length - 1]; + const newClosedSpan: ICurrControlSpan = { startLineIdx: currStatement.startLineIdx, endLineIdx: endLineIdx }; + closedFlowSpans.push(newClosedSpan); + this.flowStatementStack.pop(); + } while (this.flowStatementStack.length > 0); + } + return closedFlowSpans; + } + + public endControlFlow(possibleName: string, endLineCharOffset: number, endLineIdx: number): ICurrControlSpan[] { + // record end of possible flow control statement, reporting any flows which this completess + const closedFlowSpans: ICurrControlSpan[] = []; + const possibleNesting: boolean = this.isControlFlow(possibleName); + this._logMessage(`- SFlowCtrl: end([${possibleName}], ofs=${endLineCharOffset}, Ln#${endLineIdx + 1}) - possibleNesting=${possibleNesting}`); + if (this.flowStatementStack.length > 0) { + do { + let endThisNesting: boolean = false; + const currStatement: ICurrControlStatement = this.flowStatementStack[this.flowStatementStack.length - 1]; + const delayClose: boolean = this.delayClose(possibleName, currStatement.type); + // did this statment-indent-level end a flow control statement? + if (currStatement.startLineCharOffset < endLineCharOffset) { + // indented even further, no this does not end this one + break; // nope, abort + } else if (currStatement.startLineCharOffset == endLineCharOffset) { + // line is at same indent level we are not nesting another flow-control, yes, this ENDs it + endThisNesting = delayClose ? false : true; + } else { + // line is indented less than control, this does END it! + endThisNesting = delayClose ? false : true; + } + if (endThisNesting) { + const newClosedSpan: ICurrControlSpan = { startLineIdx: currStatement.startLineIdx, endLineIdx: endLineIdx - 1 }; + this._logMessage(`- SFlowCtrl: - close [Ln#${newClosedSpan.startLineIdx + 1} - ${newClosedSpan.endLineIdx + 1}]`); + closedFlowSpans.push(newClosedSpan); + this.flowStatementStack.pop(); + } else { + break; + } + } while (this.flowStatementStack.length > 0); + } + if (possibleNesting) { + // no prior control flow checks in progres, just start this new one + this.startControlFlow(possibleName, endLineCharOffset, endLineIdx); + } + return closedFlowSpans; + } + + private delayClose(name: string, type: eControlFlowType): boolean { + let shouldDelay: boolean = false; + if (type == eControlFlowType.inRepeat) { + if (name.toLowerCase() === "while") { + shouldDelay = true; + } else if (name.toLowerCase() === "until") { + shouldDelay = true; + } + } + return shouldDelay; + } + + private typeForControlFlowName(name: string): eControlFlowType { + let desiredType: eControlFlowType = eControlFlowType.Unknown; + if (name.toLowerCase() === "if") { + desiredType = eControlFlowType.inIf; + } else if (name.toLowerCase() === "ifnot") { + desiredType = eControlFlowType.inIf; + } else if (name.toLowerCase() === "elseif") { + desiredType = eControlFlowType.inIf; + } else if (name.toLowerCase() === "else") { + desiredType = eControlFlowType.inIf; + } else if (name.toLowerCase() === "elseifnot") { + desiredType = eControlFlowType.inIf; + } else if (name.toLowerCase() === "repeat") { + desiredType = eControlFlowType.inRepeat; + } else if (name.toLowerCase() === "case") { + desiredType = eControlFlowType.inCase; + } else if (name.toLowerCase() === "case_fast") { + desiredType = eControlFlowType.inCaseFast; + } + return desiredType; + } +} + export class ContinuedLines { private rawLines: string[] = []; private rawLineIdxs: number[] = []; diff --git a/spin2/server/src/parser/spin.semantic.findings.ts b/spin2/server/src/parser/spin.semantic.findings.ts index 731b84f..4c448b8 100644 --- a/spin2/server/src/parser/spin.semantic.findings.ts +++ b/spin2/server/src/parser/spin.semantic.findings.ts @@ -65,6 +65,11 @@ export interface IPasmCodeSpan { isInline: boolean; } +export interface ISpinCodeSpan { + startLineIdx: number; + endLineIdx: number; +} + export interface IParsedToken { line: number; startCharacter: number; @@ -160,6 +165,7 @@ export class DocumentFindings { private priorBlockStartLineIdx: number = -1; private priorInstanceCount: number = 0; private codeBlockSpans: IBlockSpan[] = []; + private spinCodeFlowSpans: ISpinCodeSpan[] = []; private continuedLineSpans: IContinuedLineSpan[] = []; // tracking spans of PASM code private pasmStartLineIdx: number = -1; @@ -248,6 +254,7 @@ export class DocumentFindings { this.priorBlockStartLineIdx = -1; this.priorInstanceCount = 0; this.codeBlockSpans = []; + this.spinCodeFlowSpans = []; this.continuedLineSpans = []; this.diagnosticMessages = []; this.outlineSymbols = []; @@ -420,6 +427,18 @@ export class DocumentFindings { }; foldingCodeSpans.push(nextSpan); } + + // spin flow-control ranges + for (let index = 0; index < this.spinCodeFlowSpans.length; index++) { + const spinFlowSpan: ISpinCodeSpan = this.spinCodeFlowSpans[index]; + const nextSpan: IFoldSpan = { + foldstart: { line: spinFlowSpan.startLineIdx, character: 0 }, + foldEnd: { line: spinFlowSpan.endLineIdx, character: Number.MAX_VALUE }, + type: eFoldSpanType.CodeBlock, + }; + foldingCodeSpans.push(nextSpan); + } + return foldingCodeSpans; } @@ -615,11 +634,21 @@ export class DocumentFindings { // TRACK ranges of continued lines within file // public recordContinuedLineBlock(startLineIdx: number, endLineIdx: number) { - this._logMessage(` -- RCD-ContLine Ln#${startLineIdx + 1} - ${endLineIdx + 1}]`); + this._logMessage(` -- RCD-ContLine [Ln#${startLineIdx + 1} - ${endLineIdx + 1}]`); const newSpan: IContinuedLineSpan = { startLineIdx: startLineIdx, endLineIdx: endLineIdx }; this.continuedLineSpans.push(newSpan); } + // ------------------------------------------------------------------------------------- + // TRACK spin control flow regions in code (IF, CASE, REPEAT) + // NOTE: these can NEST! + // + public recordSpinFlowControlSpan(startLineIdx: number, endLineIdx: number) { + this._logMessage(` -- RCD-Flow Span [Ln#${startLineIdx + 1} - ${endLineIdx + 1}]`); + const newSpan: ISpinCodeSpan = { startLineIdx: startLineIdx, endLineIdx: endLineIdx }; + this.spinCodeFlowSpans.push(newSpan); + } + // ------------------------------------------------------------------------------------- // TRACK ranges of CON/PUB/PRI/VAR/DAT/OBJ blocks within file // diff --git a/spin2/server/src/parser/spin1.documentSemanticParser.ts b/spin2/server/src/parser/spin1.documentSemanticParser.ts index 940d522..41e3a22 100644 --- a/spin2/server/src/parser/spin1.documentSemanticParser.ts +++ b/spin2/server/src/parser/spin1.documentSemanticParser.ts @@ -2,12 +2,12 @@ // src/spin2.documentSemanticParser.ts import { TextDocument } from "vscode-languageserver-textdocument"; -import { Context, ServerBehaviorConfiguration } from "../context"; +import { Context, ServerBehaviorConfiguration, EditorConfiguration } from "../context"; import { DocumentFindings, RememberedComment, eCommentType, RememberedToken, eBLockType, eSeverity, eDefinitionType } from "./spin.semantic.findings"; import { Spin1ParseUtils } from "./spin1.utils"; import { isSpin1File } from "./lang.utils"; -import { eParseState } from "./spin.common"; +import { eParseState, SpinControlFlowTracker, ICurrControlSpan } from "./spin.common"; import { fileInDirExists } from "../files"; import { ExtensionUtils } from "../parser/spin.extension.utils"; @@ -29,6 +29,7 @@ interface IFilteredStrings { export class Spin1DocumentSemanticParser { private parseUtils = new Spin1ParseUtils(); + private spinControlFlowTracker = new SpinControlFlowTracker(); private extensionUtils: ExtensionUtils; private bLogStarted: boolean = false; @@ -50,6 +51,7 @@ export class Spin1DocumentSemanticParser { private conEnumInProgress: boolean = false; private configuration: ServerBehaviorConfiguration; + private editorConfiguration: EditorConfiguration; private currentMethodName: string = ""; private currentFilespec: string = ""; @@ -61,6 +63,7 @@ export class Spin1DocumentSemanticParser { public constructor(protected readonly ctx: Context) { this.extensionUtils = new ExtensionUtils(ctx, this.spin1DebugLogEnabled); this.configuration = ctx.parserConfig; + this.editorConfiguration = ctx.editorConfig; if (this.spin1DebugLogEnabled) { if (this.bLogStarted == false) { this.bLogStarted = true; @@ -84,6 +87,7 @@ export class Spin1DocumentSemanticParser { if (this.spin1DebugLogEnabled) { this.semanticFindings.enableLogging(this.ctx); this.parseUtils.enableLogging(this.ctx); + this.spinControlFlowTracker.enableLogging(this.ctx); } this.configuration = this.ctx.parserConfig; // ensure we have latest this.isSpin1Document = isSpin1File(document.uri); @@ -136,6 +140,7 @@ export class Spin1DocumentSemanticParser { this._logMessage("---> Pre SCAN"); let bBuildingSingleLineCmtBlock: boolean = false; let bBuildingSingleLineDocCmtBlock: boolean = false; + this.spinControlFlowTracker.reset(); this.semanticFindings.recordBlockStart(eBLockType.isCon, 0); // spin file defaults to CON at 1st line for (let i = 0; i < lines.length; i++) { const lineNbr = i + 1; @@ -344,6 +349,17 @@ export class Spin1DocumentSemanticParser { this._logState("- scan Ln#" + lineNbr + " POP currState=[" + currState + "]"); } + if (currState == eParseState.inPub || currState == eParseState.inPri) { + // mark end of code fold, if we were in a method + const spanSet: ICurrControlSpan[] = this.spinControlFlowTracker.finishControlFlow(i - 1); // pass prior line number! essentially i+1 (-1) + if (spanSet.length > 0) { + for (let index = 0; index < spanSet.length; index++) { + const flowSpan: ICurrControlSpan = spanSet[index]; + this.semanticFindings.recordSpinFlowControlSpan(flowSpan.startLineIdx, flowSpan.endLineIdx); + } + } + } + // record start of next block in code // NOTE: this causes end of prior block to be recorded let newBlockType: eBLockType = eBLockType.Unknown; @@ -456,6 +472,25 @@ export class Spin1DocumentSemanticParser { } else if (currState == eParseState.inPub || currState == eParseState.inPri) { // scan SPIN2 line for object constant or method() uses //this._getSpinObjectConstantMethodDeclaration(0, lineNbr, line); + if (lineParts.length > 0) { + // handle spin control flow detection + const charsIndent: number = this.parseUtils.charsInsetCount(line, this.editorConfiguration.tabSize); + const spanSet: ICurrControlSpan[] = this.spinControlFlowTracker.endControlFlow(lineParts[0], charsIndent, i); + if (spanSet.length > 0) { + for (let index = 0; index < spanSet.length; index++) { + const flowSpan: ICurrControlSpan = spanSet[index]; + this.semanticFindings.recordSpinFlowControlSpan(flowSpan.startLineIdx, flowSpan.endLineIdx); + } + } + } + } + } + // mark end of code fold, if we had started one + const spanSet: ICurrControlSpan[] = this.spinControlFlowTracker.finishControlFlow(lines.length - 1); // pass last line index! + if (spanSet.length > 0) { + for (let index = 0; index < spanSet.length; index++) { + const flowSpan: ICurrControlSpan = spanSet[index]; + this.semanticFindings.recordSpinFlowControlSpan(flowSpan.startLineIdx, flowSpan.endLineIdx); } } this.semanticFindings.endPossibleMethod(lines.length); // report end if last line of file(+1 since method wants line number!) diff --git a/spin2/server/src/parser/spin1.utils.ts b/spin2/server/src/parser/spin1.utils.ts index 15cd5cb..4729785 100644 --- a/spin2/server/src/parser/spin1.utils.ts +++ b/spin2/server/src/parser/spin1.utils.ts @@ -305,6 +305,33 @@ export class Spin1ParseUtils { return lineWithoutTrailingCommentStr; } + public charsInsetCount(line: string, tabWidth: number): number { + // count and return the number of columns of white space at start of line + // NOTE: expands tabs appropriate to editor settings! + let insetCount: number = 0; + if (line.length > 0) { + let nonWhite: boolean = false; + for (let index = 0; index < line.length; index++) { + const char = line.charAt(index); + switch (char) { + case " ": + insetCount++; + break; + case "\t": + insetCount += tabWidth - (insetCount % tabWidth); + break; + default: + nonWhite = true; + break; // no more whitespace, return count + } + if (nonWhite) { + break; + } + } + } + return insetCount; + } + public indexOfMatchingCloseParen(line: string, openParenOffset: number): number { let desiredCloseOffset: number = -1; let nestingDepth: number = 1; diff --git a/spin2/server/src/parser/spin2.documentSemanticParser.ts b/spin2/server/src/parser/spin2.documentSemanticParser.ts index bc8c221..efdb0af 100644 --- a/spin2/server/src/parser/spin2.documentSemanticParser.ts +++ b/spin2/server/src/parser/spin2.documentSemanticParser.ts @@ -3,16 +3,16 @@ import { TextDocument } from "vscode-languageserver-textdocument"; import { Position } from "vscode-languageserver-types"; -import { Context, ServerBehaviorConfiguration } from "../context"; +import { Context, ServerBehaviorConfiguration, EditorConfiguration } from "../context"; import { DocumentFindings, RememberedComment, eCommentType, RememberedToken, eBLockType, IParsedToken, eSeverity, eDefinitionType } from "./spin.semantic.findings"; import { Spin2ParseUtils } from "./spin2.utils"; import { isSpin1File } from "./lang.utils"; -import { eParseState, eDebugDisplayType, ContinuedLines, haveDebugLine } from "./spin.common"; +import { eParseState, eDebugDisplayType, ContinuedLines, haveDebugLine, SpinControlFlowTracker, ICurrControlSpan } from "./spin.common"; import { fileInDirExists } from "../files"; //import { PublishDiagnosticsNotification } from "vscode-languageserver"; import { ExtensionUtils } from "../parser/spin.extension.utils"; -import { channel } from "diagnostics_channel"; +//import { channel } from "diagnostics_channel"; // ---------------------------------------------------------------------------- // Semantic Highlighting Provider @@ -38,11 +38,12 @@ interface ISpin2Directive { // map of display-type to etype' export class Spin2DocumentSemanticParser { private parseUtils = new Spin2ParseUtils(); + private spinControlFlowTracker = new SpinControlFlowTracker(); private extensionUtils: ExtensionUtils; private bLogStarted: boolean = false; // adjust following true/false to show specific parsing debug - private spin2DebugLogEnabled: boolean = false; // WARNING (REMOVE BEFORE FLIGHT)- change to 'false' - disable before commit + private spin2DebugLogEnabled: boolean = true; // WARNING (REMOVE BEFORE FLIGHT)- change to 'false' - disable before commit private showSpinCode: boolean = true; private showPreProc: boolean = true; private showCON: boolean = true; @@ -61,6 +62,7 @@ export class Spin2DocumentSemanticParser { private fileDirectives: ISpin2Directive[] = []; private configuration: ServerBehaviorConfiguration; + private editorConfiguration: EditorConfiguration; private currentMethodName: string = ""; private currentFilespec: string = ""; @@ -72,6 +74,7 @@ export class Spin2DocumentSemanticParser { public constructor(protected readonly ctx: Context) { this.extensionUtils = new ExtensionUtils(ctx, this.spin2DebugLogEnabled); this.configuration = ctx.parserConfig; + this.editorConfiguration = ctx.editorConfig; if (this.spin2DebugLogEnabled) { if (this.bLogStarted == false) { this.bLogStarted = true; @@ -93,6 +96,7 @@ export class Spin2DocumentSemanticParser { if (this.spin2DebugLogEnabled) { this.semanticFindings.enableLogging(this.ctx); this.parseUtils.enableLogging(this.ctx); + this.spinControlFlowTracker.enableLogging(this.ctx); } this.configuration = this.ctx.parserConfig; // ensure we have latest this.isSpin1Document = isSpin1File(document.uri); @@ -141,12 +145,13 @@ export class Spin2DocumentSemanticParser { if (this.spin2DebugLogEnabled) { continuedLineSet.enableLogging(this.ctx); } - // -------------------- PRE-PARSE just locating symbol names -------------------- + // -------------------- PRE-PARSE just locating symbol names, spin folding info -------------------- // also track and record block comments (both braces and tic's!) // let's also track prior single line and trailing comment on same line this._logMessage(`---> Pre SCAN -- `); let bBuildingSingleLineCmtBlock: boolean = false; let bBuildingSingleLineDocCmtBlock: boolean = false; + this.spinControlFlowTracker.reset(); this.semanticFindings.recordBlockStart(eBLockType.isCon, 0); // spin file defaults to CON at 1st line const DOC_COMMENT = true; const NONDOC_COMMENT = false; @@ -383,6 +388,17 @@ export class Spin2DocumentSemanticParser { this._logState("- scan Ln#" + lineNbr + " POP currState=[" + currState + "]"); } + if (currState == eParseState.inPub || currState == eParseState.inPri) { + // mark end of code fold, if we were in a method + const spanSet: ICurrControlSpan[] = this.spinControlFlowTracker.finishControlFlow(i - 1); // pass prior line number! essentially i+1 (-1) + if (spanSet.length > 0) { + for (let index = 0; index < spanSet.length; index++) { + const flowSpan: ICurrControlSpan = spanSet[index]; + this.semanticFindings.recordSpinFlowControlSpan(flowSpan.startLineIdx, flowSpan.endLineIdx); + } + } + } + currState = sectionStatus.inProgressStatus; // record start of next block in code // NOTE: this causes end of prior block to be recorded @@ -584,6 +600,17 @@ export class Spin2DocumentSemanticParser { //this._getSpin2ObjectConstantMethodDeclaration(0, lineNbr, line); } } + if (lineParts.length > 0 && !continuedSectionStatus.isSectionStart) { + // handle spin control flow detection + const charsIndent: number = this.parseUtils.charsInsetCount(line, this.editorConfiguration.tabSize); + const spanSet: ICurrControlSpan[] = this.spinControlFlowTracker.endControlFlow(lineParts[0], charsIndent, i); + if (spanSet.length > 0) { + for (let index = 0; index < spanSet.length; index++) { + const flowSpan: ICurrControlSpan = spanSet[index]; + this.semanticFindings.recordSpinFlowControlSpan(flowSpan.startLineIdx, flowSpan.endLineIdx); + } + } + } } } // we processed statements in this line, now clear prior comment associated with this line @@ -595,6 +622,14 @@ export class Spin2DocumentSemanticParser { pendingState = eParseState.Unknown; } } + // mark end of code fold, if we had started one + const spanSet: ICurrControlSpan[] = this.spinControlFlowTracker.finishControlFlow(lines.length - 1); // pass last line index! + if (spanSet.length > 0) { + for (let index = 0; index < spanSet.length; index++) { + const flowSpan: ICurrControlSpan = spanSet[index]; + this.semanticFindings.recordSpinFlowControlSpan(flowSpan.startLineIdx, flowSpan.endLineIdx); + } + } this.semanticFindings.endPossibleMethod(lines.length); // report end if last line of file(+1 since method wants line number!) this.semanticFindings.finishFinalBlock(lines.length - 1); // mark end of final block in file this.semanticFindings.finalize(); diff --git a/spin2/server/src/parser/spin2.utils.ts b/spin2/server/src/parser/spin2.utils.ts index c79ddf7..0237504 100644 --- a/spin2/server/src/parser/spin2.utils.ts +++ b/spin2/server/src/parser/spin2.utils.ts @@ -37,30 +37,31 @@ export class Spin2ParseUtils { } } - public etDebugStatement(startingOffset: number, line: string): string { - let currentOffset: number = this.skipWhite(line, startingOffset); - let debugNonCommentStr: string = line; - let openParenOffset: number = line.indexOf("(", currentOffset); - let closeParenOffset: number = this.indexOfMatchingCloseParen(line, openParenOffset); - if (line.length - startingOffset > 0 && openParenOffset != -1 && closeParenOffset != -1) { - // have scope of debug line - remove trailing comment, trim it and return it - let commentOffset: number = line.indexOf("'", closeParenOffset + 1); - if (commentOffset != -1) { - // have trailing comment remove it - const nonCommentEOL: number = commentOffset != -1 ? commentOffset - 1 : line.length - 1; - debugNonCommentStr = line.substring(currentOffset, nonCommentEOL).trim(); - } else { - debugNonCommentStr = line.substring(currentOffset).trim(); + public charsInsetCount(line: string, tabWidth: number): number { + // count and return the number of columns of white space at start of line + // NOTE: expands tabs appropriate to editor settings! + let insetCount: number = 0; + if (line.length > 0) { + let nonWhite: boolean = false; + for (let index = 0; index < line.length; index++) { + const char = line.charAt(index); + switch (char) { + case " ": + insetCount++; + break; + case "\t": + insetCount += tabWidth - (insetCount % tabWidth); + break; + default: + nonWhite = true; + break; // no more whitespace, return count + } + if (nonWhite) { + break; + } } - } else if (line.length - startingOffset == 0 || openParenOffset == -1) { - // if we don't have open paren - erase entire line - debugNonCommentStr = ""; } - //if (line.length != debugNonCommentStr.length) { - // this._logMessage(' -- DS line [' + line.substring(startingOffset) + ']'); - // this._logMessage(' -- [' + debugNonCommentStr + ']'); - //} - return debugNonCommentStr; + return insetCount; } public indexOfMatchingCloseParen(line: string, openParenOffset: number): number { @@ -1996,9 +1997,9 @@ export class Spin2ParseUtils { "Compose a zero-terminated string (quoted characters and values 1..255 allowed), return address of string

@param `listOfElements` - a comma separated list of elements to be built into a string (quoted characters and values 1..255 allowed)
@returns `StringAddress` - address of where string was placed in ram", ], //lstring: ['LSTRING("Hello",0,"Terve",0) : StringAddress', "Compose a length-headed string (quoted characters and values 0..255), return address of string."], - //bytes: ["BYTES($80,$09,$77,WORD $1234,LONG -1) : BytesAddress", "Compose a string of bytes, return address of string. WORD/LONG size overrides allowed."], - //words: ["WORDS(1_000,10_000,50_000,LONG $12345678) : WordsAddress", "Compose a string of words, return address of string. BYTE/LONG size overrides allowed."], - //longs: ["LONGS(1e-6,1e-3,1.0,1e3,1e6,-50,BYTE $FF) : LongsAddress", "Compose a string of longs, return address of string. BYTE/WORD size overrides allowed."], + byte: ["BYTE($80,$09,$77,WORD $1234,LONG -1) : BytesAddress", "Compose a string of bytes, return address of string. WORD/LONG size overrides allowed."], + word: ["WORD(1_000,10_000,50_000,LONG $12345678) : WordsAddress", "Compose a string of words, return address of string. BYTE/LONG size overrides allowed."], + long: ["LONG(1e-6,1e-3,1.0,1e3,1e6,-50,BYTE $FF) : LongsAddress", "Compose a string of longs, return address of string. BYTE/WORD size overrides allowed."], }; private _tableSpinIndexValueMethods: { [Identifier: string]: string[] } = { diff --git a/spin2/server/src/providers/TextDocumentSyncProvider.ts b/spin2/server/src/providers/TextDocumentSyncProvider.ts index f8c06df..a5b0a2c 100644 --- a/spin2/server/src/providers/TextDocumentSyncProvider.ts +++ b/spin2/server/src/providers/TextDocumentSyncProvider.ts @@ -3,7 +3,7 @@ import { TextDocument } from "vscode-languageserver-textdocument"; //import Parser from "web-tree-sitter"; import { Provider } from "."; -import { Context, ServerBehaviorConfiguration } from "../context"; +import { Context, ServerBehaviorConfiguration, EditorConfiguration } from "../context"; //import DiagnosticProcessor from "../diagnostics"; import DocumentProcessor, { ProcessedDocument } from "../DocumentProcessor"; import { positionToPoint, Point } from "../geometry"; @@ -106,6 +106,19 @@ export default class TextDocumentSyncProvider implements Provider { ` DBG -- ctx.parserConfig.maxNumberOfReportedIssues=(${this.ctx.parserConfig.maxNumberOfReportedIssues}), highlightFlexspinDirectives=[${this.ctx.parserConfig.highlightFlexspinDirectives}], changes=(${configChanged})` ); }); + await this.ctx.connection.workspace.getConfiguration("Editor").then((editorConfiguration: EditorConfiguration) => { + if (editorConfiguration == null) { + this.ctx.logger.log(`TRC: DP.process() ERROR! get editor settings received no info: config left at defaults`); + } else { + configChanged = editorConfiguration["tabSize"] != this.ctx.editorConfig.tabSize || editorConfiguration["insertSpaces"] != this.ctx.editorConfig.insertSpaces; + if (configChanged) { + this.ctx.editorConfig.tabSize = editorConfiguration["tabSize"]; + this.ctx.editorConfig.insertSpaces = editorConfiguration["insertSpaces"]; + } + } + //this.ctx.logger.log(`TRC: process() received settings RAW=[${serverConfiguration}], JSON=[${JSON.stringify(serverConfiguration)}]`); + this.ctx.logger.log(` DBG -- ctx.editorConfiguration.tabSize=(${this.ctx.editorConfig.tabSize}), insertSpaces=[${this.ctx.editorConfig.insertSpaces}], changes=(${configChanged})`); + }); return configChanged; }