implement playground

This commit is contained in:
sevki 2024-05-30 12:33:07 +01:00
parent 97843e9e66
commit c19f44d7e4
79 changed files with 10153 additions and 1180 deletions

View file

@ -4,5 +4,12 @@ default = "oksoftware"
[registries.oksoftware]
index = "https://ok.software/ok/_cargo-index.git" # Git
[registries.crates-io]
index = "https://github.com/rust-lang/crates.io-index"
[net]
git-fetch-with-cli = true
git-fetch-with-cli = true
[build]
rustflags = "--cfg=web_sys_unstable_apis"

View file

@ -0,0 +1,20 @@
{
"image": "gcr.io/crosvm-infra/crosvm_dev:latest",
"customizations": {
"vscode": {
"extensions": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"esbenp.prettier-vscode",
"ms-python.vscode-pylance",
"foxundermoon.shell-format",
"timonwong.shellcheck"
]
}
},
"runArgs": [
// Allow a higher PID limit since we launch a lot of test processes.
"--pids-limit=4096"
],
"updateContentCommand": "git config --global --add safe.directory '*' && git submodule update --init"
}

View file

@ -0,0 +1,27 @@
{
"image": "gcr.io/crosvm-infra/crosvm_dev:latest",
"customizations": {
"vscode": {
"extensions": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"esbenp.prettier-vscode",
"ms-python.vscode-pylance",
"foxundermoon.shell-format",
"timonwong.shellcheck",
"vadimcn.vscode-lldb",
"GitHub.copilot",
"llvm-vs-code-extensions.vscode-clangd",
"aiqubit.claude"
]
}
},
"runArgs": [
// Allow access to the kvm device so we can run VMs for testing
"--device=/dev/kvm",
"--group-add=kvm",
// Allow a higher PID limit since we launch a lot of test processes.
"--pids-limit=4096"
],
"updateContentCommand": "git config --global --add safe.directory '*' && git submodule update --init"
}

View file

@ -11,18 +11,28 @@ env:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
targets: [wasm32-unknown-unknown, x86_64-unknown-linux-gnu]
packages: [srclang, src-lsp-server, src-lsp-browser]
toolchains: [stable, nightly]
container: rust:latest
container: gcr.io/crosvm-infra/crosvm_dev:latest
steps:
- name: Modify hosts file
run: |
echo "66.241.125.220 ok.software" >> /etc/hosts
- name: reinstall rustup
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: rustup update
run: |
rustup toolchain install ${{ matrix.toolchains }}
rustup target add ${{ matrix.targets }}
- name: Build ${{ matrix.packages }} for ${{ matrix.targets }}
run: cargo build --verbose --target ${{ matrix.targets }} -p ${{ matrix.packages }}
- name: Test ${{ matrix.packages }} for ${{ matrix.targets }}
run: cargo test --verbose --target ${{ matrix.targets }} -p ${{ matrix.packages }}

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
book
target
node_modules

9
.rust-toolchain.toml Normal file
View file

@ -0,0 +1,9 @@
[toolchain]
channel = "1.78"
components = ["rust-src", "rustc-dev", "llvm-tools-preview"]
targets = [
"wasm32-unknown-unknown",
"x86_64-unknown-linux-musl",
"x86_64-unknown-linux-gnu",
]

1197
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,12 @@ name = "srclang"
version = "0.1.0"
edition = "2021"
[workspace]
members = [ "crates/src-collections",
"crates/src-lsp-browser",
"crates/src-lsp-server",
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["lalrpop"]
@ -13,19 +19,16 @@ anyhow = "1.0.45"
phf_codegen = "0.10"
tiny-keccak = { version = "2", features = ["sha3"] }
[dependencies]
getrandom = { version = "0.2", features = ["js"] }
salsa = { version = "0.1.0", registry = "oksoftware", package = "salsa-2022" }
salsa-macros = { version = "0.1.0", registry = "oksoftware" , package = "salsa-2022-macros" }
insta = "1.38.0"
lalrpop = "0.20.2"
lalrpop-util = { version = "0.20.2", features = ["lexer", "unicode"] }
okstd = { version = "0.1.3", features = [], default-features = false, registry = "oksoftware" }
proptest = "1.4.0"
stringzilla = "3.8.1"
okstd = { features = ["macros"], default-features = false, registry = "oksoftware", version = "0.1.9"}
syn = "2.0.60"
bitflags = "2.5.0"
[dev-dependencies]
insta = "1.38.0"
proptest = "1.4.0"

47
Makefile.toml Normal file
View file

@ -0,0 +1,47 @@
[config]
default_to_workspace = false
skip_core_tasks = true
[tasks.deps]
script = '''
cargo install wasm-bindgen-cli --version 0.2.81 --registry crates-io
npm install
'''
[tasks.build-server]
script = '''
cargo build --release
wasm-bindgen --out-dir ./packages/app/assets/wasm --target web --typescript ./target/wasm32-unknown-unknown/release/demo_lsp_browser.wasm
'''
[tasks.build-app]
script = '''
npm run build --workspace=packages/app
'''
[tasks.build]
dependencies = ["build-server", "build-app"]
[tasks.clean-server]
script = '''
cargo clean
'''
[tasks.clean-app]
script = '''
rm -rf packages/app/dist
rm -rf packages/app/assets/wasm
'''
[tasks.clean]
dependencies = ["clean-server", "clean-app"]
[tasks.format]
script = '''
cargo +nightly fmt --all
'''
[tasks.run]
script = '''
npm run app --workspace=packages/app
'''

View file

@ -0,0 +1,10 @@
[package]
name = "src-collections"
version = "0.1.0"
edition = "2021"
publish = ["oksoftware"]
[dependencies]
rustc-hash = "1.1.0"
indexmap = "1.9.1"
typed-index-collections = "3.0.3"

View file

@ -0,0 +1,10 @@
use rustc_hash::FxHasher;
use std::hash::BuildHasherDefault;
pub use rustc_hash::FxHashMap as Map;
pub use rustc_hash::FxHashSet as Set;
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>;
pub type IndexSet<V> = indexmap::IndexSet<V, BuildHasherDefault<FxHasher>>;
pub type IndexVec<K, V> = typed_index_collections::TiVec<K, V>;

View file

@ -0,0 +1,30 @@
[package]
publish = false
edition = "2021"
name = "src-lsp-browser"
version = "0.0.0"
[features]
default = ["tower-lsp/runtime-agnostic"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
src-lsp-server = { version = "0.0", path = "../src-lsp-server", default-features = false }
futures = "0.3.21"
js-sys = "0.3.57"
tower-lsp = { version = "0.17.0", default-features = false }
wasm-bindgen = "0.2.81"
wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] }
wasm-streams = "0.2.3"
[dependencies.web-sys]
version = "0.3.57"
features = [
"console",
"HtmlTextAreaElement",
"ReadableStream",
"WritableStream",
]

View file

@ -0,0 +1,62 @@
#![deny(clippy::all)]
#![deny(unsafe_code)]
use futures::stream::TryStreamExt;
use tower_lsp::{LspService, Server};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::stream::JsStream;
#[wasm_bindgen]
pub struct ServerConfig {
into_server: js_sys::AsyncIterator,
from_server: web_sys::WritableStream,
}
#[wasm_bindgen]
impl ServerConfig {
#[wasm_bindgen(constructor)]
pub fn new(into_server: js_sys::AsyncIterator, from_server: web_sys::WritableStream) -> Self {
Self {
into_server,
from_server,
}
}
}
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
#[wasm_bindgen]
pub async fn serve(config: ServerConfig) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
web_sys::console::log_1(&"server::serve".into());
let ServerConfig {
into_server,
from_server,
} = config;
let input = JsStream::from(into_server);
let input = input
.map_ok(|value| {
value
.dyn_into::<js_sys::Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
let (service, messages) = LspService::new(|client| src_lsp_server::Server::new(client));
Server::new(input, output, messages).serve(service).await;
Ok(())
}

View file

@ -0,0 +1,45 @@
[package]
publish = false
edition = "2021"
name = "src-lsp-server"
version = "0.0.0"
[features]
default = ["tower-lsp/runtime-agnostic"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.57"
async-lock = "2.5.0"
console_error_panic_hook = "0.1.7"
dashmap = "5.3.4"
futures = "0.3.21"
indoc = "1.0"
js-sys = "0.3.57"
log = "0.4"
lsp = { version = "0.93", package = "lsp-types" }
lsp-text = { version = "0.9"}
ropey = "1.5.0"
serde_json = "1.0"
srclang = { version = "0.1.0", path = "../..", registry = "oksoftware" }
salsa = { version = "0.1.0", registry = "oksoftware", package = "salsa-2022" }
salsa-macros = { version = "0.1.0", registry = "oksoftware" , package = "salsa-2022-macros" }
thiserror = "1.0"
tower-lsp = { version = "0.17.0", default-features = false }
wasm-bindgen = "0.2.81"
wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] }
wasm-streams = "0.2.3"
src-collections = { version = "0.1.0", path = "../src-collections", registry = "oksoftware" }
[dependencies.web-sys]
version = "0.3.57"
features = [
"console",
"CssStyleDeclaration",
"Document",
"ReadableStream",
"Window",
"WritableStream",
]

View file

@ -0,0 +1,10 @@
pub mod document;
pub mod error;
pub mod session;
pub mod syntax;
pub mod text;
pub use document::*;
pub use error::*;
pub use session::*;
pub use text::*;

View file

@ -0,0 +1,66 @@
use async_lock::Mutex;
use lsp_text::RopeExt;
use std::sync::Arc;
use srclang::{compiler::ir, Db};
pub struct Document {
pub content: ropey::Rope,
pub db: &'static dyn Db,
}
impl Document {
pub async fn open(
session: Arc<crate::core::Session>,
params: lsp::DidOpenTextDocumentParams,
) -> anyhow::Result<Option<Self>> {
// let mut parser = crate::core::parser::javascript(&session.language)?;
// let content = ropey::Rope::from(params.text_document.text);
// let result = {
// let content = content.clone();
// let byte_idx = 0;
// let callback = content.chunk_walker(byte_idx).callback_adapter_for_tree_sitter();
// let old_tree = None;
// parser.parse_with(callback, old_tree)?
// };
// crate::core::syntax::update_channel(result.as_ref());
Ok(None)
}
pub async fn change<'changes>(
session: Arc<crate::core::Session>,
uri: &lsp::Url,
content: &ropey::Rope,
) -> anyhow::Result<Option<ir::Program>> {
let result = {
let parser = session.get_mut_parser(uri).await?;
let mut parser = parser.lock().await;
let text = content.chunks().collect::<String>();
// parser.parse(text, None)?
};
// crate::core::syntax::update_channel(result.as_ref());
// if let Some(tree) = result {
// {
// let tree = tree.clone();
// *session.get_mut_tree(uri).await?.value_mut() = Mutex::new(tree);
// }
// Ok(Some(tree))
// } else {
Ok(None)
// }
}
/// Return the language-id and textual content portion of the [`Document`].
pub fn text(&self) -> crate::core::Text {
crate::core::Text {
content: self.content.clone(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DocumentState {
Closed,
Opened,
}

View file

@ -0,0 +1,42 @@
use crate::core;
use thiserror::Error;
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Error, PartialEq)]
pub enum Error {
#[error("ClientNotInitialzed")]
ClientNotInitialized,
#[error("core::SessionResourceNotFound: kind={kind:?}, uri={uri:?}")]
SessionResourceNotFound {
kind: core::session::SessionResourceKind,
uri: lsp::Url,
},
}
pub struct IntoJsonRpcError(pub anyhow::Error);
impl From<IntoJsonRpcError> for tower_lsp::jsonrpc::Error {
fn from(error: IntoJsonRpcError) -> Self {
let mut rpc_error = tower_lsp::jsonrpc::Error::internal_error();
rpc_error.data = Some(serde_json::to_value(format!("{}", error.0)).unwrap());
rpc_error
}
}
#[cfg(test)]
mod tests {
use super::{Error, IntoJsonRpcError};
#[test]
fn from() {
let error = Error::ClientNotInitialized;
let error = error.into();
let mut expected = tower_lsp::jsonrpc::Error::internal_error();
expected.data = Some(serde_json::to_value(format!("{}", error)).unwrap());
let actual: tower_lsp::jsonrpc::Error = IntoJsonRpcError(error).into();
assert_eq!(expected, actual);
}
}

View file

@ -0,0 +1,137 @@
use anyhow::anyhow;
use async_lock::{Mutex, RwLock};
use dashmap::{
mapref::one::{Ref, RefMut},
DashMap,
};
use srclang::compiler::ir;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SessionResourceKind {
Document,
Parser,
Tree,
}
pub struct Session {
pub server_capabilities: RwLock<lsp::ServerCapabilities>,
pub client_capabilities: RwLock<Option<lsp::ClientCapabilities>>,
pub db: RwLock<Option<srclang::compiler::db::Database>>,
client: Option<tower_lsp::Client>,
}
impl Session {
pub fn new(client: Option<tower_lsp::Client>) -> Arc<Self> {
let server_capabilities = RwLock::new(crate::server::capabilities());
let client_capabilities = Default::default();
Arc::new(Session {
db: Default::default(),
server_capabilities,
client_capabilities,
client,
})
}
pub fn client(&self) -> anyhow::Result<&tower_lsp::Client> {
self.client
.as_ref()
.ok_or_else(|| crate::core::Error::ClientNotInitialized.into())
}
pub fn insert_document(&self, uri: lsp::Url, document: crate::core::Document) -> anyhow::Result<()> {
Ok(())
}
pub fn remove_document(&self, uri: &lsp::Url) -> anyhow::Result<()> {
Ok(())
}
pub async fn semantic_tokens_legend(&self) -> Option<lsp::SemanticTokensLegend> {
let capabilities = self.server_capabilities.read().await;
if let Some(capabilities) = &capabilities.semantic_tokens_provider {
match capabilities {
lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(options) => Some(options.legend.clone()),
lsp::SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(options) => {
Some(options.semantic_tokens_options.legend.clone())
},
}
} else {
None
}
}
pub async fn get_text(&self, uri: &lsp::Url) -> anyhow::Result<Ref<'_, lsp::Url, crate::core::Text>> {
Err({
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_mut_text(&self, uri: &lsp::Url) -> anyhow::Result<RefMut<'_, lsp::Url, crate::core::Text>> {
Err({
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_mut_parser(
&self,
uri: &lsp::Url,
) -> anyhow::Result<RefMut<'_, lsp::Url, Mutex<ir::Program>>> {
Err({
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_tree(&self, uri: &lsp::Url) -> anyhow::Result<Ref<'_, lsp::Url, Mutex<ir::Program>>> {
Err({
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_mut_tree(&self, uri: &lsp::Url) -> anyhow::Result<RefMut<'_, lsp::Url, Mutex<ir::Program>>> {
Err({
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub fn get_channel_syntax() -> anyhow::Result<web_sys::HtmlTextAreaElement> {
use wasm_bindgen::JsCast;
let element_id = "channel-syntax";
let channel_syntax = web_sys::window()
.ok_or_else(|| anyhow!("failed to get window"))?
.document()
.ok_or_else(|| anyhow!("failed to get document"))?
.get_element_by_id(element_id)
.ok_or_else(|| anyhow!("failed to get channel-syntax element"))?
.unchecked_into();
Ok(channel_syntax)
}
}
pub fn get_channel_syntax() -> anyhow::Result<web_sys::HtmlTextAreaElement> {
use wasm_bindgen::JsCast;
let element_id = "channel-syntax";
let channel_syntax = web_sys::window()
.ok_or_else(|| anyhow!("failed to get window"))?
.document()
.ok_or_else(|| anyhow!("failed to get document"))?
.get_element_by_id(element_id)
.ok_or_else(|| anyhow!("failed to get channel-syntax element"))?
.unchecked_into();
Ok(channel_syntax)
}

View file

@ -0,0 +1,15 @@
use srclang::compiler::ir;
use crate::core::session::Session;
pub(crate) fn update_channel(tree: Option<&ir::Program>) {
// assume errors; use red
let mut color = "rgb(255, 87, 51)";
if let Ok(channel_syntax) = Session::get_channel_syntax() {
channel_syntax
.style()
.set_property("background-color", color)
.expect("failed to set style");
}
}

View file

@ -0,0 +1,17 @@
pub struct Text {
pub content: ropey::Rope,
}
impl Text {
pub fn new(text: impl AsRef<str>) -> anyhow::Result<Self> {
let text = text.as_ref();
let content = ropey::Rope::from_str(text);
Ok(Text { content })
}
}
impl From<crate::core::Document> for Text {
fn from(value: crate::core::Document) -> Self {
value.text()
}
}

View file

@ -0,0 +1,46 @@
use std::sync::{Arc, Mutex};
use anyhow::Result;
use lsp::{InitializeParams, InitializeResult, Url};
use salsa::ParallelDatabase;
use src_collections::Map;
use srclang::compiler::text::SourceProgram;
use tower_lsp::{jsonrpc, LanguageServer};
pub struct LspServerDatabase {
db: Mutex<srclang::compiler::db::Database>,
input_files: Map<Url, SourceProgram>,
}
impl LspServerDatabase {
pub fn new() -> Self {
Self {
db: Mutex::new(srclang::compiler::db::Database::default()),
input_files: Map::default(),
}
}
pub fn db(&self) -> std::sync::MutexGuard<srclang::compiler::db::Database> {
self.db.lock().unwrap()
}
pub fn input_files(&self) -> &Map<Url, SourceProgram> {
&self.input_files
}
}
#[tower_lsp::async_trait]
impl LanguageServer for LspServerDatabase {
async fn initialize(&self, params: InitializeParams) -> jsonrpc::Result<InitializeResult> {
web_sys::console::log_1(&"server::initialize".into());
// *self.session.client_capabilities.write().await = Some(params.capabilities);
Ok(InitializeResult {
..InitializeResult::default()
})
}
async fn shutdown(&self) -> jsonrpc::Result<()> {
web_sys::console::log_1(&"server::shutdown".into());
Ok(())
}
}

View file

@ -0,0 +1,169 @@
pub mod text_document {
use std::sync::Arc;
use lsp_text::RopeExt;
pub async fn did_open(
session: Arc<crate::core::Session>,
params: lsp::DidOpenTextDocumentParams,
) -> anyhow::Result<()> {
let uri = params.text_document.uri.clone();
if let Some(document) = crate::core::Document::open(session.clone(), params).await? {
session.insert_document(uri.clone(), document)?;
} else {
log::warn!("'textDocument/didOpen' failed :: uri: {:#?}", uri);
}
Ok(())
}
pub async fn did_change(
session: Arc<crate::core::Session>,
params: lsp::DidChangeTextDocumentParams,
) -> anyhow::Result<()> {
let uri = &params.text_document.uri;
let mut text = session.get_mut_text(uri).await?;
*text = crate::core::Text::new(params.content_changes[0].text.clone())?;
crate::core::Document::change(session.clone(), uri, &text.content).await?;
Ok(())
}
pub async fn did_close(
session: Arc<crate::core::Session>,
params: lsp::DidCloseTextDocumentParams,
) -> anyhow::Result<()> {
let uri = params.text_document.uri;
session.remove_document(&uri)?;
let diagnostics = Default::default();
let version = Default::default();
session.client()?.publish_diagnostics(uri, diagnostics, version).await;
Ok(())
}
pub async fn document_symbol(
session: Arc<crate::core::Session>,
params: lsp::DocumentSymbolParams,
) -> anyhow::Result<Option<lsp::DocumentSymbolResponse>> {
// use wasm_bindgen::JsCast;
// fn make_symbol(
// uri: &lsp::Url,
// content: &ropey::Rope,
// declaration: tree_sitter::Node,
// identifier: tree_sitter::Node,
// kind: lsp::SymbolKind,
// ) -> lsp::SymbolInformation {
// let name = content.utf8_text_for_tree_sitter_node(&identifier).into();
// let range = content.tree_sitter_range_to_lsp_range(declaration.range());
// #[allow(deprecated)]
// lsp::SymbolInformation {
// name,
// kind,
// tags: Default::default(),
// deprecated: Default::default(),
// location: lsp::Location::new(uri.clone(), range),
// container_name: Default::default(),
// }
// }
// let uri = &params.text_document.uri;
// let text = session.get_text(uri).await?;
// let content = &text.content;
// let tree = session.get_tree(uri).await?;
// let tree = tree.lock().await.clone();
// // NOTE: transmutes here because we do not yet support query functionality in
// // tree-sitter-facade; thus we use the raw bindings from web-tree-sitter-sys.
// #[allow(unsafe_code)]
// // let node = unsafe { std::mem::transmute::<_, web_tree_sitter_sys::SyntaxNode>(tree.root_node()) };
// #[allow(unsafe_code)]
// // let language = unsafe { std::mem::transmute::<_, web_tree_sitter_sys::Language>(session.language.clone()) };
// static QUERY: &str = indoc::indoc! {r"
// (function_declaration
// name: (identifier) @identifier) @function_declaration
// (lexical_declaration
// (variable_declarator
// name: (identifier) @identifier)) @class_declaration
// (variable_declaration
// (variable_declarator
// name: (identifier) @identifier)) @variable_declaration
// (class_declaration
// name: (identifier) @identifier) @class_declaration
// "};
// let query = language.query(&QUERY.into()).expect("failed to create query");
// let matches = {
// let start_position = None;
// let end_position = None;
// query
// .matches(&node, start_position, end_position)
// .into_vec()
// .into_iter()
// .map(JsCast::unchecked_into::<web_tree_sitter_sys::QueryMatch>)
// };
// let mut symbols = vec![];
// for r#match in matches {
// let captures = r#match
// .captures()
// .into_vec()
// .into_iter()
// .map(JsCast::unchecked_into::<web_tree_sitter_sys::QueryCapture>)
// .collect::<Vec<_>>();
// if let [declaration, identifier] = captures.as_slice() {
// // NOTE: reverse the transmutes from above so we can use tree-sitter-facade bindings for Node
// #[allow(unsafe_code)]
// // let declaration_node = unsafe { std::mem::transmute::<_, tree_sitter::Node>(declaration.node()) };
// #[allow(unsafe_code)]
// // let identifier_node = unsafe { std::mem::transmute::<_, tree_sitter::Node>(identifier.node()) };
// match String::from(declaration.name()).as_str() {
// "function_declaration" => {
// symbols.push(make_symbol(
// uri,
// content,
// declaration_node,
// identifier_node,
// lsp::SymbolKind::FUNCTION,
// ));
// },
// "lexical_declaration" => {
// symbols.push(make_symbol(
// uri,
// content,
// declaration_node,
// identifier_node,
// lsp::SymbolKind::VARIABLE,
// ));
// },
// "variable_declaration" => {
// symbols.push(make_symbol(
// uri,
// content,
// declaration_node,
// identifier_node,
// lsp::SymbolKind::VARIABLE,
// ));
// },
// "class_declaration" => {
// symbols.push(make_symbol(
// uri,
// content,
// declaration_node,
// identifier_node,
// lsp::SymbolKind::VARIABLE,
// ));
// },
// _ => {},
// }
// }
// }
// Ok(Some(lsp::DocumentSymbolResponse::Flat(symbols)))
Ok(None)
}
}

View file

@ -0,0 +1,10 @@
#![deny(clippy::all)]
#![deny(unsafe_code)]
// mod core;
// pub mod handler;
mod server;
mod db;
pub use server::*;

View file

@ -0,0 +1,139 @@
use anyhow::anyhow;
use std::result::Result::Ok;
use std::sync::Arc;
use tower_lsp::{jsonrpc, lsp_types::*, LanguageServer};
pub fn capabilities() -> lsp::ServerCapabilities {
let document_symbol_provider = Some(lsp::OneOf::Left(true));
let text_document_sync = {
let options = lsp::TextDocumentSyncOptions {
open_close: Some(true),
change: Some(lsp::TextDocumentSyncKind::FULL),
..Default::default()
};
Some(lsp::TextDocumentSyncCapability::Options(options))
};
let hover_provider = Some(lsp::HoverProviderCapability::Simple(true));
lsp::ServerCapabilities {
text_document_sync,
document_symbol_provider,
hover_provider,
..Default::default()
}
}
pub struct Server {
pub client: tower_lsp::Client,
pub db: Arc<crate::db::LspServerDatabase>,
}
impl Server {
pub fn new(client: tower_lsp::Client) -> Self {
// let session = crate::core::Session::new(Some(client.clone()), language);
Server {
client,
db: Arc::new(crate::db::LspServerDatabase::new()),
}
}
}
#[tower_lsp::async_trait]
impl LanguageServer for Server {
async fn initialize(&self, params: InitializeParams) -> jsonrpc::Result<InitializeResult> {
web_sys::console::log_1(&"server::initialize".into());
// *self.session.client_capabilities.write().await = Some(params.capabilities);
let capabilities = capabilities();
Ok(InitializeResult {
capabilities,
..InitializeResult::default()
})
}
async fn initialized(&self, _: lsp::InitializedParams) {
web_sys::console::log_1(&"server::initialized".into());
let typ = lsp::MessageType::INFO;
let message = "src language server initialized!";
self.client.log_message(typ, message).await;
}
async fn shutdown(&self) -> jsonrpc::Result<()> {
web_sys::console::log_1(&"server::shutdown".into());
Ok(())
}
// FIXME: for some reason this doesn't trigger
async fn did_open(&self, params: lsp::DidOpenTextDocumentParams) {
web_sys::console::log_1(&"server::did_open".into());
let typ = lsp::MessageType::INFO;
let message = format!("opened document: {}", params.text_document.uri.as_str());
self.client.log_message(typ, message).await;
let mut errors = vec![];
let wrapper = srclang::lexer::TripleIterator::new(&params.text_document.text);
let t = srclang::parser::src::SourceParser::new()
.parse(&mut errors, wrapper)
.unwrap();
let mut color = "rgb(255, 87, 51)";
if let Ok(channel_syntax) = get_channel_syntax() {
channel_syntax.set_value(format!("{:#?}", t).as_str());
if !errors.is_empty() {
channel_syntax
.style()
.set_property("background-color", color)
.expect("failed to set style");
}
}
web_sys::console::log_1(&"server::did parse".into());
self.client
.log_message(typ, format!("{:#?}", params.text_document.text))
.await;
// let session = self.session.clone();
// crate::handler::text_document::did_open(session, params).await.unwrap();
}
async fn did_change(&self, params: lsp::DidChangeTextDocumentParams) {
web_sys::console::log_1(&"server::did_change".into());
let typ = lsp::MessageType::INFO;
let mut errors = vec![];
let wrapper = srclang::lexer::TripleIterator::new("");
let t = srclang::parser::src::SourceParser::new().parse(&mut errors, wrapper);
if errors.is_empty() {
for change in params.content_changes {
self.client
.log_message(typ, format!("{:#?}", change.text))
.await;
}
} else {
self.client.log_message(typ, format!("{:#?}", errors)).await;
}
}
async fn document_symbol(
&self,
params: lsp::DocumentSymbolParams,
) -> jsonrpc::Result<Option<lsp::DocumentSymbolResponse>> {
web_sys::console::log_1(&"server::document_symbol".into());
// let session = self.session.clone();
// let result = crate::handler::text_document::document_symbol(session, params).await;
// Ok(result.map_err(crate::core::IntoJsonRpcError)?)
Ok(None)
}
}
pub fn get_channel_syntax() -> anyhow::Result<web_sys::HtmlTextAreaElement> {
use wasm_bindgen::JsCast;
let element_id = "channel-syntax";
let channel_syntax = web_sys::window()
.ok_or_else(|| anyhow!("failed to get window"))?
.document()
.ok_or_else(|| anyhow!("failed to get document"))?
.get_element_by_id(element_id)
.ok_or_else(|| anyhow!("failed to get channel-syntax element"))?
.unchecked_into();
Ok(channel_syntax)
}

View file

@ -41,3 +41,11 @@ impl Make for Local {
}
}
```
## Acknowledgements
Building upon the incredible work of the Rust community, src would not be possible without the following projects:
- [salsa-rs](https://github.com/salsa-rs/salsa)
- [lalrpop](https://github.com/lalrpop/lalrpop)
- [tower-lsp-web-demo](https://github.com/silvanshade/tower-lsp-web-demo)

View file

@ -1,5 +1,13 @@
# Summary
- [Intro](0intro.md)
# Language
- [Specification](language/0intro.md)
- [Examples](examples.md)
- [Language](language/0intro.md)
# Playground
* [Playground](playground/index.md)

22
docs/playground/index.md Normal file
View file

@ -0,0 +1,22 @@
<div id="container">
<div id="cell-editor">
<label for="editor">editor</label>
<div id="editor"></div>
</div>
<div id="cell-syntax">
<label for="channel-syntax">syntax</label>
<textarea id="channel-syntax" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
<div id="cell-client">
<label for="channel-client">message trace (client ⇒ server)</label>
<textarea id="channel-client" autocomplete="off" spellcheck="off" wrap="off" readonly rows="4"></textarea>
</div>
<div id="cell-server">
<label for="channel-server">message trace (client ⇐ server)</label>
<textarea id="channel-server" autocomplete="off" spellcheck="off" wrap="off" readonly rows="4"></textarea>
</div>
<div id="cell-console">
<label for="channel-console">console</label>
<textarea id="channel-console" autocomplete="off" spellcheck="off" wrap="off" readonly rows="3"></textarea>
</div>
</div>

BIN
docs/playground/taocp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 KiB

View file

@ -24,7 +24,6 @@
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
<link rel="stylesheet" href="{{ path_to_root }}ok.css"
{{#if print_enable}}
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
{{/if}}
@ -150,7 +149,7 @@
🍔
</label>
<button id="theme-toggle" class="nes-btn" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
🎨
💅
</button>
<ul id="theme-list" class="is-primary theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>

4
ok.css
View file

@ -2940,6 +2940,10 @@ pre {
padding: 16px;
overflow-x: auto;
}
label {
font-family: "Press Start 2P", cursive !important;
font-size: x-small !important;
}
nav,
pre
code {

6502
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"private": true,
"workspaces": [
"packages/app"
],
"dependencies": {
"tree-sitter-javascript": "^0.19.0",
"web-tree-sitter-wasm-bindgen": "silvanshade/web-tree-sitter-wasm-bindgen"
}
}

View file

@ -0,0 +1 @@
dist

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<div id="container">
<h1 id="title">browser-hosted editor and language server</h1>
<p id="synopsis">
This app demos an editor with language smarts (for JavaScript) by hosting the <a
href="https://microsoft.github.io/monaco-editor/">Monaco</a> widget with an <a
href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/">LSP</a>
server implemented via <a href="https://github.com/ebkalderon/tower-lsp">tower-lsp</a> and <a
href="https://github.com/tree-sitter/tree-sitter">tree-sitter</a>. Everything is compiled to WASM and run
client-side directly in the browser; there are no separate sever-side processes or web workers used by the app
code. (Monaco itself does use web workers, however).
</p>
<p id="features">
<strong>features</strong>: ⇧⌘O (macos) or ⇧⌃O (windows) opens symbol view; the <strong>syntax</strong> area shows
the JavaScript syntax tree (green for valid; red for errors) parsed from <strong>editor</strong>
</p>
<div id="cell-editor">
<label for="editor">editor</label>
<div id="editor"></div>
</div>
<div id="cell-syntax">
<label for="channel-syntax">syntax</label>
<textarea id="channel-syntax" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
<div id="cell-console">
<label for="channel-console">console</label>
<textarea id="channel-console" autocomplete="off" spellcheck="off" wrap="off" readonly rows="3"></textarea>
</div>
<div id="cell-client">
<label for="channel-client">message trace (client ⇒ server)</label>
<textarea id="channel-client" autocomplete="off" spellcheck="off" wrap="off" readonly rows="4"></textarea>
</div>
<div id="cell-server">
<label for="channel-server">message trace (client ⇐ server)</label>
<textarea id="channel-server" autocomplete="off" spellcheck="off" wrap="off" readonly rows="4"></textarea>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,116 @@
a {
color: mediumslateblue;
}
a:visited {
color: silver;
}
/*
body {
display: flex;
height: 100vh;
background-color: black;
color: white;
} */
div[id=container] {
display: grid;
height: 90%;
min-height: 600px;
grid-template-rows: auto auto auto minmax(0, 1fr) auto auto auto;
grid-template-columns: repeat(2, minmax(0, 1fr));
margin: auto;
}
/*
h1[id=title] {
grid-column: 1 / 3;
grid-row: 1;
font-family: monospace;
font-size: 2em;
}
p[id=synopsis] {
color: lightgrey;
grid-column: 1 / 3;
grid-row: 2;
margin: 0;
font-family: sans-serif;
font-size: 10pt;
line-height: 1.5em;
}
p[id=features] {
grid-column: 1 / 3;
grid-row: 3;
text-align: center;
font-family: sans-serif;
font-style: italic;
font-size: 10pt;
line-height: 1.5em;
color: lightgrey;
}
div[id=cell-editor] {
grid-column: 1 / 2;
grid-row: 4;
} */
[id=cell-editor] div[id=editor] {
border: 1px solid black;
height: calc(100vh - 500px);
padding-right: 1.5em;
}
/*
div[id=cell-syntax] {
grid-column: 2 / 3;
grid-row: 4;
} */
div[id=cell-syntax] textarea {
height: calc(100vh - 500px);
}
/*
[id=container] label {
display: block;
font-family: monospace;
font-size: 14pt;
}
div[id=cell-console] {
grid-column: 1 / 3;
grid-row: 5;
margin-top: 2em;
overflow: hidden;
}
div[id=cell-client] {
grid-column: 1 / 3;
grid-row: 6;
overflow: hidden;
}
div[id=cell-server] {
grid-column: 1 / 3;
grid-row: 7;
overflow: hidden;
}
div[id=container] textarea {
display: block;
box-sizing: border-box;
width: 100%;
resize: none;
overflow-y: auto;
font-family: monospace;
font-family: 10pt;
} */
main {
max-width: 100%;
}
.content main {
margin-inline-start: auto;
margin-inline-end: auto;
max-width: 100%;
}

View file

@ -0,0 +1 @@
export function bytes_literal() { return "bytes"; }

View file

@ -0,0 +1,164 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {ServerConfig} config
* @returns {Promise<void>}
*/
export function serve(config: ServerConfig): Promise<void>;
/**
*/
export class IntoUnderlyingByteSource {
free(): void;
/**
* @param {any} controller
*/
start(controller: any): void;
/**
* @param {any} controller
* @returns {Promise<any>}
*/
pull(controller: any): Promise<any>;
/**
*/
cancel(): void;
/**
*/
readonly autoAllocateChunkSize: number;
/**
*/
readonly type: any;
}
/**
*/
export class IntoUnderlyingSink {
free(): void;
/**
* @param {any} chunk
* @returns {Promise<any>}
*/
write(chunk: any): Promise<any>;
/**
* @returns {Promise<any>}
*/
close(): Promise<any>;
/**
* @param {any} reason
* @returns {Promise<any>}
*/
abort(reason: any): Promise<any>;
}
/**
*/
export class IntoUnderlyingSource {
free(): void;
/**
* @param {any} controller
* @returns {Promise<any>}
*/
pull(controller: any): Promise<any>;
/**
*/
cancel(): void;
}
/**
* Raw options for [`pipeTo()`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/pipeTo).
*/
export class PipeOptions {
free(): void;
/**
*/
readonly preventAbort: boolean;
/**
*/
readonly preventCancel: boolean;
/**
*/
readonly preventClose: boolean;
/**
*/
readonly signal: AbortSignal | undefined;
}
/**
*/
export class QueuingStrategy {
free(): void;
/**
*/
readonly highWaterMark: number;
}
/**
* Raw options for [`getReader()`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader).
*/
export class ReadableStreamGetReaderOptions {
free(): void;
/**
*/
readonly mode: any;
}
/**
*/
export class ServerConfig {
free(): void;
/**
* @param {AsyncIterator<any>} into_server
* @param {WritableStream} from_server
*/
constructor(into_server: AsyncIterator<any>, from_server: WritableStream);
}
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly __wbg_serverconfig_free: (a: number) => void;
readonly serve: (a: number) => number;
readonly serverconfig_new: (a: number, b: number) => number;
readonly __wbg_queuingstrategy_free: (a: number) => void;
readonly queuingstrategy_highWaterMark: (a: number) => number;
readonly __wbg_readablestreamgetreaderoptions_free: (a: number) => void;
readonly readablestreamgetreaderoptions_mode: (a: number) => number;
readonly __wbg_pipeoptions_free: (a: number) => void;
readonly pipeoptions_preventClose: (a: number) => number;
readonly pipeoptions_preventCancel: (a: number) => number;
readonly pipeoptions_preventAbort: (a: number) => number;
readonly pipeoptions_signal: (a: number) => number;
readonly __wbg_intounderlyingbytesource_free: (a: number) => void;
readonly intounderlyingbytesource_type: (a: number) => number;
readonly intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number;
readonly intounderlyingbytesource_start: (a: number, b: number) => void;
readonly intounderlyingbytesource_pull: (a: number, b: number) => number;
readonly intounderlyingbytesource_cancel: (a: number) => void;
readonly __wbg_intounderlyingsource_free: (a: number) => void;
readonly intounderlyingsource_pull: (a: number, b: number) => number;
readonly intounderlyingsource_cancel: (a: number) => void;
readonly __wbg_intounderlyingsink_free: (a: number) => void;
readonly intounderlyingsink_write: (a: number, b: number) => number;
readonly intounderlyingsink_close: (a: number) => number;
readonly intounderlyingsink_abort: (a: number, b: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha2071623bfe400a9: (a: number, b: number, c: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h18cbcd62478d99de: (a: number, b: number, c: number, d: number) => void;
}
/**
* Synchronously compiles the given `bytes` and instantiates the WebAssembly module.
*
* @param {BufferSource} bytes
*
* @returns {InitOutput}
*/
export function initSync(bytes: BufferSource): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

View file

@ -0,0 +1,811 @@
import { bytes_literal } from './snippets/wasm-streams-42e57edbcd526312/inline0.js';
let wasm;
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachedUint8Memory0;
function getUint8Memory0() {
if (cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
let cachedInt32Memory0;
function getInt32Memory0() {
if (cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachedInt32Memory0;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
const a = state.a;
state.a = 0;
try {
return f(a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
} else {
state.a = a;
}
}
};
real.original = state;
return real;
}
function __wbg_adapter_22(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha2071623bfe400a9(arg0, arg1, addHeapObject(arg2));
}
function _assertClass(instance, klass) {
if (!(instance instanceof klass)) {
throw new Error(`expected instance of ${klass.name}`);
}
return instance.ptr;
}
/**
* @param {ServerConfig} config
* @returns {Promise<void>}
*/
export function serve(config) {
_assertClass(config, ServerConfig);
var ptr0 = config.ptr;
config.ptr = 0;
const ret = wasm.serve(ptr0);
return takeObject(ret);
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
}
function __wbg_adapter_107(arg0, arg1, arg2, arg3) {
wasm.wasm_bindgen__convert__closures__invoke2_mut__h18cbcd62478d99de(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
}
/**
*/
export class IntoUnderlyingByteSource {
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_intounderlyingbytesource_free(ptr);
}
/**
*/
get type() {
const ret = wasm.intounderlyingbytesource_type(this.ptr);
return takeObject(ret);
}
/**
*/
get autoAllocateChunkSize() {
const ret = wasm.intounderlyingbytesource_autoAllocateChunkSize(this.ptr);
return ret >>> 0;
}
/**
* @param {any} controller
*/
start(controller) {
wasm.intounderlyingbytesource_start(this.ptr, addHeapObject(controller));
}
/**
* @param {any} controller
* @returns {Promise<any>}
*/
pull(controller) {
const ret = wasm.intounderlyingbytesource_pull(this.ptr, addHeapObject(controller));
return takeObject(ret);
}
/**
*/
cancel() {
const ptr = this.__destroy_into_raw();
wasm.intounderlyingbytesource_cancel(ptr);
}
}
/**
*/
export class IntoUnderlyingSink {
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_intounderlyingsink_free(ptr);
}
/**
* @param {any} chunk
* @returns {Promise<any>}
*/
write(chunk) {
const ret = wasm.intounderlyingsink_write(this.ptr, addHeapObject(chunk));
return takeObject(ret);
}
/**
* @returns {Promise<any>}
*/
close() {
const ptr = this.__destroy_into_raw();
const ret = wasm.intounderlyingsink_close(ptr);
return takeObject(ret);
}
/**
* @param {any} reason
* @returns {Promise<any>}
*/
abort(reason) {
const ptr = this.__destroy_into_raw();
const ret = wasm.intounderlyingsink_abort(ptr, addHeapObject(reason));
return takeObject(ret);
}
}
/**
*/
export class IntoUnderlyingSource {
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_intounderlyingsource_free(ptr);
}
/**
* @param {any} controller
* @returns {Promise<any>}
*/
pull(controller) {
const ret = wasm.intounderlyingsource_pull(this.ptr, addHeapObject(controller));
return takeObject(ret);
}
/**
*/
cancel() {
const ptr = this.__destroy_into_raw();
wasm.intounderlyingsource_cancel(ptr);
}
}
/**
* Raw options for [`pipeTo()`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/pipeTo).
*/
export class PipeOptions {
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_pipeoptions_free(ptr);
}
/**
*/
get preventClose() {
const ret = wasm.pipeoptions_preventClose(this.ptr);
return ret !== 0;
}
/**
*/
get preventCancel() {
const ret = wasm.pipeoptions_preventCancel(this.ptr);
return ret !== 0;
}
/**
*/
get preventAbort() {
const ret = wasm.pipeoptions_preventAbort(this.ptr);
return ret !== 0;
}
/**
*/
get signal() {
const ret = wasm.pipeoptions_signal(this.ptr);
return takeObject(ret);
}
}
/**
*/
export class QueuingStrategy {
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_queuingstrategy_free(ptr);
}
/**
*/
get highWaterMark() {
const ret = wasm.queuingstrategy_highWaterMark(this.ptr);
return ret;
}
}
/**
* Raw options for [`getReader()`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader).
*/
export class ReadableStreamGetReaderOptions {
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_readablestreamgetreaderoptions_free(ptr);
}
/**
*/
get mode() {
const ret = wasm.readablestreamgetreaderoptions_mode(this.ptr);
return takeObject(ret);
}
}
/**
*/
export class ServerConfig {
static __wrap(ptr) {
const obj = Object.create(ServerConfig.prototype);
obj.ptr = ptr;
return obj;
}
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_serverconfig_free(ptr);
}
/**
* @param {AsyncIterator<any>} into_server
* @param {WritableStream} from_server
*/
constructor(into_server, from_server) {
const ret = wasm.serverconfig_new(addHeapObject(into_server), addHeapObject(from_server));
return ServerConfig.__wrap(ret);
}
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function getImports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
const ret = false;
return ret;
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbg_respond_553c92471e2e621b = function(arg0, arg1) {
getObject(arg0).respond(arg1 >>> 0);
};
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
const ret = getObject(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_write_c54a0ef6d718320b = function(arg0, arg1) {
const ret = getObject(arg0).write(takeObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_ready_16241896c422ec02 = function(arg0) {
const ret = getObject(arg0).ready;
return addHeapObject(ret);
};
imports.wbg.__wbg_releaseLock_5b88080d80d6736c = function(arg0) {
getObject(arg0).releaseLock();
};
imports.wbg.__wbg_close_661a81815170ae58 = function(arg0) {
const ret = getObject(arg0).close();
return addHeapObject(ret);
};
imports.wbg.__wbg_getWriter_8c2eade28733ae9d = function() { return handleError(function (arg0) {
const ret = getObject(arg0).getWriter();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_close_146359de33accd5b = function(arg0) {
getObject(arg0).close();
};
imports.wbg.__wbg_enqueue_eb446030ed643eb5 = function(arg0, arg1) {
getObject(arg0).enqueue(getObject(arg1));
};
imports.wbg.__wbg_byobRequest_3e1cb18a6efca4e4 = function(arg0) {
const ret = getObject(arg0).byobRequest;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_view_7ad18ad26d71d774 = function(arg0) {
const ret = getObject(arg0).view;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_byteLength_6f22329fd199ce7d = function(arg0) {
const ret = getObject(arg0).byteLength;
return ret;
};
imports.wbg.__wbg_close_26866406ee8e0abb = function(arg0) {
getObject(arg0).close();
};
imports.wbg.__wbg_buffer_f00028fd9efc903b = function(arg0) {
const ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_byteOffset_b3edd58064ebb082 = function(arg0) {
const ret = getObject(arg0).byteOffset;
return ret;
};
imports.wbg.__wbg_bytesliteral_1860f600f905fea0 = function() {
const ret = bytes_literal();
return addHeapObject(ret);
};
imports.wbg.__wbg_new_693216e109162396 = function() {
const ret = new Error();
return addHeapObject(ret);
};
imports.wbg.__wbg_stack_0ddaca5d1abfb52f = function(arg0, arg1) {
const ret = getObject(arg1).stack;
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_error_09919627ac0992f5 = function(arg0, arg1) {
try {
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(arg0, arg1);
}
};
imports.wbg.__wbg_instanceof_Window_a2a08d3918d7d4d0 = function(arg0) {
const ret = getObject(arg0) instanceof Window;
return ret;
};
imports.wbg.__wbg_document_14a383364c173445 = function(arg0) {
const ret = getObject(arg0).document;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_getElementById_0c9415d96f5b9ec6 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_style_3fb37aa4b3701322 = function(arg0) {
const ret = getObject(arg0).style;
return addHeapObject(ret);
};
imports.wbg.__wbg_setvalue_61440ce246b0279d = function(arg0, arg1, arg2) {
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_log_7761a8b8a8c1864e = function(arg0) {
console.log(getObject(arg0));
};
imports.wbg.__wbg_setProperty_88447bf87ac638d7 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_newnoargs_fc5356289219b93b = function(arg0, arg1) {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_is_object = function(arg0) {
const val = getObject(arg0);
const ret = typeof(val) === 'object' && val !== null;
return ret;
};
imports.wbg.__wbg_done_2a1e30464aae6a4d = function(arg0) {
const ret = getObject(arg0).done;
return ret;
};
imports.wbg.__wbg_value_a495c29471c31da6 = function(arg0) {
const ret = getObject(arg0).value;
return addHeapObject(ret);
};
imports.wbg.__wbg_call_4573f605ca4b5f10 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_self_ba1ddafe9ea7a3a2 = function() { return handleError(function () {
const ret = self.self;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_window_be3cc430364fd32c = function() { return handleError(function () {
const ret = window.window;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_globalThis_56d9c9f814daeeee = function() { return handleError(function () {
const ret = globalThis.globalThis;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_global_8c35aeee4ac77f2b = function() { return handleError(function () {
const ret = global.global;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbindgen_is_undefined = function(arg0) {
const ret = getObject(arg0) === undefined;
return ret;
};
imports.wbg.__wbg_new_651776e932b7e9c7 = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_call_9855a4612eb496cb = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_next_e7700fbea331c507 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).next();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_toString_81e19471abb6dc98 = function(arg0) {
const ret = getObject(arg0).toString();
return addHeapObject(ret);
};
imports.wbg.__wbg_new_78403b138428b684 = function(arg0, arg1) {
try {
var state0 = {a: arg0, b: arg1};
var cb0 = (arg0, arg1) => {
const a = state0.a;
state0.a = 0;
try {
return __wbg_adapter_107(a, state0.b, arg0, arg1);
} finally {
state0.a = a;
}
};
const ret = new Promise(cb0);
return addHeapObject(ret);
} finally {
state0.a = state0.b = 0;
}
};
imports.wbg.__wbg_resolve_f269ce174f88b294 = function(arg0) {
const ret = Promise.resolve(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_1c698eedca15eed6 = function(arg0, arg1) {
const ret = getObject(arg0).then(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_4debc41d4fc92ce5 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_buffer_de1150f91b23aa89 = function(arg0) {
const ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithbyteoffsetandlength_9ca61320599a2c84 = function(arg0, arg1, arg2) {
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_new_97cf52648830a70d = function(arg0) {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_set_a0172b213e2469e9 = function(arg0, arg1, arg2) {
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
};
imports.wbg.__wbg_length_e09c0b925ab8de5d = function(arg0) {
const ret = getObject(arg0).length;
return ret;
};
imports.wbg.__wbg_instanceof_Uint8Array_fd17ec67c77de602 = function(arg0) {
const ret = getObject(arg0) instanceof Uint8Array;
return ret;
};
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
const ret = debugString(getObject(arg1));
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_memory = function() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper3620 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1230, __wbg_adapter_22);
return addHeapObject(ret);
};
return imports;
}
function initMemory(imports, maybe_memory) {
}
function finalizeInit(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
return wasm;
}
function initSync(bytes) {
const imports = getImports();
initMemory(imports);
const module = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(module, imports);
return finalizeInit(instance, module);
}
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('src_lsp_browser_bg.wasm', import.meta.url);
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
initMemory(imports);
const { instance, module } = await load(await input, imports);
return finalizeInit(instance, module);
}
export { initSync }
export default init;

Binary file not shown.

View file

@ -0,0 +1,35 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function __wbg_serverconfig_free(a: number): void;
export function serve(a: number): number;
export function serverconfig_new(a: number, b: number): number;
export function __wbg_queuingstrategy_free(a: number): void;
export function queuingstrategy_highWaterMark(a: number): number;
export function __wbg_readablestreamgetreaderoptions_free(a: number): void;
export function readablestreamgetreaderoptions_mode(a: number): number;
export function __wbg_pipeoptions_free(a: number): void;
export function pipeoptions_preventClose(a: number): number;
export function pipeoptions_preventCancel(a: number): number;
export function pipeoptions_preventAbort(a: number): number;
export function pipeoptions_signal(a: number): number;
export function __wbg_intounderlyingbytesource_free(a: number): void;
export function intounderlyingbytesource_type(a: number): number;
export function intounderlyingbytesource_autoAllocateChunkSize(a: number): number;
export function intounderlyingbytesource_start(a: number, b: number): void;
export function intounderlyingbytesource_pull(a: number, b: number): number;
export function intounderlyingbytesource_cancel(a: number): void;
export function __wbg_intounderlyingsource_free(a: number): void;
export function intounderlyingsource_pull(a: number, b: number): number;
export function intounderlyingsource_cancel(a: number): void;
export function __wbg_intounderlyingsink_free(a: number): void;
export function intounderlyingsink_write(a: number, b: number): number;
export function intounderlyingsink_close(a: number): number;
export function intounderlyingsink_abort(a: number, b: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha2071623bfe400a9(a: number, b: number, c: number): void;
export function __wbindgen_exn_store(a: number): void;
export function __wbindgen_free(a: number, b: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h18cbcd62478d99de(a: number, b: number, c: number, d: number): void;

8
packages/app/build.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
# FILEPATH: /workspaces/libsrc/packages/app/build.sh
cargo build --release -p src-lsp-browser --target wasm32-unknown-unknown;
wasm-bindgen --out-dir assets/wasm --target web --typescript /scratch/cargo_target/wasm32-unknown-unknown/release/src_lsp_browser.wasm;
webpack;
mv ../../book/playground/*.wasm ../../book
mv ../../book/playground/*.ttf ../../book
cp ../../book/taocp.png ../../book/playground

1
packages/app/declarations.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module "*.module.css";

Binary file not shown.

Binary file not shown.

2
packages/app/dist/app.bundle.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
/*! @license DOMPurify 2.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.1/LICENSE */

File diff suppressed because one or more lines are too long

65
packages/app/dist/index.html vendored Normal file
View file

@ -0,0 +1,65 @@
<!doctype html><html lang="en" class="dark" dir=""><head><meta charset="UTF-8"><title>Playground</title><meta name="description" content=""><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#ffffff"><link rel="shortcut icon" href="taocp.png"><link rel="stylesheet" href="css/variables.css"><link rel="stylesheet" href="css/general.css"><link rel="stylesheet" href="css/chrome.css"><link rel="stylesheet" href="css/print.css" media="print"><link rel="stylesheet" href="FontAwesome/css/font-awesome.css"><link rel="stylesheet" href="fonts/fonts.css"><link rel="stylesheet" href="highlight.css"><link rel="stylesheet" href="tomorrow-night.css"><link rel="stylesheet" href="ayu-highlight.css"><link rel="stylesheet" href="ok.css"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Allura&family=Inclusive+Sans:ital@0;1&display=swap" rel="stylesheet"><script type="module" src="app.bundle.js"></script><script type="module" src="editor.worker.bundle.js"></script></head><body class="sidebar-visible no-js"><div id="body-container"><script>var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "rust" : "dark";</script><script>try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }</script><script>var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('dark')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');</script><input type="checkbox" id="sidebar-toggle-anchor" class="hidden"><script>var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);</script><nav id="sidebar" class="sidebar" aria-label="Table of contents"><div class="sidebar-scrollbox"><ol class="chapter"><li class="chapter-item expanded"><a href="0intro.html"><strong aria-hidden="true">1.</strong> Intro</a></li><li class="chapter-item expanded affix"></li><li class="part-title">Language</li><li class="chapter-item expanded"><a href="language/0intro.html"><strong aria-hidden="true">2.</strong> Language</a></li><li><ol class="section"><li class="chapter-item expanded"><a href="examples.html"><strong aria-hidden="true">2.1.</strong> Examples</a></li></ol></li><li class="chapter-item expanded"></li><li class="part-title">Playground</li><li class="chapter-item expanded"><a href="playground.html" class="active"><strong aria-hidden="true">3.</strong> Playground</a></li></ol></div><div id="sidebar-resize-handle" class="sidebar-resize-handle"><div class="sidebar-resize-indicator"></div></div></nav><script>var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}</script><div id="page-wrapper" class="page-wrapper"><div class="page"><div id="menu-bar-hover-placeholder"></div><div id="menu-bar" class="menu-bar sticky"><div class="left-buttons"><label id="sidebar-toggle" class="nes-btn" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">🍔</label> <button id="theme-toggle" class="nes-btn" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">💅</button><ul id="theme-list" class="is-primary theme-popup" aria-label="Themes" role="menu"><li role="none"><button role="menuitem" class="theme" id="light">Light</button></li><li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li><li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li><li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li><li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li></ul><button id="search-toggle" class="nes-btn is-primary" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">🔎</button></div><h1 class="menu-title"></h1><div class="right-buttons"><a href="print.html" title="Print this book" aria-label="Print this book" class="nes-btn">🖨️ </a><a class="nes-btn" href="https://ok.software/ok/src" title="branch" aria-label="branch">🌿</a></div></div><div id="search-wrapper" class="hidden"><form id="searchbar-outer" class="searchbar-outer"><input type="search" id="searchbar" class="nes-field" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header"></form><div id="searchresults-outer" class="searchresults-outer hidden"><div id="searchresults-header" class="searchresults-header"></div><ul id="searchresults"></ul></div></div><script>document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});</script><div id="content" class="content"><main><div id="container"><div id="cell-editor"><label for="editor">editor</label><div id="editor"></div></div><div id="cell-syntax"><label for="channel-syntax">syntax</label> <textarea id="channel-syntax" autocomplete="off" spellcheck="off" wrap="off" readonly="readonly"></textarea></div><div id="cell-console"><label for="channel-console">console</label> <textarea id="channel-console" autocomplete="off" spellcheck="off" wrap="off" readonly="readonly" rows="3"></textarea></div><div id="cell-client"><label for="channel-client">message trace (client ⇒ server)</label> <textarea id="channel-client" autocomplete="off" spellcheck="off" wrap="off" readonly="readonly" rows="4"></textarea></div><div id="cell-server"><label for="channel-server">message trace (client ⇐ server)</label> <textarea id="channel-server" autocomplete="off" spellcheck="off" wrap="off" readonly="readonly" rows="4"></textarea></div></div></main><nav class="nav-wrapper" aria-label="Page navigation"><a rel="prev" href="examples.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAzElEQVRYR+2X0Q6AIAhF5f8/2jYXZkwEjNSVvVUjDpcrGgT7FUkI2D9xRfQETwNIiWO85wfINfQUEyxBG2ArsLwC0jioGt5zFcwF4OYDPi/mBYKm4t0U8ATgRm3ThFoAqkhNgWkA0jJLvaOVSs7j3qMnSgXWBMiWPXe94QqMBMBc1VZIvaTu5u5pQewq0EqNZvIEMCmxAawK0DNkay9QmfFNAJUXfgGgUkLaE7j/h8fnASkxHTz0DGIBMCnBeeM7AArpUd3mz2x3C7wADglA8BcWMZhZAAAAAElFTkSuQmCC" style="rotate: 270deg; width: 94px"/></a><div style="clear: both"></div></nav></div></div><nav class="nav-wide-wrapper" aria-label="Page navigation"><a rel="next prefetch" href="examples.html" class="nav-chapters previous" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right" style="display: flex; justify-content: middle; align-items: center; width: 50%">&lt;</a></nav></div><script>const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}</script><script>window.playground_copyable = true;</script><script src="elasticlunr.min.js"></script><script src="mark.min.js"></script><script src="searcher.js"></script><script src="clipboard.min.js"></script><script src="highlight.js"></script><script src="book.js"></script></div></body></html>

58
packages/app/package.json Normal file
View file

@ -0,0 +1,58 @@
{
"private": true,
"name": "monaco-lsp-streams",
"description": "",
"version": "0.0.0",
"license": "Apache-2.0 WITH LLVM-exception",
"author": {
"name": "silvanshade",
"email": "silvanshade@users.noreply.github.com",
"url": "https://github.com/silvanshade"
},
"homepage": "https://github.com/silvanshade/monaco-lsp-streams#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/silvanshade/monaco-lsp-streams.git"
},
"bugs": {
"url": "https://github.com/silvanshade/monaco-lsp-streams/issues"
},
"main": "index.js",
"scripts": {
"build": "./build.sh",
"format": "prettier --write '**/*.{js,json,ts,tsx,yml,yaml}'",
"lint": "eslint 'src/**/*.{js,ts,tsx}' && prettier --check '**/*.{json,yml,yaml}'",
"app": "webpack serve --open"
},
"devDependencies": {
"@types/debounce": "^1.2.1",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"esbuild-loader": "^2.19.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"path-browserify": "^1.0.1",
"prettier": "^2.6.2",
"source-map-loader": "^4.0.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.1",
"typescript": "^4.7.3",
"webpack": "^5.73.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.1"
},
"dependencies": {
"debounce": "^1.2.1",
"json-rpc-2.0": "^1.3.0",
"monaco-editor-core": "^0.33.0",
"monaco-languageclient": "^1.0.1",
"vscode-languageserver-protocol": "^3.17.1"
}
}

118
packages/app/src/app.ts Normal file
View file

@ -0,0 +1,118 @@
import debounce from "debounce";
import * as monaco from "monaco-editor-core";
import { MonacoToProtocolConverter } from "monaco-languageclient";
import * as proto from "vscode-languageserver-protocol";
import Client from "./client";
import { FromServer, IntoServer } from "./codec";
import Language from "./language";
import Server from "./server";
class Environment implements monaco.Environment {
getWorkerUrl(moduleId: string, label: string) {
if (label === "editorWorkerService") {
return "./editor.worker.bundle.js";
}
throw new Error(`getWorkerUrl: unexpected ${JSON.stringify({ moduleId, label })}`);
}
}
const monacoToProtocol = new MonacoToProtocolConverter(monaco);
export default class App {
readonly #window: Window & monaco.Window & typeof globalThis = self;
readonly #intoServer: IntoServer = new IntoServer();
readonly #fromServer: FromServer = FromServer.create();
initializeMonaco(): void {
this.#window.MonacoEnvironment = new Environment();
}
createModel(client: Client): monaco.editor.ITextModel {
const language = Language.initialize(client);
const value = `use { host } from std
effect Make: async + throws + execs + reads + writes {
catch() [throws]
await<T>(f: Future<T>) [async, throws] -> T
exec(arg0: string, args: stringvec) [Make] -> i32
}
struct Local {
host: host
}
impl Make for Local {
fn catch(self) [throws] {
}
fn await<T>(f: Future<T>) [async, trhows] -> T {
yield()
}
fn exec(self, arg0: string, args: vec<string>) [Vm] -> i32 {
self.host.read("jobserver")
if self.host.exec(arg0, args) {
raise(1)
}
}
}`.replace(/^\s*\n/gm, "");
const id = language.id;
const uri = monaco.Uri.parse("inmemory://exec.src");
const model = monaco.editor.createModel(value, id, uri);
model.onDidChangeContent(
debounce(() => {
const text = model.getValue();
client.notify(proto.DidChangeTextDocumentNotification.type.method, {
textDocument: {
version: 0,
uri: model.uri.toString(),
},
contentChanges: [
{
range: monacoToProtocol.asRange(model.getFullModelRange()),
text,
},
],
} as proto.DidChangeTextDocumentParams);
}, 200),
);
// eslint-disable-next-line @typescript-eslint/require-await
client.pushAfterInitializeHook(async () => {
client.notify(proto.DidOpenTextDocumentNotification.type.method, {
textDocument: {
uri: model.uri.toString(),
languageId: language.id,
version: 0,
text: model.getValue(),
},
} as proto.DidOpenTextDocumentParams);
});
return model;
}
createEditor(client: Client): void {
// get .content main element and set the max-width to 100%
document.querySelector(".content")?.querySelector("main")?.setAttribute("style", "max-width: 100%;");
// get nav-chapters previous element and set the display to none
document.querySelector(".nav-chapters")?.setAttribute("style", "display: none;");
const container = document.getElementById("editor")!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
this.initializeMonaco();
const model = this.createModel(client);
monaco.editor.create(container, {
model,
automaticLayout: true,
});
}
async run(): Promise<void> {
const client = new Client(this.#fromServer, this.#intoServer);
const server = await Server.initialize(this.#intoServer, this.#fromServer);
this.createEditor(client);
await Promise.all([server.start(), client.start()]);
}
}

View file

@ -0,0 +1,87 @@
import * as jsrpc from "json-rpc-2.0";
import * as proto from "vscode-languageserver-protocol";
import { Codec, FromServer, IntoServer } from "./codec";
const consoleChannel = document.getElementById("channel-console") as HTMLTextAreaElement;
export default class Client extends jsrpc.JSONRPCServerAndClient {
afterInitializedHooks: (() => Promise<void>)[] = [];
#fromServer: FromServer;
constructor(fromServer: FromServer, intoServer: IntoServer) {
super(
new jsrpc.JSONRPCServer(),
new jsrpc.JSONRPCClient(async (json: jsrpc.JSONRPCRequest) => {
const encoded = Codec.encode(json);
intoServer.enqueue(encoded);
if (null != json.id) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const response = await fromServer.responses.get(json.id)!;
this.client.receive(response as jsrpc.JSONRPCResponse);
}
}),
);
this.#fromServer = fromServer;
}
async start(): Promise<void> {
// process "window/logMessage": client <- server
this.addMethod(proto.LogMessageNotification.type.method, (params) => {
const { type, message } = params as { type: proto.MessageType; message: string };
switch (type) {
case proto.MessageType.Error: {
consoleChannel.value += "[error] ";
break;
}
case proto.MessageType.Warning: {
consoleChannel.value += " [warn] ";
break;
}
case proto.MessageType.Info: {
consoleChannel.value += " [info] ";
break;
}
case proto.MessageType.Log: {
consoleChannel.value += " [log] ";
break;
}
}
consoleChannel.value += message;
consoleChannel.value += "\n";
return;
});
// request "initialize": client <-> server
await (this.request(proto.InitializeRequest.type.method, {
processId: null,
clientInfo: {
name: "demo-language-client",
},
capabilities: {},
rootUri: null,
} as proto.InitializeParams) as Promise<jsrpc.JSONRPCResponse>);
// notify "initialized": client --> server
this.notify(proto.InitializedNotification.type.method, {});
await Promise.all(this.afterInitializedHooks.map((f: () => Promise<void>) => f()));
await Promise.all([this.processNotifications(), this.processRequests()]);
}
async processNotifications(): Promise<void> {
for await (const notification of this.#fromServer.notifications) {
await this.receiveAndSend(notification);
}
}
async processRequests(): Promise<void> {
for await (const request of this.#fromServer.requests) {
await this.receiveAndSend(request);
}
}
pushAfterInitializeHook(...hooks: (() => Promise<void>)[]): void {
this.afterInitializedHooks.push(...hooks);
}
}

46
packages/app/src/codec.ts Normal file
View file

@ -0,0 +1,46 @@
import * as jsrpc from "json-rpc-2.0";
import * as vsrpc from "vscode-jsonrpc";
import Bytes from "./codec/bytes";
import StreamDemuxer from "./codec/demuxer";
import Headers from "./codec/headers";
import Queue from "./codec/queue";
import Tracer from "./tracer";
export const encoder = new TextEncoder();
export const decoder = new TextDecoder();
export class Codec {
static encode(json: jsrpc.JSONRPCRequest | jsrpc.JSONRPCResponse): Uint8Array {
const message = JSON.stringify(json);
const delimited = Headers.add(message);
return Bytes.encode(delimited);
}
static decode<T>(data: Uint8Array): T {
const delimited = Bytes.decode(data);
const message = Headers.remove(delimited);
return JSON.parse(message) as T;
}
}
// FIXME: tracing effiency
export class IntoServer extends Queue<Uint8Array> implements AsyncGenerator<Uint8Array, never, void> {
enqueue(item: Uint8Array): void {
Tracer.client(Headers.remove(decoder.decode(item)));
super.enqueue(item);
}
}
export interface FromServer extends WritableStream<Uint8Array> {
readonly responses: { get(key: number | string): null | Promise<vsrpc.ResponseMessage> };
readonly notifications: AsyncGenerator<vsrpc.NotificationMessage, never, void>;
readonly requests: AsyncGenerator<vsrpc.RequestMessage, never, void>;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace FromServer {
export function create(): FromServer {
return new StreamDemuxer();
}
}

View file

@ -0,0 +1,28 @@
import { encoder, decoder } from "../codec";
export default class Bytes {
static encode(input: string): Uint8Array {
return encoder.encode(input);
}
static decode(input: Uint8Array): string {
return decoder.decode(input);
}
static append<T extends { length: number; set(arr: T, offset: number): void }>(
constructor: { new (length: number): T },
...arrays: T[]
) {
let totalLength = 0;
for (const arr of arrays) {
totalLength += arr.length;
}
const result = new constructor(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
}

View file

@ -0,0 +1,73 @@
import * as vsrpc from "vscode-jsonrpc";
import Bytes from "./bytes";
import PromiseMap from "./map";
import Queue from "./queue";
import Tracer from "../tracer";
export default class StreamDemuxer extends Queue<Uint8Array> {
readonly responses: PromiseMap<number | string, vsrpc.ResponseMessage> = new PromiseMap();
readonly notifications: Queue<vsrpc.NotificationMessage> = new Queue<vsrpc.NotificationMessage>();
readonly requests: Queue<vsrpc.RequestMessage> = new Queue<vsrpc.RequestMessage>();
readonly #start: Promise<void>;
constructor() {
super();
this.#start = this.start();
}
private async start(): Promise<void> {
let contentLength: null | number = null;
let buffer = new Uint8Array();
for await (const bytes of this) {
buffer = Bytes.append(Uint8Array, buffer, bytes);
// check if the content length is known
if (null == contentLength) {
// if not, try to match the prefixed headers
const match = Bytes.decode(buffer).match(/^Content-Length:\s*(\d+)\s*/);
if (null == match) continue;
// try to parse the content-length from the headers
const length = parseInt(match[1]);
if (isNaN(length)) throw new Error("invalid content length");
// slice the headers since we now have the content length
buffer = buffer.slice(match[0].length);
// set the content length
contentLength = length;
}
// if the buffer doesn't contain a full message; await another iteration
if (buffer.length < contentLength) continue;
// decode buffer to a string
const delimited = Bytes.decode(buffer);
// reset the buffer
buffer = buffer.slice(contentLength);
// reset the contentLength
contentLength = null;
const message = JSON.parse(delimited) as vsrpc.Message;
Tracer.server(message);
// demux the message stream
if (vsrpc.Message.isResponse(message) && null != message.id) {
this.responses.set(message.id, message);
continue;
}
if (vsrpc.Message.isNotification(message)) {
this.notifications.enqueue(message);
continue;
}
if (vsrpc.Message.isRequest(message)) {
this.requests.enqueue(message);
continue;
}
}
}
}

View file

@ -0,0 +1,9 @@
export default class Headers {
static add(message: string): string {
return `Content-Length: ${message.length}\r\n\r\n${message}`;
}
static remove(delimited: string): string {
return delimited.replace(/^Content-Length:\s*\d+\s*/, "");
}
}

View file

@ -0,0 +1,68 @@
export default class PromiseMap<K, V extends { toString(): string }> {
#map: Map<K, PromiseMap.Entry<V>> = new Map();
get(key: K & { toString(): string }): null | Promise<V> {
let initialized: PromiseMap.Entry<V>;
// if the entry doesn't exist, set it
if (!this.#map.has(key)) {
initialized = this.#set(key);
} else {
// otherwise return the entry
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
initialized = this.#map.get(key)!;
}
// if the entry is a pending promise, return it
if (initialized.status === "pending") {
return initialized.promise;
} else {
// otherwise return null
return null;
}
}
#set(key: K, value?: V): PromiseMap.Entry<V> {
if (this.#map.has(key)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.#map.get(key)!;
}
// placeholder resolver for entry
let resolve = (item: V) => {
void item;
};
// promise for entry (which assigns the resolver
const promise = new Promise<V>((resolver) => {
resolve = resolver;
});
// the initialized entry
const initialized: PromiseMap.Entry<V> = { status: "pending", resolve, promise };
if (null != value) {
initialized.resolve(value);
}
// set the entry
this.#map.set(key, initialized);
return initialized;
}
set(key: K & { toString(): string }, value: V): this {
const initialized = this.#set(key, value);
// if the promise is pending ...
if (initialized.status === "pending") {
// ... set the entry status to resolved to free the promise
this.#map.set(key, { status: "resolved" });
// ... and resolve the promise with the given value
initialized.resolve(value);
}
return this;
}
get size(): number {
return this.#map.size;
}
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace PromiseMap {
export type Entry<V> =
| { status: "pending"; resolve: (item: V) => void; promise: Promise<V> }
| { status: "resolved" };
}

View file

@ -0,0 +1,103 @@
export default class Queue<T> implements WritableStream<T>, AsyncGenerator<T, never, void> {
readonly #promises: Promise<T>[] = [];
readonly #resolvers: ((item: T) => void)[] = [];
readonly #observers: ((item: T) => void)[] = [];
#closed = false;
#locked = false;
readonly #stream: WritableStream<T>;
static #__add<X>(promises: Promise<X>[], resolvers: ((item: X) => void)[]): void {
promises.push(
new Promise((resolve) => {
resolvers.push(resolve);
}),
);
}
static #__enqueue<X>(closed: boolean, promises: Promise<X>[], resolvers: ((item: X) => void)[], item: X): void {
if (!closed) {
if (!resolvers.length) Queue.#__add(promises, resolvers);
const resolve = resolvers.shift()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
resolve(item);
}
}
constructor() {
const closed = this.#closed;
const promises = this.#promises;
const resolvers = this.#resolvers;
this.#stream = new WritableStream({
write(item: T): void {
Queue.#__enqueue(closed, promises, resolvers, item);
},
});
}
#add(): void {
return Queue.#__add(this.#promises, this.#resolvers);
}
enqueue(item: T): void {
return Queue.#__enqueue(this.#closed, this.#promises, this.#resolvers, item);
}
dequeue(): Promise<T> {
if (!this.#promises.length) this.#add();
const item = this.#promises.shift()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
return item;
}
isEmpty(): boolean {
return !this.#promises.length;
}
isBlocked(): boolean {
return !!this.#resolvers.length;
}
get length(): number {
return this.#promises.length - this.#resolvers.length;
}
async next(): Promise<IteratorResult<T, never>> {
const done = false;
const value = await this.dequeue();
for (const observer of this.#observers) {
observer(value);
}
return { done, value };
}
return(): Promise<IteratorResult<T, never>> {
return new Promise(() => {
// empty
});
}
throw(err: Error): Promise<IteratorResult<T, never>> {
return new Promise((_resolve, reject) => {
reject(err);
});
}
[Symbol.asyncIterator](): AsyncGenerator<T, never, void> {
return this;
}
get locked(): boolean {
return this.#stream.locked;
}
abort(reason?: Error): Promise<void> {
return this.#stream.abort(reason);
}
close(): Promise<void> {
return this.#stream.close();
}
getWriter(): WritableStreamDefaultWriter<T> {
return this.#stream.getWriter();
}
}

View file

@ -0,0 +1,6 @@
import "../assets/index.module.css";
import App from "./app";
const app = new App();
app.run().catch(console.error);

View file

@ -0,0 +1,69 @@
// import * as jsrpc from "json-rpc-2.0";
import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from "monaco-languageclient";
import * as monaco from "monaco-editor-core";
import * as proto from "vscode-languageserver-protocol";
import Client from "./client";
export const monacoToProtocol = new MonacoToProtocolConverter(monaco);
export const protocolToMonaco = new ProtocolToMonacoConverter(monaco);
let language: null | Language;
export default class Language implements monaco.languages.ILanguageExtensionPoint {
readonly id: string;
readonly aliases: string[];
readonly extensions: string[];
readonly mimetypes: string[];
private constructor(client: Client) {
const { id, aliases, extensions, mimetypes } = Language.extensionPoint();
this.id = id;
this.aliases = aliases;
this.extensions = extensions;
this.mimetypes = mimetypes;
this.registerLanguage(client);
}
static extensionPoint(): monaco.languages.ILanguageExtensionPoint & {
aliases: string[];
extensions: string[];
mimetypes: string[];
} {
const id = "src lang";
const aliases = ["src"];
const extensions = [".src"];
const mimetypes = ["text/src"];
return { id, extensions, aliases, mimetypes };
}
private registerLanguage(client: Client): void {
void client;
monaco.languages.register(Language.extensionPoint());
monaco.languages.registerDocumentSymbolProvider(this.id, {
// eslint-disable-next-line
async provideDocumentSymbols(model, token): Promise<monaco.languages.DocumentSymbol[]> {
void token;
const response = await (client.request(proto.DocumentSymbolRequest.type.method, {
textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
} as proto.DocumentSymbolParams) as Promise<proto.SymbolInformation[]>);
const uri = model.uri.toString();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const result: monaco.languages.DocumentSymbol[] = protocolToMonaco.asSymbolInformations(response, uri);
return result;
},
});
}
static initialize(client: Client): Language {
if (null == language) {
language = new Language(client);
} else {
console.warn("Language already initialized; ignoring");
}
return language;
}
}

View file

@ -0,0 +1,31 @@
import init, { InitOutput, serve, ServerConfig } from "../assets/wasm/src_lsp_browser";
import { FromServer, IntoServer } from "./codec";
let server: null | Server;
export default class Server {
readonly initOutput: InitOutput;
readonly #intoServer: IntoServer;
readonly #fromServer: FromServer;
private constructor(initOutput: InitOutput, intoServer: IntoServer, fromServer: FromServer) {
this.initOutput = initOutput;
this.#intoServer = intoServer;
this.#fromServer = fromServer;
}
static async initialize(intoServer: IntoServer, fromServer: FromServer): Promise<Server> {
if (null == server) {
const initOutput = await init();
server = new Server(initOutput, intoServer, fromServer);
} else {
console.warn("Server already initialized; ignoring");
}
return server;
}
async start(): Promise<void> {
const config = new ServerConfig(this.#intoServer, this.#fromServer);
await serve(config);
}
}

View file

@ -0,0 +1,17 @@
import * as proto from "vscode-languageserver-protocol";
const clientChannel = document.getElementById("channel-client") as HTMLTextAreaElement;
const serverChannel = document.getElementById("channel-server") as HTMLTextAreaElement;
export default class Tracer {
static client(message: string): void {
clientChannel.value += message;
clientChannel.value += "\n";
}
static server(input: string | proto.Message): void {
const message: string = typeof input === "string" ? input : JSON.stringify(input);
serverChannel.value += message;
serverChannel.value += "\n";
}
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"exclude": []
}

View file

@ -0,0 +1,94 @@
// @ts-check
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
const webpack = require("webpack");
/** @type {import("webpack").Configuration & { devServer?: import("webpack-dev-server").Configuration } } */
const config = {
experiments: {
asyncWebAssembly: true,
},
mode: "production",
target: "web",
entry: {
app: "./src/index.ts",
"editor.worker": "monaco-editor-core/esm/vs/editor/editor.worker.js",
},
resolve: {
alias: {
vscode: require.resolve("monaco-languageclient/vscode-compatibility"),
},
extensions: [".ts", ".js", ".json", ".ttf"],
fallback: {
fs: false,
child_process: false,
net: false,
crypto: false,
path: require.resolve("path-browserify"),
},
},
output: {
globalObject: "self",
filename: "[name].bundle.js",
path: path.resolve(__dirname, "../../book/playground"),
},
module: {
rules: [
{
test: /\.ts?$/,
loader: "esbuild-loader",
options: {
loader: "ts",
target: "es2022",
minify: true,
},
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
},
],
},
plugins: [
new webpack.ProgressPlugin(),
new CleanWebpackPlugin(),
// new CopyWebpackPlugin({
// patterns: [
// {
// from: "../../node_modules/web-tree-sitter/tree-sitter.wasm",
// },
// ],
// }),
new HtmlWebpackPlugin({
template: "../../book/playground/index.html",
scriptLoading: "module",
title: "src-lsp Playground",
}),
],
optimization: {
minimize: true,
},
performance: {
hints: false,
},
devServer: {
static: {
directory: path.join(__dirname, "dist"),
},
compress: true,
port: 9000,
client: {
progress: true,
reconnect: false,
},
},
};
module.exports = config;

View file

@ -8,7 +8,7 @@ use salsa::DebugWithDb;
#[derive(Default)]
#[salsa::db(crate::Jar)]
pub(crate) struct Database {
pub struct Database {
storage: salsa::Storage<Self>,
// The logs are only used for testing and demonstrating reuse:

View file

@ -1,21 +1,22 @@
use std::{
collections::BTreeMap,
};
use std::collections::BTreeMap;
use okstd::prelude::debug;
use crate::{
compiler::{errors::Errors},
compiler::errors::Errors,
parser::ast::{self},
Db,
};
use self::text::SourceProgram;
mod db;
mod errors;
pub mod db;
pub mod errors;
pub mod ir;
mod tests;
pub mod text;
#[cfg(test)]
mod tests;
#[salsa::tracked]
pub fn compile(db: &dyn Db, src: SourceProgram) -> ir::Program {
@ -23,11 +24,15 @@ pub fn compile(db: &dyn Db, src: SourceProgram) -> ir::Program {
let wrapper = crate::lexer::TripleIterator::new(src.text(db));
let t = crate::parser::src::SourceParser::new().parse(&mut errors, wrapper);
// let mut errors_in_positions: Vec<ir::Position> = vec![];
if !errors.is_empty() {
let spans = text::to_spans(db, src);
let _tokens = spans.tokens(db);
for _error_range in Into::<Errors>::into(errors) {
text::to_spans(db, src);
}
panic!();
// panic!();
}
let modul = t.unwrap();
@ -42,7 +47,9 @@ pub fn compile(db: &dyn Db, src: SourceProgram) -> ir::Program {
ast::Expression::Binding(_) => todo!(),
ast::Expression::FnCall(_) => todo!(),
ast::Expression::String(_) => todo!(),
ast::Expression::FnDef(_) => {}
ast::Expression::FnDef(_) => {
debug!("Function definition");
}
ast::Expression::ShellCommand(_, _) => todo!(),
ast::Expression::EffectDef(_) => todo!(),
ast::Expression::StructDef(_) => todo!(),

View file

@ -1,17 +1,10 @@
#[cfg(test)]
use okstd::prelude::*;
#[okstd::test]
use super::*;
#[okstd::log(debug)]
#[okstd::test]
fn debug() {
use salsa::{database::AsSalsaDatabase, storage::HasJarsDyn};
debug!("hello");
use super::{db, text::SourceProgram};
let src = r#"use { native_fs, native_exec } from host
use { fs } from std
@ -21,7 +14,7 @@ struct Innitguv {
current_pid: i32
}
"#;
let db = &crate::compiler::db::Database::default().enable_logging();
let db = &db::Database::default().enable_logging();
let prog = SourceProgram::new(db, src.to_string());
let res = super::compile(db, prog);

View file

@ -1,7 +1,8 @@
use std::ops::Range;
use bitflags::bitflags;
use crate::Db;
use bitflags::bitflags;
use okstd::prelude::*;
/// Represents the source program text.
#[salsa::input]
@ -30,9 +31,8 @@ pub struct Spanned {
#[salsa::interned]
pub struct Span {
/// The range of the span in the source program text.
pub span: (usize, usize),
pub span: Range<usize>
}
/// Represents a position in the source code.
#[salsa::interned]
pub struct Position {
@ -80,7 +80,6 @@ fn cmp_range<T: Ord>(a: &Range<T>, b: &Range<T>) -> SpanOverlap {
overlap
}
/// todo(sevki): split this into two functions
#[salsa::tracked]
pub fn to_spans(db: &dyn Db, src: SourceProgram) -> SourceMap {
@ -109,20 +108,25 @@ pub fn to_spans(db: &dyn Db, src: SourceProgram) -> SourceMap {
let _size = token.end - token.start;
// then we peek at the first line
let mut start: Option<(usize, usize)> = None;
loop {
if let Some((line_no, span)) = line_lengths.clone().peek() {
while let Some((line_no, span)) = line_lengths.clone().peek() {
// if the token is within the line
let overlap = cmp_range(&span, &(token.start..token.end));
if overlap == SpanOverlap::NONE && start.is_none() {
// if the token is not within the line
line_lengths.next();
}
if overlap == SpanOverlap::START || overlap == SpanOverlap::BOTH {
// if the token is within the line
let overlap = cmp_range(&span, &(token.start..token.end));
if overlap == SpanOverlap::NONE && start.is_none() {
// if the token is not within the line
line_lengths.next();
}
if overlap == SpanOverlap::START || overlap == SpanOverlap::BOTH {
// if the token is within the line
start = Some((*line_no, span.start));
// we do not need to iterate more.
break;
}
start = Some((*line_no, span.start));
// we do not need to iterate more.
break;
}
if overlap == SpanOverlap::END {
// if the token is within the line
start = Some((*line_no, span.start));
// we do not need to iterate more.
break;
}
}
@ -146,7 +150,7 @@ pub fn to_spans(db: &dyn Db, src: SourceProgram) -> SourceMap {
*/
spans.push(Spanned::new(
db,
Span::new(db, (token.start, token.end)),
Span::new(db, token.start..token.end),
src,
Position::new(db, start.0, column),
));

View file

@ -1,4 +1,4 @@
#[cfg(test)]
use proptest::{prelude::*};
#[allow(unused_imports)]

View file

@ -1,9 +1,10 @@
#[cfg(test)]
use crate::lexer::{Lexer, TokenStreamDisplay};
use insta::assert_snapshot;
use okstd::prelude::*;
#[cfg(test)]
#[okstd::test]
fn test_empty_lexer() {
let input = " ";

View file

@ -4,10 +4,8 @@ lexer.rs is a lexer for the src language
use std::{fmt::Display, iter::Iterator, iter::Peekable, str::Chars};
use okstd::prelude::*;
// Identifier
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Variable<'input> {
@ -557,9 +555,9 @@ impl<'input> Lexer<'input> {
};
Ok(Token::Word(word))
}
State::String(Quotation) => {
State::String(quotation) => {
let last_char = self.buffer.chars().last();
let quote = if Quotation == Quotation::Double {
let quote = if quotation == Quotation::Double {
Some('"')
} else {
Some('\'')
@ -962,7 +960,9 @@ impl<'input> From<Vec<Spanned<Token<'input>>>> for TokenStreamDisplay<'input> {
}
}
#[cfg(test)]
mod lexer_prop_tests;
#[cfg(test)]
mod lexer_snap_tests;
pub struct TripleIterator<'input>(Lexer<'input>);

View file

@ -1,7 +1,5 @@
use std::fmt::Display;
use proptest::prelude::*;
pub const ANON_FN_NAME: &str = "anonymous";
#[derive(PartialEq, Debug, Clone)]
@ -133,21 +131,6 @@ pub enum Operator {
Neg,
}
impl Arbitrary for Operator {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: ()) -> Self::Strategy {
prop_oneof![
Just(Operator::Add),
Just(Operator::Sub),
Just(Operator::Mul),
Just(Operator::Div),
]
.boxed()
}
}
impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let op = match self {
@ -185,4 +168,25 @@ pub struct ImplDef(pub Ident, pub Option<Ident>, pub Block<Box<Expression>>);
pub struct Branch(pub Box<Expression>, pub Vec<(Expression, Block<Box<Expression>>)>);
#[derive(PartialEq, Debug)]
pub struct Module(pub Vec<Box<Expression>>);
pub struct Module(pub Vec<Box<Expression>>);
#[cfg(test)]
use proptest::prelude::*;
#[cfg(test)]
impl Arbitrary for Operator {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: ()) -> Self::Strategy {
prop_oneof![
Just(Operator::Add),
Just(Operator::Sub),
Just(Operator::Mul),
Just(Operator::Div),
]
.boxed()
}
}

View file

@ -1,4 +1,4 @@
#[cfg(test)]
mod parser_snap_tests;
mod string;
pub mod ast;

View file

@ -1,384 +0,0 @@
---
source: src/parser/parser_snap_tests.rs
expression: "format!(\"{:#?}\", t.unwrap())"
---
Module(
[
UseDef(
UseDef(
[
Ident(
"crosmvm",
None,
),
],
Ident(
"std",
None,
),
),
),
EffectDef(
EffectDef(
Ident(
"Vm",
None,
),
[
Ident(
"async",
None,
),
Ident(
"throws",
None,
),
Ident(
"execs",
None,
),
],
Block(
[
Prototype {
name: Ident(
"catch",
None,
),
args: [],
ret: None,
effects: [
Ident(
"throws",
None,
),
],
},
Prototype {
name: Ident(
"await",
Some(
[
Ident(
"T",
None,
),
],
),
),
args: [
Field(
Field(
Ident(
"f",
None,
),
Ident(
"Future",
Some(
[
Ident(
"T",
None,
),
],
),
),
),
),
],
ret: Some(
Ident(
"T",
None,
),
),
effects: [
Ident(
"async",
None,
),
Ident(
"throws",
None,
),
],
},
Prototype {
name: Ident(
"exec",
None,
),
args: [
Field(
Field(
Ident(
"arg0",
None,
),
Ident(
"string",
None,
),
),
),
Field(
Field(
Ident(
"args",
None,
),
Ident(
"stringvec",
None,
),
),
),
],
ret: Some(
Ident(
"i32",
None,
),
),
effects: [
Ident(
"Vm",
None,
),
],
},
],
),
),
),
StructDef(
StructDef(
Ident(
"coopvm",
None,
),
Block(
[],
),
),
),
ImplDef(
ImplDef(
Ident(
"Vm",
None,
),
Some(
Ident(
"coopvm",
None,
),
),
Block(
[
FnDef(
FnDef(
Prototype {
name: Ident(
"catch",
None,
),
args: [
Reciever,
],
ret: None,
effects: [
Ident(
"throws",
None,
),
],
},
Block(
[],
),
[],
),
),
FnDef(
FnDef(
Prototype {
name: Ident(
"await",
Some(
[
Ident(
"T",
None,
),
],
),
),
args: [
Field(
Field(
Ident(
"f",
None,
),
Ident(
"Future",
Some(
[
Ident(
"T",
None,
),
],
),
),
),
),
],
ret: Some(
Ident(
"T",
None,
),
),
effects: [
Ident(
"async",
None,
),
Ident(
"trhows",
None,
),
],
},
Block(
[
FnCall(
FnCall(
Ident(
"yield",
None,
),
[],
),
),
],
),
[],
),
),
FnDef(
FnDef(
Prototype {
name: Ident(
"exec",
None,
),
args: [
Reciever,
Field(
Field(
Ident(
"arg0",
None,
),
Ident(
"string",
None,
),
),
),
Field(
Field(
Ident(
"args",
None,
),
Ident(
"vec",
Some(
[
Ident(
"string",
None,
),
],
),
),
),
),
],
ret: Some(
Ident(
"i32",
None,
),
),
effects: [
Ident(
"Vm",
None,
),
],
},
Block(
[
Branch(
Branch(
FnCall(
FnCall(
Ident(
"self.exec",
None,
),
[
Ident(
Ident(
"arg0",
None,
),
),
Ident(
Ident(
"args",
None,
),
),
],
),
),
[
(
Bool(
true,
),
Block(
[
FnCall(
FnCall(
Ident(
"raise",
None,
),
[],
),
),
],
),
),
],
),
),
],
),
[],
),
),
],
),
),
),
],
)

View file

@ -1,6 +1,5 @@
---
source: src/parser/parser_snap_tests.rs
assertion_line: 110
expression: "format!(\"{:#?}\", t.unwrap())"
---
Module(

21
tsconfig.json Normal file
View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "ES2022"],
"module": "ES2022",
"moduleResolution": "node",
"newLine": "lf",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
// "sourceMap": true,
},
"exclude": ["."],
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
},
},
}