Compare commits
7 Commits
cfa6e7e5ba
...
91e9645c37
Author | SHA1 | Date | |
---|---|---|---|
![]() |
91e9645c37 | ||
![]() |
4b9e84375e | ||
![]() |
371d74e691 | ||
![]() |
40bf188c5a | ||
![]() |
65aba3c993 | ||
![]() |
0d2a13739a | ||
![]() |
0331c06875 |
184
Cargo.lock
generated
184
Cargo.lock
generated
@ -30,18 +30,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.6.20"
|
version = "0.6.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/tokio-rs/axum.git?rev=52a90390195e884bcc12ff5bd9fd805cac806447#52a90390195e884bcc12ff5bd9fd805cac806447"
|
||||||
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"bitflags",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body 0.4.5",
|
||||||
"hyper",
|
"hyper 0.14.27",
|
||||||
|
"hyper 1.0.0-rc.4",
|
||||||
"itoa",
|
"itoa",
|
||||||
"matchit",
|
"matchit",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -56,6 +55,7 @@ dependencies = [
|
|||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
|
"tower-hyper-http-body-compat",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
@ -63,16 +63,17 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/tokio-rs/axum.git?rev=52a90390195e884bcc12ff5bd9fd805cac806447#52a90390195e884bcc12ff5bd9fd805cac806447"
|
||||||
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body 0.4.5",
|
||||||
"mime",
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
|
"sync_wrapper",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
@ -94,9 +95,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
@ -149,6 +150,12 @@ version = "0.3.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@ -201,6 +208,22 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "1.0.0-rc.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "951dfc2e32ac02d67c90c0d65bd27009a635dc9b381a2cc7d284ab01e3a0150d"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range-header"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@ -224,7 +247,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body 0.4.5",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -236,6 +259,26 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "1.0.0-rc.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d280a71f348bcc670fc55b02b63c53a04ac0bf2daff2980795aeaf53edae10e6"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body 1.0.0-rc.2",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@ -272,6 +315,16 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -339,7 +392,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"nom",
|
"nom",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -421,6 +476,20 @@ name = "serde"
|
|||||||
version = "1.0.183"
|
version = "1.0.183"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
|
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.183"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
@ -455,6 +524,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@ -499,14 +577,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2 0.5.3",
|
"socket2 0.5.3",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@ -523,6 +628,46 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body 0.4.5",
|
||||||
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-hyper-http-body-compat"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7ea3e622710ee44a8255baa6fcb2a34d67450e2ffb48e5e58d5a7bd6ff55a21"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"http-body 0.4.5",
|
||||||
|
"http-body 1.0.0-rc.2",
|
||||||
|
"hyper 1.0.0-rc.4",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -562,12 +707,27 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -4,6 +4,8 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.6.20"
|
axum = { git = "https://github.com/tokio-rs/axum.git", rev = "52a90390195e884bcc12ff5bd9fd805cac806447" }
|
||||||
nom = "7.1.1"
|
nom = "7.1.1"
|
||||||
tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread"] }
|
serde = { version = "1.0.183", features = ["derive"] }
|
||||||
|
tokio = { version = "1.30.0", default-features = false, features = ["macros", "process", "rt", "rt-multi-thread"] }
|
||||||
|
tower-http = { version = "0.4.3", features = ["fs"] }
|
||||||
|
25
src/error.rs
Normal file
25
src/error.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use nom::error::ErrorKind;
|
||||||
|
use nom::error::ParseError;
|
||||||
|
use nom::IResult;
|
||||||
|
|
||||||
|
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum CustomError<I> {
|
||||||
|
MyError(MyError<I>),
|
||||||
|
Nom(I, ErrorKind),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct MyError<I>(pub I);
|
||||||
|
|
||||||
|
impl<I> ParseError<I> for CustomError<I> {
|
||||||
|
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
|
||||||
|
CustomError::Nom(input, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self {
|
||||||
|
// Doesn't do append like VerboseError
|
||||||
|
other
|
||||||
|
}
|
||||||
|
}
|
38
src/main.rs
38
src/main.rs
@ -1,3 +1,37 @@
|
|||||||
fn main() {
|
#![feature(exit_status_error)]
|
||||||
println!("Hello, world!");
|
use axum::{http::StatusCode, routing::post, Json, Router};
|
||||||
|
use owner_tree::{build_owner_tree, OwnerTree};
|
||||||
|
use parse::emacs_parse_org_document;
|
||||||
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod owner_tree;
|
||||||
|
mod parse;
|
||||||
|
mod sexp;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let serve_dir = ServeDir::new("static").not_found_service(ServeFile::new("static/index.html"));
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/parse", post(parse_org_mode))
|
||||||
|
.fallback_service(serve_dir);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse_org_mode(
|
||||||
|
body: String,
|
||||||
|
) -> Result<(StatusCode, Json<OwnerTree>), (StatusCode, String)> {
|
||||||
|
_parse_org_mode(body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _parse_org_mode(
|
||||||
|
body: String,
|
||||||
|
) -> Result<(StatusCode, Json<OwnerTree>), Box<dyn std::error::Error>> {
|
||||||
|
let ast = emacs_parse_org_document(&body).await?;
|
||||||
|
let owner_tree = build_owner_tree(body.as_str(), ast.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
Ok((StatusCode::OK, Json(owner_tree)))
|
||||||
}
|
}
|
||||||
|
217
src/owner_tree.rs
Normal file
217
src/owner_tree.rs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::sexp::{sexp_with_padding, Token};
|
||||||
|
|
||||||
|
pub fn build_owner_tree<'a>(
|
||||||
|
body: &'a str,
|
||||||
|
ast_raw: &'a str,
|
||||||
|
) -> Result<OwnerTree, Box<dyn std::error::Error + 'a>> {
|
||||||
|
let (_remaining, parsed_sexp) = sexp_with_padding(ast_raw)?;
|
||||||
|
let lists = find_lists_in_document(body, &parsed_sexp)?;
|
||||||
|
|
||||||
|
Ok(OwnerTree {
|
||||||
|
input: body.to_owned(),
|
||||||
|
ast: ast_raw.to_owned(),
|
||||||
|
lists,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct OwnerTree {
|
||||||
|
input: String,
|
||||||
|
ast: String,
|
||||||
|
lists: Vec<PlainList>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct PlainList {
|
||||||
|
position: SourceRange,
|
||||||
|
items: Vec<PlainListItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct PlainListItem {
|
||||||
|
position: SourceRange,
|
||||||
|
lists: Vec<PlainList>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct SourceRange {
|
||||||
|
start_line: u32,
|
||||||
|
end_line: u32, // Exclusive
|
||||||
|
start_character: u32,
|
||||||
|
end_character: u32, // Exclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_lists_in_document<'a>(
|
||||||
|
original_source: &str,
|
||||||
|
current_token: &Token<'a>,
|
||||||
|
) -> Result<Vec<PlainList>, Box<dyn std::error::Error>> {
|
||||||
|
// DFS looking for top-level lists
|
||||||
|
|
||||||
|
let mut found_lists = Vec::new();
|
||||||
|
let children = current_token.as_list()?;
|
||||||
|
let token_name = "org-data";
|
||||||
|
assert_name(current_token, token_name)?;
|
||||||
|
|
||||||
|
// skip 2 to skip token name and standard properties
|
||||||
|
for child_token in children.iter().skip(2) {
|
||||||
|
found_lists.extend(recurse_token(original_source, child_token)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(found_lists)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recurse_token<'a>(
|
||||||
|
original_source: &str,
|
||||||
|
current_token: &Token<'a>,
|
||||||
|
) -> Result<Vec<PlainList>, Box<dyn std::error::Error>> {
|
||||||
|
match current_token {
|
||||||
|
Token::Atom(_) | Token::TextWithProperties(_) => Ok(Vec::new()),
|
||||||
|
Token::List(_) => {
|
||||||
|
let new_lists = find_lists_in_list(original_source, current_token)?;
|
||||||
|
Ok(new_lists)
|
||||||
|
}
|
||||||
|
Token::Vector(_) => {
|
||||||
|
let new_lists = find_lists_in_vector(original_source, current_token)?;
|
||||||
|
Ok(new_lists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_lists_in_list<'a>(
|
||||||
|
original_source: &str,
|
||||||
|
current_token: &Token<'a>,
|
||||||
|
) -> Result<Vec<PlainList>, Box<dyn std::error::Error>> {
|
||||||
|
let mut found_lists = Vec::new();
|
||||||
|
let children = current_token.as_list()?;
|
||||||
|
if assert_name(current_token, "plain-list").is_ok() {
|
||||||
|
// Found a list!
|
||||||
|
let mut found_items = Vec::new();
|
||||||
|
// skip 2 to skip token name and standard properties
|
||||||
|
for child_token in children.iter().skip(2) {
|
||||||
|
found_items.push(get_item_in_list(original_source, child_token)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
found_lists.push(PlainList {
|
||||||
|
position: get_bounds(original_source, current_token)?,
|
||||||
|
items: found_items,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// skip 2 to skip token name and standard properties
|
||||||
|
for child_token in children.iter().skip(2) {
|
||||||
|
found_lists.extend(recurse_token(original_source, child_token)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(found_lists)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_lists_in_vector<'a>(
|
||||||
|
original_source: &str,
|
||||||
|
current_token: &Token<'a>,
|
||||||
|
) -> Result<Vec<PlainList>, Box<dyn std::error::Error>> {
|
||||||
|
let mut found_lists = Vec::new();
|
||||||
|
let children = current_token.as_vector()?;
|
||||||
|
|
||||||
|
for child_token in children.iter() {
|
||||||
|
found_lists.extend(recurse_token(original_source, child_token)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(found_lists)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_item_in_list<'a>(
|
||||||
|
original_source: &str,
|
||||||
|
current_token: &Token<'a>,
|
||||||
|
) -> Result<PlainListItem, Box<dyn std::error::Error>> {
|
||||||
|
let mut found_lists = Vec::new();
|
||||||
|
let children = current_token.as_list()?;
|
||||||
|
let token_name = "item";
|
||||||
|
assert_name(current_token, token_name)?;
|
||||||
|
|
||||||
|
// skip 2 to skip token name and standard properties
|
||||||
|
for child_token in children.iter().skip(2) {
|
||||||
|
found_lists.extend(recurse_token(original_source, child_token)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PlainListItem {
|
||||||
|
position: get_bounds(original_source, current_token)?,
|
||||||
|
lists: found_lists,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let children = emacs.as_list()?;
|
||||||
|
let first_child = children
|
||||||
|
.first()
|
||||||
|
.ok_or("Should have at least one child.")?
|
||||||
|
.as_atom()?;
|
||||||
|
if first_child != name {
|
||||||
|
Err(format!(
|
||||||
|
"Expected a {expected} cell, but found a {found} cell.",
|
||||||
|
expected = name,
|
||||||
|
found = first_child
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bounds<'s>(
|
||||||
|
original_source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
) -> Result<SourceRange, Box<dyn std::error::Error>> {
|
||||||
|
let children = emacs.as_list()?;
|
||||||
|
let attributes_child = children
|
||||||
|
.iter()
|
||||||
|
.nth(1)
|
||||||
|
.ok_or("Should have an attributes child.")?;
|
||||||
|
let attributes_map = attributes_child.as_map()?;
|
||||||
|
let standard_properties = attributes_map.get(":standard-properties");
|
||||||
|
let (begin, end) = if standard_properties.is_some() {
|
||||||
|
let std_props = standard_properties
|
||||||
|
.expect("if statement proves its Some")
|
||||||
|
.as_vector()?;
|
||||||
|
let begin = std_props
|
||||||
|
.get(0)
|
||||||
|
.ok_or("Missing first element in standard properties")?
|
||||||
|
.as_atom()?;
|
||||||
|
let end = std_props
|
||||||
|
.get(1)
|
||||||
|
.ok_or("Missing first element in standard properties")?
|
||||||
|
.as_atom()?;
|
||||||
|
(begin, end)
|
||||||
|
} else {
|
||||||
|
let begin = attributes_map
|
||||||
|
.get(":begin")
|
||||||
|
.ok_or("Missing :begin attribute.")?
|
||||||
|
.as_atom()?;
|
||||||
|
let end = attributes_map
|
||||||
|
.get(":end")
|
||||||
|
.ok_or("Missing :end attribute.")?
|
||||||
|
.as_atom()?;
|
||||||
|
(begin, end)
|
||||||
|
};
|
||||||
|
let begin = begin.parse::<u32>()?;
|
||||||
|
let end = end.parse::<u32>()?;
|
||||||
|
let start_line = original_source
|
||||||
|
.chars()
|
||||||
|
.into_iter()
|
||||||
|
.take(usize::try_from(begin)? - 1)
|
||||||
|
.filter(|x| *x == '\n')
|
||||||
|
.count()
|
||||||
|
+ 1;
|
||||||
|
let end_line = original_source
|
||||||
|
.chars()
|
||||||
|
.into_iter()
|
||||||
|
.take(usize::try_from(end)? - 1)
|
||||||
|
.filter(|x| *x == '\n')
|
||||||
|
.count()
|
||||||
|
+ 1;
|
||||||
|
Ok(SourceRange {
|
||||||
|
start_line: u32::try_from(start_line)?,
|
||||||
|
end_line: u32::try_from(end_line)?,
|
||||||
|
start_character: begin,
|
||||||
|
end_character: end,
|
||||||
|
})
|
||||||
|
}
|
53
src/parse.rs
Normal file
53
src/parse.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
pub async fn emacs_parse_org_document<C>(
|
||||||
|
file_contents: C,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>>
|
||||||
|
where
|
||||||
|
C: AsRef<str>,
|
||||||
|
{
|
||||||
|
let escaped_file_contents = escape_elisp_string(file_contents);
|
||||||
|
let elisp_script = format!(
|
||||||
|
r#"(progn
|
||||||
|
(erase-buffer)
|
||||||
|
(insert "{escaped_file_contents}")
|
||||||
|
(org-mode)
|
||||||
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
|
)"#,
|
||||||
|
escaped_file_contents = escaped_file_contents
|
||||||
|
);
|
||||||
|
let mut cmd = Command::new("emacs");
|
||||||
|
let proc = cmd
|
||||||
|
.arg("-q")
|
||||||
|
.arg("--no-site-file")
|
||||||
|
.arg("--no-splash")
|
||||||
|
.arg("--batch")
|
||||||
|
.arg("--eval")
|
||||||
|
.arg(elisp_script);
|
||||||
|
|
||||||
|
let out = proc.output().await?;
|
||||||
|
out.status.exit_ok()?;
|
||||||
|
Ok(String::from_utf8(out.stderr)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape_elisp_string<C>(file_contents: C) -> String
|
||||||
|
where
|
||||||
|
C: AsRef<str>,
|
||||||
|
{
|
||||||
|
let source = file_contents.as_ref();
|
||||||
|
let source_len = source.len();
|
||||||
|
// We allocate a string 10% larger than the source to account for escape characters. Without this, we would have more allocations during processing.
|
||||||
|
let mut output = String::with_capacity(source_len + (source_len / 10));
|
||||||
|
for c in source.chars() {
|
||||||
|
match c {
|
||||||
|
'"' | '\\' => {
|
||||||
|
output.push('\\');
|
||||||
|
output.push(c);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
output.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
368
src/sexp.rs
Normal file
368
src/sexp.rs
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::escaped;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::bytes::complete::take_till1;
|
||||||
|
use nom::character::complete::multispace0;
|
||||||
|
use nom::character::complete::multispace1;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::combinator::map;
|
||||||
|
use nom::combinator::not;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
|
use nom::sequence::delimited;
|
||||||
|
use nom::sequence::preceded;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use crate::error::Res;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Token<'s> {
|
||||||
|
Atom(&'s str),
|
||||||
|
List(Vec<Token<'s>>),
|
||||||
|
TextWithProperties(TextWithProperties<'s>),
|
||||||
|
Vector(Vec<Token<'s>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TextWithProperties<'s> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub text: &'s str,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub properties: Vec<Token<'s>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> TextWithProperties<'s> {
|
||||||
|
pub fn unquote(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut out = String::with_capacity(self.text.len());
|
||||||
|
if !self.text.starts_with(r#"""#) {
|
||||||
|
return Err("Quoted text does not start with quote.".into());
|
||||||
|
}
|
||||||
|
if !self.text.ends_with(r#"""#) {
|
||||||
|
return Err("Quoted text does not end with quote.".into());
|
||||||
|
}
|
||||||
|
let interior_text = &self.text[1..(self.text.len() - 1)];
|
||||||
|
let mut state = ParseState::Normal;
|
||||||
|
for current_char in interior_text.chars().into_iter() {
|
||||||
|
state = match (state, current_char) {
|
||||||
|
(ParseState::Normal, '\\') => ParseState::Escape,
|
||||||
|
(ParseState::Normal, _) => {
|
||||||
|
out.push(current_char);
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(ParseState::Escape, 'n') => {
|
||||||
|
out.push('\n');
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(ParseState::Escape, '\\') => {
|
||||||
|
out.push('\\');
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(ParseState::Escape, '"') => {
|
||||||
|
out.push('"');
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ParseState {
|
||||||
|
Normal,
|
||||||
|
Escape,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Token<'s> {
|
||||||
|
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(match self {
|
||||||
|
Token::Vector(children) => Ok(children),
|
||||||
|
_ => Err(format!("wrong token type {:?}", self)),
|
||||||
|
}?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(match self {
|
||||||
|
Token::List(children) => Ok(children),
|
||||||
|
_ => Err(format!("wrong token type {:?}", self)),
|
||||||
|
}?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
||||||
|
Ok(match self {
|
||||||
|
Token::Atom(body) => Ok(*body),
|
||||||
|
_ => Err(format!("wrong token type {:?}", self)),
|
||||||
|
}?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(match self {
|
||||||
|
Token::TextWithProperties(body) => Ok(body),
|
||||||
|
_ => Err(format!("wrong token type {:?}", self)),
|
||||||
|
}?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_map<'p>(
|
||||||
|
&'p self,
|
||||||
|
) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
|
||||||
|
let children = self.as_list()?;
|
||||||
|
if children.len() % 2 != 0 {
|
||||||
|
return Err("Expecting an even number of children".into());
|
||||||
|
}
|
||||||
|
let mut key: Option<&str> = None;
|
||||||
|
for child in children.iter() {
|
||||||
|
match key {
|
||||||
|
None => {
|
||||||
|
key = Some(child.as_atom()?);
|
||||||
|
}
|
||||||
|
Some(key_val) => {
|
||||||
|
key = None;
|
||||||
|
hashmap.insert(key_val, child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(hashmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, _) = multispace0(input)?;
|
||||||
|
let (remaining, tkn) = token(remaining)?;
|
||||||
|
let (remaining, _) = multispace0(remaining)?;
|
||||||
|
Ok((remaining, tkn))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, tkn) = token(input)?;
|
||||||
|
Ok((remaining, tkn))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
alt((list, vector, atom))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, _) = tag("(")(input)?;
|
||||||
|
let (remaining, children) = delimited(
|
||||||
|
multispace0,
|
||||||
|
separated_list1(multispace1, token),
|
||||||
|
multispace0,
|
||||||
|
)(remaining)?;
|
||||||
|
let (remaining, _) = tag(")")(remaining)?;
|
||||||
|
Ok((remaining, Token::List(children)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
let (remaining, children) = delimited(
|
||||||
|
multispace0,
|
||||||
|
separated_list1(multispace1, token),
|
||||||
|
multispace0,
|
||||||
|
)(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
Ok((remaining, Token::Vector(children)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
not(peek(one_of(")]")))(input)?;
|
||||||
|
alt((
|
||||||
|
text_with_properties,
|
||||||
|
hash_notation,
|
||||||
|
quoted_atom,
|
||||||
|
unquoted_atom,
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, body) = take_till1(|c| match c {
|
||||||
|
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
||||||
|
_ => false,
|
||||||
|
})(input)?;
|
||||||
|
Ok((remaining, Token::Atom(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, _) = tag(r#"""#)(input)?;
|
||||||
|
let (remaining, _) = escaped(
|
||||||
|
take_till1(|c| match c {
|
||||||
|
'\\' | '"' => true,
|
||||||
|
_ => false,
|
||||||
|
}),
|
||||||
|
'\\',
|
||||||
|
one_of(r#""n\\"#),
|
||||||
|
)(remaining)?;
|
||||||
|
let (remaining, _) = tag(r#"""#)(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, Token::Atom(source)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, _) = tag("#<")(input)?;
|
||||||
|
let (remaining, _body) = take_till1(|c| match c {
|
||||||
|
'>' => true,
|
||||||
|
_ => false,
|
||||||
|
})(remaining)?;
|
||||||
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, Token::Atom(source)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
|
let (remaining, _) = tag("#(")(input)?;
|
||||||
|
let (remaining, (text, props)) = delimited(
|
||||||
|
multispace0,
|
||||||
|
tuple((
|
||||||
|
map(quoted_atom, |atom| match atom {
|
||||||
|
Token::Atom(body) => body,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}),
|
||||||
|
preceded(multispace1, opt(separated_list1(multispace1, token))),
|
||||||
|
)),
|
||||||
|
multispace0,
|
||||||
|
)(remaining)?;
|
||||||
|
let (remaining, _) = tag(")")(remaining)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Token::TextWithProperties(TextWithProperties {
|
||||||
|
text,
|
||||||
|
properties: props.unwrap_or(Vec::new()),
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
||||||
|
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||||
|
assert!(is_slice_of(input, remaining));
|
||||||
|
let source = {
|
||||||
|
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||||
|
&input[..offset]
|
||||||
|
};
|
||||||
|
source
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the child string slice is a slice of the parent string slice.
|
||||||
|
fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||||
|
let parent_start = parent.as_ptr() as usize;
|
||||||
|
let parent_end = parent_start + parent.len();
|
||||||
|
let child_start = child.as_ptr() as usize;
|
||||||
|
let child_end = child_start + child.len();
|
||||||
|
child_start >= parent_start && child_end <= parent_end
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple() {
|
||||||
|
let input = " (foo bar baz ) ";
|
||||||
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert!(match parsed {
|
||||||
|
Token::Atom(_) => false,
|
||||||
|
Token::List(_) => true,
|
||||||
|
Token::TextWithProperties(_) => false,
|
||||||
|
Token::Vector(_) => false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quoted() {
|
||||||
|
let input = r#" ("foo" bar baz ) "#;
|
||||||
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert!(match parsed {
|
||||||
|
Token::Atom(_) => false,
|
||||||
|
Token::List(_) => true,
|
||||||
|
Token::TextWithProperties(_) => false,
|
||||||
|
Token::Vector(_) => false,
|
||||||
|
});
|
||||||
|
let children = match parsed {
|
||||||
|
Token::List(children) => children,
|
||||||
|
_ => panic!("Should be a list."),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
match children.first() {
|
||||||
|
Some(Token::Atom(body)) => *body,
|
||||||
|
_ => panic!("First child should be an atom."),
|
||||||
|
},
|
||||||
|
r#""foo""#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quoted_containing_paren() {
|
||||||
|
let input = r#" (foo "b(a)r" baz ) "#;
|
||||||
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert!(match parsed {
|
||||||
|
Token::List(_) => true,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
let children = match parsed {
|
||||||
|
Token::List(children) => children,
|
||||||
|
_ => panic!("Should be a list."),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
match children.first() {
|
||||||
|
Some(Token::Atom(body)) => *body,
|
||||||
|
_ => panic!("First child should be an atom."),
|
||||||
|
},
|
||||||
|
r#"foo"#
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
match children.iter().nth(1) {
|
||||||
|
Some(Token::Atom(body)) => *body,
|
||||||
|
_ => panic!("Second child should be an atom."),
|
||||||
|
},
|
||||||
|
r#""b(a)r""#
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
match children.iter().nth(2) {
|
||||||
|
Some(Token::Atom(body)) => *body,
|
||||||
|
_ => panic!("Third child should be an atom."),
|
||||||
|
},
|
||||||
|
r#"baz"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_containing_escaped_characters() {
|
||||||
|
let input = r#" (foo "\\( x=2 \\)" bar) "#;
|
||||||
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert!(match parsed {
|
||||||
|
Token::Atom(_) => false,
|
||||||
|
Token::List(_) => true,
|
||||||
|
Token::TextWithProperties(_) => false,
|
||||||
|
Token::Vector(_) => false,
|
||||||
|
});
|
||||||
|
let children = match parsed {
|
||||||
|
Token::List(children) => children,
|
||||||
|
_ => panic!("Should be a list."),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
match children.get(1) {
|
||||||
|
Some(Token::Atom(body)) => *body,
|
||||||
|
_ => panic!("First child should be an atom."),
|
||||||
|
},
|
||||||
|
r#""\\( x=2 \\)""#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
5
static/index.html
Normal file
5
static/index.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Test html file.
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user