Improved syntax, added full word select

This commit is contained in:
2025-07-31 17:42:00 -06:00
parent 889c150b08
commit b362770d90
4 changed files with 264 additions and 343 deletions

View File

@@ -1,7 +1,9 @@
{ {
"comments": { "comments": {
// symbol used for single line comment. Remove this entry if your language does not support line comments // symbol used for single line comment. Remove this entry if your language does not support line comments
"lineComment": "!" "lineComment": {
"comment": "!"
}
}, },
// symbols used as brackets // symbols used as brackets
"brackets": [ "brackets": [

View File

@@ -1,54 +1,69 @@
{ {
"name": "format-4690", "name": "format-4690",
"displayName": "format-4690", "displayName": "format-4690",
"description": "Formatter for 4690 BASIC", "description": "Formatter for 4690 BASIC",
"version": "0.0.1", "version": "0.0.1",
"engines": { "engines": {
"vscode": "^1.101.0" "vscode": "^1.101.0"
}, },
"categories": [ "categories": [
"Programming Languages" "Programming Languages"
], ],
"activationEvents": [], "activationEvents": [],
"main": "./dist/extension.js", "main": "./dist/extension.js",
"contributes": { "contributes": {
"languages": [{ "languages": [
"id": "4690basic", {
"aliases": ["4690 BASIC", "4690basic"], "id": "4690basic",
"extensions": [".BAS", ".J86"], "aliases": [
"configuration": "./language-configuration.json" "4690 BASIC",
}], "4690basic"
"grammars": [ ],
{ "extensions": [
"language": "4690basic", ".BAS",
"scopeName": "source.4690basic", ".J86"
"path": "./syntaxes/4690basic.tmLanguage.json" ],
} "configuration": "./language-configuration.json"
] }
}, ],
"scripts": { "grammars": [
"vscode:prepublish": "npm run package", {
"compile": "webpack", "language": "4690basic",
"watch": "webpack --watch", "scopeName": "source.4690basic",
"package": "webpack --mode production --devtool hidden-source-map", "path": "./syntaxes/4690basic.tmLanguage.json"
"compile-tests": "tsc -p . --outDir out", }
"watch-tests": "tsc -p . -w --outDir out", ],
"pretest": "npm run compile-tests && npm run compile && npm run lint", "keybindings": [
"lint": "eslint src", {
"test": "vscode-test" "command": "4690basic.selectWord",
}, "key": "ctrl+d",
"devDependencies": { "when": "editorTextFocus && editorLangId == 4690basic"
"@types/vscode": "^1.101.0", }
"@types/mocha": "^10.0.10", ]
"@types/node": "20.x", },
"@typescript-eslint/eslint-plugin": "^8.31.1", "scripts": {
"@typescript-eslint/parser": "^8.31.1", "vscode:prepublish": "npm run package",
"eslint": "^9.25.1", "compile": "webpack",
"typescript": "^5.8.3", "watch": "webpack --watch",
"ts-loader": "^9.5.2", "package": "webpack --mode production --devtool hidden-source-map",
"webpack": "^5.99.7", "compile-tests": "tsc -p . --outDir out",
"webpack-cli": "^6.0.1", "watch-tests": "tsc -p . -w --outDir out",
"@vscode/test-cli": "^0.0.10", "pretest": "npm run compile-tests && npm run compile && npm run lint",
"@vscode/test-electron": "^2.5.2" "lint": "eslint src",
} "test": "vscode-test"
},
"devDependencies": {
"@types/vscode": "^1.101.0",
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@typescript-eslint/eslint-plugin": "^8.31.1",
"@typescript-eslint/parser": "^8.31.1",
"eslint": "^9.25.1",
"typescript": "^5.8.3",
"ts-loader": "^9.5.2",
"webpack": "^5.99.7",
"webpack-cli": "^6.0.1",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.5.2"
}
} }

View File

@@ -1,5 +1,10 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
interface FunctionParameterInfo {
name: string;
type: string;
}
interface FunctionDefinition { interface FunctionDefinition {
name: string; name: string;
location: vscode.Location; location: vscode.Location;
@@ -14,7 +19,29 @@ const symbolIndex = new Map<string, FunctionDefinition[]>();
// File index - maps file URIs to their function definitions // File index - maps file URIs to their function definitions
const fileIndex = new Map<string, FunctionDefinition[]>(); const fileIndex = new Map<string, FunctionDefinition[]>();
function getCustomWordRange(document: vscode.TextDocument, position: vscode.Position) {
const line = document.lineAt(position.line);
const text = line.text;
const char = position.character;
const wordPattern = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[$#%]?/g;
let match;
while ((match = wordPattern.exec(text)) !== null) {
const startChar = match.index;
const endChar = match.index + match[0].length;
if (char >= startChar && char <= endChar) {
return new vscode.Range(
new vscode.Position(position.line, startChar),
new vscode.Position(position.line, endChar)
);
}
}
}
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
const selector: vscode.DocumentSelector = { language: '4690basic' }; const selector: vscode.DocumentSelector = { language: '4690basic' };
const saveWatcher = vscode.workspace.onDidSaveTextDocument(async (document) => { const saveWatcher = vscode.workspace.onDidSaveTextDocument(async (document) => {
@@ -39,6 +66,22 @@ export async function activate(context: vscode.ExtensionContext) {
// Build initial symbol index // Build initial symbol index
await buildInitialSymbolIndex(); await buildInitialSymbolIndex();
const disposable = vscode.commands.registerCommand('4690basic.selectWord', () => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== '4690basic') return;
const position = editor.selection.active;
const document = editor.document;
const wordRange = getCustomWordRange(document, position);
if (wordRange) {
editor.selection = new vscode.Selection(wordRange.start, wordRange.end);
}
});
// Existing formatter provider (unchanged) // Existing formatter provider (unchanged)
const formatterProvider: vscode.DocumentFormattingEditProvider = { const formatterProvider: vscode.DocumentFormattingEditProvider = {
provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] { provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] {
@@ -52,205 +95,178 @@ export async function activate(context: vscode.ExtensionContext) {
let indentLevel = 0; let indentLevel = 0;
let i = 0; let i = 0;
// Helper function to remove comments from a line (but preserve line continuations) // Helper function to remove comments and continuation markers from a line
function removeComments(line: string): string { function cleanLine(line: string): string {
let inQuotes = false; let inQuotes = false;
let quoteChar = ''; let quoteChar = '';
let result = '';
for (let j = 0; j < line.length; j++) {
const char = line[j];
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (!inQuotes && (char === '"' || char === "'")) { if (!inQuotes && (char === '"' || char === "'")) {
inQuotes = true; inQuotes = true;
quoteChar = char; quoteChar = char;
result += char;
} else if (inQuotes && char === quoteChar) { } else if (inQuotes && char === quoteChar) {
inQuotes = false; inQuotes = false;
quoteChar = ''; quoteChar = '';
result += char;
} else if (!inQuotes && char === '!') { } else if (!inQuotes && char === '!') {
return line.substring(0, i).trim(); // Comment starts here, ignore rest of line
break;
} else if (!inQuotes && char === '\\') {
// Continuation marker, ignore rest of line
break;
} else {
result += char;
} }
} }
return line;
return result.trim();
} }
// Helper function to check if a line should continue // Helper function to build a complete logical line by looking ahead
function shouldContinue(line: string, allLines: string[], currentIndex: number): boolean { function buildLogicalLine(startIndex: number): { logicalLine: string, physicalLines: string[], endIndex: number } {
const trimmedLine = line.trim(); const physicalLines = [];
let logicalParts = [];
let currentIndex = startIndex;
// Skip comment lines that start with backslash (like \REM!!) if (!/(IF|THEN|ENDIF|ELSE|FUNCTION|DEF|SUB|WHILE|FOR)\b/i.test(lines[currentIndex])) {
// These should not be treated as continuation lines return { logicalLine: lines[currentIndex], physicalLines: [lines[currentIndex]], endIndex: currentIndex };
if (/^\s*\\(REM|rem)/i.test(trimmedLine)) {
return false;
} }
const withoutComments = removeComments(line).trim(); while (currentIndex < lines.length) {
const line = lines[currentIndex];
const trimmedLine = line.trim();
// Check for explicit continuation with \ // Skip comment lines that start with backslash (like \REM!!)
if (/^(?:[^"\\]|"[^"]*")*\\/.test(withoutComments)) { if (/^\s*(\\|REM|!)/i.test(trimmedLine)) {
// Special case: if this is a THEN \ line, don't continue physicalLines.push(line);
// Let the next line be processed as its own logical group currentIndex++;
if (/\bTHEN\s*\\/.test(withoutComments)) { continue;
return false;
}
return true;
}
// Check for IF without THEN on the same line
if (/^\s*IF\b/i.test(withoutComments) && !/\bTHEN\b/i.test(withoutComments)) {
return true;
}
// Check if current line looks like a continuation of a condition
if (/^\s*(AND\b|OR\b|NOT\b|\()/i.test(withoutComments) && currentIndex > 0) {
// BUT: if this line contains THEN, it terminates the condition, so it should NOT continue
if (/\bTHEN\b/i.test(withoutComments)) {
return false;
} }
// Look back to see if we're in a continuation chain physicalLines.push(line);
let prevIndex = currentIndex - 1; const cleanedLine = cleanLine(line);
while (prevIndex >= 0) {
const prevLine = removeComments(allLines[prevIndex]).trim(); if (cleanedLine) {
if (prevLine === '') { logicalParts.push(cleanedLine);
prevIndex--; }
continue;
} // Check if this line continues (ends with backslash outside quotes/comments)
if (/\\.*$/.test(prevLine) || let hasContinuation = false;
/^\s*IF\b/i.test(prevLine) || let inQuotes = false;
/^\s*(AND\b|OR\b|NOT\b|\()/i.test(prevLine)) {
return true; for (let j = 0; j < line.length; j++) {
const char = line[j];
if (!inQuotes && char === '"') {
inQuotes = true;
} else if (inQuotes && char === '"') {
inQuotes = false;
} else if (!inQuotes && char === '!') {
// Comment starts, no continuation possible after this
break;
} else if (!inQuotes && char === '\\') {
if (!/BEGIN/.test(cleanedLine)) {
hasContinuation = true;
}
const nextLineText = lines[currentIndex + 1];
if (nextLineText && /THEN\s*(!|\\)\\/i.test(cleanedLine) && /^\s*(IF|WHILE|FOR)\b/i.test(nextLineText)) {
hasContinuation = false;
}
break;
} }
}
currentIndex++;
// If no continuation marker, this logical line is complete
if (!hasContinuation) {
break; break;
} }
} }
const logicalLine = logicalParts.join(' ').trim();
return {
logicalLine,
physicalLines,
endIndex: currentIndex - 1
};
}
// Helper function to check if a logical line should start an indent block
function shouldStartIndentBlock(logicalLine: string): boolean {
if (/^\s*((FUNCTION|DEF|SUB|FOR|WHILE)\b)|(BEGIN\b)/i.test(logicalLine)) {
return true;
}
return false; return false;
} }
// Helper function to check if a logical line should end an indent block
function shouldEndIndentBlock(logicalLine: string): boolean {
return /^\s*(END\s+FUNCTION|FEND|END\s+SUB|ENDIF|WEND|NEXT)\b/i.test(logicalLine);
}
// Helper function to check if a logical line is ELSE (which decreases then increases indent)
function isElseStatement(logicalLine: string): boolean {
return /^\s*ELSE\b/i.test(logicalLine) && !/\bBEGIN\b/i.test(logicalLine);
}
while (i < lines.length) { while (i < lines.length) {
const originalLine = lines[i];
const trimmedLine = originalLine.trim();
// Skip empty lines // Skip empty lines
if (trimmedLine === '') { if (lines[i].trim() === '') {
i++; i++;
continue; continue;
} }
// Collect continuation lines // Build complete logical line
let continuationLines = [originalLine]; const { logicalLine, physicalLines, endIndex } = buildLogicalLine(i);
let j = i;
while (j < lines.length && shouldContinue(lines[j], lines, j)) { // console.log({ logicalLine, physicalLines, endIndex });
if (j + 1 < lines.length) {
j++;
continuationLines.push(lines[j]);
} else {
break;
}
}
// Build the complete logical line by joining all parts // Determine indentation changes
let logicalParts: string[] = []; // const isCompound = isCompoundStatement(logicalLine);
for (let k = 0; k < continuationLines.length; k++) { const isElse = isElseStatement(logicalLine);
let part = continuationLines[k].trim(); let shouldEnd = shouldEndIndentBlock(logicalLine);
part = removeComments(part); const shouldStart = shouldStartIndentBlock(logicalLine);
if (part.endsWith('\\')) {
part = part.slice(0, -1).trim();
}
if (part) {
logicalParts.push(part);
}
}
const fullLogical = logicalParts.join(' ').trim(); // Adjust indent level BEFORE formatting (for closing statements)
const firstLineTrimmed = removeComments(continuationLines[0]).trim(); if (isElse || shouldEnd) {
// Check if this line follows a THEN \ continuation
const isPrevThenContinuation = i > 0 && /\bTHEN\s*\\/.test(removeComments(lines[i - 1]));
// Check if this is a THEN continuation that starts a new block
const isThenContinuation = /\bTHEN\s*\\/.test(removeComments(continuationLines[0]));
let thenBlockStarts = false;
if (isThenContinuation && continuationLines.length > 1) {
// Check if the continuation contains an opening construct
for (let k = 1; k < continuationLines.length; k++) {
const contLine = removeComments(continuationLines[k]).trim();
if (/^\s*(FUNCTION|DEF|SUB|WHILE|FOR|IF|BEGIN)\b/i.test(contLine)) {
thenBlockStarts = true;
break;
}
}
}
// Simple approach: look for keywords that affect indentation
const containsBegin = /\bBEGIN\b/i.test(fullLogical);
const containsNext = /^\s*NEXT\b/i.test(firstLineTrimmed);
const isClosing = /^\s*(END\s+FUNCTION|FEND|END\s+SUB|ENDIF|WEND|NEXT)\b/i.test(firstLineTrimmed) ||
containsNext ||
(/^\s*ELSE\b/i.test(firstLineTrimmed) && !containsBegin);
const isOpening = /^\s*(FUNCTION|DEF|SUB|WHILE|FOR)\b/i.test(fullLogical) ||
/^\s*(FUNCTION|DEF|SUB|WHILE|FOR)\b/i.test(firstLineTrimmed) ||
containsBegin ||
thenBlockStarts;
// Handle compound statements like ENDIF ELSE BEGIN
const isCompoundEndifElse = /\bENDIF\b.*\bELSE\s+BEGIN\b/i.test(fullLogical);
const isCompoundEndifElseIf = /\bENDIF\s+ELSE\s+IF\b/i.test(fullLogical) && containsBegin;
// Adjust indent level for closing statements BEFORE formatting
if (isCompoundEndifElse || isCompoundEndifElseIf) {
indentLevel = Math.max(indentLevel - 1, 0);
} else if (isClosing) {
indentLevel = Math.max(indentLevel - 1, 0); indentLevel = Math.max(indentLevel - 1, 0);
} }
// Format all lines in this logical group with current indent level shouldEnd = false;
for (let k = 0; k < continuationLines.length; k++) {
const lineIndex = i + k; // Format all physical lines in this logical group
const original = continuationLines[k]; for (let j = 0; j < physicalLines.length; j++) {
const lineIndex = i + j;
const original = physicalLines[j];
const trimmed = original.trim(); const trimmed = original.trim();
if (trimmed === '') continue; if (trimmed === '') continue;
// For continuation lines (k > 0), use appropriate indentation // Determine indentation for this line
let lineIndentLevel = indentLevel; let lineIndentLevel = indentLevel;
if (isPrevThenContinuation) {
// This line follows a THEN \ - it should be indented
lineIndentLevel = indentLevel + 1;
} else if (k > 0 && isThenContinuation) {
// This is a continuation after THEN - it should be indented
lineIndentLevel = indentLevel + 1;
} else if (k > 0) {
// Regular continuation line - check if the previous line ended with \
const prevLine = removeComments(continuationLines[k - 1]).trim();
if (prevLine.endsWith('\\')) {
// Keep the same indentation as the main statement
lineIndentLevel = indentLevel;
}
}
const indent = indentUnit.repeat(lineIndentLevel); const indent = indentUnit.repeat(lineIndentLevel);
const formatted = indent + trimmed; const formatted = indent + trimmed;
if (formatted !== original) { // if (formatted !== original) {
const lineRange = document.lineAt(lineIndex).range; const lineRange = document.lineAt(lineIndex).range;
edits.push(vscode.TextEdit.replace(lineRange, formatted)); edits.push(vscode.TextEdit.replace(lineRange, formatted));
} // }
} }
// Adjust indent level for opening statements AFTER formatting if (shouldStart && !shouldEnd) {
if (isCompoundEndifElse || isCompoundEndifElseIf) {
indentLevel++;
} else if (isOpening) {
indentLevel++; indentLevel++;
} }
// Move to the next logical group // Move to next logical group
i = j + 1; i = endIndex + 1;
} }
return edits; return edits;
@@ -262,6 +278,7 @@ export async function activate(context: vscode.ExtensionContext) {
const definitions: FunctionDefinition[] = []; const definitions: FunctionDefinition[] = [];
const lines = document.getText().split('\n'); const lines = document.getText().split('\n');
const identifierRegex = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?/; const identifierRegex = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?/;
const labelRegex = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?:/;
// Helper function to remove comments from a line // Helper function to remove comments from a line
function removeComments(line: string): string { function removeComments(line: string): string {
@@ -500,26 +517,26 @@ export async function activate(context: vscode.ExtensionContext) {
position: vscode.Position, position: vscode.Position,
token: vscode.CancellationToken token: vscode.CancellationToken
): Promise<vscode.Definition | undefined> { ): Promise<vscode.Definition | undefined> {
const wordRange = document.getWordRangeAtPosition(position, /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?/); const functionWordRange = document.getWordRangeAtPosition(position, /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?/);
if (!wordRange) { if (!functionWordRange) {
return undefined; return undefined;
} }
const word = document.getText(wordRange).toLowerCase(); const word = document.getText(functionWordRange).toLowerCase();
const definitions = symbolIndex.get(word); const functionDefinitions = symbolIndex.get(word);
if (!definitions || definitions.length === 0) { if (!functionDefinitions || functionDefinitions.length === 0) {
return undefined; return undefined;
} }
// If multiple definitions exist, prefer the one in the current document // If multiple definitions exist, prefer the one in the current document
const localDef = definitions.find(def => def.location.uri.toString() === document.uri.toString()); const localDef = functionDefinitions.find(def => def.location.uri.toString() === document.uri.toString());
if (localDef) { if (localDef) {
return localDef.location; return localDef.location;
} }
// Otherwise return the first definition found // Otherwise return the first definition found
return definitions[0].location; return functionDefinitions[0].location;
} }
}; };
@@ -613,16 +630,12 @@ export async function activate(context: vscode.ExtensionContext) {
function findFunctionEnd(startLine: number, functionType: 'FUNCTION' | 'DEF' | 'SUB'): number { function findFunctionEnd(startLine: number, functionType: 'FUNCTION' | 'DEF' | 'SUB'): number {
let endPattern: RegExp; let endPattern: RegExp;
// console.log('functionType: ', functionType);
if (functionType === 'FUNCTION' || functionType === 'DEF') { if (functionType === 'FUNCTION' || functionType === 'DEF') {
endPattern = /^\s*(END\s+FUNCTION)|(FEND)\b/i; endPattern = /^\s*(END\s+FUNCTION)|(FEND)\b/i;
} else { // SUB } else { // SUB
endPattern = /^\s*END\s+SUB\b/i; endPattern = /^\s*END\s+SUB\b/i;
} }
// console.log('endPattern: ', endPattern);
for (let i = startLine + 1; i < lines.length; i++) { for (let i = startLine + 1; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
@@ -636,17 +649,12 @@ export async function activate(context: vscode.ExtensionContext) {
continue; continue;
} }
// console.log('Evaluating for end: ', line);
// Check if this line matches the end pattern // Check if this line matches the end pattern
const endMatch = endPattern.exec(line); const endMatch = endPattern.exec(line);
if (endMatch) { if (endMatch) {
// console.log('Found match: ', endMatch);
// Check if the end keyword is not commented out // Check if the end keyword is not commented out
if (!isKeywordCommentedOut(line, endMatch)) { if (!isKeywordCommentedOut(line, endMatch)) {
// console.log('Returning i');
return i; return i;
} }
} }
@@ -683,21 +691,13 @@ export async function activate(context: vscode.ExtensionContext) {
continue; continue;
} }
// console.log('Current line: ', trimmedLine);
// Extract the function type and name // Extract the function type and name
const defType = functionMatch[1].toUpperCase() as 'FUNCTION' | 'SUB' | 'DEF'; const defType = functionMatch[1].toUpperCase() as 'FUNCTION' | 'SUB' | 'DEF';
// console.log('functionMatch: ', functionMatch);
// console.log('Definition type: ', defType);
// Find the identifier after the function keyword // Find the identifier after the function keyword
const afterKeyword = line.substring(functionMatch[0].length).trim(); const afterKeyword = line.substring(functionMatch[0].length).trim();
const nameMatch = identifierRegex.exec(afterKeyword); const nameMatch = identifierRegex.exec(afterKeyword);
// console.log('afterKeyword: ', afterKeyword);
// console.log('nameMatch: ', nameMatch);
if (!nameMatch) { if (!nameMatch) {
continue; continue;
} }
@@ -805,7 +805,8 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.languages.registerDocumentSymbolProvider(selector, documentSymbolProvider), vscode.languages.registerDocumentSymbolProvider(selector, documentSymbolProvider),
vscode.languages.registerHoverProvider(selector, hoverProvider), vscode.languages.registerHoverProvider(selector, hoverProvider),
saveWatcher, saveWatcher,
fsWatcher fsWatcher,
disposable,
); );
} }

View File

@@ -12,6 +12,10 @@
"name": "string.regexp", "name": "string.regexp",
"match": "\\b[\\w\\.]+:" "match": "\\b[\\w\\.]+:"
}, },
{
"name": "meta",
"match": "\\s:"
},
{ {
"include": "#function-decl" "include": "#function-decl"
}, },
@@ -42,6 +46,12 @@
{ {
"include": "#if-statement-end" "include": "#if-statement-end"
}, },
{
"include": "#else"
},
{
"include": "#then"
},
{ {
"include": "#for-statement" "include": "#for-statement"
}, },
@@ -407,7 +417,7 @@
}, },
"logical-operator": { "logical-operator": {
"name": "keyword.operator.logical", "name": "keyword.operator.logical",
"match": "(?i)\\b(NOT|AND|OR|XOR)\\b" "match": "(?i)\\b(NOT|AND|OR|XOR)($|\\n|\\s\\()"
}, },
"string-operator": { "string-operator": {
"name": "keyword.operator", "name": "keyword.operator",
@@ -513,7 +523,7 @@
}, },
{ {
"name": "support.function", "name": "support.function",
"match": "(?i)\\b(NULL|TRUE|FALSE|READ|WRITE|KEY)\\b[^%\\$\\.]" "match": "(?i)\\b(NULL|TRUE|FALSE|READ|WRITE|KEY|NOWRITE|NODEL)\\b[^%\\$\\.]"
} }
] ]
}, },
@@ -633,127 +643,20 @@
} }
}, },
"if-statement": { "if-statement": {
"name": "meta.if", "name": "keyword.control",
"begin": "(?i)\\b(IF)", "match": "(?i)\\b(IF|BEGIN)\\b"
"beginCaptures": {
"0": {
"name": "keyword.control"
}
},
"patterns": [
{
"include": "#math-function"
},
{
"include": "#string-function"
},
{
"include": "#number-literal"
},
{
"include": "#arithmetic-operator"
},
{
"include": "#relational-operator"
},
{
"include": "#logical-operator"
},
{
"include": "#keywords"
},
{
"include": "#strings"
},
{
"name": "keyword.control",
"match": "(?i)THEN"
},
{
"name": "keyword.control",
"match": "(?i)ELSE"
},
{
"name": "keyword.control",
"match": "(?i)(BEGIN)"
},
{
"include": "#identifier"
},
{
"include": "#line-continuation"
},
{
"include": "#comments"
},
{
"match": "\\s+",
"name": "text.whitespace"
}
],
"end": "(?<!\\\\)(\\n)"
}, },
"if-statement-end": { "if-statement-end": {
"name": "meta.if.end", "name": "keyword.control",
"begin": "(?i)ENDIF", "match": "(?i)ENDIF"
"beginCaptures": { },
"0": { "else": {
"name": "keyword.control" "name": "keyword.control",
} "match": "(?i)ELSE"
}, },
"patterns": [ "then": {
{ "name": "keyword.control",
"name": "keyword.control", "match": "(?i)THEN"
"match": "ELSE"
},
{
"include": "#math-function"
},
{
"include": "#string-function"
},
{
"include": "#number-literal"
},
{
"include": "#arithmetic-operator"
},
{
"include": "#relational-operator"
},
{
"include": "#logical-operator"
},
{
"include": "#keywords"
},
{
"include": "#strings"
},
{
"name": "keyword.control",
"match": "THEN|then"
},
{
"name": "keyword.control",
"match": "(?i)BEGIN"
},
{
"name": "keyword.control",
"match": "(?i)ELSE"
},
{
"name": "keyword.control",
"match": "(?i)IF"
},
{
"include": "#line-continuation"
},
{
"include": "#comments"
}
],
"end": "(?<!\\\\)\\n"
}, },
"for-statement": { "for-statement": {
"name": "meta.for", "name": "meta.for",