2024-10-17 12:30:44 -07:00
|
|
|
use sha2::{Digest as DigestTrait, Sha256};
|
|
|
|
|
2025-01-22 08:29:30 +13:00
|
|
|
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, LiteralValue, VariableKind};
|
2024-12-05 17:56:49 +13:00
|
|
|
use crate::parsing::ast::types::{
|
2024-12-02 15:23:18 -06:00
|
|
|
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
|
Remove non code from Digests (#4772)
Remove non code from Digests
@jessfraz and I talked it over; for the time being we're going to remove
comments from the AST digest. We already exclude source position, so
this is just increasing the degree to which we're going to ignore things
that are not germane to execution.
Before, we'd digest *some* but not all of the comments in the AST.
Silly, I know, right?
So, this code:
```
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line([-24, 0], %) // my thing
|> close(%)
|> extrude(6, %)
```
Would digest differently than:
```
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line([-24, 0], %)
|> close(%)
|> extrude(6, %)
```
Which is wrong. We've fully divested of hashing code comments, so this
will now hash to be the same. Hooray.
2024-12-12 13:55:09 -05:00
|
|
|
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
|
|
|
|
ImportSelector, ImportStatement, KclNone, Literal, LiteralIdentifier, MemberExpression, MemberObject,
|
|
|
|
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
|
|
|
|
TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
|
2024-10-17 12:30:44 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/// 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| {
|
2024-12-07 07:16:04 +13:00
|
|
|
match &mut slf.selector {
|
|
|
|
ImportSelector::List { items } => {
|
|
|
|
for item in items {
|
|
|
|
hasher.update(item.compute_digest());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"),
|
2024-12-17 09:38:32 +13:00
|
|
|
ImportSelector::None { alias: None } => hasher.update(b"ImportSelector::None"),
|
|
|
|
ImportSelector::None { alias: Some(alias) } => {
|
2024-12-07 07:16:04 +13:00
|
|
|
hasher.update(b"ImportSelector::None");
|
|
|
|
hasher.update(alias.compute_digest());
|
|
|
|
}
|
2024-10-17 12:30:44 -07:00
|
|
|
}
|
2024-12-07 07:16:04 +13:00
|
|
|
hasher.update(slf.visibility.digestable_id());
|
2025-01-29 08:28:32 +13:00
|
|
|
let path = slf.path.to_string();
|
|
|
|
let path = path.as_bytes();
|
2024-10-17 12:30:44 -07:00
|
|
|
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());
|
|
|
|
}
|
2024-11-26 16:39:57 +13:00
|
|
|
if let Some(shebang) = &slf.shebang {
|
|
|
|
hasher.update(&shebang.inner.content);
|
|
|
|
}
|
2024-10-17 12:30:44 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BodyItem {
|
|
|
|
pub fn compute_digest(&mut self) -> Digest {
|
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
2024-12-02 15:23:18 -06:00
|
|
|
Expr::CallExpressionKw(ce) => ce.compute_digest(),
|
2024-10-17 12:30:44 -07:00
|
|
|
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(),
|
2024-12-11 21:26:42 +13:00
|
|
|
Expr::LabelledExpression(e) => e.compute_digest(),
|
2024-10-17 12:30:44 -07:00
|
|
|
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(),
|
2024-12-02 15:23:18 -06:00
|
|
|
BinaryPart::CallExpressionKw(ce) => ce.compute_digest(),
|
2024-10-17 12:30:44 -07:00
|
|
|
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 FnArgType {
|
|
|
|
pub fn compute_digest(&mut self) -> Digest {
|
|
|
|
let mut hasher = Sha256::new();
|
|
|
|
|
|
|
|
match self {
|
|
|
|
FnArgType::Primitive(prim) => {
|
|
|
|
hasher.update(b"FnArgType::Primitive");
|
|
|
|
hasher.update(prim.digestable_id())
|
|
|
|
}
|
|
|
|
FnArgType::Array(prim) => {
|
|
|
|
hasher.update(b"FnArgType::Array");
|
|
|
|
hasher.update(prim.digestable_id())
|
|
|
|
}
|
|
|
|
FnArgType::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()
|
|
|
|
}
|
|
|
|
}
|
2024-12-05 21:04:40 -06:00
|
|
|
|
2024-10-17 12:30:44 -07:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
2024-12-05 21:04:40 -06:00
|
|
|
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()),
|
|
|
|
}
|
2024-10-17 12:30:44 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-12-11 20:49:18 -05:00
|
|
|
impl KclNone {
|
|
|
|
compute_digest!(|slf, hasher| {
|
|
|
|
hasher.update(b"KclNone");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-10-17 12:30:44 -07:00
|
|
|
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| {
|
2024-12-07 07:16:04 +13:00
|
|
|
hasher.update(slf.declaration.compute_digest());
|
2024-10-17 12:30:44 -07:00
|
|
|
hasher.update(slf.visibility.digestable_id());
|
|
|
|
hasher.update(slf.kind.digestable_id());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-12-05 17:56:49 +13:00
|
|
|
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],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-17 12:30:44 -07:00
|
|
|
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());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-01-22 08:29:30 +13:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-17 12:30:44 -07:00
|
|
|
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());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-12-11 21:26:42 +13:00
|
|
|
impl LabelledExpression {
|
|
|
|
compute_digest!(|slf, hasher| {
|
|
|
|
hasher.update(slf.expr.compute_digest());
|
|
|
|
hasher.update(slf.label.compute_digest());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-10-17 12:30:44 -07:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-12-02 15:23:18 -06:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-10-17 12:30:44 -07:00
|
|
|
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());
|
|
|
|
});
|
|
|
|
}
|
2024-12-05 17:56:49 +13:00
|
|
|
|
|
|
|
#[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);
|
|
|
|
}
|
|
|
|
}
|