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;
}