diff options
Diffstat (limited to 'plugin')
| -rw-r--r-- | plugin/.gitignore | 3 | ||||
| -rw-r--r-- | plugin/examples/highlighting.cps | 41 | ||||
| -rw-r--r-- | plugin/language-configuration.json | 13 | ||||
| -rw-r--r-- | plugin/package-lock.json | 34 | ||||
| -rw-r--r-- | plugin/package.json | 36 | ||||
| -rw-r--r-- | plugin/src/extension.js | 6 | ||||
| -rw-r--r-- | plugin/syntaxes/cie-pseudocode.tmLanguage.json | 118 | ||||
| -rw-r--r-- | plugin/test/highlighting.test.js | 61 |
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')); +}); |
