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"),