parse the shebang and make it work with recast (#2289)
* parse the shebang and make it work with recast Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix playwright Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix playwright Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix playwright Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -279,7 +279,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
|||||||
const bottomAng = 25
|
const bottomAng = 25
|
||||||
*/
|
*/
|
||||||
await page.click('.cm-content')
|
await page.click('.cm-content')
|
||||||
await page.keyboard.type('# error')
|
await page.keyboard.type('$ error')
|
||||||
|
|
||||||
// press arrows to clear autocomplete
|
// press arrows to clear autocomplete
|
||||||
await page.keyboard.press('ArrowLeft')
|
await page.keyboard.press('ArrowLeft')
|
||||||
@ -296,10 +296,10 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
|||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
await expect(page.getByText("found unknown token '#'")).toBeVisible()
|
await expect(page.getByText("found unknown token '$'")).toBeVisible()
|
||||||
|
|
||||||
// select the line that's causing the error and delete it
|
// select the line that's causing the error and delete it
|
||||||
await page.getByText('# error').click()
|
await page.getByText('$ error').click()
|
||||||
await page.keyboard.press('End')
|
await page.keyboard.press('End')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('Home')
|
await page.keyboard.press('Home')
|
||||||
|
24
src-tauri/Cargo.lock
generated
24
src-tauri/Cargo.lock
generated
@ -416,9 +416,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
@ -2426,7 +2426,7 @@ dependencies = [
|
|||||||
"approx",
|
"approx",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.0",
|
"base64 0.22.1",
|
||||||
"bson",
|
"bson",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
@ -3886,7 +3886,7 @@ version = "0.12.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -4136,7 +4136,7 @@ version = "2.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
|
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4303,9 +4303,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.199"
|
version = "1.0.200"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
|
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -4321,9 +4321,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.199"
|
version = "1.0.200"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
|
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -5013,7 +5013,7 @@ version = "2.0.0-beta.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b383f341efb803852b0235a2f330ca90c4c113f422dd6d646b888685b372cace"
|
checksum = "b383f341efb803852b0235a2f330ca90c4c113f422dd6d646b888685b372cace"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
"ico",
|
"ico",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
@ -5207,7 +5207,7 @@ version = "2.0.0-beta.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f34be6851c7e84ca99b3bddd57e033d55d8bfca1dd153d6e8d18e9f1fb95469"
|
checksum = "3f34be6851c7e84ca99b3bddd57e033d55d8bfca1dd153d6e8d18e9f1fb95469"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.1",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -6663,7 +6663,7 @@ version = "0.39.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e180ac2740d6cb4d5cec0abf63eacbea90f1b7e5e3803043b13c1c84c4b7884"
|
checksum = "6e180ac2740d6cb4d5cec0abf63eacbea90f1b7e5e3803043b13c1c84c4b7884"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.1",
|
||||||
"block",
|
"block",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
|
@ -334,7 +334,7 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
|
|||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
Command::new("explorer")
|
Command::new("explorer")
|
||||||
.args(["/select,", &path]) // The comma after select is not a typo
|
.args(["/select,", path]) // The comma after select is not a typo
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
}
|
}
|
||||||
@ -342,7 +342,7 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
Command::new("open")
|
Command::new("open")
|
||||||
.args(["-R", &path])
|
.args(["-R", path])
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
}
|
}
|
||||||
|
@ -821,6 +821,7 @@ impl NonCodeNode {
|
|||||||
|
|
||||||
pub fn value(&self) -> String {
|
pub fn value(&self) -> String {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
|
NonCodeValue::Shebang { value } => value.clone(),
|
||||||
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
|
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
|
||||||
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
|
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
|
||||||
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
|
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
|
||||||
@ -830,6 +831,7 @@ impl NonCodeNode {
|
|||||||
|
|
||||||
pub fn format(&self, indentation: &str) -> String {
|
pub fn format(&self, indentation: &str) -> String {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
|
NonCodeValue::Shebang { value } => format!("{}\n\n", value),
|
||||||
NonCodeValue::InlineComment {
|
NonCodeValue::InlineComment {
|
||||||
value,
|
value,
|
||||||
style: CommentStyle::Line,
|
style: CommentStyle::Line,
|
||||||
@ -882,6 +884,15 @@ pub enum CommentStyle {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub enum NonCodeValue {
|
pub enum NonCodeValue {
|
||||||
|
/// A shebang.
|
||||||
|
/// This is a special type of comment that is at the top of the file.
|
||||||
|
/// It looks like this:
|
||||||
|
/// ```python,no_run
|
||||||
|
/// #!/usr/bin/env python
|
||||||
|
/// ```
|
||||||
|
Shebang {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
/// An inline comment.
|
/// An inline comment.
|
||||||
/// Here are examples:
|
/// Here are examples:
|
||||||
/// `1 + 1 // This is an inline comment`.
|
/// `1 + 1 // This is an inline comment`.
|
||||||
@ -3273,6 +3284,117 @@ fn ghi = (x) => {
|
|||||||
assert_eq!(recasted, r#""#);
|
assert_eq!(recasted, r#""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_shebang_only() {
|
||||||
|
let some_program_string = r#"#!/usr/local/env zoo kcl"#;
|
||||||
|
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([21, 24])], message: "Unexpected end of file. The compiler expected a function body items (functions are made up of variable declarations, expressions, and return statements, each of those is a possible body item" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_shebang() {
|
||||||
|
let some_program_string = r#"#!/usr/local/env zoo kcl
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
recasted,
|
||||||
|
r#"#!/usr/local/env zoo kcl
|
||||||
|
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_shebang_new_lines() {
|
||||||
|
let some_program_string = r#"#!/usr/local/env zoo kcl
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
recasted,
|
||||||
|
r#"#!/usr/local/env zoo kcl
|
||||||
|
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_shebang_with_comments() {
|
||||||
|
let some_program_string = r#"#!/usr/local/env zoo kcl
|
||||||
|
|
||||||
|
// Yo yo my comments.
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
recasted,
|
||||||
|
r#"#!/usr/local/env zoo kcl
|
||||||
|
|
||||||
|
// Yo yo my comments.
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_large_file() {
|
fn test_recast_large_file() {
|
||||||
let some_program_string = r#"// define constants
|
let some_program_string = r#"// define constants
|
||||||
|
@ -67,7 +67,7 @@ impl ProgramMemory {
|
|||||||
|
|
||||||
/// Add to the program memory.
|
/// Add to the program memory.
|
||||||
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
||||||
if self.root.get(key).is_some() {
|
if self.root.contains_key(key) {
|
||||||
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
||||||
message: format!("Cannot redefine {}", key),
|
message: format!("Cannot redefine {}", key),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
|
@ -742,7 +742,7 @@ async fn test_kcl_lsp_create_zip() {
|
|||||||
|
|
||||||
assert_eq!(files.len(), 11);
|
assert_eq!(files.len(), 11);
|
||||||
let util_path = format!("{}/util.rs", string_path).replace("file://", "");
|
let util_path = format!("{}/util.rs", string_path).replace("file://", "");
|
||||||
assert!(files.get(&util_path).is_some());
|
assert!(files.contains_key(&util_path));
|
||||||
assert_eq!(files.get("/test.kcl"), Some(&4));
|
assert_eq!(files.get("/test.kcl"), Some(&4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use winnow::{
|
|||||||
dispatch,
|
dispatch,
|
||||||
error::{ErrMode, StrContext, StrContextValue},
|
error::{ErrMode, StrContext, StrContextValue},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
token::{any, one_of},
|
token::{any, one_of, take_till},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -39,7 +39,13 @@ fn expected(what: &'static str) -> StrContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn program(i: TokenSlice) -> PResult<Program> {
|
fn program(i: TokenSlice) -> PResult<Program> {
|
||||||
|
let shebang = opt(shebang).parse_next(i)?;
|
||||||
let mut out = function_body.parse_next(i)?;
|
let mut out = function_body.parse_next(i)?;
|
||||||
|
|
||||||
|
// Add the shebang to the non-code meta.
|
||||||
|
if let Some(shebang) = shebang {
|
||||||
|
out.non_code_meta.start.insert(0, shebang);
|
||||||
|
}
|
||||||
// Match original parser behaviour, for now.
|
// Match original parser behaviour, for now.
|
||||||
// Once this is merged and stable, consider changing this as I think it's more accurate
|
// Once this is merged and stable, consider changing this as I think it's more accurate
|
||||||
// without the -1.
|
// without the -1.
|
||||||
@ -386,6 +392,39 @@ fn whitespace(i: TokenSlice) -> PResult<Vec<Token>> {
|
|||||||
.parse_next(i)
|
.parse_next(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A shebang is a line at the start of a file that starts with `#!`.
|
||||||
|
/// If the shebang is present it takes up the whole line.
|
||||||
|
fn shebang(i: TokenSlice) -> PResult<NonCodeNode> {
|
||||||
|
// Parse the hash and the bang.
|
||||||
|
hash.parse_next(i)?;
|
||||||
|
bang.parse_next(i)?;
|
||||||
|
// Get the rest of the line.
|
||||||
|
// Parse everything until the next newline.
|
||||||
|
let tokens = take_till(0.., |token: Token| token.value.contains('\n')).parse_next(i)?;
|
||||||
|
let value = tokens.iter().map(|t| t.value.as_str()).collect::<String>();
|
||||||
|
|
||||||
|
if tokens.is_empty() {
|
||||||
|
return Err(ErrMode::Cut(
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![],
|
||||||
|
message: "expected a shebang value after #!".to_owned(),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip all the whitespace after the shebang.
|
||||||
|
opt(whitespace).parse_next(i)?;
|
||||||
|
|
||||||
|
Ok(NonCodeNode {
|
||||||
|
start: 0,
|
||||||
|
end: tokens.last().unwrap().end,
|
||||||
|
value: NonCodeValue::Shebang {
|
||||||
|
value: format!("#!{}", value),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the = operator.
|
/// Parse the = operator.
|
||||||
fn equals(i: TokenSlice) -> PResult<Token> {
|
fn equals(i: TokenSlice) -> PResult<Token> {
|
||||||
one_of((TokenType::Operator, "="))
|
one_of((TokenType::Operator, "="))
|
||||||
@ -601,6 +640,7 @@ fn noncode_just_after_code(i: TokenSlice) -> PResult<NonCodeNode> {
|
|||||||
// There's an empty line between the body item and the comment,
|
// There's an empty line between the body item and the comment,
|
||||||
// This means the comment is a NewLineBlockComment!
|
// This means the comment is a NewLineBlockComment!
|
||||||
let value = match nc.value {
|
let value = match nc.value {
|
||||||
|
NonCodeValue::Shebang { value } => NonCodeValue::Shebang { value },
|
||||||
// Change block comments to inline, as discussed above
|
// Change block comments to inline, as discussed above
|
||||||
NonCodeValue::BlockComment { value, style } => NonCodeValue::NewLineBlockComment { value, style },
|
NonCodeValue::BlockComment { value, style } => NonCodeValue::NewLineBlockComment { value, style },
|
||||||
// Other variants don't need to change.
|
// Other variants don't need to change.
|
||||||
@ -620,6 +660,7 @@ fn noncode_just_after_code(i: TokenSlice) -> PResult<NonCodeNode> {
|
|||||||
// There's no newline between the body item and comment,
|
// There's no newline between the body item and comment,
|
||||||
// so if this is a comment, it must be inline with code.
|
// so if this is a comment, it must be inline with code.
|
||||||
let value = match nc.value {
|
let value = match nc.value {
|
||||||
|
NonCodeValue::Shebang { value } => NonCodeValue::Shebang { value },
|
||||||
// Change block comments to inline, as discussed above
|
// Change block comments to inline, as discussed above
|
||||||
NonCodeValue::BlockComment { value, style } => NonCodeValue::InlineComment { value, style },
|
NonCodeValue::BlockComment { value, style } => NonCodeValue::InlineComment { value, style },
|
||||||
// Other variants don't need to change.
|
// Other variants don't need to change.
|
||||||
@ -1204,6 +1245,16 @@ fn comma(i: TokenSlice) -> PResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hash(i: TokenSlice) -> PResult<()> {
|
||||||
|
TokenType::Hash.parse_from(i)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bang(i: TokenSlice) -> PResult<()> {
|
||||||
|
TokenType::Bang.parse_from(i)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn period(i: TokenSlice) -> PResult<()> {
|
fn period(i: TokenSlice) -> PResult<()> {
|
||||||
TokenType::Period.parse_from(i)?;
|
TokenType::Period.parse_from(i)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -2331,7 +2382,7 @@ const secondExtrude = startSketchOn('XY')
|
|||||||
let err = parser.ast().unwrap_err();
|
let err = parser.ast().unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
r#"lexical: KclErrorDetails { source_ranges: [SourceRange([1, 2])], message: "found unknown token '!'" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 1])], message: "Unexpected token" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2398,7 +2449,7 @@ z(-[["#,
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"lexical: KclErrorDetails { source_ranges: [SourceRange([6, 7])], message: "found unknown token '#'" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([3, 4])], message: "Unexpected token" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2410,7 +2461,7 @@ z(-[["#,
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"lexical: KclErrorDetails { source_ranges: [SourceRange([25, 26]), SourceRange([26, 27])], message: "found unknown tokens [#, #]" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([2, 3])], message: "Unexpected token" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ impl Configuration {
|
|||||||
|
|
||||||
if let Some(project_directory) = &settings.settings.app.project_directory {
|
if let Some(project_directory) = &settings.settings.app.project_directory {
|
||||||
if settings.settings.project.directory.to_string_lossy().is_empty() {
|
if settings.settings.project.directory.to_string_lossy().is_empty() {
|
||||||
settings.settings.project.directory = project_directory.clone();
|
settings.settings.project.directory.clone_from(project_directory);
|
||||||
settings.settings.app.project_directory = None;
|
settings.settings.app.project_directory = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ lazy_static::lazy_static! {
|
|||||||
|
|
||||||
/// Walk a directory recursively and return a list of all files.
|
/// Walk a directory recursively and return a list of all files.
|
||||||
#[async_recursion::async_recursion]
|
#[async_recursion::async_recursion]
|
||||||
pub async fn walk_dir<P: AsRef<Path> + Send>(dir: P) -> Result<FileEntry> {
|
pub async fn walk_dir<P>(dir: P) -> Result<FileEntry>
|
||||||
|
where
|
||||||
|
P: AsRef<Path> + Send,
|
||||||
|
{
|
||||||
let mut entry = FileEntry {
|
let mut entry = FileEntry {
|
||||||
name: dir
|
name: dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -31,6 +31,10 @@ pub enum TokenType {
|
|||||||
Type,
|
Type,
|
||||||
/// A brace.
|
/// A brace.
|
||||||
Brace,
|
Brace,
|
||||||
|
/// A hash.
|
||||||
|
Hash,
|
||||||
|
/// A bang.
|
||||||
|
Bang,
|
||||||
/// Whitespace.
|
/// Whitespace.
|
||||||
Whitespace,
|
Whitespace,
|
||||||
/// A comma.
|
/// A comma.
|
||||||
@ -74,6 +78,8 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
|||||||
| TokenType::Colon
|
| TokenType::Colon
|
||||||
| TokenType::Period
|
| TokenType::Period
|
||||||
| TokenType::DoublePeriod
|
| TokenType::DoublePeriod
|
||||||
|
| TokenType::Hash
|
||||||
|
| TokenType::Bang
|
||||||
| TokenType::Unknown => {
|
| TokenType::Unknown => {
|
||||||
anyhow::bail!("unsupported token type: {:?}", token_type)
|
anyhow::bail!("unsupported token type: {:?}", token_type)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ pub fn token(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
'0'..='9' => number,
|
'0'..='9' => number,
|
||||||
':' => colon,
|
':' => colon,
|
||||||
'.' => alt((number, double_period, period)),
|
'.' => alt((number, double_period, period)),
|
||||||
|
'#' => hash,
|
||||||
|
'!' => bang,
|
||||||
' ' | '\t' | '\n' => whitespace,
|
' ' | '\t' | '\n' => whitespace,
|
||||||
_ => alt((operator, keyword,type_, word))
|
_ => alt((operator, keyword,type_, word))
|
||||||
}
|
}
|
||||||
@ -109,6 +111,16 @@ fn comma(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
Ok(Token::from_range(range, TokenType::Comma, value.to_string()))
|
Ok(Token::from_range(range, TokenType::Comma, value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hash(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
|
let (value, range) = '#'.with_span().parse_next(i)?;
|
||||||
|
Ok(Token::from_range(range, TokenType::Hash, value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bang(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
|
let (value, range) = '!'.with_span().parse_next(i)?;
|
||||||
|
Ok(Token::from_range(range, TokenType::Bang, value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
fn question_mark(i: &mut Located<&str>) -> PResult<Token> {
|
fn question_mark(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let (value, range) = '?'.with_span().parse_next(i)?;
|
let (value, range) = '?'.with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::QuestionMark, value.to_string()))
|
Ok(Token::from_range(range, TokenType::QuestionMark, value.to_string()))
|
||||||
|
Reference in New Issue
Block a user