Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ its frontend, compiler, reconstruction, or Workshop integration internally.

## Status

- Protocol versions: **1.0**, additive **1.1** file-entry loading, and additive **1.2** directory targets (specified in [`spec/lpp-v1.md`](spec/lpp-v1.md)).
- Protocol versions: **1.0**, additive **1.1** file-entry loading, additive **1.2** directory targets, and additive **1.3** source identity for entry-based compilation (specified in [`spec/lpp-v1.md`](spec/lpp-v1.md)).
- Repository state: initial published contract and conformance suite.
- Wright is a client/consumer of the protocol; LPP is not a dependency from the
language implementation back into Wright tooling internals.
Expand Down
4 changes: 2 additions & 2 deletions conformance/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LPP v1 Conformance Suite

This directory contains the test suite and fixtures for the Language Provider Protocol v1 wire contract, including the LPP 1.1 file-entry and LPP 1.2 directory-target project-loading revisions (see [`../spec/lpp-v1.md`](../spec/lpp-v1.md)).
This directory contains the test suite and fixtures for the Language Provider Protocol v1 wire contract, including the LPP 1.1 file-entry, LPP 1.2 directory-target project-loading, and LPP 1.3 source-identity revisions (see [`../spec/lpp-v1.md`](../spec/lpp-v1.md)).

## Layout

Expand All @@ -10,7 +10,7 @@ mock-provider/ Reference provider for the "x-demo-lang" equation DSL (Rus
runner/ Conformance runner that replays fixtures against any provider binary
```

* **Fixtures** (`fixtures/v1/`): one JSON file per scenario. Each scenario defines a session with request/response steps, optional CLI flags, and the expected exit code. Responses are compared after JSON parsing so key order does not matter. The directory contains LPP 1.0, LPP 1.1, and LPP 1.2 scenarios.
* **Fixtures** (`fixtures/v1/`): one JSON file per scenario. Each scenario defines a session with request/response steps, optional CLI flags, and the expected exit code. Responses are compared after JSON parsing so key order does not matter. The directory contains LPP 1.0, LPP 1.1, LPP 1.2, and LPP 1.3 scenarios.
* **Mock provider** (`mock-provider/`): a small Rust binary implementing the full LPP v1 surface for a demonstration language distinct from OPY and OSTW. It runs over stdio so clients (like the Wright LPP client in wrightkit/wright#142) can test against it directly.
* **Runner** (`runner/`): spawns a fresh provider process per scenario, feeds requests over stdin, validates stdout responses against expectations, and checks the process exit code.

Expand Down
16 changes: 16 additions & 0 deletions conformance/fixtures/v1/43-project-loading-source-identity.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "project-loading-source-identity",
"description": "LPP 1.3 advertises source identity and returns it for an owner-selected directory compile target.",
"scope": "semantics",
"providerArgs": ["--protocol-version", "1.3"],
"projectFiles": {
"entry.xdl": "puzzle entry {\n target = 2\n start = 1\n ops {\n plus1: x => x + 1\n }\n solution = [ plus1 ]\n}\n",
"support.xdl": "puzzle support {\n target = 2\n start = 1\n ops {\n plus1: x => x + 1\n }\n solution = [ plus1 ]\n}\n"
},
"steps": [
{"request":{"jsonrpc":"2.0","id":1,"method":"lpp/initialize","params":{"protocolVersion":"1.3"}},"expectResponse":{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"1.3","serverInfo":{"name":"lpp-mock-provider","version":"0.1.0"},"languages":[{"id":"x-demo-lang","extensions":["xdl"]}],"capabilities":{"check":true,"compile":true,"reconstruct":true,"symbols":true,"definition":true,"references":true,"rename":true,"editValidation":true,"projectLoading":true,"sourceIdentity":true}}}},
{"request":{"jsonrpc":"2.0","id":2,"method":"lpp/compile","params":{"entry":{"uri":"${PROJECT_URI}","languageId":"x-demo-lang","version":7,"kind":"directory"}}},"expectResponse":{"jsonrpc":"2.0","id":2,"result":{"diagnostics":[{"uri":"${PROJECT_URI}/entry.xdl","version":7,"diagnostics":[]},{"uri":"${PROJECT_URI}/support.xdl","version":7,"diagnostics":[]}],"sourceIdentity":"a92a4daf6631cbb574095cef0296d3d8217459a48e7899f98d832bb63f510822","artifact":{"format":"x-demo/puzzle-eval-v1","content":"{\"name\":\"entry\",\"ops\":[{\"arg\":1,\"name\":\"plus1\",\"op\":\"+\"}],\"solution\":[\"plus1\"],\"start\":1,\"target\":2,\"value\":2}"}}}},
{"request":{"jsonrpc":"2.0","id":3,"method":"lpp/shutdown","params":{}},"expectResponse":{"jsonrpc":"2.0","id":3,"result":null}}
],
"expectExitCode": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "project-loading-source-identity-unavailable",
"description": "An LPP 1.3 provider may decline source identity independently; entry compilation remains available without the optional field.",
"scope": "protocol",
"providerArgs": ["--protocol-version", "1.3", "--without", "sourceIdentity"],
"projectFiles": {
"entry.xdl": "puzzle entry {\n target = 2\n start = 1\n ops {\n plus1: x => x + 1\n }\n solution = [ plus1 ]\n}\n",
"support.xdl": "puzzle support {\n target = 2\n start = 1\n ops {\n plus1: x => x + 1\n }\n solution = [ plus1 ]\n}\n"
},
"steps": [
{"request":{"jsonrpc":"2.0","id":1,"method":"lpp/initialize","params":{"protocolVersion":"1.3"}},"expectResponse":{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"1.3","serverInfo":{"name":"lpp-mock-provider","version":"0.1.0"},"languages":[{"id":"x-demo-lang","extensions":["xdl"]}],"capabilities":{"check":true,"compile":true,"reconstruct":true,"symbols":true,"definition":true,"references":true,"rename":true,"editValidation":true,"projectLoading":true,"sourceIdentity":false}}}},
{"request":{"jsonrpc":"2.0","id":2,"method":"lpp/compile","params":{"entry":{"uri":"${PROJECT_URI}","languageId":"x-demo-lang","version":7,"kind":"directory"}}},"expectResponse":{"jsonrpc":"2.0","id":2,"result":{"diagnostics":[{"uri":"${PROJECT_URI}/entry.xdl","version":7,"diagnostics":[]},{"uri":"${PROJECT_URI}/support.xdl","version":7,"diagnostics":[]}],"artifact":{"format":"x-demo/puzzle-eval-v1","content":"{\"name\":\"entry\",\"ops\":[{\"arg\":1,\"name\":\"plus1\",\"op\":\"+\"}],\"solution\":[\"plus1\"],\"start\":1,\"target\":2,\"value\":2}"}}}},
{"request":{"jsonrpc":"2.0","id":3,"method":"lpp/shutdown","params":{}},"expectResponse":{"jsonrpc":"2.0","id":3,"result":null}}
],
"expectExitCode": 0
}
1 change: 1 addition & 0 deletions conformance/mock-provider/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ description = "LPP v1 conformance mock provider for the x-demo-lang puzzle/equat
publish = false

[dependencies]
sha2 = "0.10"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

Expand Down
42 changes: 36 additions & 6 deletions conformance/mock-provider/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};

use serde::Deserialize;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};

use puzzle::{
ARTIFACT_FORMAT, KIND_OP, KIND_PUZZLE, ParseOutput, Range, SourceText, compile_artifact,
Expand All @@ -15,6 +16,7 @@ use puzzle::{
const DEFAULT_PROTOCOL_VERSION: &str = "1.0";
const PROTOCOL_VERSION_1_1: &str = "1.1";
const PROTOCOL_VERSION_1_2: &str = "1.2";
const PROTOCOL_VERSION_1_3: &str = "1.3";
const SERVER_NAME: &str = "lpp-mock-provider";
const LANGUAGE_ID: &str = "x-demo-lang";
const LANGUAGE_EXTENSIONS: [&str; 1] = ["xdl"];
Expand All @@ -30,6 +32,7 @@ struct Capabilities {
rename: bool,
edit_validation: bool,
project_loading: bool,
source_identity: bool,
}

impl Capabilities {
Expand All @@ -44,6 +47,7 @@ impl Capabilities {
rename: true,
edit_validation: true,
project_loading: true,
source_identity: true,
}
}

Expand All @@ -58,6 +62,7 @@ impl Capabilities {
"rename" => &mut self.rename,
"editValidation" => &mut self.edit_validation,
"projectLoading" => &mut self.project_loading,
"sourceIdentity" => &mut self.source_identity,
_ => return false,
};
*field = false;
Expand All @@ -75,6 +80,7 @@ impl Capabilities {
"rename" => self.rename,
"editValidation" => self.edit_validation,
"projectLoading" => self.project_loading,
"sourceIdentity" => self.source_identity,
_ => false,
}
}
Expand All @@ -92,10 +98,13 @@ impl Capabilities {
});
if matches!(
protocol_version,
PROTOCOL_VERSION_1_1 | PROTOCOL_VERSION_1_2
PROTOCOL_VERSION_1_1 | PROTOCOL_VERSION_1_2 | PROTOCOL_VERSION_1_3
) {
capabilities["projectLoading"] = json!(self.project_loading);
}
if protocol_version == PROTOCOL_VERSION_1_3 {
capabilities["sourceIdentity"] = json!(self.source_identity);
}
capabilities
}
}
Expand Down Expand Up @@ -290,6 +299,7 @@ fn parse_args() -> (String, Capabilities) {
if version != DEFAULT_PROTOCOL_VERSION
&& version != PROTOCOL_VERSION_1_1
&& version != PROTOCOL_VERSION_1_2
&& version != PROTOCOL_VERSION_1_3
{
eprintln!("lpp-mock-provider: unsupported protocol version '{version}'");
std::process::exit(2);
Expand Down Expand Up @@ -488,7 +498,7 @@ impl Server {
));
}
let doc = match entry_uri {
Some(uri) => documents_set.get(&uri).expect("loaded entry is present"),
Some(ref uri) => documents_set.get(uri).expect("loaded entry is present"),
None => documents_set.values().next().expect("len == 1"),
};
check_document(doc)?;
Expand All @@ -515,10 +525,23 @@ impl Server {
.expect("artifact serializes");
json!({ "format": ARTIFACT_FORMAT, "content": content })
};
Ok(json!({
let source_identity = entry_uri.as_ref().map(|uri| {
let mut hasher = Sha256::new();
hasher.update(documents_set[uri].text.as_bytes());
format!("{:x}", hasher.finalize())
});
let mut result = json!({
"diagnostics": diagnostics,
"artifact": artifact,
}))
});
if self.protocol_version.as_deref() == Some(PROTOCOL_VERSION_1_3)
&& self.caps.source_identity
{
if let Some(source_identity) = source_identity {
result["sourceIdentity"] = Value::String(source_identity);
}
}
Ok(result)
}

fn reconstruct(&self, params: Value) -> Result<Value, HandlerError> {
Expand Down Expand Up @@ -854,7 +877,9 @@ fn documents_for_request(
(None, Some(entry)) => {
if !matches!(
server.protocol_version.as_deref(),
Some(PROTOCOL_VERSION_1_1) | Some(PROTOCOL_VERSION_1_2)
Some(PROTOCOL_VERSION_1_1)
| Some(PROTOCOL_VERSION_1_2)
| Some(PROTOCOL_VERSION_1_3)
) {
return Err(HandlerError::Std(-32602, "Invalid params"));
}
Expand Down Expand Up @@ -887,7 +912,12 @@ fn project_target_kind(
) -> Result<ProjectTargetKind, HandlerError> {
match entry.kind.as_deref() {
None | Some("file") => Ok(ProjectTargetKind::File),
Some("directory") if protocol_version == PROTOCOL_VERSION_1_2 => {
Some("directory")
if matches!(
protocol_version,
PROTOCOL_VERSION_1_2 | PROTOCOL_VERSION_1_3
) =>
{
Ok(ProjectTargetKind::Directory)
}
Some("directory") => Err(HandlerError::Lpp(
Expand Down
Loading