diff --git a/grammars/qsharp.tmLanguage b/grammars/qsharp.tmLanguage index 6787a0c..d6637e2 100644 --- a/grammars/qsharp.tmLanguage +++ b/grammars/qsharp.tmLanguage @@ -116,6 +116,10 @@ include #expression-operators + + include + #declaration-keywords + include #identifier @@ -226,6 +230,48 @@ + declaration-keywords + + patterns + + + name + keyword.other.callable.qsharp + match + \b(operation|function)\b + + + name + keyword.other.let.qsharp + match + \b(let)\b + + + name + keyword.other.mutable.qsharp + match + \b(mutable)\b + + + name + keyword.other.set.qsharp + match + \b(set)\b + + + name + keyword.other.use.qsharp + match + \b(use)\b + + + name + keyword.other.borrow.qsharp + match + \b(borrow)\b + + + operations patterns @@ -575,6 +621,10 @@ include #expression-operators + + include + #declaration-keywords + include #identifier @@ -1327,6 +1377,10 @@ include #expression-operators + + include + #declaration-keywords + include #identifier @@ -1758,43 +1812,93 @@ tuple-binding + begin + (?x) +\b(?:(let)|(mutable)|(set)|(use)|(borrow))\b\s* +(?=\() + beginCaptures + + 1 + + name + keyword.other.let.qsharp + + 2 + + name + keyword.other.mutable.qsharp + + 3 + + name + keyword.other.set.qsharp + + 4 + + name + keyword.other.use.qsharp + + 5 + + name + keyword.other.borrow.qsharp + + + end + (?=;|\bin\b) patterns - match - \b(let|mutable|set)\b(?=\s*\() - captures - - 1 - - name - keyword.other.qsharp - - + include + #tuple-pattern - match - \b(use)\b(?=\s*\() - captures - - 1 - - name - keyword.other.use.qsharp - - + include + #variable-initializer + + + include + #expression + + + + tuple-pattern + + begin + \( + beginCaptures + + 0 + + name + punctuation.parenthesis.open.qsharp + + + end + \) + endCaptures + + 0 + + name + punctuation.parenthesis.close.qsharp + + + patterns + + + include + #tuple-pattern + name + entity.name.variable.local.qsharp match - \b(borrow)\b(?=\s*\() - captures - - 1 - - name - keyword.other.borrow.qsharp - - + \b[_[:alpha:]][_[:alnum:]]*\b + + + include + #punctuation-comma @@ -1802,11 +1906,9 @@ begin (?x) -(?:\b(set)\b\s*) +\b(set)\b\s* ([_[:alpha:]][_[:alnum:]]*)\b\s* -(?:(=)\s*([_[:alpha:]][_[:alnum:]]*)\b\s*(w\/)\s*|\b(w\/=)\s*)? -(\d+)\s* -(<-)\s* +(?:(=)\s*([_[:alpha:]][_[:alnum:]]*)\b\s*(w\/)|(w\/=)) beginCaptures 1 @@ -1839,40 +1941,20 @@ name keyword.operator.assignment.qsharp - 7 - - name - constant.numeric.decimal.qsharp - - 8 - - name - keyword.operator.assignment.qsharp - end (?=;|\)) patterns - include - #callable-invocation - - - include - #strings - - - include - #types - - - include - #literals + name + keyword.operator.assignment.qsharp + match + <- include - #identifier + #expression diff --git a/grammars/qsharp.tmLanguage.json b/grammars/qsharp.tmLanguage.json index 4ee5955..5de613d 100644 --- a/grammars/qsharp.tmLanguage.json +++ b/grammars/qsharp.tmLanguage.json @@ -83,6 +83,9 @@ { "include": "#expression-operators" }, + { + "include": "#declaration-keywords" + }, { "include": "#identifier" } @@ -156,6 +159,34 @@ } ] }, + "declaration-keywords": { + "patterns": [ + { + "name": "keyword.other.callable.qsharp", + "match": "\\b(operation|function)\\b" + }, + { + "name": "keyword.other.let.qsharp", + "match": "\\b(let)\\b" + }, + { + "name": "keyword.other.mutable.qsharp", + "match": "\\b(mutable)\\b" + }, + { + "name": "keyword.other.set.qsharp", + "match": "\\b(set)\\b" + }, + { + "name": "keyword.other.use.qsharp", + "match": "\\b(use)\\b" + }, + { + "name": "keyword.other.borrow.qsharp", + "match": "\\b(borrow)\\b" + } + ] + }, "operations": { "patterns": [ { @@ -390,6 +421,9 @@ { "include": "#expression-operators" }, + { + "include": "#declaration-keywords" + }, { "include": "#identifier" } @@ -884,6 +918,9 @@ { "include": "#expression-operators" }, + { + "include": "#declaration-keywords" + }, { "include": "#identifier" } @@ -1165,35 +1202,65 @@ ] }, "tuple-binding": { + "begin": "(?x)\n\\b(?:(let)|(mutable)|(set)|(use)|(borrow))\\b\\s*\n(?=\\()", + "beginCaptures": { + "1": { + "name": "keyword.other.let.qsharp" + }, + "2": { + "name": "keyword.other.mutable.qsharp" + }, + "3": { + "name": "keyword.other.set.qsharp" + }, + "4": { + "name": "keyword.other.use.qsharp" + }, + "5": { + "name": "keyword.other.borrow.qsharp" + } + }, + "end": "(?=;|\\bin\\b)", "patterns": [ { - "match": "\\b(let|mutable|set)\\b(?=\\s*\\()", - "captures": { - "1": { - "name": "keyword.other.qsharp" - } - } + "include": "#tuple-pattern" }, { - "match": "\\b(use)\\b(?=\\s*\\()", - "captures": { - "1": { - "name": "keyword.other.use.qsharp" - } - } + "include": "#variable-initializer" }, { - "match": "\\b(borrow)\\b(?=\\s*\\()", - "captures": { - "1": { - "name": "keyword.other.borrow.qsharp" - } - } + "include": "#expression" + } + ] + }, + "tuple-pattern": { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.parenthesis.open.qsharp" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.parenthesis.close.qsharp" + } + }, + "patterns": [ + { + "include": "#tuple-pattern" + }, + { + "name": "entity.name.variable.local.qsharp", + "match": "\\b[_[:alpha:]][_[:alnum:]]*\\b" + }, + { + "include": "#punctuation-comma" } ] }, "array-assignment": { - "begin": "(?x)\n(?:\\b(set)\\b\\s*)\n([_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(?:(=)\\s*([_[:alpha:]][_[:alnum:]]*)\\b\\s*(w\\/)\\s*|\\b(w\\/=)\\s*)?\n(\\d+)\\s*\n(<-)\\s*", + "begin": "(?x)\n\\b(set)\\b\\s*\n([_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(?:(=)\\s*([_[:alpha:]][_[:alnum:]]*)\\b\\s*(w\\/)|(w\\/=))", "beginCaptures": { "1": { "name": "keyword.other.set.qsharp" @@ -1212,30 +1279,16 @@ }, "6": { "name": "keyword.operator.assignment.qsharp" - }, - "7": { - "name": "constant.numeric.decimal.qsharp" - }, - "8": { - "name": "keyword.operator.assignment.qsharp" } }, "end": "(?=;|\\))", "patterns": [ { - "include": "#callable-invocation" - }, - { - "include": "#strings" - }, - { - "include": "#types" - }, - { - "include": "#literals" + "name": "keyword.operator.assignment.qsharp", + "match": "<-" }, { - "include": "#identifier" + "include": "#expression" } ] }, diff --git a/src/qsharp.tmLanguage.yml b/src/qsharp.tmLanguage.yml index c08a2aa..3176f08 100644 --- a/src/qsharp.tmLanguage.yml +++ b/src/qsharp.tmLanguage.yml @@ -28,6 +28,7 @@ patterns: - include: '#if-statement' - include: '#parenthesized-expression' - include: '#expression-operators' +- include: '#declaration-keywords' - include: '#identifier' repository: @@ -69,6 +70,25 @@ repository: - name: keyword.other.qsharp match: \b(new)\b + # Low-priority fallback so declaration/callable keywords are colored the + # moment they are typed, before the rest of the construct (name, parens, + # `=`, `;`) exists to satisfy the more specific rules. Must be included + # AFTER those specific rules but BEFORE #identifier. + declaration-keywords: + patterns: + - name: keyword.other.callable.qsharp + match: \b(operation|function)\b + - name: keyword.other.let.qsharp + match: \b(let)\b + - name: keyword.other.mutable.qsharp + match: \b(mutable)\b + - name: keyword.other.set.qsharp + match: \b(set)\b + - name: keyword.other.use.qsharp + match: \b(use)\b + - name: keyword.other.borrow.qsharp + match: \b(borrow)\b + operations: patterns: - name: keyword.other.qsharp @@ -198,6 +218,7 @@ repository: - include: '#if-statement' - include: '#parenthesized-expression' - include: '#expression-operators' + - include: '#declaration-keywords' - include: '#identifier' callable-invocation: @@ -485,6 +506,7 @@ repository: - include: '#if-statement' - include: '#parenthesized-expression' - include: '#expression-operators' + - include: '#declaration-keywords' - include: '#identifier' # general-purpose expression context, reused wherever a value can appear @@ -654,31 +676,56 @@ repository: # tuple binding keywords immediately followed by a tuple pattern: # let (a, b) = pair; mutable (x, _) = ...; set (a, b) = ...; # use (a, b) = (Qubit(), Qubit()); borrow (a, b) = (...); + # The bound identifiers are scoped as local variables so they match the + # coloring of a simple `let x = ...;` declaration. tuple-binding: + begin: |- + (?x) + \b(?:(let)|(mutable)|(set)|(use)|(borrow))\b\s* + (?=\() + beginCaptures: + '1': { name: keyword.other.let.qsharp } + '2': { name: keyword.other.mutable.qsharp } + '3': { name: keyword.other.set.qsharp } + '4': { name: keyword.other.use.qsharp } + '5': { name: keyword.other.borrow.qsharp } + end: (?=;|\bin\b) patterns: - - match: \b(let|mutable|set)\b(?=\s*\() - captures: - '1': - name: keyword.other.qsharp - - match: \b(use)\b(?=\s*\() - captures: - '1': - name: keyword.other.use.qsharp - - match: \b(borrow)\b(?=\s*\() - captures: - '1': - name: keyword.other.borrow.qsharp + - include: '#tuple-pattern' + - include: '#variable-initializer' + - include: '#expression' - # note this captures both set foo w/= 0 <- 1 as well as set foo = foo w/ 0 <-1 - # this is good enough for now, but probably the latter should be moved out + # the parenthesized identifier list of a tuple binding: (a, b), (x, _), + # and nested forms like (a, (b, c)). Identifiers become local variables. + tuple-pattern: + begin: \( + beginCaptures: + '0': + name: punctuation.parenthesis.open.qsharp + end: \) + endCaptures: + '0': + name: punctuation.parenthesis.close.qsharp + patterns: + - include: '#tuple-pattern' + - name: entity.name.variable.local.qsharp + match: \b[_[:alpha:]][_[:alnum:]]*\b + - include: '#punctuation-comma' + + # copy-and-update assignment. Two spellings, both requiring the copy-update + # operator so that plain reassignment (set foo = ...) is left to + # #local-assignment: + # set foo w/= <- ; + # set foo = foo w/ <- ; + # The index and value are full expressions, so variable indices such as + # set ary w/= x <- 10; + # are highlighted correctly rather than mis-scoping `set` as an identifier. array-assignment: begin: |- (?x) - (?:\b(set)\b\s*) + \b(set)\b\s* ([_[:alpha:]][_[:alnum:]]*)\b\s* - (?:(=)\s*([_[:alpha:]][_[:alnum:]]*)\b\s*(w\/)\s*|\b(w\/=)\s*)? - (\d+)\s* - (<-)\s* + (?:(=)\s*([_[:alpha:]][_[:alnum:]]*)\b\s*(w\/)|(w\/=)) beginCaptures: '1': { name: keyword.other.set.qsharp } '2': { name: entity.name.variable.local.qsharp } @@ -686,15 +733,11 @@ repository: '4': { name: entity.name.variable.local.qsharp } '5': { name: keyword.operator.assignment.qsharp } '6': { name: keyword.operator.assignment.qsharp } - '7': { name: constant.numeric.decimal.qsharp } - '8': { name: keyword.operator.assignment.qsharp } end: (?=;|\)) patterns: - - include: '#callable-invocation' - - include: '#strings' - - include: '#types' - - include: '#literals' - - include: '#identifier' + - name: keyword.operator.assignment.qsharp + match: <- + - include: '#expression' local-assignment: begin: |- diff --git a/test/assignment.array.tests.ts b/test/assignment.array.tests.ts index e3790c9..89476d2 100644 --- a/test/assignment.array.tests.ts +++ b/test/assignment.array.tests.ts @@ -41,4 +41,21 @@ describe("Array Assignment", () => { createToken("1", "constant.numeric.decimal.qsharp"), ]); }); + + it("set ary w/= x <- 10; (variable index)", async () => { + const tokens = await tokenize("set ary w/= x <- 10;"); + tokens.should.deep.equal([ + createToken("set", "keyword.other.set.qsharp"), + createToken(" ", "source.qsharp"), + createToken("ary", "entity.name.variable.local.qsharp"), + createToken(" ", "source.qsharp"), + createToken("w/=", "keyword.operator.assignment.qsharp"), + createToken(" ", "source.qsharp"), + createToken("x", "variable.other.readwrite.qsharp"), + createToken(" ", "source.qsharp"), + createToken("<-", "keyword.operator.assignment.qsharp"), + createToken(" ", "source.qsharp"), + createToken("10", "constant.numeric.decimal.qsharp"), + ]); + }); }); \ No newline at end of file diff --git a/test/declaration.local.tests.ts b/test/declaration.local.tests.ts index 56adc09..85c3b60 100644 --- a/test/declaration.local.tests.ts +++ b/test/declaration.local.tests.ts @@ -7,13 +7,13 @@ describe("Local declarations", () => { it("Tuple destructuring", async () => { const tokens = await tokenize(`let (a, b) = pair;`); tokens.should.deep.equal([ - createToken("let", "keyword.other.qsharp"), + createToken("let", "keyword.other.let.qsharp"), createToken(" ", "source.qsharp"), createToken("(", "punctuation.parenthesis.open.qsharp"), - createToken("a", "variable.other.readwrite.qsharp"), + createToken("a", "entity.name.variable.local.qsharp"), createToken(",", "punctuation.separator.comma.qsharp"), createToken(" ", "source.qsharp"), - createToken("b", "variable.other.readwrite.qsharp"), + createToken("b", "entity.name.variable.local.qsharp"), createToken(")", "punctuation.parenthesis.close.qsharp"), createToken(" ", "source.qsharp"), createToken("=", "keyword.operator.assignment.qsharp"), diff --git a/test/expression.elementaccess.tests.ts b/test/expression.elementaccess.tests.ts new file mode 100644 index 0000000..a66d55c --- /dev/null +++ b/test/expression.elementaccess.tests.ts @@ -0,0 +1,27 @@ +import { should } from 'chai'; +import { tokenize, createToken, Token } from './utils/tokenize'; + +// regression test for array element acccess (ensure proper tokenization of the square brackets and the index expression) +describe("Array element access", () => { + before(() => { should(); }); + + it("index by variable: total[y]", async () => { + const tokens = await tokenize(`total[y];`); + tokens.should.deep.equal([ + createToken("total", "variable.other.readwrite.qsharp"), + createToken("[", "punctuation.squarebracket.open.qsharp"), + createToken("y", "variable.other.readwrite.qsharp"), + createToken("]", "punctuation.squarebracket.close.qsharp"), + ]); + }); + + it("index by literal: total[0]", async () => { + const tokens = await tokenize(`total[0];`); + tokens.should.deep.equal([ + createToken("total", "variable.other.readwrite.qsharp"), + createToken("[", "punctuation.squarebracket.open.qsharp"), + createToken("0", "constant.numeric.decimal.qsharp"), + createToken("]", "punctuation.squarebracket.close.qsharp"), + ]); + }); +}); diff --git a/test/keywords.incremental.tests.ts b/test/keywords.incremental.tests.ts new file mode 100644 index 0000000..89d2af3 --- /dev/null +++ b/test/keywords.incremental.tests.ts @@ -0,0 +1,72 @@ +import { should } from 'chai'; +import { tokenize, createToken, Token } from './utils/tokenize'; + +// While a declaration is being typed, its leading keyword should be colored +// immediately, before the rest of the construct (name, parens, `=`, `;`) +// exists to satisfy the more specific declaration rules. +describe("Incremental keyword highlighting", () => { + before(() => { should(); }); + + it("operation without parameter list", async () => { + const tokens = await tokenize(`operation Foo`); + tokens.should.deep.equal([ + createToken("operation", "keyword.other.callable.qsharp"), + createToken(" ", "source.qsharp"), + createToken("Foo", "variable.other.readwrite.qsharp"), + ]); + }); + + it("function without parameter list", async () => { + const tokens = await tokenize(`function Bar`); + tokens.should.deep.equal([ + createToken("function", "keyword.other.callable.qsharp"), + createToken(" ", "source.qsharp"), + createToken("Bar", "variable.other.readwrite.qsharp"), + ]); + }); + + it("let without initializer", async () => { + const tokens = await tokenize(`let temp`); + tokens.should.deep.equal([ + createToken("let", "keyword.other.let.qsharp"), + createToken(" ", "source.qsharp"), + createToken("temp", "variable.other.readwrite.qsharp"), + ]); + }); + + it("mutable without initializer", async () => { + const tokens = await tokenize(`mutable temp`); + tokens.should.deep.equal([ + createToken("mutable", "keyword.other.mutable.qsharp"), + createToken(" ", "source.qsharp"), + createToken("temp", "variable.other.readwrite.qsharp"), + ]); + }); + + it("set without initializer", async () => { + const tokens = await tokenize(`set temp`); + tokens.should.deep.equal([ + createToken("set", "keyword.other.set.qsharp"), + createToken(" ", "source.qsharp"), + createToken("temp", "variable.other.readwrite.qsharp"), + ]); + }); + + it("use without initializer", async () => { + const tokens = await tokenize(`use q`); + tokens.should.deep.equal([ + createToken("use", "keyword.other.use.qsharp"), + createToken(" ", "source.qsharp"), + createToken("q", "variable.other.readwrite.qsharp"), + ]); + }); + + it("borrow without initializer", async () => { + const tokens = await tokenize(`borrow q`); + tokens.should.deep.equal([ + createToken("borrow", "keyword.other.borrow.qsharp"), + createToken(" ", "source.qsharp"), + createToken("q", "variable.other.readwrite.qsharp"), + ]); + }); +}); diff --git a/test/quantum.allocation.tests.ts b/test/quantum.allocation.tests.ts index ffa8fe9..c20e82e 100644 --- a/test/quantum.allocation.tests.ts +++ b/test/quantum.allocation.tests.ts @@ -72,10 +72,10 @@ describe("Quantum: Allocation", () => { createToken("use", "keyword.other.use.qsharp"), createToken(" ", "source.qsharp"), createToken("(", "punctuation.parenthesis.open.qsharp"), - createToken("q1", "variable.other.readwrite.qsharp"), + createToken("q1", "entity.name.variable.local.qsharp"), createToken(",", "punctuation.separator.comma.qsharp"), createToken(" ", "source.qsharp"), - createToken("q2", "variable.other.readwrite.qsharp"), + createToken("q2", "entity.name.variable.local.qsharp"), createToken(")", "punctuation.parenthesis.close.qsharp"), createToken(" ", "source.qsharp"), createToken("=", "keyword.operator.assignment.qsharp"),