Remove annotations from non-code-meta and store them in nodes (#5360)

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-02-13 16:17:09 +13:00
committed by GitHub
parent 0874891dd0
commit 1f405b9327
16 changed files with 547 additions and 584 deletions

View File

@ -60,7 +60,8 @@ export class KclManager {
nonCodeNodes: {},
startNodes: [],
},
trivia: [],
innerAttrs: [],
outerAttrs: [],
}
private _execState: ExecState = emptyExecState()
private _variables: VariableMap = {}
@ -255,7 +256,8 @@ export class KclManager {
nonCodeNodes: {},
startNodes: [],
},
trivia: [],
innerAttrs: [],
outerAttrs: [],
}
}

View File

@ -134,7 +134,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -142,7 +142,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -150,7 +150,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -158,7 +158,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -166,7 +166,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -174,7 +174,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -182,7 +182,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -190,7 +190,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -198,7 +198,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
] satisfies Node<Identifier>[]),
'yo',
@ -217,7 +217,8 @@ describe('Testing addSketchTo', () => {
end: 0,
moduleId: 0,
nonCodeMeta: { nonCodeNodes: {}, startNodes: [] },
trivia: [],
innerAttrs: [],
outerAttrs: [],
},
'yz'
)

View File

@ -278,7 +278,7 @@ export function mutateObjExpProp(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
})
}
}
@ -891,7 +891,7 @@ export function createLiteral(value: LiteralValue | number): Node<Literal> {
moduleId: 0,
value,
raw,
trivia: [],
outerAttrs: [],
}
}
@ -901,7 +901,7 @@ export function createTagDeclarator(value: string): Node<TagDeclarator> {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
value,
}
@ -913,7 +913,7 @@ export function createIdentifier(name: string): Node<Identifier> {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
}
@ -925,7 +925,7 @@ export function createPipeSubstitution(): Node<PipeSubstitution> {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
}
}
@ -938,13 +938,13 @@ export function createCallExpressionStdLib(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
callee: {
type: 'Identifier',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
},
@ -962,13 +962,13 @@ export function createCallExpressionStdLibKw(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
callee: {
type: 'Identifier',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
},
@ -986,13 +986,13 @@ export function createCallExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
callee: {
type: 'Identifier',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
},
@ -1008,7 +1008,7 @@ export function createArrayExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
nonCodeMeta: nonCodeMetaEmpty(),
elements,
@ -1023,7 +1023,7 @@ export function createPipeExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
body,
nonCodeMeta: nonCodeMetaEmpty(),
@ -1041,14 +1041,14 @@ export function createVariableDeclaration(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
declaration: {
type: 'VariableDeclarator',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
id: createIdentifier(varName),
init,
@ -1066,7 +1066,7 @@ export function createObjectExpression(properties: {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
nonCodeMeta: nonCodeMetaEmpty(),
properties: Object.entries(properties).map(([key, value]) => ({
@ -1074,7 +1074,7 @@ export function createObjectExpression(properties: {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
key: createIdentifier(key),
value,
@ -1091,7 +1091,7 @@ export function createUnaryExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
operator,
argument,
@ -1108,7 +1108,7 @@ export function createBinaryExpression([left, operator, right]: [
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
operator,
left,

View File

@ -1945,7 +1945,8 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
startNodes: [],
nonCodeNodes: [],
},
trivia: [],
innerAttrs: [],
outerAttrs: [],
},
pathToNode,
}
@ -2527,7 +2528,7 @@ function addTagKw(): addTagFn {
start: callExpr.node.start,
end: callExpr.node.end,
moduleId: callExpr.node.moduleId,
trivia: callExpr.node.trivia,
outerAttrs: callExpr.node.outerAttrs,
})
}

View File

@ -5,14 +5,18 @@ use kittycad_modeling_cmds::coord::{System, KITTYCAD, OPENGL, VULKAN};
use crate::{
errors::KclErrorDetails,
execution::kcl_value::{UnitAngle, UnitLen},
parsing::ast::types::{Expr, Node, NonCodeValue, ObjectProperty},
parsing::ast::types::{Annotation, Expr, Node, ObjectProperty},
KclError, SourceRange,
};
/// Annotations which should cause re-execution if they change.
pub(super) const SIGNIFICANT_ATTRS: [&str; 2] = [SETTINGS, NO_PRELUDE];
pub(crate) const SETTINGS: &str = "settings";
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) const NO_PRELUDE: &str = "no_prelude";
pub(super) const IMPORT_FORMAT: &str = "format";
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
pub(super) const IMPORT_COORDS: &str = "coords";
@ -25,35 +29,24 @@ pub(super) enum AnnotationScope {
Module,
}
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.as_ref().unwrap().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 is_significant(attr: &&Node<Annotation>) -> bool {
match attr.name() {
Some(name) => SIGNIFICANT_ATTRS.contains(&name),
None => true,
}
}
pub(super) fn unnamed_properties<'a>(
annotations: impl Iterator<Item = &'a NonCodeValue>,
) -> Option<&'a [Node<ObjectProperty>]> {
for annotation in annotations {
if let NonCodeValue::Annotation { name: None, properties } = annotation {
return properties.as_deref();
}
}
None
pub(super) fn expect_properties<'a>(
for_key: &'static str,
annotation: &'a Node<Annotation>,
) -> Result<&'a [Node<ObjectProperty>], KclError> {
assert_eq!(annotation.name().unwrap(), for_key);
Ok(&**annotation.properties.as_ref().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Empty `{for_key}` annotation"),
source_ranges: vec![annotation.as_source_range()],
})
})?)
}
pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {

View File

@ -6,8 +6,8 @@ use itertools::{EitherOrBoth, Itertools};
use tokio::sync::RwLock;
use crate::{
execution::{memory::ProgramMemory, ExecState, ExecutorSettings},
parsing::ast::types::{Node, NonCodeValue, Program},
execution::{annotations, memory::ProgramMemory, ExecState, ExecutorSettings},
parsing::ast::types::{Annotation, Node, Program},
walk::Node as WalkNode,
};
@ -91,7 +91,7 @@ pub(super) enum CacheResult {
/// Returns `None` when there are no changes to the program, i.e. it is
/// fully cached.
pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInformation<'_>) -> CacheResult {
let mut try_reapply_settings = false;
let mut reapply_settings = false;
// If the settings are different we might need to bust the cache.
// We specifically do this before checking if they are the exact same.
@ -107,13 +107,13 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
// If anything else is different we may not need to re-execute, but rather just
// run the settings again.
try_reapply_settings = true;
reapply_settings = true;
}
// If the ASTs are the EXACT same we return None.
// We don't even need to waste time computing the digests.
if old.ast == new.ast {
return CacheResult::NoAction(try_reapply_settings);
return CacheResult::NoAction(reapply_settings);
}
// We have to clone just because the digests are stored inline :-(
@ -127,23 +127,27 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
// Check if the digest is the same.
if old_ast.digest == new_ast.digest {
return CacheResult::NoAction(try_reapply_settings);
return CacheResult::NoAction(reapply_settings);
}
// Check if the annotations are different.
if !old_ast.annotations().zip_longest(new_ast.annotations()).all(|pair| {
if !old_ast
.inner_attrs
.iter()
.filter(annotations::is_significant)
.zip_longest(new_ast.inner_attrs.iter().filter(annotations::is_significant))
.all(|pair| {
match pair {
EitherOrBoth::Both(old, new) => {
// Compare annotations, ignoring source ranges. Digests must
// have been computed before this.
match (&old.value, &new.value) {
(
NonCodeValue::Annotation { name, properties },
NonCodeValue::Annotation {
let Annotation { name, properties, .. } = &old.inner;
let Annotation {
name: new_name,
properties: new_properties,
},
) => {
..
} = &new.inner;
name.as_ref().map(|n| n.digest) == new_name.as_ref().map(|n| n.digest)
&& properties
.as_ref()
@ -154,10 +158,8 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
}
_ => false,
}
}
_ => false,
}
}) {
})
{
// If any of the annotations are different at the beginning of the
// program, it's likely the settings, and we have to bust the cache and
// re-execute the whole thing.
@ -169,12 +171,7 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
}
// Check if the changes were only to Non-code areas, like comments or whitespace.
let (clear_scene, program) = generate_changed_program(old_ast, new_ast);
CacheResult::ReExecute {
clear_scene,
reapply_settings: try_reapply_settings,
program,
}
generate_changed_program(old_ast, new_ast, reapply_settings)
}
/// Force-generate a new CacheResult, even if one shouldn't be made. The
@ -183,7 +180,7 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
/// how we construct a new [CacheResult].
///
/// Digests *must* be computed before calling this.
fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>) -> (bool, Node<Program>) {
fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>, reapply_settings: bool) -> CacheResult {
if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| {
let old_node: WalkNode = old.into();
let new_node: WalkNode = new.into();
@ -194,7 +191,11 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>)
// means a single insertion or deletion will result in a cache
// bust.
return (true, new_ast);
return CacheResult::ReExecute {
clear_scene: true,
reapply_settings,
program: new_ast,
};
}
// otherwise the overlapping section of the ast bodies matches.
@ -210,7 +211,11 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>)
// supporting that.
// Cache bust time.
(true, new_ast)
CacheResult::ReExecute {
clear_scene: true,
reapply_settings,
program: new_ast,
}
}
std::cmp::Ordering::Greater => {
// the new AST is longer than the old AST, which means
@ -222,7 +227,11 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>)
new_ast.body = new_ast.body[old_ast.body.len()..].to_owned();
(false, new_ast)
CacheResult::ReExecute {
clear_scene: false,
reapply_settings,
program: new_ast,
}
}
std::cmp::Ordering::Equal => {
// currently unreachable, but let's pretend like the code
@ -234,9 +243,7 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>)
// so but i think many things. This def needs to change
// when the code above changes.
new_ast.body = vec![];
(false, new_ast)
CacheResult::NoAction(reapply_settings)
}
}
}
@ -368,8 +375,10 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_changed_program_same_code_changed_code_comments() {
let old = r#" // Removed the end face for the extrusion.
async fn test_get_changed_program_same_code_changed_code_comments_attrs() {
let old = r#"@foo(whatever = whatever)
@bar
// Removed the end face for the extrusion.
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
@ -381,7 +390,9 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25) "#;
let new = r#"// Remove the end face for the extrusion.
let new = r#"@foo(whatever = 42)
@baz
// Remove the end face for the extrusion.
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])

View File

@ -17,9 +17,9 @@ use crate::{
},
modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeNode, NonCodeValue,
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef,
ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, UnaryOperator,
},
source_range::SourceRange,
@ -37,37 +37,36 @@ enum StatementKind<'a> {
impl ExecutorContext {
async fn handle_annotations(
&self,
annotations: impl Iterator<Item = (&NonCodeValue, SourceRange)>,
annotations: impl Iterator<Item = &Node<Annotation>>,
scope: annotations::AnnotationScope,
exec_state: &mut ExecState,
) -> Result<bool, KclError> {
let mut no_prelude = false;
for (annotation, source_range) in annotations {
if annotation.annotation_name() == Some(annotations::SETTINGS) {
for annotation in annotations {
if annotation.name() == Some(annotations::SETTINGS) {
if scope == annotations::AnnotationScope::Module {
let old_units = exec_state.length_unit();
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
exec_state.mod_local.settings.update_from_annotation(annotation)?;
let new_units = exec_state.length_unit();
if !self.engine.execution_kind().is_isolated() && old_units != new_units {
self.engine.set_units(new_units.into(), source_range).await?;
self.engine
.set_units(new_units.into(), annotation.as_source_range())
.await?;
}
} else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Settings can only be modified at the top level scope of a file".to_owned(),
source_ranges: vec![source_range],
source_ranges: vec![annotation.as_source_range()],
}));
}
}
if annotation.annotation_name() == Some(annotations::NO_PRELUDE) {
if annotation.name() == Some(annotations::NO_PRELUDE) {
if scope == annotations::AnnotationScope::Module {
no_prelude = true;
} else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Prelude can only be skipped at the top level scope of a file".to_owned(),
source_ranges: vec![source_range],
source_ranges: vec![annotation.as_source_range()],
}));
}
}
@ -87,11 +86,7 @@ impl ExecutorContext {
if body_type == BodyType::Root {
let _no_prelude = self
.handle_annotations(
program
.non_code_meta
.start_nodes
.iter()
.filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))),
program.inner_attrs.iter(),
annotations::AnnotationScope::Module,
exec_state,
)
@ -100,7 +95,7 @@ impl ExecutorContext {
let mut last_expr = None;
// Iterate over the body of the program.
for (i, statement) in program.body.iter().enumerate() {
for statement in &program.body {
match statement {
BodyItem::ImportStatement(import_stmt) => {
if body_type != BodyType::Root {
@ -111,9 +106,9 @@ impl ExecutorContext {
}
let source_range = SourceRange::from(import_stmt);
let meta_nodes = program.non_code_meta.get(i);
let attrs = &import_stmt.outer_attrs;
let module_id = self
.open_module(&import_stmt.path, meta_nodes, exec_state, source_range)
.open_module(&import_stmt.path, attrs, exec_state, source_range)
.await?;
match &import_stmt.selector {
@ -209,7 +204,7 @@ impl ExecutorContext {
let source_range = SourceRange::from(&variable_declaration.declaration.init);
let metadata = Metadata { source_range };
let _meta_nodes = program.non_code_meta.get(i);
let _annotations = &variable_declaration.outer_attrs;
let memory_item = self
.execute_expr(
@ -279,7 +274,7 @@ impl ExecutorContext {
async fn open_module(
&self,
path: &ImportPath,
non_code_meta: &[Node<NonCodeNode>],
attrs: &[Node<Annotation>],
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<ModuleId, KclError> {
@ -307,7 +302,7 @@ impl ExecutorContext {
let id = exec_state.next_module_id();
let path = resolved_path.expect_path();
let format = super::import::format_from_annotations(non_code_meta, path, source_range)?;
let format = super::import::format_from_annotations(attrs, path, source_range)?;
let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom));
Ok(id)
@ -1838,18 +1833,7 @@ mod test {
// Run each test.
let func_expr = &Node::no_src(FunctionExpression {
params,
body: Node {
inner: crate::parsing::ast::types::Program {
body: Vec::new(),
non_code_meta: Default::default(),
shebang: None,
digest: None,
},
start: 0,
end: 0,
module_id: ModuleId::default(),
trivia: Vec::new(),
},
body: crate::parsing::ast::types::Program::empty(),
return_type: None,
digest: None,
});

View File

@ -19,7 +19,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{annotations, kcl_value::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
fs::FileSystem,
parsing::ast::types::{Node, NonCodeNode},
parsing::ast::types::{Annotation, Node},
source_range::SourceRange,
};
@ -155,16 +155,18 @@ pub async fn import_foreign(
}
pub(super) fn format_from_annotations(
non_code_meta: &[Node<NonCodeNode>],
annotations: &[Node<Annotation>],
path: &Path,
import_source_range: SourceRange,
) -> Result<Option<InputFormat>, KclError> {
let Some(props) = annotations::unnamed_properties(non_code_meta.iter().map(|n| &n.value)) else {
if annotations.is_empty() {
return Ok(None);
};
}
let props = annotations.iter().flat_map(|a| a.properties.as_deref().unwrap_or(&[]));
let mut result = None;
for p in props {
for p in props.clone() {
if p.key.name == annotations::IMPORT_FORMAT {
result = Some(
get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| {
@ -422,8 +424,8 @@ mod test {
// no format, no options
let text = "@()\nimport '../foo.gltf' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let non_code_meta = parsed.non_code_meta.get(0);
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.gltf"), SourceRange::default())
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.gltf"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -434,8 +436,8 @@ mod test {
// format, no options
let text = "@(format = gltf)\nimport '../foo.txt' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let non_code_meta = parsed.non_code_meta.get(0);
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.txt"), SourceRange::default())
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.txt"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -444,7 +446,7 @@ mod test {
);
// format, no extension (wouldn't parse but might some day)
let fmt = format_from_annotations(non_code_meta, Path::new("../foo"), SourceRange::default())
let fmt = format_from_annotations(attrs, Path::new("../foo"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -455,8 +457,8 @@ mod test {
// format, options
let text = "@(format = obj, coords = vulkan, lengthUnit = ft)\nimport '../foo.txt' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let non_code_meta = parsed.non_code_meta.get(0);
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.txt"), SourceRange::default())
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.txt"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -470,8 +472,8 @@ mod test {
// no format, options
let text = "@(coords = vulkan, lengthUnit = ft)\nimport '../foo.obj' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let non_code_meta = parsed.non_code_meta.get(0);
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.obj"), SourceRange::default())
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.obj"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -523,8 +525,8 @@ mod test {
#[track_caller]
fn assert_annotation_error(src: &str, path: &str, expected: &str) {
let parsed = crate::Program::parse_no_errs(src).unwrap().ast;
let non_code_meta = parsed.non_code_meta.get(0);
let err = format_from_annotations(non_code_meta, Path::new(path), SourceRange::default()).unwrap_err();
let attrs = parsed.body[0].get_attrs();
let err = format_from_annotations(attrs, Path::new(path), SourceRange::default()).unwrap_err();
assert!(
err.message().contains(expected),
"Expected: `{expected}`, found `{}`",

View File

@ -12,7 +12,7 @@ use crate::{
ExecOutcome, ExecutorSettings, KclValue, Operation, UnitAngle, UnitLen,
},
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
parsing::ast::types::NonCodeValue,
parsing::ast::types::Annotation,
source_range::SourceRange,
};
@ -236,21 +236,20 @@ pub struct MetaSettings {
impl MetaSettings {
pub(crate) fn update_from_annotation(
&mut self,
annotation: &NonCodeValue,
source_range: SourceRange,
annotation: &crate::parsing::ast::types::Node<Annotation>,
) -> Result<(), KclError> {
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
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)?;
let value = kcl_value::UnitLen::from_str(value, annotation.as_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)?;
let value = kcl_value::UnitAngle::from_str(value, annotation.as_source_range())?;
self.default_angle_units = value;
}
name => {
@ -260,7 +259,7 @@ impl MetaSettings {
annotations::SETTINGS_UNIT_LENGTH,
annotations::SETTINGS_UNIT_ANGLE
),
source_ranges: vec![source_range],
source_ranges: vec![annotation.as_source_range()],
}))
}
}

View File

@ -1,13 +1,10 @@
use sha2::{Digest as DigestTrait, Sha256};
use super::types::{
DefaultParamVal, ItemVisibility, LabelledExpression, LiteralValue, NonCodeMeta, NonCodeNode, NonCodeValue,
VariableKind,
};
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, LiteralValue, VariableKind};
use crate::parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
ImportSelector, ImportStatement, KclNone, Literal, LiteralIdentifier, MemberExpression, MemberObject,
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression,
CallExpressionKw, 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,
};
@ -82,49 +79,22 @@ impl Program {
for body_item in slf.body.iter_mut() {
hasher.update(body_item.compute_digest());
}
// This contains settings annotations.
hasher.update(slf.non_code_meta.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 NonCodeMeta {
compute_digest!(|slf, hasher| {
for non_code_node in slf.start_nodes.iter_mut() {
hasher.update(non_code_node.compute_digest());
}
for (_, non_code_nodes) in slf.non_code_nodes.iter_mut() {
for non_code_node in non_code_nodes.iter_mut() {
hasher.update(non_code_node.compute_digest());
}
}
});
}
impl NonCodeNode {
compute_digest!(|slf, hasher| {
hasher.update(slf.value.compute_digest());
});
}
impl NonCodeValue {
impl Annotation {
pub fn compute_digest(&mut self) -> Digest {
let mut hasher = Sha256::new();
match self {
NonCodeValue::InlineComment { .. } => {}
NonCodeValue::BlockComment { .. } => {}
NonCodeValue::NewLineBlockComment { .. } => {}
NonCodeValue::NewLine => {}
NonCodeValue::Annotation {
ref mut name,
properties,
} => {
if let Some(name) = name {
if let Some(name) = &mut self.name {
hasher.update(name.compute_digest());
}
if let Some(properties) = properties {
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());
@ -132,20 +102,24 @@ impl NonCodeValue {
} else {
hasher.update("no_properties");
}
}
}
hasher.finalize().into()
}
}
impl BodyItem {
pub fn compute_digest(&mut self) -> Digest {
match self {
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()
}
}

View File

@ -52,7 +52,7 @@ pub struct Node<T> {
#[serde(default, skip_serializing_if = "ModuleId::is_top_level")]
pub module_id: ModuleId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub trivia: NodeList<NonCodeNode>,
pub outer_attrs: NodeList<Annotation>,
}
impl<T> Node<T> {
@ -96,7 +96,7 @@ impl<T> Node<T> {
start,
end,
module_id,
trivia: Vec::new(),
outer_attrs: Vec::new(),
}
}
@ -106,7 +106,7 @@ impl<T> Node<T> {
start: 0,
end: 0,
module_id: ModuleId::default(),
trivia: Vec::new(),
outer_attrs: Vec::new(),
}
}
@ -116,7 +116,7 @@ impl<T> Node<T> {
start,
end,
module_id,
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -181,6 +181,8 @@ pub struct Program {
pub non_code_meta: NonCodeMeta,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shebang: Option<Node<Shebang>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub inner_attrs: NodeList<Annotation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
@ -261,28 +263,12 @@ impl Node<Program> {
Ok(findings)
}
pub fn annotations(&self) -> impl Iterator<Item = &Node<NonCodeNode>> {
self.non_code_meta
.start_nodes
.iter()
.filter(|n| n.value_is_annotation())
}
pub fn annotations_mut(&mut self) -> impl Iterator<Item = &mut Node<NonCodeNode>> {
self.non_code_meta
.start_nodes
.iter_mut()
.filter(|n| n.value_is_annotation())
}
/// Get the annotations for the meta settings from the kcl file.
pub fn meta_settings(&self) -> Result<Option<crate::execution::MetaSettings>, KclError> {
for annotation_node in self.annotations() {
let annotation = &annotation_node.value;
if annotation.annotation_name() == Some(annotations::SETTINGS) {
let source_range = annotation_node.as_source_range();
for annotation in &self.inner_attrs {
if annotation.name() == Some(annotations::SETTINGS) {
let mut meta_settings = crate::execution::MetaSettings::default();
meta_settings.update_from_annotation(annotation, source_range)?;
meta_settings.update_from_annotation(annotation)?;
return Ok(Some(meta_settings));
}
}
@ -293,24 +279,18 @@ impl Node<Program> {
pub fn change_meta_settings(&mut self, settings: crate::execution::MetaSettings) -> Result<Self, KclError> {
let mut new_program = self.clone();
let mut found = false;
for node in new_program.annotations_mut() {
if node.value.annotation_name() == Some(annotations::SETTINGS) {
let annotation = NonCodeValue::new_from_meta_settings(&settings);
*node = Node::no_src(NonCodeNode {
value: annotation,
digest: None,
});
for node in &mut new_program.inner_attrs {
if node.name() == Some(annotations::SETTINGS) {
*node = Node::no_src(Annotation::new_from_meta_settings(&settings));
found = true;
break;
}
}
if !found {
let annotation = NonCodeValue::new_from_meta_settings(&settings);
new_program.non_code_meta.start_nodes.push(Node::no_src(NonCodeNode {
value: annotation,
digest: None,
}));
new_program
.inner_attrs
.push(Node::no_src(Annotation::new_from_meta_settings(&settings)));
}
Ok(new_program)
@ -318,6 +298,10 @@ impl Node<Program> {
}
impl Program {
#[cfg(test)]
pub fn empty() -> Node<Self> {
Node::no_src(Program::default())
}
/// Is the last body item an expression?
pub fn ends_with_expr(&self) -> bool {
let Some(ref last) = self.body.last() else {
@ -629,6 +613,33 @@ impl BodyItem {
BodyItem::ReturnStatement(return_statement) => return_statement.end,
}
}
pub(crate) fn set_attrs(&mut self, attr: NodeList<Annotation>) {
match self {
BodyItem::ImportStatement(node) => node.outer_attrs = attr,
BodyItem::ExpressionStatement(node) => node.outer_attrs = attr,
BodyItem::VariableDeclaration(node) => node.outer_attrs = attr,
BodyItem::ReturnStatement(node) => node.outer_attrs = attr,
}
}
pub(crate) fn get_attrs(&self) -> &[Node<Annotation>] {
match self {
BodyItem::ImportStatement(node) => &node.outer_attrs,
BodyItem::ExpressionStatement(node) => &node.outer_attrs,
BodyItem::VariableDeclaration(node) => &node.outer_attrs,
BodyItem::ReturnStatement(node) => &node.outer_attrs,
}
}
pub(crate) fn get_attrs_mut(&mut self) -> &mut [Node<Annotation>] {
match self {
BodyItem::ImportStatement(node) => &mut node.outer_attrs,
BodyItem::ExpressionStatement(node) => &mut node.outer_attrs,
BodyItem::VariableDeclaration(node) => &mut node.outer_attrs,
BodyItem::ReturnStatement(node) => &mut node.outer_attrs,
}
}
}
impl From<BodyItem> for SourceRange {
@ -1134,24 +1145,6 @@ impl NonCodeNode {
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
NonCodeValue::NewLine => "\n\n".to_string(),
n @ NonCodeValue::Annotation { .. } => n.annotation_name().unwrap_or("").to_owned(),
}
}
pub fn annotation(&self) -> Option<&NonCodeValue> {
match &self.value {
a @ NonCodeValue::Annotation { .. } => Some(a),
_ => None,
}
}
pub fn value_is_annotation(&self) -> bool {
match self.value {
NonCodeValue::InlineComment { .. }
| NonCodeValue::BlockComment { .. }
| NonCodeValue::NewLineBlockComment { .. }
| NonCodeValue::NewLine => false,
NonCodeValue::Annotation { .. } => true,
}
}
}
@ -1202,37 +1195,6 @@ pub enum NonCodeValue {
// A new line like `\n\n` NOT a new line like `\n`.
// This is also not a comment.
NewLine,
Annotation {
name: Option<Node<Identifier>>,
properties: Option<Vec<Node<ObjectProperty>>>,
},
}
impl NonCodeValue {
pub fn annotation_name(&self) -> Option<&str> {
match self {
NonCodeValue::Annotation { name, .. } => name.as_ref().map(|i| &*i.name),
_ => None,
}
}
pub fn new_from_meta_settings(settings: &crate::execution::MetaSettings) -> NonCodeValue {
let mut properties: Vec<Node<ObjectProperty>> = vec![ObjectProperty::new(
Identifier::new(annotations::SETTINGS_UNIT_LENGTH),
Expr::Identifier(Box::new(Identifier::new(&settings.default_length_units.to_string()))),
)];
if settings.default_angle_units != Default::default() {
properties.push(ObjectProperty::new(
Identifier::new(annotations::SETTINGS_UNIT_ANGLE),
Expr::Identifier(Box::new(Identifier::new(&settings.default_angle_units.to_string()))),
));
}
NonCodeValue::Annotation {
name: Some(Identifier::new(annotations::SETTINGS)),
properties: Some(properties),
}
}
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -1314,6 +1276,47 @@ impl<'de> Deserialize<'de> for NonCodeMeta {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct Annotation {
pub name: Option<Node<Identifier>>,
pub properties: Option<Vec<Node<ObjectProperty>>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<Digest>,
}
impl Annotation {
pub fn is_inner(&self) -> bool {
self.name.is_some()
}
pub fn name(&self) -> Option<&str> {
self.name.as_ref().map(|n| &*n.name)
}
pub fn new_from_meta_settings(settings: &crate::execution::MetaSettings) -> Annotation {
let mut properties: Vec<Node<ObjectProperty>> = vec![ObjectProperty::new(
Identifier::new(annotations::SETTINGS_UNIT_LENGTH),
Expr::Identifier(Box::new(Identifier::new(&settings.default_length_units.to_string()))),
)];
if settings.default_angle_units != Default::default() {
properties.push(ObjectProperty::new(
Identifier::new(annotations::SETTINGS_UNIT_ANGLE),
Expr::Identifier(Box::new(Identifier::new(&settings.default_angle_units.to_string()))),
));
}
Annotation {
name: Some(Identifier::new(annotations::SETTINGS)),
properties: Some(properties),
digest: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
@ -3823,12 +3826,7 @@ const cylinder = startSketchOn('-XZ')
(0..=0),
Node::no_src(FunctionExpression {
params: vec![],
body: Node::no_src(Program {
body: Vec::new(),
non_code_meta: Default::default(),
shebang: None,
digest: None,
}),
body: Program::empty(),
return_type: None,
digest: None,
}),
@ -3847,18 +3845,7 @@ const cylinder = startSketchOn('-XZ')
labeled: true,
digest: None,
}],
body: Node {
inner: Program {
body: Vec::new(),
non_code_meta: Default::default(),
shebang: None,
digest: None,
},
start: 0,
end: 0,
module_id: ModuleId::default(),
trivia: Vec::new(),
},
body: Program::empty(),
return_type: None,
digest: None,
}),
@ -3877,18 +3864,7 @@ const cylinder = startSketchOn('-XZ')
labeled: true,
digest: None,
}],
body: Node {
inner: Program {
body: Vec::new(),
non_code_meta: Default::default(),
shebang: None,
digest: None,
},
start: 0,
end: 0,
module_id: ModuleId::default(),
trivia: Vec::new(),
},
body: Program::empty(),
return_type: None,
digest: None,
}),
@ -3919,18 +3895,7 @@ const cylinder = startSketchOn('-XZ')
digest: None,
},
],
body: Node {
inner: Program {
body: Vec::new(),
non_code_meta: Default::default(),
shebang: None,
digest: None,
},
start: 0,
end: 0,
module_id: ModuleId::default(),
trivia: Vec::new(),
},
body: Program::empty(),
return_type: None,
digest: None,
}),

View File

@ -21,13 +21,13 @@ use crate::{
errors::{CompilationError, Severity, Tag},
parsing::{
ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, BoxNode,
CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ImportSelector,
ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression,
MemberObject, Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty,
Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, Shebang, TagDeclarator,
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
BoxNode, CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr,
ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
MemberExpression, MemberObject, Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, Shebang,
TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
},
math::BinaryExpressionToken,
token::{Token, TokenSlice, TokenType},
@ -284,7 +284,7 @@ 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)
}
fn annotation(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
fn annotation(i: &mut TokenSlice) -> PResult<Node<Annotation>> {
let at = at_sign.parse_next(i)?;
let name = opt(binding_name).parse_next(i)?;
let mut end = name.as_ref().map(|n| n.end).unwrap_or(at.end);
@ -308,7 +308,7 @@ fn annotation(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
value,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
}),
comma_sep,
)
@ -327,19 +327,16 @@ fn annotation(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
));
}
let value = NonCodeValue::Annotation { name, properties };
Ok(Node::new(
NonCodeNode { value, digest: None },
at.start,
end,
at.module_id,
))
let value = Annotation {
name,
properties,
digest: None,
};
Ok(Node::new(value, at.start, end, at.module_id))
}
// Matches remaining three cases of NonCodeValue
fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
alt((
annotation,
any.verify_map(|token: Token| {
if token.is_code_token() {
None
@ -369,8 +366,7 @@ fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> PResult<Node<NonCo
))
}
})
.context(expected("Non-code token (comments or whitespace)")),
))
.context(expected("Non-code token (comments or whitespace)"))
.parse_next(i)
}
@ -427,7 +423,7 @@ fn pipe_expression(i: &mut TokenSlice) -> PResult<Node<PipeExpression>> {
non_code_meta,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -830,7 +826,7 @@ fn object_property_same_key_and_val(i: &mut TokenSlice) -> PResult<Node<ObjectPr
key,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -859,7 +855,7 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
value: expr,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
};
if sep.token_type == TokenType::Colon {
@ -1131,17 +1127,7 @@ fn function_decl(i: &mut TokenSlice) -> PResult<(Node<FunctionExpression>, bool)
let close: Option<(Vec<Vec<Token>>, Token)> = opt((repeat(0.., whitespace), close_brace)).parse_next(i)?;
let (body, end) = match close {
Some((_, end)) => (
Node::new(
Program {
body: Vec::new(),
non_code_meta: NonCodeMeta::default(),
shebang: None,
digest: None,
},
brace.end,
brace.end,
brace.module_id,
),
Node::new(Program::default(), brace.end, brace.end, brace.module_id),
end.end,
),
None => (function_body(i)?, close_brace(i)?.end),
@ -1277,7 +1263,6 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
x @ NonCodeValue::InlineComment { .. } => x,
x @ NonCodeValue::NewLineBlockComment { .. } => x,
x @ NonCodeValue::NewLine => x,
x @ NonCodeValue::Annotation { .. } => x,
};
Node::new(
NonCodeNode { value, ..nc.inner },
@ -1298,7 +1283,6 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
x @ NonCodeValue::InlineComment { .. } => x,
x @ NonCodeValue::NewLineBlockComment { .. } => x,
x @ NonCodeValue::NewLine => x,
x @ NonCodeValue::Annotation { .. } => x,
};
Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
}
@ -1314,6 +1298,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum WithinFunction {
Annotation(Node<Annotation>),
BodyItem((BodyItem, Option<Node<NonCodeNode>>)),
NonCode(Node<NonCodeNode>),
}
@ -1338,9 +1323,12 @@ fn body_items_within_function(i: &mut TokenSlice) -> PResult<WithinFunction> {
(import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
Token { ref value, .. } if value == "return" =>
(return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
token if !token.is_code_token() || token.token_type == TokenType::At => {
token if !token.is_code_token() => {
non_code_node.map(WithinFunction::NonCode)
},
token if token.token_type == TokenType::At => {
annotation.map(WithinFunction::Annotation)
},
_ =>
alt((
(
@ -1447,16 +1435,32 @@ fn function_body(i: &mut TokenSlice) -> PResult<Node<Program>> {
}
let mut body = Vec::new();
let mut inner_attrs = Vec::new();
let mut pending_attrs = Vec::new();
let mut non_code_meta = NonCodeMeta::default();
let mut end = 0;
let mut start = leading_whitespace_start;
for thing_in_body in things_within_body {
match thing_in_body {
WithinFunction::BodyItem((b, maybe_noncode)) => {
WithinFunction::Annotation(attr) => {
if start.is_none() {
start = Some((attr.start, attr.module_id))
}
if attr.is_inner() {
inner_attrs.push(attr);
} else {
pending_attrs.push(attr);
}
}
WithinFunction::BodyItem((mut b, maybe_noncode)) => {
if start.is_none() {
start = Some((b.start(), b.module_id()));
}
end = b.end();
if !pending_attrs.is_empty() {
b.set_attrs(pending_attrs);
pending_attrs = Vec::new();
}
body.push(b);
if let Some(nc) = maybe_noncode {
end = nc.end;
@ -1476,9 +1480,26 @@ fn function_body(i: &mut TokenSlice) -> PResult<Node<Program>> {
}
}
}
let start = start.expect(
"the `things_within_body` vec should have looped at least once, and each loop overwrites `start` if it is None",
);
if !pending_attrs.is_empty() {
for a in pending_attrs {
ParseContext::err(CompilationError::err(
a.as_source_range(),
"Attribute is not attached to any item",
));
}
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(start.0, end, start.1),
"Block contains un-attached attributes",
)
.into(),
));
}
// Safe to unwrap `body.first()` because `body` is `separated1` therefore guaranteed
// to have len >= 1.
let end_ws = opt(whitespace)
@ -1492,6 +1513,7 @@ fn function_body(i: &mut TokenSlice) -> PResult<Node<Program>> {
Program {
body,
non_code_meta,
inner_attrs,
shebang: None,
digest: None,
},
@ -1790,7 +1812,7 @@ fn return_stmt(i: &mut TokenSlice) -> PResult<Node<ReturnStatement>> {
end: argument.end(),
module_id: ret.module_id,
inner: ReturnStatement { argument, digest: None },
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -2017,13 +2039,13 @@ fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
init: val,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
},
visibility,
kind,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
}))
}
@ -2229,7 +2251,7 @@ fn unary_expression(i: &mut TokenSlice) -> PResult<Node<UnaryExpression>> {
argument,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -2310,7 +2332,7 @@ fn expression_stmt(i: &mut TokenSlice) -> PResult<Node<ExpressionStatement>> {
expression: val,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -2750,7 +2772,7 @@ fn fn_call(i: &mut TokenSlice) -> PResult<Node<CallExpression>> {
arguments: args,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -2780,7 +2802,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
arguments: args,
digest: None,
},
trivia: Vec::new(),
outer_attrs: Vec::new(),
})
}
@ -3023,6 +3045,7 @@ mySk1 = startSketchAt([0, 0])"#;
)],
digest: None,
},
inner_attrs: Vec::new(),
shebang: None,
digest: None,
},
@ -3700,6 +3723,7 @@ mySk1 = startSketchAt([0, 0])"#;
))],
shebang: None,
non_code_meta: NonCodeMeta::default(),
inner_attrs: Vec::new(),
digest: None,
},
0,

View File

@ -2,11 +2,12 @@ use std::fmt::Write;
use crate::parsing::{
ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression,
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FnArgType, FormatOptions,
FunctionExpression, IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue,
ObjectExpression, Parameter, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
VariableKind,
},
token::NumericSuffix,
PIPE_OPERATOR,
@ -22,6 +23,9 @@ impl Program {
.map(|sh| format!("{}\n\n", sh.inner.content))
.unwrap_or_default();
for attr in &self.inner_attrs {
result.push_str(&attr.recast(options, indentation_level));
}
for start in &self.non_code_meta.start_nodes {
result.push_str(&start.recast(options, indentation_level));
}
@ -30,7 +34,12 @@ impl Program {
let result = self
.body
.iter()
.map(|body_item| match body_item.clone() {
.map(|body_item| {
let mut result = String::new();
for attr in body_item.get_attrs() {
result.push_str(&attr.recast(options, indentation_level));
}
result.push_str(&match body_item.clone() {
BodyItem::ImportStatement(stmt) => stmt.recast(options, indentation_level),
BodyItem::ExpressionStatement(expression_statement) => {
expression_statement
@ -50,10 +59,13 @@ impl Program {
.trim_start()
)
}
});
result
})
.enumerate()
.fold(result, |mut output, (index, recast_str)| {
let start_string = if index == 0 && self.non_code_meta.start_nodes.is_empty() {
let start_string =
if index == 0 && self.non_code_meta.start_nodes.is_empty() && self.inner_attrs.is_empty() {
// We need to indent.
indentation.to_string()
} else {
@ -113,9 +125,7 @@ impl NonCodeValue {
fn should_cause_array_newline(&self) -> bool {
match self {
Self::InlineComment { .. } => false,
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine | Self::Annotation { .. } => {
true
}
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine => true,
}
}
}
@ -156,12 +166,17 @@ impl Node<NonCodeNode> {
}
}
NonCodeValue::NewLine => "\n\n".to_string(),
NonCodeValue::Annotation { name, properties } => {
}
}
}
impl Node<Annotation> {
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
let mut result = "@".to_owned();
if let Some(name) = name {
if let Some(name) = &self.name {
result.push_str(&name.name);
}
if let Some(properties) = properties {
if let Some(properties) = &self.properties {
result.push('(');
result.push_str(
&properties
@ -184,8 +199,6 @@ impl Node<NonCodeNode> {
result
}
}
}
}
impl ImportStatement {

View File

@ -103,26 +103,9 @@ description: Result of parsing import_cycle1.kcl
}
],
"end": 110,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 71,
"start": 69,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": [
"innerAttrs": [
{
"end": 33,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "annotation",
"name": {
"end": 9,
"name": "settings",
@ -148,11 +131,26 @@ description: Result of parsing import_cycle1.kcl
"type": "Identifier"
}
}
]
],
"start": 0,
"type": "Annotation"
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 71,
"start": 69,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -103,26 +103,9 @@ description: Result of parsing import_function_not_sketch.kcl
}
],
"end": 109,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 70,
"start": 68,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": [
"innerAttrs": [
{
"end": 33,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "annotation",
"name": {
"end": 9,
"name": "settings",
@ -148,11 +131,26 @@ description: Result of parsing import_function_not_sketch.kcl
"type": "Identifier"
}
}
]
],
"start": 0,
"type": "Annotation"
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 70,
"start": 68,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -115,26 +115,9 @@ description: Result of parsing import_whole.kcl
}
],
"end": 124,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 68,
"start": 66,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": [
"innerAttrs": [
{
"end": 33,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "annotation",
"name": {
"end": 9,
"name": "settings",
@ -160,11 +143,26 @@ description: Result of parsing import_whole.kcl
"type": "Identifier"
}
}
]
],
"start": 0,
"type": "Annotation"
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 68,
"start": 66,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}