Annotations syntax and per-file default units preparatory work (#4822)
* Parse annotations Signed-off-by: Nick Cameron <nrc@ncameron.org> * Propagate settings from annotations to exec_state Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
73
src/wasm-lib/kcl/src/execution/annotations.rs
Normal file
73
src/wasm-lib/kcl/src/execution/annotations.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//! Data on available annotations.
|
||||||
|
|
||||||
|
use super::kcl_value::{UnitAngle, UnitLen};
|
||||||
|
use crate::{
|
||||||
|
errors::KclErrorDetails,
|
||||||
|
parsing::ast::types::{Expr, Node, NonCodeValue, ObjectProperty},
|
||||||
|
KclError, SourceRange,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) const SETTINGS: &str = "settings";
|
||||||
|
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
|
||||||
|
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
||||||
|
|
||||||
|
pub(super) fn expect_properties<'a>(
|
||||||
|
for_key: &'static str,
|
||||||
|
annotation: &'a NonCodeValue,
|
||||||
|
source_range: SourceRange,
|
||||||
|
) -> Result<&'a [Node<ObjectProperty>], KclError> {
|
||||||
|
match annotation {
|
||||||
|
NonCodeValue::Annotation { name, properties } => {
|
||||||
|
assert_eq!(name.name, for_key);
|
||||||
|
Ok(&**properties.as_ref().ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Empty `{for_key}` annotation"),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||||
|
match expr {
|
||||||
|
Expr::Identifier(id) => Ok(&id.name),
|
||||||
|
e => Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
|
||||||
|
source_ranges: vec![e.into()],
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnitLen {
|
||||||
|
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
|
||||||
|
match s {
|
||||||
|
"mm" => Ok(UnitLen::Mm),
|
||||||
|
"cm" => Ok(UnitLen::Cm),
|
||||||
|
"m" => Ok(UnitLen::M),
|
||||||
|
"inch" | "in" => Ok(UnitLen::Inches),
|
||||||
|
"ft" => Ok(UnitLen::Feet),
|
||||||
|
"yd" => Ok(UnitLen::Yards),
|
||||||
|
value => Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Unexpected settings value: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
|
||||||
|
),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnitAngle {
|
||||||
|
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
|
||||||
|
match s {
|
||||||
|
"deg" => Ok(UnitAngle::Degrees),
|
||||||
|
"rad" => Ok(UnitAngle::Radians),
|
||||||
|
value => Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Unexpected settings value: `{value}`; expected one of `deg`, `rad`"),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,10 @@ use crate::{
|
|||||||
errors::KclErrorDetails,
|
errors::KclErrorDetails,
|
||||||
exec::{ProgramMemory, Sketch},
|
exec::{ProgramMemory, Sketch},
|
||||||
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
||||||
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
parsing::{
|
||||||
|
ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
||||||
|
token::NumericSuffix,
|
||||||
|
},
|
||||||
std::{args::Arg, FnAsArg},
|
std::{args::Arg, FnAsArg},
|
||||||
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
||||||
};
|
};
|
||||||
@ -561,3 +564,52 @@ impl KclValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO called UnitLen so as not to clash with UnitLength in settings)
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum UnitLen {
|
||||||
|
Mm,
|
||||||
|
Cm,
|
||||||
|
M,
|
||||||
|
Inches,
|
||||||
|
Feet,
|
||||||
|
Yards,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NumericSuffix> for UnitLen {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match suffix {
|
||||||
|
NumericSuffix::Mm => Ok(Self::Mm),
|
||||||
|
NumericSuffix::Cm => Ok(Self::Cm),
|
||||||
|
NumericSuffix::M => Ok(Self::M),
|
||||||
|
NumericSuffix::Inch => Ok(Self::Inches),
|
||||||
|
NumericSuffix::Ft => Ok(Self::Feet),
|
||||||
|
NumericSuffix::Yd => Ok(Self::Yards),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum UnitAngle {
|
||||||
|
Degrees,
|
||||||
|
Radians,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NumericSuffix> for UnitAngle {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match suffix {
|
||||||
|
NumericSuffix::Deg => Ok(Self::Degrees),
|
||||||
|
NumericSuffix::Rad => Ok(Self::Radians),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ pub use function_param::FunctionParam;
|
|||||||
pub use kcl_value::{KclObjectFields, KclValue};
|
pub use kcl_value::{KclObjectFields, KclValue};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
mod annotations;
|
||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
mod cad_op;
|
mod cad_op;
|
||||||
mod exec_ast;
|
mod exec_ast;
|
||||||
@ -36,8 +37,8 @@ use crate::{
|
|||||||
execution::cache::{CacheInformation, CacheResult},
|
execution::cache::{CacheInformation, CacheResult},
|
||||||
fs::{FileManager, FileSystem},
|
fs::{FileManager, FileSystem},
|
||||||
parsing::ast::types::{
|
parsing::ast::types::{
|
||||||
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, Program as AstProgram,
|
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
|
||||||
TagDeclarator, TagNode,
|
Program as AstProgram, TagDeclarator, TagNode,
|
||||||
},
|
},
|
||||||
settings::types::UnitLength,
|
settings::types::UnitLength,
|
||||||
source_range::{ModuleId, SourceRange},
|
source_range::{ModuleId, SourceRange},
|
||||||
@ -88,6 +89,8 @@ pub struct ModuleState {
|
|||||||
/// Operations that have been performed in execution order, for display in
|
/// Operations that have been performed in execution order, for display in
|
||||||
/// the Feature Tree.
|
/// the Feature Tree.
|
||||||
pub operations: Vec<Operation>,
|
pub operations: Vec<Operation>,
|
||||||
|
/// Settings specified from annotations.
|
||||||
|
pub settings: MetaSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExecState {
|
impl Default for ExecState {
|
||||||
@ -186,6 +189,56 @@ impl GlobalState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MetaSettings {
|
||||||
|
pub default_length_units: kcl_value::UnitLen,
|
||||||
|
pub default_angle_units: kcl_value::UnitAngle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MetaSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
MetaSettings {
|
||||||
|
default_length_units: kcl_value::UnitLen::Mm,
|
||||||
|
default_angle_units: kcl_value::UnitAngle::Degrees,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaSettings {
|
||||||
|
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
||||||
|
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
||||||
|
|
||||||
|
for p in properties {
|
||||||
|
match &*p.inner.key.name {
|
||||||
|
annotations::SETTINGS_UNIT_LENGTH => {
|
||||||
|
let value = annotations::expect_ident(&p.inner.value)?;
|
||||||
|
let value = kcl_value::UnitLen::from_str(value, source_range)?;
|
||||||
|
self.default_length_units = value;
|
||||||
|
}
|
||||||
|
annotations::SETTINGS_UNIT_ANGLE => {
|
||||||
|
let value = annotations::expect_ident(&p.inner.value)?;
|
||||||
|
let value = kcl_value::UnitAngle::from_str(value, source_range)?;
|
||||||
|
self.default_angle_units = value;
|
||||||
|
}
|
||||||
|
name => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
|
||||||
|
annotations::SETTINGS_UNIT_LENGTH,
|
||||||
|
annotations::SETTINGS_UNIT_ANGLE
|
||||||
|
),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -2020,6 +2073,22 @@ impl ExecutorContext {
|
|||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
body_type: BodyType,
|
body_type: BodyType,
|
||||||
) -> Result<Option<KclValue>, KclError> {
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
if let Some((annotation, source_range)) = program
|
||||||
|
.non_code_meta
|
||||||
|
.start_nodes
|
||||||
|
.iter()
|
||||||
|
.filter_map(|n| {
|
||||||
|
n.annotation(annotations::SETTINGS)
|
||||||
|
.map(|result| (result, n.as_source_range()))
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
exec_state
|
||||||
|
.mod_local
|
||||||
|
.settings
|
||||||
|
.update_from_annotation(annotation, source_range)?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_expr = None;
|
let mut last_expr = None;
|
||||||
// Iterate over the body of the program.
|
// Iterate over the body of the program.
|
||||||
for statement in &program.body {
|
for statement in &program.body {
|
||||||
|
@ -1000,52 +1000,22 @@ pub struct NonCodeNode {
|
|||||||
pub digest: Option<Digest>,
|
pub digest: Option<Digest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node<NonCodeNode> {
|
|
||||||
pub fn format(&self, indentation: &str) -> String {
|
|
||||||
match &self.value {
|
|
||||||
NonCodeValue::InlineComment {
|
|
||||||
value,
|
|
||||||
style: CommentStyle::Line,
|
|
||||||
} => format!(" // {}\n", value),
|
|
||||||
NonCodeValue::InlineComment {
|
|
||||||
value,
|
|
||||||
style: CommentStyle::Block,
|
|
||||||
} => format!(" /* {} */", value),
|
|
||||||
NonCodeValue::BlockComment { value, style } => match style {
|
|
||||||
CommentStyle::Block => format!("{}/* {} */", indentation, value),
|
|
||||||
CommentStyle::Line => {
|
|
||||||
if value.trim().is_empty() {
|
|
||||||
format!("{}//\n", indentation)
|
|
||||||
} else {
|
|
||||||
format!("{}// {}\n", indentation, value.trim())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
NonCodeValue::NewLineBlockComment { value, style } => {
|
|
||||||
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
|
|
||||||
match style {
|
|
||||||
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
|
|
||||||
CommentStyle::Line => {
|
|
||||||
if value.trim().is_empty() {
|
|
||||||
format!("{}{}//\n", add_start_new_line, indentation)
|
|
||||||
} else {
|
|
||||||
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NonCodeNode {
|
impl NonCodeNode {
|
||||||
|
#[cfg(test)]
|
||||||
pub fn value(&self) -> String {
|
pub fn value(&self) -> String {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
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(),
|
||||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||||
|
NonCodeValue::Annotation { name, .. } => name.name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn annotation(&self, expected_name: &str) -> Option<&NonCodeValue> {
|
||||||
|
match &self.value {
|
||||||
|
a @ NonCodeValue::Annotation { name, .. } if name.name == expected_name => Some(a),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1063,6 +1033,7 @@ pub enum CommentStyle {
|
|||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum NonCodeValue {
|
pub enum NonCodeValue {
|
||||||
/// An inline comment.
|
/// An inline comment.
|
||||||
/// Here are examples:
|
/// Here are examples:
|
||||||
@ -1095,6 +1066,10 @@ pub enum NonCodeValue {
|
|||||||
// A new line like `\n\n` NOT a new line like `\n`.
|
// A new line like `\n\n` NOT a new line like `\n`.
|
||||||
// This is also not a comment.
|
// This is also not a comment.
|
||||||
NewLine,
|
NewLine,
|
||||||
|
Annotation {
|
||||||
|
name: Node<Identifier>,
|
||||||
|
properties: Option<Vec<Node<ObjectProperty>>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
@ -283,38 +283,86 @@ fn non_code_node(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
|||||||
alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
|
alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn annotation(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||||
|
let at = at_sign.parse_next(i)?;
|
||||||
|
let name = binding_name.parse_next(i)?;
|
||||||
|
let mut end = name.end;
|
||||||
|
|
||||||
|
let properties = if peek(open_paren).parse_next(i).is_ok() {
|
||||||
|
open_paren(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let properties: Vec<_> = separated(
|
||||||
|
0..,
|
||||||
|
separated_pair(
|
||||||
|
terminated(identifier, opt(whitespace)),
|
||||||
|
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
|
||||||
|
expression,
|
||||||
|
)
|
||||||
|
.map(|(key, value)| Node {
|
||||||
|
start: key.start,
|
||||||
|
end: value.end(),
|
||||||
|
module_id: key.module_id,
|
||||||
|
inner: ObjectProperty {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
digest: None,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
comma_sep,
|
||||||
|
)
|
||||||
|
.parse_next(i)?;
|
||||||
|
ignore_trailing_comma(i);
|
||||||
|
ignore_whitespace(i);
|
||||||
|
end = close_paren(i)?.end;
|
||||||
|
Some(properties)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = NonCodeValue::Annotation { name, properties };
|
||||||
|
Ok(Node::new(
|
||||||
|
NonCodeNode { value, digest: None },
|
||||||
|
at.start,
|
||||||
|
end,
|
||||||
|
at.module_id,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Matches remaining three cases of NonCodeValue
|
// Matches remaining three cases of NonCodeValue
|
||||||
fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||||
any.verify_map(|token: Token| {
|
alt((
|
||||||
if token.is_code_token() {
|
annotation,
|
||||||
None
|
any.verify_map(|token: Token| {
|
||||||
} else {
|
if token.is_code_token() {
|
||||||
let value = match token.token_type {
|
None
|
||||||
TokenType::Whitespace if token.value.contains("\n\n") => NonCodeValue::NewLine,
|
} else {
|
||||||
TokenType::LineComment => NonCodeValue::BlockComment {
|
let value = match token.token_type {
|
||||||
value: token.value.trim_start_matches("//").trim().to_owned(),
|
TokenType::Whitespace if token.value.contains("\n\n") => NonCodeValue::NewLine,
|
||||||
style: CommentStyle::Line,
|
TokenType::LineComment => NonCodeValue::BlockComment {
|
||||||
},
|
value: token.value.trim_start_matches("//").trim().to_owned(),
|
||||||
TokenType::BlockComment => NonCodeValue::BlockComment {
|
style: CommentStyle::Line,
|
||||||
style: CommentStyle::Block,
|
},
|
||||||
value: token
|
TokenType::BlockComment => NonCodeValue::BlockComment {
|
||||||
.value
|
style: CommentStyle::Block,
|
||||||
.trim_start_matches("/*")
|
value: token
|
||||||
.trim_end_matches("*/")
|
.value
|
||||||
.trim()
|
.trim_start_matches("/*")
|
||||||
.to_owned(),
|
.trim_end_matches("*/")
|
||||||
},
|
.trim()
|
||||||
_ => return None,
|
.to_owned(),
|
||||||
};
|
},
|
||||||
Some(Node::new(
|
_ => return None,
|
||||||
NonCodeNode { value, digest: None },
|
};
|
||||||
token.start,
|
Some(Node::new(
|
||||||
token.end,
|
NonCodeNode { value, digest: None },
|
||||||
token.module_id,
|
token.start,
|
||||||
))
|
token.end,
|
||||||
}
|
token.module_id,
|
||||||
})
|
))
|
||||||
.context(expected("Non-code token (comments or whitespace)"))
|
}
|
||||||
|
})
|
||||||
|
.context(expected("Non-code token (comments or whitespace)")),
|
||||||
|
))
|
||||||
.parse_next(i)
|
.parse_next(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1191,6 +1239,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
|||||||
x @ NonCodeValue::InlineComment { .. } => x,
|
x @ NonCodeValue::InlineComment { .. } => x,
|
||||||
x @ NonCodeValue::NewLineBlockComment { .. } => x,
|
x @ NonCodeValue::NewLineBlockComment { .. } => x,
|
||||||
x @ NonCodeValue::NewLine => x,
|
x @ NonCodeValue::NewLine => x,
|
||||||
|
x @ NonCodeValue::Annotation { .. } => x,
|
||||||
};
|
};
|
||||||
Node::new(
|
Node::new(
|
||||||
NonCodeNode { value, ..nc.inner },
|
NonCodeNode { value, ..nc.inner },
|
||||||
@ -1211,6 +1260,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
|||||||
x @ NonCodeValue::InlineComment { .. } => x,
|
x @ NonCodeValue::InlineComment { .. } => x,
|
||||||
x @ NonCodeValue::NewLineBlockComment { .. } => x,
|
x @ NonCodeValue::NewLineBlockComment { .. } => x,
|
||||||
x @ NonCodeValue::NewLine => x,
|
x @ NonCodeValue::NewLine => x,
|
||||||
|
x @ NonCodeValue::Annotation { .. } => x,
|
||||||
};
|
};
|
||||||
Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
|
Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
|
||||||
}
|
}
|
||||||
@ -1250,7 +1300,7 @@ fn body_items_within_function(i: &mut TokenSlice) -> PResult<WithinFunction> {
|
|||||||
(import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
(import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
||||||
Token { ref value, .. } if value == "return" =>
|
Token { ref value, .. } if value == "return" =>
|
||||||
(return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
(return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
||||||
token if !token.is_code_token() => {
|
token if !token.is_code_token() || token.token_type == TokenType::At => {
|
||||||
non_code_node.map(WithinFunction::NonCode)
|
non_code_node.map(WithinFunction::NonCode)
|
||||||
},
|
},
|
||||||
_ =>
|
_ =>
|
||||||
@ -2267,9 +2317,8 @@ fn question_mark(i: &mut TokenSlice) -> PResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn at_sign(i: &mut TokenSlice) -> PResult<()> {
|
fn at_sign(i: &mut TokenSlice) -> PResult<Token> {
|
||||||
TokenType::At.parse_from(i)?;
|
TokenType::At.parse_from(i)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fun(i: &mut TokenSlice) -> PResult<Token> {
|
fn fun(i: &mut TokenSlice) -> PResult<Token> {
|
||||||
@ -3626,6 +3675,22 @@ height = [obj["a"] -1, 0]"#;
|
|||||||
crate::parsing::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
|
crate::parsing::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_annotation_fn() {
|
||||||
|
crate::parsing::top_level_parse(
|
||||||
|
r#"fn foo() {
|
||||||
|
@annotated
|
||||||
|
return 1
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_annotation_settings() {
|
||||||
|
crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_anon_fn_no_fn() {
|
fn test_anon_fn_no_fn() {
|
||||||
assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
|
assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
|
||||||
|
@ -56,14 +56,13 @@ impl NumericSuffix {
|
|||||||
impl FromStr for NumericSuffix {
|
impl FromStr for NumericSuffix {
|
||||||
type Err = CompilationError;
|
type Err = CompilationError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"_" => Ok(NumericSuffix::Count),
|
"_" => Ok(NumericSuffix::Count),
|
||||||
"mm" => Ok(NumericSuffix::Mm),
|
"mm" => Ok(NumericSuffix::Mm),
|
||||||
"cm" => Ok(NumericSuffix::Cm),
|
"cm" => Ok(NumericSuffix::Cm),
|
||||||
"m" => Ok(NumericSuffix::M),
|
"m" => Ok(NumericSuffix::M),
|
||||||
"inch" => Ok(NumericSuffix::Inch),
|
"inch" | "in" => Ok(NumericSuffix::Inch),
|
||||||
"in" => Ok(NumericSuffix::Inch),
|
|
||||||
"ft" => Ok(NumericSuffix::Ft),
|
"ft" => Ok(NumericSuffix::Ft),
|
||||||
"yd" => Ok(NumericSuffix::Yd),
|
"yd" => Ok(NumericSuffix::Yd),
|
||||||
"deg" => Ok(NumericSuffix::Deg),
|
"deg" => Ok(NumericSuffix::Deg),
|
||||||
|
@ -3,10 +3,10 @@ use std::fmt::Write;
|
|||||||
use crate::parsing::{
|
use crate::parsing::{
|
||||||
ast::types::{
|
ast::types::{
|
||||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||||
CallExpressionKw, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression,
|
CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression,
|
||||||
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
|
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
|
||||||
MemberExpression, MemberObject, Node, NonCodeValue, ObjectExpression, Parameter, PipeExpression, Program,
|
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
|
||||||
TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
|
PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
|
||||||
},
|
},
|
||||||
PIPE_OPERATOR,
|
PIPE_OPERATOR,
|
||||||
};
|
};
|
||||||
@ -55,7 +55,7 @@ impl Program {
|
|||||||
self.non_code_meta
|
self.non_code_meta
|
||||||
.start_nodes
|
.start_nodes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|start| start.format(&indentation))
|
.map(|start| start.recast(options, indentation_level))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -76,7 +76,7 @@ impl Program {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, custom_white_space_or_comment)| {
|
.map(|(i, custom_white_space_or_comment)| {
|
||||||
let formatted = custom_white_space_or_comment.format(&indentation);
|
let formatted = custom_white_space_or_comment.recast(options, indentation_level);
|
||||||
if i == 0 && !formatted.trim().is_empty() {
|
if i == 0 && !formatted.trim().is_empty() {
|
||||||
if let NonCodeValue::BlockComment { .. } = custom_white_space_or_comment.value {
|
if let NonCodeValue::BlockComment { .. } = custom_white_space_or_comment.value {
|
||||||
format!("\n{}", formatted)
|
format!("\n{}", formatted)
|
||||||
@ -115,7 +115,75 @@ impl NonCodeValue {
|
|||||||
fn should_cause_array_newline(&self) -> bool {
|
fn should_cause_array_newline(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::InlineComment { .. } => false,
|
Self::InlineComment { .. } => false,
|
||||||
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine => true,
|
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine | Self::Annotation { .. } => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node<NonCodeNode> {
|
||||||
|
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
|
let indentation = options.get_indentation(indentation_level);
|
||||||
|
match &self.value {
|
||||||
|
NonCodeValue::InlineComment {
|
||||||
|
value,
|
||||||
|
style: CommentStyle::Line,
|
||||||
|
} => format!(" // {}\n", value),
|
||||||
|
NonCodeValue::InlineComment {
|
||||||
|
value,
|
||||||
|
style: CommentStyle::Block,
|
||||||
|
} => format!(" /* {} */", value),
|
||||||
|
NonCodeValue::BlockComment { value, style } => match style {
|
||||||
|
CommentStyle::Block => format!("{}/* {} */", indentation, value),
|
||||||
|
CommentStyle::Line => {
|
||||||
|
if value.trim().is_empty() {
|
||||||
|
format!("{}//\n", indentation)
|
||||||
|
} else {
|
||||||
|
format!("{}// {}\n", indentation, value.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NonCodeValue::NewLineBlockComment { value, style } => {
|
||||||
|
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
|
||||||
|
match style {
|
||||||
|
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
|
||||||
|
CommentStyle::Line => {
|
||||||
|
if value.trim().is_empty() {
|
||||||
|
format!("{}{}//\n", add_start_new_line, indentation)
|
||||||
|
} else {
|
||||||
|
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||||
|
NonCodeValue::Annotation { name, properties } => {
|
||||||
|
let mut result = "@".to_owned();
|
||||||
|
result.push_str(&name.name);
|
||||||
|
if let Some(properties) = properties {
|
||||||
|
result.push('(');
|
||||||
|
result.push_str(
|
||||||
|
&properties
|
||||||
|
.iter()
|
||||||
|
.map(|prop| {
|
||||||
|
format!(
|
||||||
|
"{} = {}",
|
||||||
|
prop.key.name,
|
||||||
|
prop.value
|
||||||
|
.recast(options, indentation_level + 1, ExprContext::Other)
|
||||||
|
.trim()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", "),
|
||||||
|
);
|
||||||
|
result.push(')');
|
||||||
|
result.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,7 +411,7 @@ impl ArrayExpression {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|nc| {
|
.map(|nc| {
|
||||||
found_line_comment |= nc.value.should_cause_array_newline();
|
found_line_comment |= nc.value.should_cause_array_newline();
|
||||||
nc.format("")
|
nc.recast(options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
} else {
|
} else {
|
||||||
@ -481,7 +549,7 @@ impl ObjectExpression {
|
|||||||
let format_items: Vec<_> = (0..num_items)
|
let format_items: Vec<_> = (0..num_items)
|
||||||
.flat_map(|i| {
|
.flat_map(|i| {
|
||||||
if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
|
if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
|
||||||
noncode.iter().map(|nc| nc.format("")).collect::<Vec<_>>()
|
noncode.iter().map(|nc| nc.recast(options, 0)).collect::<Vec<_>>()
|
||||||
} else {
|
} else {
|
||||||
let prop = props.next().unwrap();
|
let prop = props.next().unwrap();
|
||||||
// Use a comma unless it's the last item
|
// Use a comma unless it's the last item
|
||||||
@ -617,10 +685,13 @@ impl Node<PipeExpression> {
|
|||||||
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
|
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
|
||||||
for val in non_code_meta_value {
|
for val in non_code_meta_value {
|
||||||
let formatted = if val.end == self.end {
|
let formatted = if val.end == self.end {
|
||||||
let indentation = options.get_indentation(indentation_level);
|
val.recast(options, indentation_level)
|
||||||
val.format(&indentation).trim_end_matches('\n').to_string()
|
.trim_end_matches('\n')
|
||||||
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
val.format(&indentation).trim_end_matches('\n').to_string()
|
val.recast(options, indentation_level + 1)
|
||||||
|
.trim_end_matches('\n')
|
||||||
|
.to_string()
|
||||||
};
|
};
|
||||||
if let NonCodeValue::BlockComment { .. } = val.value {
|
if let NonCodeValue::BlockComment { .. } = val.value {
|
||||||
s += "\n";
|
s += "\n";
|
||||||
@ -1252,7 +1323,8 @@ part001 = startSketchOn('XY')
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_large_file() {
|
fn test_recast_large_file() {
|
||||||
let some_program_string = r#"// define nts
|
let some_program_string = r#"@settings(units=mm)
|
||||||
|
// define nts
|
||||||
radius = 6.0
|
radius = 6.0
|
||||||
width = 144.0
|
width = 144.0
|
||||||
length = 83.0
|
length = 83.0
|
||||||
@ -1376,7 +1448,8 @@ tabs_l = startSketchOn({
|
|||||||
// Its VERY important this comes back with zero new lines.
|
// Its VERY important this comes back with zero new lines.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"// define nts
|
r#"@settings(units = mm)
|
||||||
|
// define nts
|
||||||
radius = 6.0
|
radius = 6.0
|
||||||
width = 144.0
|
width = 144.0
|
||||||
length = 83.0
|
length = 83.0
|
||||||
|
Reference in New Issue
Block a user