Skip to content

Commit

Permalink
finish code folding support (add spin control-flow parsing)
Browse files Browse the repository at this point in the history
- now retrieve edit tab width
- add spin control-flow tracking logic
  • Loading branch information
ironsheep committed Dec 24, 2023
1 parent ea3d41b commit 1effb65
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 40 deletions.
6 changes: 6 additions & 0 deletions spin2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions spin2/TEST_LANG_SERVER/spin/fold_tests.spin
Original file line number Diff line number Diff line change
@@ -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
12 changes: 6 additions & 6 deletions spin2/TEST_LANG_SERVER/spin2/231112-fixes.spin2
Original file line number Diff line number Diff line change
Expand Up @@ -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)
72 changes: 72 additions & 0 deletions spin2/TEST_LANG_SERVER/spin2/fold_tests.spin2
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions spin2/server/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +30,7 @@ export interface Context {
logger: lsp.Logger;
connection: lsp.Connection;
parserConfig: ServerBehaviorConfiguration;
editorConfig: EditorConfiguration;
}

let language: string = "spin2";
Expand Down Expand Up @@ -56,5 +62,6 @@ export async function createContext(workspaceFolders: lsp.WorkspaceFolder[], log
logger,
connection,
parserConfig: new ServerBehaviorConfiguration(),
editorConfig: new EditorConfiguration(),
};
}
146 changes: 146 additions & 0 deletions spin2/server/src/parser/spin.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]
Expand All @@ -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[] = [];
Expand Down
Loading

0 comments on commit 1effb65

Please sign in to comment.