Move the wasm lib, and cleanup rust directory and all references (#5585)
* git mv src/wasm-lib rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv wasm-lib to workspace Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv kcl-lib Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv derive docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * resolve file paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * move more shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * make yarn build:wasm work Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix scripts Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * better references Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix cargo ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix reference Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * cargo sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix script Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix a dep Signed-off-by: Jess Frazelle <github@jessfraz.com> * sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove unused deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * Revert "remove unused deps" This reverts commit fbabdb062e275fd5cbc1476f8480a1afee15d972. * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * deps; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
514
rust/kcl-lib/src/parsing/ast/digest.rs
Normal file
514
rust/kcl-lib/src/parsing/ast/digest.rs
Normal file
@ -0,0 +1,514 @@
|
||||
use sha2::{Digest as DigestTrait, Sha256};
|
||||
|
||||
use crate::parsing::ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, Ascription, BinaryExpression, BinaryPart, BodyItem,
|
||||
CallExpression, CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression,
|
||||
Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression,
|
||||
Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, ObjectExpression, ObjectProperty,
|
||||
Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator, Type, UnaryExpression,
|
||||
VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
};
|
||||
|
||||
/// Position-independent digest of the AST node.
|
||||
pub type Digest = [u8; 32];
|
||||
|
||||
macro_rules! compute_digest {
|
||||
(|$slf:ident, $hasher:ident| $body:block) => {
|
||||
/// Compute a digest over the AST node.
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
if let Some(node_digest) = self.digest {
|
||||
return node_digest;
|
||||
}
|
||||
|
||||
let mut $hasher = Sha256::new();
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut $slf = self;
|
||||
|
||||
$hasher.update(std::any::type_name::<Self>());
|
||||
|
||||
$body
|
||||
|
||||
let node_digest: Digest = $hasher.finalize().into();
|
||||
$slf.digest = Some(node_digest);
|
||||
node_digest
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ImportItem {
|
||||
compute_digest!(|slf, hasher| {
|
||||
let name = slf.name.name.as_bytes();
|
||||
hasher.update(name.len().to_ne_bytes());
|
||||
hasher.update(name);
|
||||
if let Some(alias) = &mut slf.alias {
|
||||
hasher.update([1]);
|
||||
hasher.update(alias.compute_digest());
|
||||
} else {
|
||||
hasher.update([0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl ImportStatement {
|
||||
compute_digest!(|slf, hasher| {
|
||||
match &mut slf.selector {
|
||||
ImportSelector::List { items } => {
|
||||
for item in items {
|
||||
hasher.update(item.compute_digest());
|
||||
}
|
||||
}
|
||||
ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"),
|
||||
ImportSelector::None { alias: None } => hasher.update(b"ImportSelector::None"),
|
||||
ImportSelector::None { alias: Some(alias) } => {
|
||||
hasher.update(b"ImportSelector::None");
|
||||
hasher.update(alias.compute_digest());
|
||||
}
|
||||
}
|
||||
hasher.update(slf.visibility.digestable_id());
|
||||
let path = slf.path.to_string();
|
||||
let path = path.as_bytes();
|
||||
hasher.update(path.len().to_ne_bytes());
|
||||
hasher.update(path);
|
||||
});
|
||||
}
|
||||
|
||||
impl Program {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.body.len().to_ne_bytes());
|
||||
for body_item in slf.body.iter_mut() {
|
||||
hasher.update(body_item.compute_digest());
|
||||
}
|
||||
for attr in &mut slf.inner_attrs {
|
||||
hasher.update(attr.compute_digest());
|
||||
}
|
||||
if let Some(shebang) = &slf.shebang {
|
||||
hasher.update(&shebang.inner.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
let mut hasher = Sha256::new();
|
||||
if let Some(name) = &mut self.name {
|
||||
hasher.update(name.compute_digest());
|
||||
}
|
||||
if let Some(properties) = &mut self.properties {
|
||||
hasher.update(properties.len().to_ne_bytes());
|
||||
for property in properties.iter_mut() {
|
||||
hasher.update(property.compute_digest());
|
||||
}
|
||||
} else {
|
||||
hasher.update("no_properties");
|
||||
}
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl BodyItem {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(match self {
|
||||
BodyItem::ImportStatement(s) => s.compute_digest(),
|
||||
BodyItem::ExpressionStatement(es) => es.compute_digest(),
|
||||
BodyItem::VariableDeclaration(vs) => vs.compute_digest(),
|
||||
BodyItem::ReturnStatement(rs) => rs.compute_digest(),
|
||||
});
|
||||
|
||||
for a in self.get_attrs_mut() {
|
||||
hasher.update(a.compute_digest());
|
||||
}
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
Expr::Literal(lit) => lit.compute_digest(),
|
||||
Expr::Identifier(id) => id.compute_digest(),
|
||||
Expr::TagDeclarator(tag) => tag.compute_digest(),
|
||||
Expr::BinaryExpression(be) => be.compute_digest(),
|
||||
Expr::FunctionExpression(fe) => fe.compute_digest(),
|
||||
Expr::CallExpression(ce) => ce.compute_digest(),
|
||||
Expr::CallExpressionKw(ce) => ce.compute_digest(),
|
||||
Expr::PipeExpression(pe) => pe.compute_digest(),
|
||||
Expr::PipeSubstitution(ps) => ps.compute_digest(),
|
||||
Expr::ArrayExpression(ae) => ae.compute_digest(),
|
||||
Expr::ArrayRangeExpression(are) => are.compute_digest(),
|
||||
Expr::ObjectExpression(oe) => oe.compute_digest(),
|
||||
Expr::MemberExpression(me) => me.compute_digest(),
|
||||
Expr::UnaryExpression(ue) => ue.compute_digest(),
|
||||
Expr::IfExpression(e) => e.compute_digest(),
|
||||
Expr::LabelledExpression(e) => e.compute_digest(),
|
||||
Expr::AscribedExpression(e) => e.compute_digest(),
|
||||
Expr::None(_) => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"Value::None");
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
BinaryPart::Literal(lit) => lit.compute_digest(),
|
||||
BinaryPart::Identifier(id) => id.compute_digest(),
|
||||
BinaryPart::BinaryExpression(be) => be.compute_digest(),
|
||||
BinaryPart::CallExpression(ce) => ce.compute_digest(),
|
||||
BinaryPart::CallExpressionKw(ce) => ce.compute_digest(),
|
||||
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
|
||||
BinaryPart::MemberExpression(me) => me.compute_digest(),
|
||||
BinaryPart::IfExpression(e) => e.compute_digest(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
MemberObject::MemberExpression(me) => me.compute_digest(),
|
||||
MemberObject::Identifier(id) => id.compute_digest(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiteralIdentifier {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
LiteralIdentifier::Identifier(id) => id.compute_digest(),
|
||||
LiteralIdentifier::Literal(lit) => lit.compute_digest(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Type {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
match self {
|
||||
Type::Primitive(prim) => {
|
||||
hasher.update(b"FnArgType::Primitive");
|
||||
hasher.update(prim.digestable_id())
|
||||
}
|
||||
Type::Array(prim) => {
|
||||
hasher.update(b"FnArgType::Array");
|
||||
hasher.update(prim.digestable_id())
|
||||
}
|
||||
Type::Object { properties } => {
|
||||
hasher.update(b"FnArgType::Object");
|
||||
hasher.update(properties.len().to_ne_bytes());
|
||||
for prop in properties.iter_mut() {
|
||||
hasher.update(prop.compute_digest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parameter {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.identifier.compute_digest());
|
||||
match &mut slf.type_ {
|
||||
Some(arg) => {
|
||||
hasher.update(b"Parameter::type_::Some");
|
||||
hasher.update(arg.compute_digest())
|
||||
}
|
||||
None => {
|
||||
hasher.update(b"Parameter::type_::None");
|
||||
}
|
||||
}
|
||||
match slf.default_value {
|
||||
None => hasher.update(vec![0]),
|
||||
Some(DefaultParamVal::KclNone(ref _kcl_none)) => hasher.update(vec![1]),
|
||||
Some(DefaultParamVal::Literal(ref mut literal)) => hasher.update(literal.compute_digest()),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl KclNone {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(b"KclNone");
|
||||
});
|
||||
}
|
||||
|
||||
impl FunctionExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.params.len().to_ne_bytes());
|
||||
for param in slf.params.iter_mut() {
|
||||
hasher.update(param.compute_digest());
|
||||
}
|
||||
hasher.update(slf.body.compute_digest());
|
||||
match &mut slf.return_type {
|
||||
Some(rt) => {
|
||||
hasher.update(b"FunctionExpression::return_type::Some");
|
||||
hasher.update(rt.compute_digest());
|
||||
}
|
||||
None => {
|
||||
hasher.update(b"FunctionExpression::return_type::None");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl ReturnStatement {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.argument.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl ExpressionStatement {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.expression.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl VariableDeclaration {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.declaration.compute_digest());
|
||||
hasher.update(slf.visibility.digestable_id());
|
||||
hasher.update(slf.kind.digestable_id());
|
||||
});
|
||||
}
|
||||
|
||||
impl VariableKind {
|
||||
fn digestable_id(&self) -> [u8; 1] {
|
||||
match self {
|
||||
VariableKind::Const => [2],
|
||||
VariableKind::Fn => [3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemVisibility {
|
||||
fn digestable_id(&self) -> [u8; 1] {
|
||||
match self {
|
||||
ItemVisibility::Default => [0],
|
||||
ItemVisibility::Export => [1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VariableDeclarator {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.id.compute_digest());
|
||||
hasher.update(slf.init.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl Literal {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.value.digestable_id());
|
||||
});
|
||||
}
|
||||
|
||||
impl LiteralValue {
|
||||
fn digestable_id(&self) -> Vec<u8> {
|
||||
match self {
|
||||
LiteralValue::Number { value, suffix } => {
|
||||
let mut result: Vec<u8> = value.to_ne_bytes().into();
|
||||
result.extend((*suffix as u32).to_ne_bytes());
|
||||
result
|
||||
}
|
||||
LiteralValue::String(st) => st.as_bytes().into(),
|
||||
LiteralValue::Bool(b) => {
|
||||
if *b {
|
||||
vec![1]
|
||||
} else {
|
||||
vec![0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Identifier {
|
||||
compute_digest!(|slf, hasher| {
|
||||
let name = slf.name.as_bytes();
|
||||
hasher.update(name.len().to_ne_bytes());
|
||||
hasher.update(name);
|
||||
});
|
||||
}
|
||||
|
||||
impl TagDeclarator {
|
||||
compute_digest!(|slf, hasher| {
|
||||
let name = slf.name.as_bytes();
|
||||
hasher.update(name.len().to_ne_bytes());
|
||||
hasher.update(name);
|
||||
});
|
||||
}
|
||||
|
||||
impl PipeSubstitution {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(b"PipeSubstitution");
|
||||
});
|
||||
}
|
||||
|
||||
impl ArrayExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.elements.len().to_ne_bytes());
|
||||
for value in slf.elements.iter_mut() {
|
||||
hasher.update(value.compute_digest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl ArrayRangeExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.start_element.compute_digest());
|
||||
hasher.update(slf.end_element.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl ObjectExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.properties.len().to_ne_bytes());
|
||||
for prop in slf.properties.iter_mut() {
|
||||
hasher.update(prop.compute_digest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl ObjectProperty {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.key.compute_digest());
|
||||
hasher.update(slf.value.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl MemberExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.object.compute_digest());
|
||||
hasher.update(slf.property.compute_digest());
|
||||
hasher.update(if slf.computed { [1] } else { [0] });
|
||||
});
|
||||
}
|
||||
|
||||
impl BinaryExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.operator.digestable_id());
|
||||
hasher.update(slf.left.compute_digest());
|
||||
hasher.update(slf.right.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl UnaryExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.operator.digestable_id());
|
||||
hasher.update(slf.argument.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl LabelledExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.expr.compute_digest());
|
||||
hasher.update(slf.label.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl Ascription {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.expr.compute_digest());
|
||||
hasher.update(slf.ty.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
impl PipeExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.body.len().to_ne_bytes());
|
||||
for value in slf.body.iter_mut() {
|
||||
hasher.update(value.compute_digest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl CallExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.callee.compute_digest());
|
||||
hasher.update(slf.arguments.len().to_ne_bytes());
|
||||
for argument in slf.arguments.iter_mut() {
|
||||
hasher.update(argument.compute_digest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl CallExpressionKw {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.callee.compute_digest());
|
||||
if let Some(ref mut unlabeled) = slf.unlabeled {
|
||||
hasher.update(unlabeled.compute_digest());
|
||||
} else {
|
||||
hasher.update("no_unlabeled");
|
||||
}
|
||||
hasher.update(slf.arguments.len().to_ne_bytes());
|
||||
for argument in slf.arguments.iter_mut() {
|
||||
hasher.update(argument.label.compute_digest());
|
||||
hasher.update(argument.arg.compute_digest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl IfExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.cond.compute_digest());
|
||||
hasher.update(slf.then_val.compute_digest());
|
||||
for else_if in &mut slf.else_ifs {
|
||||
hasher.update(else_if.compute_digest());
|
||||
}
|
||||
hasher.update(slf.final_else.compute_digest());
|
||||
});
|
||||
}
|
||||
impl ElseIf {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.cond.compute_digest());
|
||||
hasher.update(slf.then_val.compute_digest());
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_parse_digest() {
|
||||
let prog1_string = r#"startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([5, 5], %)
|
||||
"#;
|
||||
let prog1_digest = crate::parsing::top_level_parse(prog1_string).unwrap().compute_digest();
|
||||
|
||||
let prog2_string = r#"startSketchOn('XY')
|
||||
|> startProfileAt([0, 2], %)
|
||||
|> line([5, 5], %)
|
||||
"#;
|
||||
let prog2_digest = crate::parsing::top_level_parse(prog2_string).unwrap().compute_digest();
|
||||
|
||||
assert!(prog1_digest != prog2_digest);
|
||||
|
||||
let prog3_string = r#"startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([5, 5], %)
|
||||
"#;
|
||||
let prog3_digest = crate::parsing::top_level_parse(prog3_string).unwrap().compute_digest();
|
||||
|
||||
assert_eq!(prog1_digest, prog3_digest);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_annotations_digest() {
|
||||
// Settings annotations should be included in the digest.
|
||||
let prog1_string = r#"@settings(defaultLengthUnit = in)
|
||||
startSketchOn('XY')
|
||||
"#;
|
||||
let prog1_digest = crate::parsing::top_level_parse(prog1_string).unwrap().compute_digest();
|
||||
|
||||
let prog2_string = r#"@settings(defaultLengthUnit = mm)
|
||||
startSketchOn('XY')
|
||||
"#;
|
||||
let prog2_digest = crate::parsing::top_level_parse(prog2_string).unwrap().compute_digest();
|
||||
|
||||
assert!(prog1_digest != prog2_digest);
|
||||
}
|
||||
}
|
77
rust/kcl-lib/src/parsing/ast/mod.rs
Normal file
77
rust/kcl-lib/src/parsing/ast/mod.rs
Normal file
@ -0,0 +1,77 @@
|
||||
pub(crate) mod digest;
|
||||
pub mod modify;
|
||||
pub mod types;
|
||||
|
||||
use crate::{
|
||||
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
impl BodyItem {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
BodyItem::ImportStatement(stmt) => stmt.module_id,
|
||||
BodyItem::ExpressionStatement(expression_statement) => expression_statement.module_id,
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.module_id,
|
||||
BodyItem::ReturnStatement(return_statement) => return_statement.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
Expr::Literal(literal) => literal.module_id,
|
||||
Expr::Identifier(identifier) => identifier.module_id,
|
||||
Expr::TagDeclarator(tag) => tag.module_id,
|
||||
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
|
||||
Expr::FunctionExpression(function_expression) => function_expression.module_id,
|
||||
Expr::CallExpression(call_expression) => call_expression.module_id,
|
||||
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
|
||||
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
|
||||
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
|
||||
Expr::ArrayExpression(array_expression) => array_expression.module_id,
|
||||
Expr::ArrayRangeExpression(array_range) => array_range.module_id,
|
||||
Expr::ObjectExpression(object_expression) => object_expression.module_id,
|
||||
Expr::MemberExpression(member_expression) => member_expression.module_id,
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
|
||||
Expr::IfExpression(expr) => expr.module_id,
|
||||
Expr::LabelledExpression(expr) => expr.expr.module_id(),
|
||||
Expr::AscribedExpression(expr) => expr.expr.module_id(),
|
||||
Expr::None(none) => none.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => literal.module_id,
|
||||
BinaryPart::Identifier(identifier) => identifier.module_id,
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
|
||||
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
|
||||
BinaryPart::IfExpression(e) => e.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
|
||||
MemberObject::Identifier(identifier) => identifier.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiteralIdentifier {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
LiteralIdentifier::Identifier(identifier) => identifier.module_id,
|
||||
LiteralIdentifier::Literal(literal) => literal.module_id,
|
||||
}
|
||||
}
|
||||
}
|
282
rust/kcl-lib/src/parsing/ast/modify.rs
Normal file
282
rust/kcl-lib/src/parsing/ast/modify.rs
Normal file
@ -0,0 +1,282 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use kcmc::{
|
||||
each_cmd as mcmd, ok_response::OkModelingCmdResponse, shared::PathCommand, websocket::OkWebSocketResponseData,
|
||||
ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
|
||||
use super::types::{CallExpressionKw, Identifier, LabeledArg, LiteralValue};
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::Point2d,
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
|
||||
PipeSubstitution, VariableDeclarator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
ModuleId, Program,
|
||||
};
|
||||
|
||||
type Point3d = kcmc::shared::Point3d<f64>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The control point data for a curve or line.
|
||||
struct ControlPointData {
|
||||
/// The control points for the curve or line.
|
||||
points: Vec<Point3d>,
|
||||
/// The command that created this curve or line.
|
||||
_command: PathCommand,
|
||||
/// The id of the curve or line.
|
||||
_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
const EPSILON: f64 = 0.015625; // or 2^-6
|
||||
|
||||
/// Update the AST to reflect the new state of the program after something like
|
||||
/// a move or a new line.
|
||||
pub async fn modify_ast_for_sketch(
|
||||
engine: &Arc<Box<dyn EngineManager>>,
|
||||
program: &mut Program,
|
||||
module_id: ModuleId,
|
||||
// The name of the sketch.
|
||||
sketch_name: &str,
|
||||
// The type of plane the sketch is on. `XY` or `XZ`, etc
|
||||
plane: crate::execution::PlaneType,
|
||||
// The ID of the parent sketch.
|
||||
sketch_id: uuid::Uuid,
|
||||
) -> Result<String, KclError> {
|
||||
// First we need to check if this sketch is constrained (even partially).
|
||||
// If it is, we cannot modify it.
|
||||
|
||||
// Get the information about the sketch.
|
||||
if let Some(ast_sketch) = program.ast.get_variable(sketch_name) {
|
||||
let constraint_level = match ast_sketch {
|
||||
super::types::Definition::Variable(var) => var.get_constraint_level(),
|
||||
super::types::Definition::Import(import) => import.get_constraint_level(),
|
||||
};
|
||||
match &constraint_level {
|
||||
ConstraintLevel::None { source_ranges: _ } => {}
|
||||
ConstraintLevel::Ignore { source_ranges: _ } => {}
|
||||
ConstraintLevel::Partial {
|
||||
source_ranges: _,
|
||||
levels,
|
||||
} => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!(
|
||||
"Sketch {} is constrained `{}` and cannot be modified",
|
||||
sketch_name, constraint_level
|
||||
),
|
||||
source_ranges: levels.get_all_partial_or_full_source_ranges(),
|
||||
}));
|
||||
}
|
||||
ConstraintLevel::Full { source_ranges } => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!(
|
||||
"Sketch {} is constrained `{}` and cannot be modified",
|
||||
sketch_name, constraint_level
|
||||
),
|
||||
source_ranges: source_ranges.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's start by getting the path info.
|
||||
|
||||
// Let's get the path info.
|
||||
let resp = engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
&ModelingCmd::PathGetInfo(mcmd::PathGetInfo { path_id: sketch_id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::PathGetInfo(path_info),
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Get path info response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
// Now let's get the control points for all the segments.
|
||||
// TODO: We should probably await all these at once so we aren't going one by one.
|
||||
// But I guess this is fine for now.
|
||||
// We absolutely have to preserve the order of the control points.
|
||||
let mut control_points = Vec::new();
|
||||
for segment in &path_info.segments {
|
||||
if let Some(command_id) = &segment.command_id {
|
||||
let cmd = ModelingCmd::from(mcmd::CurveGetControlPoints {
|
||||
curve_id: (*command_id).into(),
|
||||
});
|
||||
let h = engine.send_modeling_cmd(uuid::Uuid::new_v4(), SourceRange::default(), &cmd);
|
||||
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::CurveGetControlPoints(data),
|
||||
} = h.await?
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Curve get control points response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
control_points.push(ControlPointData {
|
||||
points: data.control_points.clone(),
|
||||
_command: segment.command,
|
||||
_id: (*command_id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if control_points.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("No control points found for sketch {}", sketch_name),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
}
|
||||
|
||||
let first_control_points = control_points.first().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("No control points found for sketch {}", sketch_name),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut additional_lines = Vec::new();
|
||||
let mut last_point = first_control_points.points[1];
|
||||
for control_point in control_points[1..].iter() {
|
||||
additional_lines.push([
|
||||
(control_point.points[1].x - last_point.x),
|
||||
(control_point.points[1].y - last_point.y),
|
||||
]);
|
||||
last_point = Point3d {
|
||||
x: control_point.points[1].x,
|
||||
y: control_point.points[1].y,
|
||||
z: control_point.points[1].z,
|
||||
};
|
||||
}
|
||||
|
||||
// Okay now let's recalculate the sketch from the control points.
|
||||
let start_sketch_at_end = Point3d {
|
||||
x: (first_control_points.points[1].x - first_control_points.points[0].x),
|
||||
y: (first_control_points.points[1].y - first_control_points.points[0].y),
|
||||
z: (first_control_points.points[1].z - first_control_points.points[0].z),
|
||||
};
|
||||
let sketch = create_start_sketch_on(
|
||||
sketch_name,
|
||||
[first_control_points.points[0].x, first_control_points.points[0].y],
|
||||
[start_sketch_at_end.x, start_sketch_at_end.y],
|
||||
plane,
|
||||
additional_lines,
|
||||
)?;
|
||||
|
||||
// Add the sketch back to the program.
|
||||
program.ast.replace_variable(sketch_name, sketch);
|
||||
|
||||
let recasted = program.ast.recast(&FormatOptions::default(), 0);
|
||||
|
||||
// Re-parse the ast so we get the correct source ranges.
|
||||
program.ast = crate::parsing::parse_str(&recasted, module_id).parse_errs_as_err()?;
|
||||
|
||||
Ok(recasted)
|
||||
}
|
||||
|
||||
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
|
||||
fn create_start_sketch_on(
|
||||
name: &str,
|
||||
start: [f64; 2],
|
||||
end: [f64; 2],
|
||||
plane: crate::execution::PlaneType,
|
||||
additional_lines: Vec<[f64; 2]>,
|
||||
) -> Result<Node<VariableDeclarator>, KclError> {
|
||||
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;
|
||||
let start_profile_at = CallExpression::new(
|
||||
"startProfileAt",
|
||||
vec![
|
||||
ArrayExpression::new(vec![
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[0]))).into(),
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[1]))).into(),
|
||||
])
|
||||
.into(),
|
||||
PipeSubstitution::new().into(),
|
||||
],
|
||||
)?;
|
||||
|
||||
// Keep track of where we are so we can close the sketch if we need to.
|
||||
let mut current_position = Point2d {
|
||||
x: start[0],
|
||||
y: start[1],
|
||||
};
|
||||
current_position.x += end[0];
|
||||
current_position.y += end[1];
|
||||
|
||||
let expr = ArrayExpression::new(vec![
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[0]))).into(),
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[1]))).into(),
|
||||
])
|
||||
.into();
|
||||
let initial_line = CallExpressionKw::new(
|
||||
"line",
|
||||
None,
|
||||
vec![LabeledArg {
|
||||
label: super::types::Identifier {
|
||||
name: "end".to_owned(),
|
||||
digest: None,
|
||||
},
|
||||
arg: expr,
|
||||
}],
|
||||
)?;
|
||||
|
||||
let mut pipe_body = vec![start_sketch_on.into(), start_profile_at.into(), initial_line.into()];
|
||||
|
||||
for (index, line) in additional_lines.iter().enumerate() {
|
||||
current_position.x += line[0];
|
||||
current_position.y += line[1];
|
||||
|
||||
// If we are on the last line, check if we have to close the sketch.
|
||||
if index == additional_lines.len() - 1 {
|
||||
let diff_x = (current_position.x - start[0]).abs();
|
||||
let diff_y = (current_position.y - start[1]).abs();
|
||||
// Compare the end of the last line to the start of the first line.
|
||||
// This is a bit more lenient if you look at the value of epsilon.
|
||||
if diff_x <= EPSILON && diff_y <= EPSILON {
|
||||
// We have to close the sketch.
|
||||
let close = CallExpressionKw::new("close", None, vec![])?;
|
||||
pipe_body.push(close.into());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we should check if we should close the sketch.
|
||||
let expr = ArrayExpression::new(vec![
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[0]))).into(),
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[1]))).into(),
|
||||
])
|
||||
.into();
|
||||
let line = CallExpressionKw::new(
|
||||
"line",
|
||||
None,
|
||||
vec![LabeledArg {
|
||||
arg: expr,
|
||||
label: Identifier {
|
||||
name: "end".to_owned(),
|
||||
digest: None,
|
||||
},
|
||||
}],
|
||||
)?;
|
||||
pipe_body.push(line.into());
|
||||
}
|
||||
|
||||
Ok(VariableDeclarator::new(name, PipeExpression::new(pipe_body).into()))
|
||||
}
|
||||
|
||||
fn round_before_recast(num: f64) -> f64 {
|
||||
// We use 2 decimal places.
|
||||
(num * 100.0).round() / 100.0
|
||||
}
|
102
rust/kcl-lib/src/parsing/ast/types/condition.rs
Normal file
102
rust/kcl-lib/src/parsing/ast/types/condition.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{BoxNode, ConstraintLevel, Digest, Expr, Hover, Node, NodeList};
|
||||
use crate::SourceRange;
|
||||
|
||||
// TODO: This should be its own type, similar to Program,
|
||||
// but guaranteed to have an Expression as its final item.
|
||||
// https://github.com/KittyCAD/modeling-app/issues/4015
|
||||
type IfBlock = crate::parsing::ast::types::Program;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct IfExpression {
|
||||
pub cond: Box<Expr>,
|
||||
pub then_val: BoxNode<IfBlock>,
|
||||
pub else_ifs: NodeList<ElseIf>,
|
||||
pub final_else: BoxNode<IfBlock>,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct ElseIf {
|
||||
pub cond: Expr,
|
||||
pub then_val: BoxNode<IfBlock>,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
// Source code metadata
|
||||
|
||||
impl Node<IfExpression> {
|
||||
fn source_ranges(&self) -> Vec<SourceRange> {
|
||||
vec![SourceRange::from(self)]
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<ElseIf> {
|
||||
#[allow(dead_code)]
|
||||
fn source_ranges(&self) -> Vec<SourceRange> {
|
||||
vec![SourceRange::new(self.start, self.end, self.module_id)]
|
||||
}
|
||||
}
|
||||
|
||||
// IDE support and refactors
|
||||
|
||||
impl Node<IfExpression> {
|
||||
/// Get the constraint level.
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
ConstraintLevel::Full {
|
||||
source_ranges: self.source_ranges(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IfExpression {
|
||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
self.cond
|
||||
.get_hover_value_for_position(pos, code)
|
||||
.or_else(|| self.then_val.get_hover_value_for_position(pos, code))
|
||||
.or_else(|| {
|
||||
self.else_ifs
|
||||
.iter()
|
||||
.find_map(|else_if| else_if.get_hover_value_for_position(pos, code))
|
||||
})
|
||||
.or_else(|| self.final_else.get_hover_value_for_position(pos, code))
|
||||
}
|
||||
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
self.cond.rename_identifiers(old_name, new_name);
|
||||
self.then_val.rename_identifiers(old_name, new_name);
|
||||
for else_if in &mut self.else_ifs {
|
||||
else_if.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
self.final_else.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
|
||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||
self.cond.replace_value(source_range, new_value.clone());
|
||||
for else_if in &mut self.else_ifs {
|
||||
else_if.cond.replace_value(source_range, new_value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ElseIf {
|
||||
fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
self.cond
|
||||
.get_hover_value_for_position(pos, code)
|
||||
.or_else(|| self.then_val.get_hover_value_for_position(pos, code))
|
||||
}
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
self.cond.rename_identifiers(old_name, new_name);
|
||||
self.then_val.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
}
|
74
rust/kcl-lib/src/parsing/ast/types/literal_value.rs
Normal file
74
rust/kcl-lib/src/parsing/ast/types/literal_value.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::fmt;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Node;
|
||||
use crate::parsing::{
|
||||
ast::types::{Expr, Literal},
|
||||
token::NumericSuffix,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(untagged, rename_all = "snake_case")]
|
||||
pub enum LiteralValue {
|
||||
Number { value: f64, suffix: NumericSuffix },
|
||||
String(String),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
impl LiteralValue {
|
||||
pub fn from_f64_no_uom(value: f64) -> Self {
|
||||
LiteralValue::Number {
|
||||
value,
|
||||
suffix: NumericSuffix::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LiteralValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LiteralValue::Number { value, suffix } => {
|
||||
let int_value = *value as u64;
|
||||
if int_value as f64 == *value {
|
||||
write!(f, "{int_value}")?;
|
||||
} else {
|
||||
write!(f, "{value}")?;
|
||||
}
|
||||
if *suffix != NumericSuffix::None {
|
||||
write!(f, "{suffix}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
LiteralValue::String(s) => write!(f, "\"{s}\""),
|
||||
LiteralValue::Bool(b) => write!(f, "{b}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Node<Literal>> for Expr {
|
||||
fn from(literal: Node<Literal>) -> Self {
|
||||
Expr::Literal(Box::new(literal))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for LiteralValue {
|
||||
fn from(value: String) -> Self {
|
||||
Self::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for LiteralValue {
|
||||
fn from(value: &'static str) -> Self {
|
||||
// TODO: Make this Cow<str>
|
||||
Self::String(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for LiteralValue {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Bool(value)
|
||||
}
|
||||
}
|
4110
rust/kcl-lib/src/parsing/ast/types/mod.rs
Normal file
4110
rust/kcl-lib/src/parsing/ast/types/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
105
rust/kcl-lib/src/parsing/ast/types/none.rs
Normal file
105
rust/kcl-lib/src/parsing/ast/types/none.rs
Normal file
@ -0,0 +1,105 @@
|
||||
//! KCL has optional parameters. Their type is [`KclOption`].
|
||||
//! If an optional parameter is not given, it will have a value of type [`KclNone`].
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{super::digest::Digest, Node};
|
||||
use crate::{execution::KclValue, parsing::ast::types::ConstraintLevel};
|
||||
|
||||
const KCL_NONE_ID: &str = "KCL_NONE_ID";
|
||||
|
||||
/// KCL value for an optional parameter which was not given an argument.
|
||||
/// (remember, parameters are in the function declaration,
|
||||
/// arguments are in the function call/application).
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Default, Copy)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct KclNone {
|
||||
#[serde(deserialize_with = "deser_private")]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
__private: Private,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
impl KclNone {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
__private: Private {},
|
||||
digest: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
struct Private;
|
||||
|
||||
impl Serialize for Private {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(KCL_NONE_ID)
|
||||
}
|
||||
}
|
||||
|
||||
fn deser_private<'de, D>(deserializer: D) -> Result<Private, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s == KCL_NONE_ID {
|
||||
Ok(Private {})
|
||||
} else {
|
||||
Err(serde::de::Error::custom("not a KCL none"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KclNone> for KclValue {
|
||||
fn from(none: &KclNone) -> Self {
|
||||
KclValue::KclNone {
|
||||
value: *none,
|
||||
meta: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Node<KclNone>> for KclValue {
|
||||
fn from(none: &Node<KclNone>) -> Self {
|
||||
Self::from(&none.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<KclNone> {
|
||||
/// Get the constraint level.
|
||||
/// KCL None is never constrained.
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
ConstraintLevel::None {
|
||||
source_ranges: self.as_source_ranges(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn other_types_will_not_deserialize() {
|
||||
// This shouldn't deserialize into a KCL None,
|
||||
// because it's missing the special Private tag.
|
||||
let j = r#"{"start": 0, "end": 0}"#;
|
||||
let _e = serde_json::from_str::<KclNone>(j).unwrap_err();
|
||||
}
|
||||
#[test]
|
||||
fn serialize_then_deserialize() {
|
||||
// Serializing, then deserializing a None should produce the same value.
|
||||
let before = KclNone::default();
|
||||
let j = serde_json::to_string_pretty(&before).unwrap();
|
||||
let after: KclNone = serde_json::from_str(&j).unwrap();
|
||||
assert_eq!(before, after);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user