diff --git a/.gitignore b/.gitignore
index 9041a63..de604ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,3 +106,6 @@ out/
# macOS
.DS_Store
+
+# generated copy for the grammar test extension (see build.js)
+test-extension/qsharp.tmLanguage.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..e40717f
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Test grammar (Extension Development Host)",
+ "type": "extensionHost",
+ "request": "launch",
+ "args": [
+ "--disable-extension=quantum.qsharp-lang-vscode",
+ "--extensionDevelopmentPath=${workspaceFolder}/test-extension",
+ "${workspaceFolder}/test-extension/sample.qs"
+ ]
+ }
+ ]
+}
diff --git a/build.js b/build.js
index fa90067..d3194ac 100644
--- a/build.js
+++ b/build.js
@@ -31,6 +31,17 @@ function build() {
);
console.log("Built grammars/qsharp.tmLanguage and grammars/qsharp.tmLanguage.json");
+
+ // Keep a copy inside the test extension (gitignored) so its manifest can
+ // reference a grammar within its own folder — VS Code warns otherwise.
+ const testExtDir = path.join(__dirname, "test-extension");
+ if (fs.existsSync(testExtDir)) {
+ fs.writeFileSync(
+ path.join(testExtDir, "qsharp.tmLanguage.json"),
+ JSON.stringify(grammar, null, "\t") + "\n"
+ );
+ console.log("Refreshed test-extension/qsharp.tmLanguage.json");
+ }
}
build();
diff --git a/grammars/qsharp.tmLanguage b/grammars/qsharp.tmLanguage
index 0395032..6787a0c 100644
--- a/grammars/qsharp.tmLanguage
+++ b/grammars/qsharp.tmLanguage
@@ -718,7 +718,7 @@
name
support.type.constraint.qsharp
match
- \b(Eq|Add|Sub|Mul|Div|Mod|Signed|Ord|Show|Integral|Exp)\b
+ \b(Eq|Add|Sub|Mul|Div|Mod|Signed|Ord|Show|Integral|Iterable|Exp)\b
name
diff --git a/grammars/qsharp.tmLanguage.json b/grammars/qsharp.tmLanguage.json
index 34b2b66..4ee5955 100644
--- a/grammars/qsharp.tmLanguage.json
+++ b/grammars/qsharp.tmLanguage.json
@@ -477,7 +477,7 @@
},
{
"name": "support.type.constraint.qsharp",
- "match": "\\b(Eq|Add|Sub|Mul|Div|Mod|Signed|Ord|Show|Integral|Exp)\\b"
+ "match": "\\b(Eq|Add|Sub|Mul|Div|Mod|Signed|Ord|Show|Integral|Iterable|Exp)\\b"
},
{
"name": "punctuation.squarebracket.open.qsharp",
diff --git a/src/qsharp.tmLanguage.yml b/src/qsharp.tmLanguage.yml
index f4e5b0d..c08a2aa 100644
--- a/src/qsharp.tmLanguage.yml
+++ b/src/qsharp.tmLanguage.yml
@@ -259,7 +259,7 @@ repository:
- name: keyword.operator.qsharp
match: \+
- name: support.type.constraint.qsharp
- match: \b(Eq|Add|Sub|Mul|Div|Mod|Signed|Ord|Show|Integral|Exp)\b
+ match: \b(Eq|Add|Sub|Mul|Div|Mod|Signed|Ord|Show|Integral|Iterable|Exp)\b
- name: punctuation.squarebracket.open.qsharp
match: \[
- name: punctuation.squarebracket.close.qsharp
diff --git a/test-extension/README.md b/test-extension/README.md
new file mode 100644
index 0000000..6e48da9
--- /dev/null
+++ b/test-extension/README.md
@@ -0,0 +1,38 @@
+# Grammar test extension
+
+A throwaway VS Code extension used to manually test the Q# TextMate grammar in a
+real editor, isolated from the QDK extension (no language server, so no semantic
+highlighting paints over the grammar).
+
+It contributes the `qsharp` language for `.qs` files. Its grammar
+(`qsharp.tmLanguage.json`) is generated into this folder by `npm run build`
+(gitignored) so the manifest can reference a file inside its own folder — VS Code
+warns about grammar paths that escape the extension directory.
+
+## Usage
+
+1. Build the grammar so `grammars/qsharp.tmLanguage.json` is current:
+
+ ```bash
+ npm run build
+ ```
+
+2. Open the **repository root** in VS Code and press **F5** (the
+ `Test grammar (Extension Development Host)` launch config). This opens an
+ Extension Development Host with the official QDK extension disabled and
+ [`sample.qs`](./sample.qs) loaded.
+
+3. Verify tokenization with **`Developer: Inspect Editor Tokens and Scopes`**
+ (Command Palette) — hover a token to see its `source.qsharp` scopes. This is
+ the authoritative check; colors are just the active theme's scope mapping.
+
+4. After editing `src/qsharp.tmLanguage.yml`, re-run `npm run build` and reload
+ the host window (`Cmd/Ctrl+R`) to pick up the new grammar.
+
+## Notes
+
+- The launch config disables `quantum.qsharp-lang-vscode` only inside the
+ development host — your normal editor is untouched.
+- If you prefer not to use F5, symlink this folder into your extensions dir
+ (`ln -s "$PWD/test-extension" ~/.vscode/extensions/qsharp-tmlanguage-test`),
+ disable the QDK extension, and reload.
diff --git a/test-extension/language-configuration.json b/test-extension/language-configuration.json
new file mode 100644
index 0000000..07c64f3
--- /dev/null
+++ b/test-extension/language-configuration.json
@@ -0,0 +1,22 @@
+{
+ "comments": {
+ "lineComment": "//"
+ },
+ "brackets": [
+ ["[", "]"],
+ ["(", ")"],
+ ["{", "}"]
+ ],
+ "autoClosingPairs": [
+ ["[", "]"],
+ ["(", ")"],
+ ["{", "}"],
+ { "open": "\"", "close": "\"", "notIn": ["string"] }
+ ],
+ "surroundingPairs": [
+ ["[", "]"],
+ ["(", ")"],
+ ["{", "}"],
+ ["\"", "\""]
+ ]
+}
diff --git a/test-extension/package.json b/test-extension/package.json
new file mode 100644
index 0000000..8732fe3
--- /dev/null
+++ b/test-extension/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "qsharp-tmlanguage-test-extension",
+ "displayName": "Q# TextMate grammar (dev)",
+ "description": "Loads the locally-built Q# grammar for manual testing in an Extension Development Host.",
+ "publisher": "local",
+ "version": "0.0.0",
+ "private": true,
+ "engines": {
+ "vscode": "^1.75.0"
+ },
+ "contributes": {
+ "languages": [
+ {
+ "id": "qsharp",
+ "aliases": ["Q#", "qsharp"],
+ "extensions": [".qs"],
+ "configuration": "./language-configuration.json"
+ }
+ ],
+ "grammars": [
+ {
+ "language": "qsharp",
+ "scopeName": "source.qsharp",
+ "path": "./qsharp.tmLanguage.json"
+ }
+ ]
+ }
+}
diff --git a/test-extension/sample.qs b/test-extension/sample.qs
new file mode 100644
index 0000000..d024852
--- /dev/null
+++ b/test-extension/sample.qs
@@ -0,0 +1,63 @@
+import Std.Diagnostics.*;
+import Std.Math.PI as Pi;
+
+/// A doc comment describing a struct.
+struct Complex { Re : Double, Im : Double }
+
+@EntryPoint()
+operation Main() : Result[] {
+ // literals: decimal, hex, binary, octal, BigInt, float, separators
+ let ints = [42, 0xFF, 0b1010, 0o52, 1_000];
+ let big = 42L;
+ let approx = 1.0e-3;
+
+ // qubit allocation (single + tuple), gates, measurement, reset
+ use qs = Qubit[3];
+ use (control, target) = (Qubit(), Qubit());
+ for i in 0..Length(qs) - 1 {
+ H(qs[i]);
+ }
+ Controlled X([control], target);
+ Rx(Pi() / 4.0, target);
+
+ // structs: construction, copy-and-update, field access
+ let c = new Complex { Re = 1.0, Im = 0.0 };
+ let c2 = new Complex { ...c, Im = 2.0 };
+ let magnitudeSquared = c2.Re * c2.Re + c2.Im * c2.Im;
+
+ // operators: comparison, bitwise, ternary, ranges
+ let flags = (5 &&& 3) ||| (1 <<< 2);
+ let sign = magnitudeSquared > 0.0 ? One | Zero;
+ let slice = ints[1..2];
+
+ // lambdas, partial application, string interpolation
+ let addOne = x -> x + 1;
+ let applyX = q => X(q);
+ Message($"c2 = {c2.Re} + {c2.Im}i, sign = {sign}");
+
+ // control flow: if / elif / else, within-apply, mutation without `set`
+ mutable counter = 0;
+ counter += 1;
+ if counter == 1 {
+ Message("one");
+ } elif counter == 2 {
+ Message("two");
+ } else {
+ Message("many");
+ }
+
+ within {
+ H(qs[0]);
+ } apply {
+ Z(qs[0]);
+ }
+
+ ResetAll(qs);
+ return MeasureEachZ(qs);
+}
+
+function Sum2<'T : Add>(a : 'T, b : 'T) : 'T {
+ a + b
+}
+
+export Main, Sum2, Complex;