aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 05:53:16 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 05:53:16 +0000
commitbe8154fac8a0ea5eb3dddef4053f12d67102009d (patch)
treef782d438ffa451ebd2efc00ccc0b7a4579a020c4
parent38ff4e782ef844a21512eb2ca0e491387ae89ec5 (diff)
feat(plugin): add CIE language registration and C++ style highlighting
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
-rw-r--r--plugin/.gitignore3
-rw-r--r--plugin/examples/highlighting.cps41
-rw-r--r--plugin/language-configuration.json13
-rw-r--r--plugin/package-lock.json34
-rw-r--r--plugin/package.json36
-rw-r--r--plugin/src/extension.js6
-rw-r--r--plugin/syntaxes/cie-pseudocode.tmLanguage.json118
-rw-r--r--plugin/test/highlighting.test.js61
8 files changed, 312 insertions, 0 deletions
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": "(?<!\\w)(?:IF|THEN|ELSE|ENDIF|WHILE|DO|ENDWHILE|REPEAT|UNTIL|FOR|TO|STEP|NEXT|RETURN|CALL)(?!\\w)",
+ "name": "keyword.control.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)(?:DECLARE|TYPE|ENDTYPE|FUNCTION|ENDFUNCTION|PROCEDURE|ENDPROCEDURE|RETURNS|BYREF|BYVAL|OF)(?!\\w)",
+ "name": "storage.modifier.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)(?:INTEGER|REAL|BOOLEAN|STRING|CHAR|ARRAY)(?!\\w)",
+ "name": "storage.type.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)(?:TRUE|FALSE)(?!\\w)",
+ "name": "constant.language.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)(?:INPUT|OUTPUT|OPENFILE|READFILE|WRITEFILE|CLOSEFILE|READ|WRITE|APPEND)(?!\\w)",
+ "name": "keyword.other.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)(?:LENGTH|MID|RIGHT|LEFT|LCASE|UCASE|ASC|CHR|IS_NUM|NUM_TO_STR|STR_TO_NUM|EOF)(?!\\w)(?=\\s*\\()",
+ "name": "entity.name.function.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)(?:DIV|MOD|AND|OR|NOT)(?!\\w)",
+ "name": "keyword.operator.word.cie-pseudocode"
+ },
+ {
+ "match": "<-|<>|<=|>=|[+*/=&^<>-]",
+ "name": "keyword.operator.cie-pseudocode"
+ },
+ {
+ "match": "(?<!\\w)\\d+(?:\\.\\d*)?(?!\\w)",
+ "name": "constant.numeric.cie-pseudocode"
+ },
+ {
+ "match": "\\b[A-Za-z][A-Za-z0-9_]*(?=\\s*\\()",
+ "name": "entity.name.function.cie-pseudocode"
+ },
+ {
+ "match": "[()]",
+ "name": "punctuation.section.parens.cie-pseudocode"
+ },
+ {
+ "match": "[\\[\\]]",
+ "name": "punctuation.section.brackets.cie-pseudocode"
+ },
+ {
+ "match": ",",
+ "name": "punctuation.separator.delimiter.comma.cie-pseudocode"
+ },
+ {
+ "match": "[:.]",
+ "name": "punctuation.separator.cie-pseudocode"
+ }
+ ],
+ "repository": {
+ "line_comment": {
+ "begin": "(//)",
+ "end": "$",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.comment.cie-pseudocode"
+ }
+ },
+ "endCaptures": {},
+ "name": "comment.line.double-slash.cie-pseudocode",
+ "patterns": []
+ },
+ "strings": {
+ "patterns": [
+ {
+ "begin": "\"",
+ "end": "\"|$",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.begin.cie-pseudocode"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.end.cie-pseudocode"
+ }
+ },
+ "name": "string.quoted.double.cie-pseudocode",
+ "patterns": []
+ },
+ {
+ "begin": "'",
+ "end": "'|$",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.begin.cie-pseudocode"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.end.cie-pseudocode"
+ }
+ },
+ "name": "string.quoted.single.cie-pseudocode",
+ "patterns": []
+ }
+ ]
+ }
+ }
+}
diff --git a/plugin/test/highlighting.test.js b/plugin/test/highlighting.test.js
new file mode 100644
index 0000000..df18f6a
--- /dev/null
+++ b/plugin/test/highlighting.test.js
@@ -0,0 +1,61 @@
+'use strict';
+
+const assert = require('node:assert/strict');
+const { test, before } = require('node:test');
+const fs = require('node:fs');
+const path = require('node:path');
+const { Registry } = require('vscode-textmate');
+const { loadWASM, OnigScanner, OnigString } = require('vscode-oniguruma');
+
+let grammar;
+before(async () => {
+ 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'));
+});