open-standards/wai/wai-quantum-cli/src/lsp.rs
Transaction Science c52c83d2fd wai-quantum: the language service and server for editors, and fmt
The engine gains `engine::editor`, the language service an editor needs,
pure and with no protocol in it:

- diagnostics: the reader's refusal on its line (one mapping function,
  `reader_diagnostic`, where stable codes and exact spans plug in), and
  the critique's smells on theirs, with their kind as the code; a
  generator is read at the given inputs, or 4;
- completion of keywords, the standard gates (with parameter
  placeholders), and the registers, gates, subroutines and variables in
  scope, by what the cursor's place takes; snippets, and sample programs
  in an empty file;
- hover: each standard gate's matrix written exactly, held entry by entry
  to the engine's own matrix under all 46 names the reader accepts; a
  user gate's matrix from the engine's matrices of its gates; each gate's
  cost as the engine counts it (two-qubit gates, T count, depth), at the
  call's angles or at generic ones, said which; a register's size;
- definition, references, highlights and rename over an index of
  declarations and scopes that reads the whole of OpenQASM 3, and a
  half-typed program; symbols, folding (blocks, comments, held blocks);
- a formatter with one layout, refusing a program whose brackets do not
  match, checked to keep every token, and tested stable and
  meaning-preserving (the engine reads the formatted program to the same
  circuit) on a corpus in every part of the language;
- lenses for Run, Plan, Critique, Shortcuts and Receipt.

The command line gains two verbs. `wai-quantum lsp` serves it over
standard input and output, UTF-16 or UTF-8 positions as agreed, the
commands run in-process on a thread of their own and signed with the
user's key; `wai-quantum fmt` formats, `--check`s and `--write`s.
tests/lsp.rs drives the binary through a scripted session from
initialize to exit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 19:51:34 -04:00

642 lines
30 KiB
Rust

//! `wai-quantum lsp`: the engine's language service for editors, as a server
//! of the language server protocol on standard input and output (each message
//! a `Content-Length` header and its JSON).
//!
//! What a program means is the engine's to say: diagnostics are its reader's
//! refusal and its critique's smells, hover shows its matrices and its
//! counts, and the commands (Run, Plan, Critique, Shortcuts, Receipt) run
//! here, through the engine the command line uses, a run signed with this
//! user's key as `wai-quantum run` signs it. Nothing leaves the machine.
//!
//! The service itself is [`wai_quantum::engine::editor`]; this module only
//! reads and writes the protocol, keeps the open documents, and turns byte
//! offsets into the editor's positions (UTF-16 by default, UTF-8 when the
//! editor offers it).
use serde_json::{Value, json};
use std::collections::BTreeMap;
use std::io::{BufRead, Write};
use std::sync::{Arc, Mutex};
use wai_quantum::ed25519_dalek::SigningKey;
use wai_quantum::engine::editor::{self, Analysis, Severity, index::DeclKind};
use wai_quantum::engine::{catalog, critique, job, shortcuts};
use wai_quantum::qasm;
/// What the commands run with: the memory a job may take, and the key a run is signed with.
pub struct Config {
pub memory_gb: u64,
pub key: SigningKey,
}
/// The settings an editor may give, in `initializationOptions` or a
/// configuration change, at the top level or under `wai-quantum`: the values a
/// generator is read at, and the shots a run samples.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Settings {
pub inputs: qasm::Inputs,
pub shots: Option<u64>,
}
impl Settings {
fn read(v: &Value) -> Option<Settings> {
let s = ["wai-quantum", "waiQuantum"].iter().find_map(|k| v.get(*k)).unwrap_or(v);
if !s.is_object() {
return None;
}
let inputs = s.get("inputs").and_then(Value::as_object).map(|o| o.iter().filter_map(|(k, v)| Some((k.clone(), v.as_i64()?))).collect()).unwrap_or_default();
Some(Settings { inputs, shots: s.get("shots").and_then(Value::as_u64) })
}
}
struct Doc {
version: Value,
analysis: Analysis,
}
type Out = Arc<Mutex<Box<dyn Write + Send>>>;
struct Server {
out: Out,
docs: BTreeMap<String, Doc>,
utf8: bool,
snippets: bool,
settings: Settings,
cfg: Arc<Config>,
initialized: bool,
shut_down: bool,
workers: Vec<std::thread::JoinHandle<()>>,
}
/// Writes one message.
fn send(out: &Out, msg: &Value) {
let body = msg.to_string();
if let Ok(mut w) = out.lock() {
let _ = write!(w, "Content-Length: {}\r\n\r\n{body}", body.len()).and_then(|_| w.flush());
}
}
/// Reads one message: `Ok(None)` at the end of the input, `Err` for a body that is not JSON.
fn read_message(r: &mut impl BufRead) -> std::io::Result<Option<Result<Value, String>>> {
let mut len: Option<usize> = None;
loop {
let mut line = String::new();
if r.read_line(&mut line)? == 0 {
return Ok(None);
}
let line = line.trim_end_matches(['\r', '\n']);
if line.is_empty() {
if len.is_some() {
break;
}
continue;
}
if let Some((k, v)) = line.split_once(':')
&& k.trim().eq_ignore_ascii_case("content-length")
{
len = v.trim().parse().ok();
}
}
let mut buf = vec![0u8; len.unwrap_or(0)];
r.read_exact(&mut buf)?;
Ok(Some(serde_json::from_slice(&buf).map_err(|e| e.to_string())))
}
/// The protocol's error codes this server answers with.
const METHOD_NOT_FOUND: i64 = -32601;
const INVALID_PARAMS: i64 = -32602;
const INTERNAL_ERROR: i64 = -32603;
const NOT_INITIALIZED: i64 = -32002;
const REQUEST_FAILED: i64 = -32803;
/// Serves on standard input and output until the editor says `exit`; the
/// process's exit status (0 after `shutdown`, 1 otherwise).
pub fn serve(memory_gb: u64, key: SigningKey) -> i32 {
let stdin = std::io::stdin();
serve_on(stdin.lock(), Box::new(std::io::stdout()), Config { memory_gb, key })
}
/// [`serve`] on any reader and writer.
pub fn serve_on(mut reader: impl BufRead, writer: Box<dyn Write + Send>, cfg: Config) -> i32 {
let mut s = Server {
out: Arc::new(Mutex::new(writer)),
docs: BTreeMap::new(),
utf8: false,
snippets: false,
settings: Settings::default(),
cfg: Arc::new(cfg),
initialized: false,
shut_down: false,
workers: Vec::new(),
};
let code = loop {
let msg = match read_message(&mut reader) {
Ok(Some(Ok(m))) => m,
Ok(Some(Err(e))) => {
send(&s.out, &json!({ "jsonrpc": "2.0", "id": null, "error": { "code": -32700, "message": format!("not JSON: {e}") } }));
continue;
}
Ok(None) | Err(_) => break 1,
};
let method = msg.get("method").and_then(Value::as_str).unwrap_or("").to_string();
if method == "exit" {
break if s.shut_down { 0 } else { 1 };
}
let params = msg.get("params").cloned().unwrap_or(Value::Null);
match msg.get("id").cloned() {
Some(id) => {
let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| s.request(&method, &params, &id)));
match reply {
Ok(Some(Ok(result))) => send(&s.out, &json!({ "jsonrpc": "2.0", "id": id, "result": result })),
Ok(Some(Err((code, message)))) => send(&s.out, &json!({ "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": message } })),
// A command answers from its own thread.
Ok(None) => {}
Err(_) => send(&s.out, &json!({ "jsonrpc": "2.0", "id": id, "error": { "code": INTERNAL_ERROR, "message": format!("{method} failed inside the server") } })),
}
}
None => {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| s.notification(&method, &params)));
}
}
};
for w in s.workers.drain(..) {
let _ = w.join();
}
code
}
type Reply = Option<Result<Value, (i64, String)>>;
impl Server {
fn capabilities(&self) -> Value {
json!({
"positionEncoding": if self.utf8 { "utf-8" } else { "utf-16" },
"textDocumentSync": { "openClose": true, "change": 1, "save": false },
"completionProvider": { "resolveProvider": false },
"hoverProvider": true,
"definitionProvider": true,
"referencesProvider": true,
"documentHighlightProvider": true,
"renameProvider": { "prepareProvider": true },
"documentSymbolProvider": true,
"foldingRangeProvider": true,
"documentFormattingProvider": true,
"codeLensProvider": { "resolveProvider": false },
"executeCommandProvider": { "commands": editor::COMMANDS.iter().map(|c| c.0).collect::<Vec<_>>() },
})
}
fn request(&mut self, method: &str, params: &Value, id: &Value) -> Reply {
if method == "initialize" {
let encodings = params.pointer("/capabilities/general/positionEncodings").and_then(Value::as_array).cloned().unwrap_or_default();
self.utf8 = encodings.iter().any(|e| e == "utf-8");
self.snippets = params.pointer("/capabilities/textDocument/completion/completionItem/snippetSupport") == Some(&Value::Bool(true));
if let Some(s) = params.get("initializationOptions").and_then(Settings::read) {
self.settings = s;
}
self.initialized = true;
return Some(Ok(json!({ "capabilities": self.capabilities(), "serverInfo": { "name": "wai-quantum", "version": env!("CARGO_PKG_VERSION") } })));
}
if !self.initialized {
return Some(Err((NOT_INITIALIZED, "the server has not been initialized".into())));
}
if method == "shutdown" {
self.shut_down = true;
return Some(Ok(Value::Null));
}
if method == "workspace/executeCommand" {
return self.execute(params, id);
}
let uri = params.pointer("/textDocument/uri").and_then(Value::as_str).unwrap_or("").to_string();
let Some(doc) = self.docs.get(&uri) else {
return Some(match method {
m if m.starts_with("textDocument/") => Err((INVALID_PARAMS, format!("{uri} is not open"))),
_ => Err((METHOD_NOT_FOUND, format!("no method {method}"))),
});
};
let a = &doc.analysis;
let at = || params.get("position").map(|p| self.offset(a, p)).unwrap_or(0);
let loc = |s: editor::index::Span| json!({ "uri": uri, "range": self.range(a, s) });
Some(Ok(match method {
"textDocument/completion" => {
let items: Vec<Value> = editor::complete(a, at()).iter().map(|i| self.item(i)).collect();
json!({ "isIncomplete": false, "items": items })
}
"textDocument/hover" => match editor::hover(a, at()) {
Some(h) => json!({ "contents": { "kind": "markdown", "value": h.markdown }, "range": self.range(a, h.span) }),
None => Value::Null,
},
"textDocument/definition" => editor::definition(a, at()).map(loc).unwrap_or(Value::Null),
"textDocument/references" => {
let with_decl = params.pointer("/context/includeDeclaration") != Some(&Value::Bool(false));
Value::Array(editor::references(a, at(), with_decl).into_iter().map(loc).collect())
}
"textDocument/documentHighlight" => {
let spans = editor::references(a, at(), true);
Value::Array(spans.iter().enumerate().map(|(i, &s)| json!({ "range": self.range(a, s), "kind": if i == 0 { 3 } else { 2 } })).collect())
}
"textDocument/prepareRename" => match editor::prepare_rename(a, at()) {
Ok((span, name)) => json!({ "range": self.range(a, span), "placeholder": name }),
Err(e) => return Some(Err((REQUEST_FAILED, e))),
},
"textDocument/rename" => {
let new = params.get("newName").and_then(Value::as_str).unwrap_or("");
match editor::rename(a, at(), new) {
Ok(spans) => {
let edits: Vec<Value> = spans.iter().map(|&s| json!({ "range": self.range(a, s), "newText": new })).collect();
json!({ "changes": { uri.clone(): edits } })
}
Err(e) => return Some(Err((REQUEST_FAILED, e))),
}
}
"textDocument/documentSymbol" => Value::Array(editor::symbols(a).iter().map(|s| self.symbol(a, s)).collect()),
"textDocument/foldingRange" => Value::Array(
editor::folding(a)
.iter()
.map(|f| {
let mut v = json!({ "startLine": f.start, "endLine": f.end });
if let Some(k) = f.kind {
v["kind"] = json!(k);
}
v
})
.collect(),
),
"textDocument/formatting" => match editor::format(&a.src) {
Ok(f) if f == a.src => json!([]),
Ok(f) => json!([{ "range": self.range(a, (0, a.src.len())), "newText": f }]),
Err(e) => return Some(Err((REQUEST_FAILED, e))),
},
"textDocument/codeLens" => Value::Array(
editor::lenses(a).iter().map(|l| json!({ "range": self.range(a, l.span), "command": { "title": l.title, "command": l.command, "arguments": [uri] } })).collect(),
),
_ => return Some(Err((METHOD_NOT_FOUND, format!("no method {method}")))),
}))
}
fn notification(&mut self, method: &str, params: &Value) {
let uri = params.pointer("/textDocument/uri").and_then(Value::as_str).unwrap_or("").to_string();
match method {
"textDocument/didOpen" => {
let text = params.pointer("/textDocument/text").and_then(Value::as_str).unwrap_or("");
let version = params.pointer("/textDocument/version").cloned().unwrap_or(Value::Null);
self.docs.insert(uri.clone(), Doc { version, analysis: Analysis::new(text) });
self.publish(&uri);
}
"textDocument/didChange" => {
let Some(doc) = self.docs.get_mut(&uri) else { return };
let mut text = doc.analysis.src.clone();
for c in params.get("contentChanges").and_then(Value::as_array).into_iter().flatten() {
let new = c.get("text").and_then(Value::as_str).unwrap_or("");
match c.get("range") {
Some(r) => {
let a = Analysis::new(&text);
let (s, e) = (offset_in(&a, &r["start"], self.utf8), offset_in(&a, &r["end"], self.utf8));
text.replace_range(s.min(e)..e.max(s), new);
}
None => text = new.to_string(),
}
}
doc.version = params.pointer("/textDocument/version").cloned().unwrap_or(Value::Null);
doc.analysis = Analysis::new(&text);
self.publish(&uri);
}
"textDocument/didClose" => {
self.docs.remove(&uri);
send(&self.out, &json!({ "jsonrpc": "2.0", "method": "textDocument/publishDiagnostics", "params": { "uri": uri, "diagnostics": [] } }));
}
"workspace/didChangeConfiguration" => {
if let Some(s) = params.get("settings").and_then(Settings::read) {
self.settings = s;
let uris: Vec<String> = self.docs.keys().cloned().collect();
for u in uris {
self.publish(&u);
}
}
}
_ => {}
}
}
fn publish(&self, uri: &str) {
let Some(doc) = self.docs.get(uri) else { return };
let a = &doc.analysis;
let diags: Vec<Value> = editor::diagnostics(a, &self.settings.inputs)
.iter()
.map(|d| {
let severity = match d.severity {
Severity::Error => 1,
Severity::Warning => 2,
Severity::Information => 3,
Severity::Hint => 4,
};
let mut v = json!({ "range": self.range(a, d.span), "severity": severity, "source": "wai-quantum", "message": d.message });
if let Some(c) = &d.code {
v["code"] = json!(c);
}
v
})
.collect();
send(&self.out, &json!({ "jsonrpc": "2.0", "method": "textDocument/publishDiagnostics", "params": { "uri": uri, "version": doc.version, "diagnostics": diags } }));
}
fn offset(&self, a: &Analysis, p: &Value) -> usize {
offset_in(a, p, self.utf8)
}
fn position(&self, a: &Analysis, off: usize) -> Value {
position_in(a, off, self.utf8)
}
fn range(&self, a: &Analysis, s: editor::index::Span) -> Value {
json!({ "start": self.position(a, s.0), "end": self.position(a, s.1) })
}
fn item(&self, i: &editor::Item) -> Value {
use editor::ItemKind::*;
let kind = match i.kind {
Keyword => 14,
Gate | Subroutine => 3,
Register | Variable => 6,
Constant => 21,
Function => 3,
Parameter => 25,
Snippet => 15,
};
let mut v = json!({ "label": i.label, "kind": kind, "detail": i.detail, "sortText": i.sort });
if let Some(d) = &i.doc {
v["documentation"] = json!({ "kind": "markdown", "value": d });
}
match (&i.snippet, &i.plain, self.snippets) {
(Some(s), _, true) => {
v["insertText"] = json!(s);
v["insertTextFormat"] = json!(2);
}
(_, Some(p), _) => v["insertText"] = json!(p),
_ => {}
}
v
}
fn symbol(&self, a: &Analysis, s: &editor::Symbol) -> Value {
let kind = match s.kind {
DeclKind::QubitRegister | DeclKind::BitRegister => 18,
DeclKind::Gate | DeclKind::Subroutine | DeclKind::Extern => 12,
DeclKind::Constant => 14,
DeclKind::GateParameter | DeclKind::Argument => 26,
_ => 13,
};
json!({
"name": s.name,
"detail": s.detail,
"kind": kind,
"range": self.range(a, s.span),
"selectionRange": self.range(a, s.name_span),
"children": s.children.iter().map(|c| self.symbol(a, c)).collect::<Vec<_>>(),
})
}
/// A command: run on a thread of its own, so the editor's other requests
/// are answered meanwhile; the answer is sent from there.
fn execute(&mut self, params: &Value, id: &Value) -> Reply {
let command = params.get("command").and_then(Value::as_str).unwrap_or("").to_string();
if !editor::COMMANDS.iter().any(|c| c.0 == command) {
return Some(Err((INVALID_PARAMS, format!("no command {command}; the commands are {}", editor::COMMANDS.iter().map(|c| c.0).collect::<Vec<_>>().join(", ")))));
}
let arg = params.pointer("/arguments/0").cloned().unwrap_or(Value::Null);
let uri = arg.as_str().or_else(|| arg.get("uri").and_then(Value::as_str)).unwrap_or("").to_string();
let Some(doc) = self.docs.get(&uri) else { return Some(Err((INVALID_PARAMS, format!("{uri} is not open")))) };
let src = doc.analysis.src.clone();
let (settings, cfg, out, id) = (self.settings.clone(), self.cfg.clone(), self.out.clone(), id.clone());
let work = move || {
let msg = match run_command(&command, &src, &settings, &cfg) {
Ok(r) => {
let level = if r["ok"] == true { 3 } else { 1 };
send(&out, &json!({ "jsonrpc": "2.0", "method": "window/logMessage", "params": { "type": level, "message": r["text"] } }));
send(&out, &json!({ "jsonrpc": "2.0", "method": "window/showMessage", "params": { "type": level, "message": r["summary"] } }));
json!({ "jsonrpc": "2.0", "id": id, "result": r })
}
Err(e) => json!({ "jsonrpc": "2.0", "id": id, "error": { "code": REQUEST_FAILED, "message": e } }),
};
send(&out, &msg);
};
type Work = Box<dyn FnOnce() + Send>;
let slot: Arc<Mutex<Option<Work>>> = Arc::new(Mutex::new(Some(Box::new(work))));
let theirs = slot.clone();
let spawned = std::thread::Builder::new().name("wai-quantum-command".into()).spawn(move || {
if let Some(w) = theirs.lock().ok().and_then(|mut g| g.take()) {
w()
}
});
match spawned {
Ok(h) => self.workers.push(h),
// Where there are no threads (a WASI build), the command runs here.
Err(_) => {
if let Some(w) = slot.lock().ok().and_then(|mut g| g.take()) {
w()
}
}
}
self.workers.retain(|h| !h.is_finished());
None
}
}
/// The byte offset of an editor's position.
fn offset_in(a: &Analysis, p: &Value, utf8: bool) -> usize {
let line = p.get("line").and_then(Value::as_u64).unwrap_or(0) as usize;
let ch = p.get("character").and_then(Value::as_u64).unwrap_or(0) as usize;
let Some(start) = a.lines.start(line) else { return a.src.len() };
let end = a.lines.start(line + 1).map(|e| e - 1).unwrap_or(a.src.len());
let text = &a.src[start..end];
let mut units = 0usize;
for (i, c) in text.char_indices() {
if units >= ch {
return start + i;
}
units += if utf8 { c.len_utf8() } else { c.len_utf16() };
}
end
}
/// An editor's position of a byte offset.
fn position_in(a: &Analysis, off: usize, utf8: bool) -> Value {
let off = off.min(a.src.len());
let line = a.line(off);
let start = a.lines.start(line).unwrap_or(0);
let text = &a.src[start..off];
let ch: usize = if utf8 { text.len() } else { text.chars().map(char::len_utf16).sum() };
json!({ "line": line, "character": ch })
}
/// A command's answer: `ok`, a one-line `summary`, the `text` a reader takes
/// in, and the engine's `json`.
fn run_command(command: &str, src: &str, settings: &Settings, cfg: &Config) -> Result<Value, String> {
let inputs = editor::read_inputs(src, &settings.inputs);
let at = if inputs.is_empty() { String::new() } else { format!(" at {}", editor::inputs_text(&inputs)) };
// A generator runs as the circuit it writes out at its inputs.
let program = || if inputs.is_empty() { Ok(src.to_string()) } else { qasm::instantiate(src, &inputs) };
let request = |qasm: String| {
let mut r = json!({ "qasm": qasm });
if let Some(s) = settings.shots {
r["shots"] = json!(s);
}
r
};
let answer = |ok: bool, summary: String, text: String, json: Value| json!({ "command": command, "ok": ok, "summary": summary, "text": text, "json": json });
match command {
"wai-quantum.run" | "wai-quantum.receipt" => {
let body = crate::run_here(&request(program()?), cfg.memory_gb, &cfg.key)?;
let (verified, key) = job::verify(&body)?;
let signer = key.as_str().and_then(job::did_of).unwrap_or_default();
let method = body["method"].as_str().unwrap_or("").to_string();
if command == "wai-quantum.run" {
let summary = format!("Run{at}: {method}, {}; the receipt {}", body["result"]["accuracy"]["class"].as_str().unwrap_or(""), if verified { "verifies" } else { "DOES NOT VERIFY" });
return Ok(answer(verified, summary, job_text(&body, verified, &signer), body));
}
let receipt = &body["result"]["receipt"];
let text = format!(
"receipt {}\nsigned by {signer}\nbinds the request and the result, as given below\nmethod {method}\n\n{}\n",
if verified { "verifies" } else { "DOES NOT VERIFY" },
serde_json::to_string_pretty(&body).unwrap_or_default()
);
let summary = format!("Receipt{at}: {} by {signer}, digest {}", if verified { "verifies, signed" } else { "DOES NOT VERIFY, signed" }, receipt["digest"].as_str().unwrap_or(""));
Ok(answer(verified, summary, text, body))
}
"wai-quantum.plan" => {
let p = catalog::plan(&request(program()?), cfg.memory_gb << 30)?;
let text = format!("method {}\nwhy {}\nqubits {}\nmemory {:.1} MiB at most\nwork {}\n", p.method, p.why, p.qubits, p.memory_bytes as f64 / (1u64 << 20) as f64, p.work);
let summary = format!("Plan{at}: {} — {}", p.method, p.why);
Ok(answer(true, summary, text, serde_json::to_value(&p).unwrap_or(Value::Null)))
}
"wai-quantum.critique" => {
let c = if inputs.is_empty() { critique::critique(src) } else { critique::critique_with(src, &inputs) };
if c["ok"] != true {
return Err(c["error"].as_str().unwrap_or("the program does not read").to_string());
}
let n = |k: &str| c[k].as_u64().unwrap_or(0);
let summary = format!("Critique{at}: {} gates, {} two-qubit, depth {}, T count {}, {} smell{}", n("gates"), n("two_qubit"), n("depth"), n("t_count"), c["smells"].as_array().map_or(0, Vec::len), if c["smells"].as_array().map_or(0, Vec::len) == 1 { "" } else { "s" });
Ok(answer(true, summary, critique_text(&c), c))
}
"wai-quantum.shortcuts" => {
let v = shortcuts::shortcuts(&program()?, false);
if v["ok"] != true {
return Err(v["error"].as_str().unwrap_or("the program does not read").to_string());
}
let summary = format!("Shortcuts{at}: {}", v["summary"].as_str().unwrap_or(""));
Ok(answer(true, summary, shortcuts_text(&v), v))
}
_ => Err(format!("no command {command}")),
}
}
fn s(v: &Value) -> String {
v.as_str().map(str::to_string).unwrap_or_else(|| v.to_string())
}
/// A job as a reader takes it in.
fn job_text(body: &Value, verified: bool, signer: &str) -> String {
let r = &body["result"];
let mut out = format!("method {} — {}\n", s(&body["method"]), s(&body["why"]));
if let Some(acc) = r.get("accuracy") {
out.push_str(&format!("accuracy {}{}\n", s(&acc["class"]), acc.get("note").map(|n| format!(" ({})", s(n))).unwrap_or_default()));
}
out.push_str(&format!("circuit {} qubits, {} gates\n", r["qubits"], r["gates"]));
if let Some(w) = r.get("warning") {
out.push_str(&format!("warning {}\n", s(w)));
}
if let Some(counts) = r.get("counts").and_then(Value::as_object) {
let total: u64 = counts.values().filter_map(Value::as_u64).sum::<u64>() + r["omitted_shots"].as_u64().unwrap_or(0);
out.push_str(&format!("counts {total} shots, {} outcomes\n", r["distinct_outcomes"].as_u64().unwrap_or(counts.len() as u64)));
let mut v: Vec<(&String, u64)> = counts.iter().map(|(k, c)| (k, c.as_u64().unwrap_or(0))).collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
for (k, c) in v.iter().take(16) {
out.push_str(&format!(" {k} {c:>8} {:.4}\n", *c as f64 / total.max(1) as f64));
}
if v.len() > 16 {
out.push_str(&format!(" … {} more\n", v.len() - 16));
}
}
for e in r.get("expectations").and_then(Value::as_array).into_iter().flatten() {
out.push_str(&format!("⟨{}⟩ {}\n", s(&e["observable"]), s(&e["value"])));
}
out.push_str(&format!("receipt {}, signed by {signer}\n", if verified { "verifies" } else { "DOES NOT VERIFY" }));
out
}
/// A critique as a reader takes it in.
fn critique_text(c: &Value) -> String {
let n = |k: &str| c[k].as_u64().unwrap_or(0);
let mut out = format!(
"{} qubits, {} gates ({} one-qubit, {} two-qubit, {} three-qubit), depth {}, two-qubit depth {}\n{} T gates; {}; {} qubits measured\n",
n("qubits"),
n("gates"),
n("one_qubit"),
n("two_qubit"),
n("three_qubit"),
n("depth"),
n("two_qubit_depth"),
n("t_count"),
if c["clifford"] == true { "a Clifford circuit".to_string() } else { format!("{} gates off the Clifford angles", n("non_clifford")) },
n("measured")
);
let f = &c["fidelity_model"];
out.push_str(&format!("fidelity {:.4}, modelled: {}\n", f["estimate"].as_f64().unwrap_or(f64::NAN), s(&f["note"])));
for sm in c["smells"].as_array().into_iter().flatten() {
out.push_str(&format!("{}: {}{}\n", s(&sm["kind"]), s(&sm["message"]), if sm["fixable"] == true { " (improve fixes it)" } else { "" }));
}
out
}
/// The classical routes as a reader takes them in.
fn shortcuts_text(v: &Value) -> String {
let mut out = format!("{}\n\n", s(&v["summary"]));
for r in v["routes"].as_array().into_iter().flatten() {
let applies = r["applies"] == true;
let cost = match (applies, r.get("log2_work").and_then(Value::as_f64)) {
(true, Some(w)) => format!("2^{w:.2} operations"),
(true, None) => "—".into(),
_ => "no".into(),
};
out.push_str(&format!("{:<15} {cost:<22} {}\n", s(&r["route"]), s(&r["why"])));
}
out.push_str(&format!("\n{}\n", s(&v["note"])));
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn positions_count_utf16_units_or_bytes_as_agreed() {
let a = Analysis::new("rz(π) q;\nx q;\n");
// After `π`: four UTF-16 units in, five bytes.
let at = a.src.find(')').unwrap();
assert_eq!(position_in(&a, at, false), json!({ "line": 0, "character": 4 }));
assert_eq!(position_in(&a, at, true), json!({ "line": 0, "character": 5 }));
assert_eq!(offset_in(&a, &json!({ "line": 0, "character": 4 }), false), at);
assert_eq!(offset_in(&a, &json!({ "line": 0, "character": 5 }), true), at);
assert_eq!(offset_in(&a, &json!({ "line": 1, "character": 0 }), false), a.src.find("x q").unwrap());
// Past a line's end: its end; past the last line: the end.
assert_eq!(offset_in(&a, &json!({ "line": 1, "character": 99 }), false), a.src.len() - 1);
assert_eq!(offset_in(&a, &json!({ "line": 9, "character": 0 }), false), a.src.len());
}
#[test]
fn a_message_is_read_by_its_length() {
let body = r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#;
let text = format!("Content-Length: {}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{body}Content-Length: 3\r\n\r\n{{x}}", body.len());
let mut r = std::io::Cursor::new(text.into_bytes());
assert_eq!(read_message(&mut r).unwrap().unwrap().unwrap()["id"], 1);
assert!(read_message(&mut r).unwrap().unwrap().is_err());
assert!(read_message(&mut r).unwrap().is_none());
}
#[test]
fn settings_are_read_at_the_top_or_under_the_server_s_name() {
let want = Settings { inputs: [("n".to_string(), 6)].into_iter().collect(), shots: Some(100) };
assert_eq!(Settings::read(&json!({ "inputs": { "n": 6 }, "shots": 100 })), Some(want.clone()));
assert_eq!(Settings::read(&json!({ "wai-quantum": { "inputs": { "n": 6 }, "shots": 100 } })), Some(want));
assert_eq!(Settings::read(&json!(null)), None);
}
}