From be8154fac8a0ea5eb3dddef4053f12d67102009d Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 13 Oct 2026 05:53:16 +0000 Subject: feat(plugin): add CIE language registration and C++ style highlighting Co-Authored-By: Claude Fable 5.1 --- plugin/.gitignore | 3 + plugin/examples/highlighting.cps | 41 +++++++++ plugin/language-configuration.json | 13 +++ plugin/package-lock.json | 34 +++++++ plugin/package.json | 36 ++++++++ plugin/src/extension.js | 6 ++ plugin/syntaxes/cie-pseudocode.tmLanguage.json | 118 +++++++++++++++++++++++++ plugin/test/highlighting.test.js | 61 +++++++++++++ 8 files changed, 312 insertions(+) create mode 100644 plugin/.gitignore create mode 100644 plugin/examples/highlighting.cps create mode 100644 plugin/language-configuration.json create mode 100644 plugin/package-lock.json create mode 100644 plugin/package.json create mode 100644 plugin/src/extension.js create mode 100644 plugin/syntaxes/cie-pseudocode.tmLanguage.json create mode 100644 plugin/test/highlighting.test.js (limited to 'plugin') diff --git a/plugin/.gitignore b/plugin/.gitignore new file mode 100644 index 0000000..7443abf --- /dev/null +++ b/plugin/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*.vsix +.vscode-test/ diff --git a/plugin/examples/highlighting.cps b/plugin/examples/highlighting.cps new file mode 100644 index 0000000..9925bf3 --- /dev/null +++ b/plugin/examples/highlighting.cps @@ -0,0 +1,41 @@ +// Open in a dark theme; keywords are uppercase and comments ignore [( +TYPE Season = (Spring, Summer, Autumn, Winter) +TYPE IntPointer = ^INTEGER +TYPE Student + DECLARE Name : STRING + DECLARE Initial : CHAR +ENDTYPE + +DECLARE Values : ARRAY[1:3] OF INTEGER +DECLARE Index : INTEGER +DECLARE Total : REAL +DECLARE Ready : BOOLEAN +DECLARE Text : STRING +DECLARE Letter : CHAR +Ready <- TRUE AND NOT FALSE +Total <- -3.5 + 2 * 4 / 2 +Text <- "CIE // [()]" & " pseudocode" +Letter <- 'A' + +FOR Index <- 1 TO 3 STEP 1 + Values[Index] <- Index MOD 2 +NEXT Index +IF Ready OR Total >= 1 THEN + OUTPUT MID(Text, 1, LENGTH(Text)) +ELSE + OUTPUT "Try again" +ENDIF + +FUNCTION Double(Value : INTEGER) RETURNS INTEGER + RETURN Value * 2 +ENDFUNCTION +PROCEDURE Swap(BYREF X : INTEGER, Y : INTEGER) + DECLARE Temp : INTEGER + Temp <- X + X <- Y + Y <- Temp +ENDPROCEDURE + +OPENFILE "example.txt" FOR WRITE +WRITEFILE "example.txt", Text +CLOSEFILE "example.txt" diff --git a/plugin/language-configuration.json b/plugin/language-configuration.json new file mode 100644 index 0000000..ed3e464 --- /dev/null +++ b/plugin/language-configuration.json @@ -0,0 +1,13 @@ +{ + "comments": { "lineComment": "//" }, + "brackets": [["[", "]"], ["(", ")"]], + "colorizedBracketPairs": [["[", "]"], ["(", ")"]], + "autoClosingPairs": [ + { "open": "[", "close": "]", "notIn": ["string", "comment"] }, + { "open": "(", "close": ")", "notIn": ["string", "comment"] }, + { "open": "'", "close": "'", "notIn": ["string", "comment"] }, + { "open": "\"", "close": "\"", "notIn": ["string", "comment"] } + ], + "surroundingPairs": [["[", "]"], ["(", ")"], ["\"", "\""], ["'", "'"]], + "wordPattern": "[A-Za-z][A-Za-z0-9_]*" +} diff --git a/plugin/package-lock.json b/plugin/package-lock.json new file mode 100644 index 0000000..18cd920 --- /dev/null +++ b/plugin/package-lock.json @@ -0,0 +1,34 @@ +{ + "name": "cie-pseudocode", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cie-pseudocode", + "version": "0.1.0", + "license": "GPL-3.0-only", + "devDependencies": { + "vscode-oniguruma": "^2.0.1", + "vscode-textmate": "^9.3.2" + }, + "engines": { + "vscode": "^1.85.0" + } + }, + "node_modules/vscode-oniguruma": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz", + "integrity": "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-textmate": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.3.2.tgz", + "integrity": "sha512-n2uGbUcrjhUEBH16uGA0TvUfhWwliFZ1e3+pTjrkim1Mt7ydB41lV08aUvsi70OlzDWp6X7Bx3w/x3fAXIsN0Q==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/plugin/package.json b/plugin/package.json new file mode 100644 index 0000000..c7a31d1 --- /dev/null +++ b/plugin/package.json @@ -0,0 +1,36 @@ +{ + "name": "cie-pseudocode", + "displayName": "CIE Pseudocode", + "description": "CIE pseudocode highlighting, Tab indentation and bracket checking for .cps files.", + "version": "0.1.0", + "publisher": "ovfor4", + "license": "GPL-3.0-only", + "repository": { + "type": "git", + "url": "https://github.com/ovfor4/cie-pseudocode-compiler.git", + "directory": "plugin" + }, + "engines": { "vscode": "^1.85.0" }, + "categories": ["Programming Languages", "Formatters"], + "scripts": { + "check": "node --check src/extension.js", + "test": "node --test test/*.test.js" + }, + "contributes": { + "languages": [{ + "id": "cie-pseudocode", + "aliases": ["CIE Pseudocode", "cie-pseudocode"], + "extensions": [".cps"], + "configuration": "./language-configuration.json" + }], + "grammars": [{ + "language": "cie-pseudocode", + "scopeName": "source.cie-pseudocode", + "path": "./syntaxes/cie-pseudocode.tmLanguage.json" + }] + }, + "devDependencies": { + "vscode-oniguruma": "^2.0.1", + "vscode-textmate": "^9.3.2" + } +} diff --git a/plugin/src/extension.js b/plugin/src/extension.js new file mode 100644 index 0000000..1101ed6 --- /dev/null +++ b/plugin/src/extension.js @@ -0,0 +1,6 @@ +'use strict'; + +// Language registration and TextMate highlighting are declarative. +function activate() {} + +module.exports = { activate }; diff --git a/plugin/syntaxes/cie-pseudocode.tmLanguage.json b/plugin/syntaxes/cie-pseudocode.tmLanguage.json new file mode 100644 index 0000000..40a67df --- /dev/null +++ b/plugin/syntaxes/cie-pseudocode.tmLanguage.json @@ -0,0 +1,118 @@ +{ + "name": "CIE Pseudocode", + "scopeName": "source.cie-pseudocode", + "patterns": [ + { + "include": "#line_comment" + }, + { + "include": "#strings" + }, + { + "match": "(?|<=|>=|[+*/=&^<>-]", + "name": "keyword.operator.cie-pseudocode" + }, + { + "match": "(? { + const wasm = fs.readFileSync(require.resolve('vscode-oniguruma/release/onig.wasm')); + await loadWASM(wasm.buffer.slice(wasm.byteOffset, wasm.byteOffset + wasm.byteLength)); + const registry = new Registry({ + onigLib: Promise.resolve({ + createOnigScanner: patterns => new OnigScanner(patterns), + createOnigString: text => new OnigString(text) + }), + loadGrammar: async () => JSON.parse(fs.readFileSync( + path.join(__dirname, '../syntaxes/cie-pseudocode.tmLanguage.json'), 'utf8')) + }); + grammar = await registry.loadGrammar('source.cie-pseudocode'); +}); + +function scopesAt(text, word) { + const offset = text.indexOf(word); + return grammar.tokenizeLine(text).tokens.find( + token => token.startIndex <= offset && token.endIndex > offset).scopes; +} + +test('C++ style scopes distinguish keywords, types, literals, operators and builtins', () => { + for (const [text, word, scope] of [ + ['IF Ready THEN', 'IF', 'keyword.control'], + ['DECLARE X : INTEGER', 'DECLARE', 'storage.modifier'], + ['DECLARE X : INTEGER', 'INTEGER', 'storage.type'], + ['X <- TRUE', 'TRUE', 'constant.language'], + ['X <- 12.5', '12.5', 'constant.numeric'], + ['X <- 2 DIV 1', 'DIV', 'keyword.operator.word'], + ['X <- 1', '<-', 'keyword.operator'], + ['OUTPUT "text"', 'text', 'string.quoted.double'], + ["X <- 'A'", 'A', 'string.quoted.single'] + ]) assert.ok(scopesAt(text, word).some(s => s.startsWith(scope)), text); + for (const name of ['LENGTH', 'MID', 'RIGHT', 'LEFT', 'LCASE', 'UCASE', + 'ASC', 'CHR', 'IS_NUM', 'NUM_TO_STR', 'STR_TO_NUM', 'EOF']) { + assert.ok(scopesAt(`${name}(X)`, name).includes('entity.name.function.cie-pseudocode')); + } +}); + +test('comments, strings, lowercase and keyword prefixes cannot become keywords', () => { + assert.ok(scopesAt('// IF [(', 'IF').some(s => s.startsWith('comment.'))); + assert.ok(scopesAt('OUTPUT "IF // ["', 'IF').some(s => s.startsWith('string.'))); + for (const text of ['if X then', 'IFValue <- 1', 'CASE OF X', 'CLASS Example']) { + assert.ok(!scopesAt(text, text.split(' ')[0]).some( + s => /^(keyword|storage)\./.test(s)), text); + } + // CIE has neither backslash escapes nor multiline string literals. + let state = grammar.tokenizeLine('OUTPUT "unfinished').ruleStack; + assert.ok(grammar.tokenizeLine('IF X THEN', state).tokens[0].scopes.includes( + 'keyword.control.cie-pseudocode')); + assert.ok(scopesAt('OUTPUT "x\\" + 1', '+').includes('keyword.operator.cie-pseudocode')); +}); -- cgit v1.3.1-4-g156e