2024-09-07 12:51:35 -04:00
use std ::fmt ::Write ;
use crate ::{
ast ::types ::{
2024-10-17 02:58:04 +13:00
ArrayExpression , ArrayRangeExpression , BinaryExpression , BinaryOperator , BinaryPart , BodyItem , CallExpression ,
2024-11-27 15:46:58 +13:00
Expr , FnArgType , FormatOptions , FunctionExpression , IfExpression , ImportStatement , ItemVisibility , Literal ,
2024-10-30 16:52:17 -04:00
LiteralIdentifier , LiteralValue , MemberExpression , MemberObject , Node , NonCodeValue , ObjectExpression ,
2024-11-27 15:46:58 +13:00
Parameter , PipeExpression , Program , TagDeclarator , UnaryExpression , VariableDeclaration , VariableKind ,
2024-09-07 12:51:35 -04:00
} ,
parser ::PIPE_OPERATOR ,
} ;
impl Program {
pub fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
let indentation = options . get_indentation ( indentation_level ) ;
2024-11-26 16:39:57 +13:00
let result = self
. shebang
. as_ref ( )
. map ( | sh | format! ( " {} \n \n " , sh . inner . content ) )
. unwrap_or_default ( ) ;
2024-09-07 12:51:35 -04:00
let result = self
. body
. iter ( )
2024-11-20 08:23:30 -06:00
. map ( | body_item | match body_item . clone ( ) {
2024-10-17 00:48:33 -04:00
BodyItem ::ImportStatement ( stmt ) = > stmt . recast ( options , indentation_level ) ,
2024-09-07 12:51:35 -04:00
BodyItem ::ExpressionStatement ( expression_statement ) = > {
expression_statement
. expression
2024-11-27 15:46:58 +13:00
. recast ( options , indentation_level , ExprContext ::Other )
2024-09-07 12:51:35 -04:00
}
BodyItem ::VariableDeclaration ( variable_declaration ) = > {
variable_declaration . recast ( options , indentation_level )
}
BodyItem ::ReturnStatement ( return_statement ) = > {
format! (
" {}return {} " ,
indentation ,
2024-11-20 08:23:30 -06:00
return_statement
. argument
2024-11-27 15:46:58 +13:00
. recast ( options , indentation_level , ExprContext ::Other )
2024-11-20 08:23:30 -06:00
. trim_start ( )
2024-09-07 12:51:35 -04:00
)
}
} )
. enumerate ( )
2024-11-26 16:39:57 +13:00
. fold ( result , | mut output , ( index , recast_str ) | {
2024-09-07 12:51:35 -04:00
let start_string = if index = = 0 {
// We need to indent.
2024-10-30 16:52:17 -04:00
if self . non_code_meta . start_nodes . is_empty ( ) {
2024-09-07 12:51:35 -04:00
indentation . to_string ( )
} else {
self . non_code_meta
2024-10-30 16:52:17 -04:00
. start_nodes
2024-09-07 12:51:35 -04:00
. iter ( )
. map ( | start | start . format ( & indentation ) )
. collect ( )
}
} else {
// Do nothing, we already applied the indentation elsewhere.
String ::new ( )
} ;
// determine the value of the end string
// basically if we are inside a nested function we want to end with a new line
let maybe_line_break : String = if index = = self . body . len ( ) - 1 & & indentation_level = = 0 {
String ::new ( )
} else {
" \n " . to_string ( )
} ;
let custom_white_space_or_comment = match self . non_code_meta . non_code_nodes . get ( & index ) {
Some ( noncodes ) = > noncodes
. iter ( )
. enumerate ( )
. map ( | ( i , custom_white_space_or_comment ) | {
let formatted = custom_white_space_or_comment . format ( & indentation ) ;
if i = = 0 & & ! formatted . trim ( ) . is_empty ( ) {
if let NonCodeValue ::BlockComment { .. } = custom_white_space_or_comment . value {
format! ( " \n {} " , formatted )
} else {
formatted
}
} else {
formatted
}
} )
. collect ::< String > ( ) ,
None = > String ::new ( ) ,
} ;
let end_string = if custom_white_space_or_comment . is_empty ( ) {
maybe_line_break
} else {
custom_white_space_or_comment
} ;
let _ = write! ( output , " {}{}{} " , start_string , recast_str , end_string ) ;
output
} )
. trim ( )
. to_string ( ) ;
// Insert a final new line if the user wants it.
if options . insert_final_newline & & ! result . is_empty ( ) {
format! ( " {} \n " , result )
} else {
result
}
}
}
impl NonCodeValue {
fn should_cause_array_newline ( & self ) -> bool {
match self {
Self ::InlineComment { .. } = > false ,
2024-11-26 16:39:57 +13:00
Self ::BlockComment { .. } | Self ::NewLineBlockComment { .. } | Self ::NewLine = > true ,
2024-09-07 12:51:35 -04:00
}
}
}
2024-10-17 00:48:33 -04:00
impl ImportStatement {
pub fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
let indentation = options . get_indentation ( indentation_level ) ;
let mut string = format! ( " {} import " , indentation ) ;
for ( i , item ) in self . items . iter ( ) . enumerate ( ) {
if i > 0 {
string . push_str ( " , " ) ;
}
string . push_str ( & item . name . name ) ;
if let Some ( alias ) = & item . alias {
// If the alias is the same, don't output it.
if item . name . name ! = alias . name {
string . push_str ( & format! ( " as {} " , alias . name ) ) ;
}
}
}
string . push_str ( & format! ( " from {} " , self . raw_path ) ) ;
string
}
}
2024-11-27 15:46:58 +13:00
#[ derive(Copy, Clone, Debug, Eq, PartialEq) ]
pub ( crate ) enum ExprContext {
Pipe ,
Decl ,
Other ,
}
2024-09-07 12:51:35 -04:00
impl Expr {
2024-11-27 15:46:58 +13:00
pub ( crate ) fn recast ( & self , options : & FormatOptions , indentation_level : usize , ctxt : ExprContext ) -> String {
2024-09-07 12:51:35 -04:00
match & self {
Expr ::BinaryExpression ( bin_exp ) = > bin_exp . recast ( options ) ,
2024-11-27 15:46:58 +13:00
Expr ::ArrayExpression ( array_exp ) = > array_exp . recast ( options , indentation_level , ctxt ) ,
Expr ::ArrayRangeExpression ( range_exp ) = > range_exp . recast ( options , indentation_level , ctxt ) ,
Expr ::ObjectExpression ( ref obj_exp ) = > obj_exp . recast ( options , indentation_level , ctxt ) ,
2024-09-07 12:51:35 -04:00
Expr ::MemberExpression ( mem_exp ) = > mem_exp . recast ( ) ,
Expr ::Literal ( literal ) = > literal . recast ( ) ,
2024-11-27 15:46:58 +13:00
Expr ::FunctionExpression ( func_exp ) = > {
let mut result = if ctxt = = ExprContext ::Decl {
String ::new ( )
} else {
" fn " . to_owned ( )
} ;
result + = & func_exp . recast ( options , indentation_level ) ;
result
}
Expr ::CallExpression ( call_exp ) = > call_exp . recast ( options , indentation_level , ctxt ) ,
2024-09-07 12:51:35 -04:00
Expr ::Identifier ( ident ) = > ident . name . to_string ( ) ,
Expr ::TagDeclarator ( tag ) = > tag . recast ( ) ,
Expr ::PipeExpression ( pipe_exp ) = > pipe_exp . recast ( options , indentation_level ) ,
Expr ::UnaryExpression ( unary_exp ) = > unary_exp . recast ( options ) ,
2024-11-27 15:46:58 +13:00
Expr ::IfExpression ( e ) = > e . recast ( options , indentation_level , ctxt ) ,
2024-09-07 12:51:35 -04:00
Expr ::PipeSubstitution ( _ ) = > crate ::parser ::PIPE_SUBSTITUTION_OPERATOR . to_string ( ) ,
Expr ::None ( _ ) = > {
unimplemented! ( " there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115 " )
}
}
}
}
impl BinaryPart {
fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
match & self {
BinaryPart ::Literal ( literal ) = > literal . recast ( ) ,
BinaryPart ::Identifier ( identifier ) = > identifier . name . to_string ( ) ,
BinaryPart ::BinaryExpression ( binary_expression ) = > binary_expression . recast ( options ) ,
2024-11-27 15:46:58 +13:00
BinaryPart ::CallExpression ( call_expression ) = > {
call_expression . recast ( options , indentation_level , ExprContext ::Other )
}
2024-09-07 12:51:35 -04:00
BinaryPart ::UnaryExpression ( unary_expression ) = > unary_expression . recast ( options ) ,
BinaryPart ::MemberExpression ( member_expression ) = > member_expression . recast ( ) ,
2024-11-27 15:46:58 +13:00
BinaryPart ::IfExpression ( e ) = > e . recast ( options , indentation_level , ExprContext ::Other ) ,
2024-09-07 12:51:35 -04:00
}
}
}
impl CallExpression {
2024-11-27 15:46:58 +13:00
fn recast ( & self , options : & FormatOptions , indentation_level : usize , ctxt : ExprContext ) -> String {
2024-09-07 12:51:35 -04:00
format! (
" {}{}({}) " ,
2024-11-27 15:46:58 +13:00
if ctxt = = ExprContext ::Pipe {
2024-09-07 12:51:35 -04:00
" " . to_string ( )
} else {
options . get_indentation ( indentation_level )
} ,
self . callee . name ,
self . arguments
. iter ( )
2024-11-27 15:46:58 +13:00
. map ( | arg | arg . recast ( options , indentation_level , ctxt ) )
2024-09-07 12:51:35 -04:00
. collect ::< Vec < String > > ( )
. join ( " , " )
)
}
}
impl VariableDeclaration {
pub fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
let indentation = options . get_indentation ( indentation_level ) ;
2024-10-17 00:48:33 -04:00
let output = match self . visibility {
ItemVisibility ::Default = > String ::new ( ) ,
ItemVisibility ::Export = > " export " . to_owned ( ) ,
} ;
self . declarations . iter ( ) . fold ( output , | mut output , declaration | {
2024-11-27 15:46:58 +13:00
let ( keyword , eq ) = match self . kind {
VariableKind ::Fn = > ( " fn " , " " ) ,
VariableKind ::Const = > ( " " , " = " ) ,
2024-10-02 14:19:40 -05:00
} ;
2024-09-07 12:51:35 -04:00
let _ = write! (
output ,
2024-11-27 15:46:58 +13:00
" {}{keyword}{}{eq}{} " ,
2024-09-07 12:51:35 -04:00
indentation ,
declaration . id . name ,
2024-11-27 15:46:58 +13:00
declaration
. init
. recast ( options , indentation_level , ExprContext ::Decl )
. trim ( )
2024-09-07 12:51:35 -04:00
) ;
output
} )
}
}
impl Literal {
fn recast ( & self ) -> String {
match self . value {
2024-11-25 10:50:43 +13:00
LiteralValue ::Number ( x ) = > {
if self . raw . contains ( '.' ) & & x . fract ( ) = = 0.0 {
2024-09-07 12:51:35 -04:00
format! ( " {x:?} " )
} else {
self . raw . clone ( )
}
}
LiteralValue ::String ( ref s ) = > {
let quote = if self . raw . trim ( ) . starts_with ( '"' ) { '"' } else { '\'' } ;
format! ( " {quote} {s} {quote} " )
}
LiteralValue ::Bool ( _ ) = > self . raw . clone ( ) ,
}
}
}
impl TagDeclarator {
pub fn recast ( & self ) -> String {
// TagDeclarators are always prefixed with a dollar sign.
format! ( " $ {} " , self . name )
}
}
impl ArrayExpression {
2024-11-27 15:46:58 +13:00
fn recast ( & self , options : & FormatOptions , indentation_level : usize , ctxt : ExprContext ) -> String {
2024-09-07 12:51:35 -04:00
// Reconstruct the order of items in the array.
// An item can be an element (i.e. an expression for a KCL value),
// or a non-code item (e.g. a comment)
let num_items = self . elements . len ( ) + self . non_code_meta . non_code_nodes_len ( ) ;
let mut elems = self . elements . iter ( ) ;
let mut found_line_comment = false ;
let mut format_items : Vec < _ > = ( 0 .. num_items )
. flat_map ( | i | {
if let Some ( noncode ) = self . non_code_meta . non_code_nodes . get ( & i ) {
noncode
. iter ( )
. map ( | nc | {
found_line_comment | = nc . value . should_cause_array_newline ( ) ;
nc . format ( " " )
} )
. collect ::< Vec < _ > > ( )
} else {
let el = elems . next ( ) . unwrap ( ) ;
2024-11-27 15:46:58 +13:00
let s = format! ( " {} , " , el . recast ( options , 0 , ExprContext ::Other ) ) ;
2024-09-07 12:51:35 -04:00
vec! [ s ]
}
} )
. collect ( ) ;
// Format these items into a one-line array.
if let Some ( item ) = format_items . last_mut ( ) {
if let Some ( norm ) = item . strip_suffix ( " , " ) {
* item = norm . to_owned ( ) ;
}
}
let format_items = format_items ; // Remove mutability
let flat_recast = format! ( " [ {} ] " , format_items . join ( " " ) ) ;
// We might keep the one-line representation, if it's short enough.
let max_array_length = 40 ;
let multi_line = flat_recast . len ( ) > max_array_length | | found_line_comment ;
if ! multi_line {
return flat_recast ;
}
// Otherwise, we format a multi-line representation.
2024-11-27 15:46:58 +13:00
let inner_indentation = if ctxt = = ExprContext ::Pipe {
2024-09-07 12:51:35 -04:00
options . get_indentation_offset_pipe ( indentation_level + 1 )
} else {
options . get_indentation ( indentation_level + 1 )
} ;
let formatted_array_lines = format_items
. iter ( )
. map ( | s | {
format! (
" {inner_indentation}{}{} " ,
if let Some ( x ) = s . strip_suffix ( " " ) { x } else { s } ,
if s . ends_with ( '\n' ) { " " } else { " \n " }
)
} )
. collect ::< Vec < String > > ( )
. join ( " " )
. to_owned ( ) ;
2024-11-27 15:46:58 +13:00
let end_indent = if ctxt = = ExprContext ::Pipe {
2024-09-07 12:51:35 -04:00
options . get_indentation_offset_pipe ( indentation_level )
} else {
options . get_indentation ( indentation_level )
} ;
format! ( " [ \n {formatted_array_lines} {end_indent} ] " )
}
}
2024-10-17 02:58:04 +13:00
/// An expression is syntactically trivial: i.e., a literal, identifier, or similar.
fn expr_is_trivial ( expr : & Expr ) -> bool {
match expr {
Expr ::Literal ( _ ) | Expr ::Identifier ( _ ) | Expr ::TagDeclarator ( _ ) | Expr ::PipeSubstitution ( _ ) | Expr ::None ( _ ) = > {
true
}
Expr ::BinaryExpression ( _ )
| Expr ::FunctionExpression ( _ )
| Expr ::CallExpression ( _ )
| Expr ::PipeExpression ( _ )
| Expr ::ArrayExpression ( _ )
| Expr ::ArrayRangeExpression ( _ )
| Expr ::ObjectExpression ( _ )
| Expr ::MemberExpression ( _ )
| Expr ::UnaryExpression ( _ )
| Expr ::IfExpression ( _ ) = > false ,
}
}
impl ArrayRangeExpression {
2024-11-27 15:46:58 +13:00
fn recast ( & self , options : & FormatOptions , _ : usize , _ : ExprContext ) -> String {
let s1 = self . start_element . recast ( options , 0 , ExprContext ::Other ) ;
let s2 = self . end_element . recast ( options , 0 , ExprContext ::Other ) ;
2024-10-17 02:58:04 +13:00
// Format these items into a one-line array. Put spaces around the `..` if either expression
// is non-trivial. This is a bit arbitrary but people seem to like simple ranges to be formatted
// tightly, but this is a misleading visual representation of the precedence if the range
// components are compound expressions.
if expr_is_trivial ( & self . start_element ) & & expr_is_trivial ( & self . end_element ) {
format! ( " [ {s1} .. {s2} ] " )
} else {
format! ( " [ {s1} .. {s2} ] " )
}
// Assume a range expression fits on one line.
}
}
2024-09-07 12:51:35 -04:00
impl ObjectExpression {
2024-11-27 15:46:58 +13:00
fn recast ( & self , options : & FormatOptions , indentation_level : usize , ctxt : ExprContext ) -> String {
2024-09-07 12:51:35 -04:00
if self
. non_code_meta
. non_code_nodes
. values ( )
. any ( | nc | nc . iter ( ) . any ( | nc | nc . value . should_cause_array_newline ( ) ) )
{
2024-11-27 15:46:58 +13:00
return self . recast_multi_line ( options , indentation_level , ctxt ) ;
2024-09-07 12:51:35 -04:00
}
let flat_recast = format! (
" {{ {} }} " ,
self . properties
. iter ( )
. map ( | prop | {
format! (
2024-11-25 09:21:55 +13:00
" {} = {} " ,
2024-09-07 12:51:35 -04:00
prop . key . name ,
2024-11-27 15:46:58 +13:00
prop . value . recast ( options , indentation_level + 1 , ctxt ) . trim ( )
2024-09-07 12:51:35 -04:00
)
} )
. collect ::< Vec < String > > ( )
. join ( " , " )
) ;
let max_array_length = 40 ;
let needs_multiple_lines = flat_recast . len ( ) > max_array_length ;
if ! needs_multiple_lines {
return flat_recast ;
}
2024-11-27 15:46:58 +13:00
self . recast_multi_line ( options , indentation_level , ctxt )
2024-09-07 12:51:35 -04:00
}
/// Recast, but always outputs the object with newlines between each property.
2024-11-27 15:46:58 +13:00
fn recast_multi_line ( & self , options : & FormatOptions , indentation_level : usize , ctxt : ExprContext ) -> String {
let inner_indentation = if ctxt = = ExprContext ::Pipe {
2024-09-07 12:51:35 -04:00
options . get_indentation_offset_pipe ( indentation_level + 1 )
} else {
options . get_indentation ( indentation_level + 1 )
} ;
let num_items = self . properties . len ( ) + self . non_code_meta . non_code_nodes_len ( ) ;
let mut props = self . properties . iter ( ) ;
let format_items : Vec < _ > = ( 0 .. num_items )
. flat_map ( | i | {
if let Some ( noncode ) = self . non_code_meta . non_code_nodes . get ( & i ) {
noncode . iter ( ) . map ( | nc | nc . format ( " " ) ) . collect ::< Vec < _ > > ( )
} else {
let prop = props . next ( ) . unwrap ( ) ;
// Use a comma unless it's the last item
let comma = if i = = num_items - 1 { " " } else { " , \n " } ;
let s = format! (
2024-11-25 09:21:55 +13:00
" {} = {}{comma} " ,
2024-09-07 12:51:35 -04:00
prop . key . name ,
2024-11-27 15:46:58 +13:00
prop . value . recast ( options , indentation_level + 1 , ctxt ) . trim ( )
2024-09-07 12:51:35 -04:00
) ;
vec! [ s ]
}
} )
. collect ( ) ;
2024-11-27 15:46:58 +13:00
let end_indent = if ctxt = = ExprContext ::Pipe {
2024-09-07 12:51:35 -04:00
options . get_indentation_offset_pipe ( indentation_level )
} else {
options . get_indentation ( indentation_level )
} ;
format! (
" {{ \n {inner_indentation}{} \n {end_indent}}} " ,
format_items . join ( & inner_indentation ) ,
)
}
}
impl MemberExpression {
fn recast ( & self ) -> String {
let key_str = match & self . property {
LiteralIdentifier ::Identifier ( identifier ) = > {
if self . computed {
format! ( " [ {} ] " , & ( * identifier . name ) )
} else {
format! ( " . {} " , & ( * identifier . name ) )
}
}
LiteralIdentifier ::Literal ( lit ) = > format! ( " [ {} ] " , & ( * lit . raw ) ) ,
} ;
match & self . object {
MemberObject ::MemberExpression ( member_exp ) = > member_exp . recast ( ) + key_str . as_str ( ) ,
MemberObject ::Identifier ( identifier ) = > identifier . name . to_string ( ) + key_str . as_str ( ) ,
}
}
}
impl BinaryExpression {
fn recast ( & self , options : & FormatOptions ) -> String {
let maybe_wrap_it = | a : String , doit : bool | -> String {
if doit {
format! ( " ( {} ) " , a )
} else {
a
}
} ;
let should_wrap_right = match & self . right {
BinaryPart ::BinaryExpression ( bin_exp ) = > {
self . precedence ( ) > bin_exp . precedence ( )
| | self . operator = = BinaryOperator ::Sub
| | self . operator = = BinaryOperator ::Div
}
_ = > false ,
} ;
let should_wrap_left = match & self . left {
BinaryPart ::BinaryExpression ( bin_exp ) = > self . precedence ( ) > bin_exp . precedence ( ) ,
_ = > false ,
} ;
format! (
" {} {} {} " ,
maybe_wrap_it ( self . left . recast ( options , 0 ) , should_wrap_left ) ,
self . operator ,
maybe_wrap_it ( self . right . recast ( options , 0 ) , should_wrap_right )
)
}
}
impl UnaryExpression {
fn recast ( & self , options : & FormatOptions ) -> String {
match self . argument {
BinaryPart ::Literal ( _ )
| BinaryPart ::Identifier ( _ )
| BinaryPart ::MemberExpression ( _ )
2024-09-30 15:40:50 -05:00
| BinaryPart ::IfExpression ( _ )
2024-09-07 12:51:35 -04:00
| BinaryPart ::CallExpression ( _ ) = > {
format! ( " {} {} " , & self . operator , self . argument . recast ( options , 0 ) )
}
BinaryPart ::BinaryExpression ( _ ) | BinaryPart ::UnaryExpression ( _ ) = > {
format! ( " {} ( {} ) " , & self . operator , self . argument . recast ( options , 0 ) )
}
}
}
}
2024-09-30 15:40:50 -05:00
impl IfExpression {
2024-11-27 15:46:58 +13:00
fn recast ( & self , options : & FormatOptions , indentation_level : usize , ctxt : ExprContext ) -> String {
2024-09-30 15:40:50 -05:00
// We can calculate how many lines this will take, so let's do it and avoid growing the vec.
// Total lines = starting lines, else-if lines, ending lines.
let n = 2 + ( self . else_ifs . len ( ) * 2 ) + 3 ;
let mut lines = Vec ::with_capacity ( n ) ;
2024-11-27 15:46:58 +13:00
let cond = self . cond . recast ( options , indentation_level , ctxt ) ;
2024-09-30 15:40:50 -05:00
lines . push ( ( 0 , format! ( " if {cond} {{ " ) ) ) ;
lines . push ( ( 1 , self . then_val . recast ( options , indentation_level + 1 ) ) ) ;
for else_if in & self . else_ifs {
2024-11-27 15:46:58 +13:00
let cond = else_if . cond . recast ( options , indentation_level , ctxt ) ;
2024-09-30 15:40:50 -05:00
lines . push ( ( 0 , format! ( " }} else if {cond} {{ " ) ) ) ;
lines . push ( ( 1 , else_if . then_val . recast ( options , indentation_level + 1 ) ) ) ;
}
lines . push ( ( 0 , " } else { " . to_owned ( ) ) ) ;
lines . push ( ( 1 , self . final_else . recast ( options , indentation_level + 1 ) ) ) ;
lines . push ( ( 0 , " } " . to_owned ( ) ) ) ;
lines
. into_iter ( )
. map ( | ( ind , line ) | format! ( " {} {} " , options . get_indentation ( indentation_level + ind ) , line . trim ( ) ) )
. collect ::< Vec < _ > > ( )
. join ( " \n " )
}
}
2024-10-30 16:52:17 -04:00
impl Node < PipeExpression > {
2024-09-07 12:51:35 -04:00
fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
let pipe = self
. body
. iter ( )
. enumerate ( )
. map ( | ( index , statement ) | {
let indentation = options . get_indentation ( indentation_level + 1 ) ;
2024-11-27 15:46:58 +13:00
let mut s = statement . recast ( options , indentation_level + 1 , ExprContext ::Pipe ) ;
2024-09-07 12:51:35 -04:00
let non_code_meta = self . non_code_meta . clone ( ) ;
if let Some ( non_code_meta_value ) = non_code_meta . non_code_nodes . get ( & index ) {
for val in non_code_meta_value {
let formatted = if val . end = = self . end {
let indentation = options . get_indentation ( indentation_level ) ;
val . format ( & indentation ) . trim_end_matches ( '\n' ) . to_string ( )
} else {
val . format ( & indentation ) . trim_end_matches ( '\n' ) . to_string ( )
} ;
if let NonCodeValue ::BlockComment { .. } = val . value {
s + = " \n " ;
s + = & formatted ;
} else {
s + = & formatted ;
}
}
}
if index ! = self . body . len ( ) - 1 {
s + = " \n " ;
s + = & indentation ;
s + = PIPE_OPERATOR ;
s + = " " ;
}
s
} )
. collect ::< String > ( ) ;
format! ( " {} {} " , options . get_indentation ( indentation_level ) , pipe )
}
}
impl FunctionExpression {
pub fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
// We don't want to end with a new line inside nested functions.
let mut new_options = options . clone ( ) ;
new_options . insert_final_newline = false ;
2024-11-20 08:23:30 -06:00
let param_list = self
. params
. iter ( )
2024-11-27 15:46:58 +13:00
. map ( | param | param . recast ( options , indentation_level ) )
2024-11-20 08:23:30 -06:00
. collect ::< Vec < String > > ( )
. join ( " , " ) ;
let tab0 = options . get_indentation ( indentation_level ) ;
let tab1 = options . get_indentation ( indentation_level + 1 ) ;
2024-11-27 15:46:58 +13:00
let return_type = match & self . return_type {
Some ( rt ) = > format! ( " : {} " , rt . recast ( & new_options , indentation_level ) ) ,
None = > String ::new ( ) ,
} ;
2024-11-20 08:23:30 -06:00
let body = self . body . recast ( & new_options , indentation_level + 1 ) ;
2024-11-27 15:46:58 +13:00
format! ( " ( {param_list} ) {return_type} {{ \n {tab1} {body} \n {tab0} }} " )
}
}
impl Parameter {
pub fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
let mut result = self . identifier . name . clone ( ) ;
if let Some ( ty ) = & self . type_ {
result + = " : " ;
result + = & ty . recast ( options , indentation_level ) ;
}
result
}
}
impl FnArgType {
pub fn recast ( & self , options : & FormatOptions , indentation_level : usize ) -> String {
match self {
FnArgType ::Primitive ( t ) = > t . to_string ( ) ,
FnArgType ::Array ( t ) = > format! ( " {t} [] " ) ,
FnArgType ::Object { properties } = > {
let mut result = " { " . to_owned ( ) ;
for p in properties {
result + = " " ;
result + = & p . recast ( options , indentation_level ) ;
result + = " , " ;
}
if result . ends_with ( ',' ) {
result . pop ( ) ;
result + = " " ;
}
result + = " } " ;
result
}
}
2024-09-07 12:51:35 -04:00
}
}
#[ cfg(test) ]
mod tests {
use pretty_assertions ::assert_eq ;
2024-11-27 15:46:58 +13:00
use super ::* ;
2024-11-07 11:23:41 -05:00
use crate ::ast ::types ::{ FormatOptions , ModuleId } ;
2024-09-07 12:51:35 -04:00
2024-09-30 15:40:50 -05:00
#[ test ]
fn test_recast_if_else_if_same ( ) {
2024-10-02 14:19:40 -05:00
let input = r #" b = if false {
2024-09-30 15:40:50 -05:00
3
} else if true {
4
} else {
5
}
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( input ) . unwrap ( ) ;
2024-09-30 15:40:50 -05:00
let output = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( output , input ) ;
}
#[ test ]
fn test_recast_if_same ( ) {
2024-10-02 14:19:40 -05:00
let input = r #" b = if false {
2024-09-30 15:40:50 -05:00
3
} else {
5
}
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( input ) . unwrap ( ) ;
2024-09-30 15:40:50 -05:00
let output = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( output , input ) ;
}
2024-10-17 00:48:33 -04:00
#[ test ]
fn test_recast_import ( ) {
let input = r #" import a from " a . kcl "
import a as aaa from " a.kcl "
import a , b from " a.kcl "
import a as aaa , b from " a.kcl "
import a , b as bbb from " a.kcl "
import a as aaa , b as bbb from " a.kcl "
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( input ) . unwrap ( ) ;
2024-10-17 00:48:33 -04:00
let output = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( output , input ) ;
}
#[ test ]
fn test_recast_import_as_same_name ( ) {
let input = r #" import a as a from " a . kcl "
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( input ) . unwrap ( ) ;
2024-10-17 00:48:33 -04:00
let output = program . recast ( & Default ::default ( ) , 0 ) ;
let expected = r #" import a from " a . kcl "
" #;
assert_eq! ( output , expected ) ;
}
#[ test ]
fn test_recast_export_fn ( ) {
2024-11-27 15:46:58 +13:00
let input = r #" export fn a() {
2024-10-17 00:48:33 -04:00
return 0
}
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( input ) . unwrap ( ) ;
2024-10-17 00:48:33 -04:00
let output = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( output , input ) ;
}
2024-09-07 12:51:35 -04:00
#[ test ]
fn test_recast_bug_fn_in_fn ( ) {
let some_program_string = r #" // Start point (top left)
2024-10-02 14:19:40 -05:00
zoo_x = - 20
zoo_y = 7
2024-09-07 12:51:35 -04:00
// Scale
2024-10-02 14:19:40 -05:00
s = 1 // s = 1 -> height of Z is 13.4mm
2024-09-07 12:51:35 -04:00
// Depth
2024-10-02 14:19:40 -05:00
d = 1
2024-09-07 12:51:35 -04:00
2024-11-27 15:46:58 +13:00
fn rect ( x , y , w , h ) {
2024-09-07 12:51:35 -04:00
startSketchOn ( ' XY ' )
| > startProfileAt ( [ x , y ] , % )
| > xLine ( w , % )
| > yLine ( h , % )
| > xLine ( - w , % )
| > close ( % )
| > extrude ( d , % )
}
2024-11-27 15:46:58 +13:00
fn quad ( x1 , y1 , x2 , y2 , x3 , y3 , x4 , y4 ) {
2024-09-07 12:51:35 -04:00
startSketchOn ( ' XY ' )
| > startProfileAt ( [ x1 , y1 ] , % )
| > lineTo ( [ x2 , y2 ] , % )
| > lineTo ( [ x3 , y3 ] , % )
| > lineTo ( [ x4 , y4 ] , % )
| > close ( % )
| > extrude ( d , % )
}
2024-11-27 15:46:58 +13:00
fn crosshair ( x , y ) {
2024-09-07 12:51:35 -04:00
startSketchOn ( ' XY ' )
| > startProfileAt ( [ x , y ] , % )
| > yLine ( 1 , % )
| > yLine ( - 2 , % )
| > yLine ( 1 , % )
| > xLine ( 1 , % )
| > xLine ( - 2 , % )
}
2024-11-27 15:46:58 +13:00
fn z ( z_x , z_y ) {
2024-10-02 14:19:40 -05:00
z_end_w = s * 8.4
z_end_h = s * 3
z_corner = s * 2
z_w = z_end_w + 2 * z_corner
z_h = z_w * 1.08130081300813
2024-09-07 12:51:35 -04:00
rect ( z_x , z_y , z_end_w , - z_end_h )
rect ( z_x + z_w , z_y , - z_corner , - z_corner )
rect ( z_x + z_w , z_y - z_h , - z_end_w , z_end_h )
rect ( z_x , z_y - z_h , z_corner , z_corner )
quad ( z_x , z_y - z_h + z_corner , z_x + z_w - z_corner , z_y , z_x + z_w , z_y - z_corner , z_x + z_corner , z_y - z_h )
}
2024-11-27 15:46:58 +13:00
fn o ( c_x , c_y ) {
2024-09-07 12:51:35 -04:00
// Outer and inner radii
2024-10-02 14:19:40 -05:00
o_r = s * 6.95
i_r = 0.5652173913043478 * o_r
2024-09-07 12:51:35 -04:00
// Angle offset for diagonal break
2024-10-02 14:19:40 -05:00
a = 7
2024-09-07 12:51:35 -04:00
// Start point for the top sketch
2024-10-02 14:19:40 -05:00
o_x1 = c_x + o_r * cos ( ( 45 + a ) / 360 * tau ( ) )
o_y1 = c_y + o_r * sin ( ( 45 + a ) / 360 * tau ( ) )
2024-09-07 12:51:35 -04:00
// Start point for the bottom sketch
2024-10-02 14:19:40 -05:00
o_x2 = c_x + o_r * cos ( ( 225 + a ) / 360 * tau ( ) )
o_y2 = c_y + o_r * sin ( ( 225 + a ) / 360 * tau ( ) )
2024-09-07 12:51:35 -04:00
// End point for the bottom startSketchAt
2024-10-02 14:19:40 -05:00
o_x3 = c_x + o_r * cos ( ( 45 - a ) / 360 * tau ( ) )
o_y3 = c_y + o_r * sin ( ( 45 - a ) / 360 * tau ( ) )
2024-09-07 12:51:35 -04:00
// Where is the center?
// crosshair(c_x, c_y)
startSketchOn ( ' XY ' )
| > startProfileAt ( [ o_x1 , o_y1 ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
radius = o_r ,
angle_start = 45 + a ,
angle_end = 225 - a
2024-09-07 12:51:35 -04:00
} , % )
| > angledLine ( [ 45 , o_r - i_r ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
radius = i_r ,
angle_start = 225 - a ,
angle_end = 45 + a
2024-09-07 12:51:35 -04:00
} , % )
| > close ( % )
| > extrude ( d , % )
startSketchOn ( ' XY ' )
| > startProfileAt ( [ o_x2 , o_y2 ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
radius = o_r ,
angle_start = 225 + a ,
angle_end = 360 + 45 - a
2024-09-07 12:51:35 -04:00
} , % )
| > angledLine ( [ 225 , o_r - i_r ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
radius = i_r ,
angle_start = 45 - a ,
angle_end = 225 + a - 360
2024-09-07 12:51:35 -04:00
} , % )
| > close ( % )
| > extrude ( d , % )
}
2024-11-27 15:46:58 +13:00
fn zoo ( x0 , y0 ) {
2024-09-07 12:51:35 -04:00
z ( x0 , y0 )
o ( x0 + s * 20 , y0 - ( s * 6.7 ) )
o ( x0 + s * 35 , y0 - ( s * 6.7 ) )
}
zoo ( zoo_x , zoo_y )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted , some_program_string ) ;
}
#[ test ]
fn test_recast_bug_extra_parens ( ) {
let some_program_string = r #" // Ball Bearing
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
// Define constants like ball diameter, inside diameter, overhange length, and thickness
2024-10-02 14:19:40 -05:00
sphereDia = 0.5
insideDia = 1
thickness = 0.25
overHangLength = . 4
2024-09-07 12:51:35 -04:00
// Sketch and revolve the inside bearing piece
2024-10-02 14:19:40 -05:00
insideRevolve = startSketchOn ( ' XZ ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ insideDia / 2 , 0 ] , % )
| > line ( [ 0 , thickness + sphereDia / 2 ] , % )
| > line ( [ overHangLength , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - overHangLength + thickness , 0 ] , % )
| > line ( [ 0 , - sphereDia ] , % )
| > line ( [ overHangLength - thickness , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - overHangLength , 0 ] , % )
| > close ( % )
| > revolve ( { axis : ' y ' } , % )
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
2024-10-02 14:19:40 -05:00
sphere = startSketchOn ( ' XZ ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [
0.05 + insideDia / 2 + thickness ,
0 - 0.05
] , % )
| > line ( [ sphereDia - 0.1 , 0 ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
angle_start = 0 ,
angle_end = - 180 ,
radius = sphereDia / 2 - 0.05
2024-09-07 12:51:35 -04:00
} , % )
| > close ( % )
| > revolve ( { axis : ' x ' } , % )
| > patternCircular3d ( {
2024-11-25 09:21:55 +13:00
axis = [ 0 , 0 , 1 ] ,
center = [ 0 , 0 , 0 ] ,
repetitions = 10 ,
arcDegrees = 360 ,
rotateDuplicates = true
2024-09-07 12:51:35 -04:00
} , % )
// Sketch and revolve the outside bearing
2024-10-02 14:19:40 -05:00
outsideRevolve = startSketchOn ( ' XZ ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [
insideDia / 2 + thickness + sphereDia ,
0
] , % )
| > line ( [ 0 , sphereDia / 2 ] , % )
| > line ( [ - overHangLength + thickness , 0 ] , % )
| > line ( [ 0 , thickness ] , % )
| > line ( [ overHangLength , 0 ] , % )
| > line ( [ 0 , - 2 * thickness - sphereDia ] , % )
| > line ( [ - overHangLength , 0 ] , % )
| > line ( [ 0 , thickness ] , % )
| > line ( [ overHangLength - thickness , 0 ] , % )
| > close ( % )
| > revolve ( { axis : ' y ' } , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" // Ball Bearing
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
// Define constants like ball diameter, inside diameter, overhange length, and thickness
2024-10-02 14:19:40 -05:00
sphereDia = 0.5
insideDia = 1
thickness = 0.25
overHangLength = . 4
2024-09-07 12:51:35 -04:00
// Sketch and revolve the inside bearing piece
2024-10-02 14:19:40 -05:00
insideRevolve = startSketchOn ( ' XZ ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ insideDia / 2 , 0 ] , % )
| > line ( [ 0 , thickness + sphereDia / 2 ] , % )
| > line ( [ overHangLength , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - overHangLength + thickness , 0 ] , % )
| > line ( [ 0 , - sphereDia ] , % )
| > line ( [ overHangLength - thickness , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - overHangLength , 0 ] , % )
| > close ( % )
2024-11-25 09:21:55 +13:00
| > revolve ( { axis = 'y' } , % )
2024-09-07 12:51:35 -04:00
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
2024-10-02 14:19:40 -05:00
sphere = startSketchOn ( ' XZ ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [
0.05 + insideDia / 2 + thickness ,
0 - 0.05
] , % )
| > line ( [ sphereDia - 0.1 , 0 ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
angle_start = 0 ,
angle_end = - 180 ,
radius = sphereDia / 2 - 0.05
2024-09-07 12:51:35 -04:00
} , % )
| > close ( % )
2024-11-25 09:21:55 +13:00
| > revolve ( { axis = 'x' } , % )
2024-09-07 12:51:35 -04:00
| > patternCircular3d ( {
2024-11-25 09:21:55 +13:00
axis = [ 0 , 0 , 1 ] ,
center = [ 0 , 0 , 0 ] ,
repetitions = 10 ,
arcDegrees = 360 ,
rotateDuplicates = true
2024-09-07 12:51:35 -04:00
} , % )
// Sketch and revolve the outside bearing
2024-10-02 14:19:40 -05:00
outsideRevolve = startSketchOn ( ' XZ ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [
insideDia / 2 + thickness + sphereDia ,
0
] , % )
| > line ( [ 0 , sphereDia / 2 ] , % )
| > line ( [ - overHangLength + thickness , 0 ] , % )
| > line ( [ 0 , thickness ] , % )
| > line ( [ overHangLength , 0 ] , % )
| > line ( [ 0 , - 2 * thickness - sphereDia ] , % )
| > line ( [ - overHangLength , 0 ] , % )
| > line ( [ 0 , thickness ] , % )
| > line ( [ overHangLength - thickness , 0 ] , % )
| > close ( % )
2024-11-25 09:21:55 +13:00
| > revolve ( { axis = 'y' } , % )
2024-09-07 12:51:35 -04:00
" #
) ;
}
#[ test ]
fn test_recast_fn_in_object ( ) {
2024-11-25 09:21:55 +13:00
let some_program_string = r #" bing = { yo = 55 }
myNestedVar = [ { prop = callExp ( bing . yo ) } ]
2024-09-07 12:51:35 -04:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted , some_program_string ) ;
}
#[ test ]
fn test_recast_fn_in_array ( ) {
2024-11-25 09:21:55 +13:00
let some_program_string = r #" bing = { yo = 55 }
2024-10-02 14:19:40 -05:00
myNestedVar = [ callExp ( bing . yo ) ]
2024-10-17 02:58:04 +13:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-10-17 02:58:04 +13:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted , some_program_string ) ;
}
#[ test ]
fn test_recast_ranges ( ) {
let some_program_string = r #" foo = [0..10]
ten = 10
bar = [ 0 + 1 .. ten ]
2024-09-07 12:51:35 -04:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted , some_program_string ) ;
}
#[ test ]
fn test_recast_space_in_fn_call ( ) {
let some_program_string = r #" fn thing = (x) => {
return x + 1
}
thing ( 1 )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-11-27 15:46:58 +13:00
r #" fn thing(x) {
2024-09-07 12:51:35 -04:00
return x + 1
}
thing ( 1 )
" #
) ;
}
2024-11-27 15:46:58 +13:00
#[ test ]
fn test_recast_typed_fn ( ) {
let some_program_string = r #" fn thing(x: string, y: bool[]): number {
return x + 1
}
" #;
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted , some_program_string ) ;
}
2024-09-07 12:51:35 -04:00
#[ test ]
fn test_recast_object_fn_in_array_weird_bracket ( ) {
2024-11-25 09:21:55 +13:00
let some_program_string = r #" bing = { yo = 55 }
2024-10-02 14:19:40 -05:00
myNestedVar = [
2024-09-07 12:51:35 -04:00
{
prop : line ( [ bing . yo , 21 ] , sketch001 )
}
]
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-11-25 09:21:55 +13:00
r #" bing = { yo = 55 }
2024-10-02 14:19:40 -05:00
myNestedVar = [
2024-11-25 09:21:55 +13:00
{
prop = line ( [ bing . yo , 21 ] , sketch001 )
}
2024-09-07 12:51:35 -04:00
]
" #
) ;
}
#[ test ]
fn test_recast_empty_file ( ) {
let some_program_string = r # ""# ;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
// Its VERY important this comes back with zero new lines.
assert_eq! ( recasted , r # ""# ) ;
}
#[ test ]
fn test_recast_empty_file_new_line ( ) {
let some_program_string = r #"
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
// Its VERY important this comes back with zero new lines.
assert_eq! ( recasted , r # ""# ) ;
}
#[ test ]
fn test_recast_shebang ( ) {
let some_program_string = r #" #!/usr/local/env zoo kcl
2024-10-02 14:19:40 -05:00
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 10 , - 10 ] , % )
| > line ( [ 20 , 0 ] , % )
| > line ( [ 0 , 20 ] , % )
| > line ( [ - 20 , 0 ] , % )
| > close ( % )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" #!/usr/local/env zoo kcl
2024-10-02 14:19:40 -05:00
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 10 , - 10 ] , % )
| > line ( [ 20 , 0 ] , % )
| > line ( [ 0 , 20 ] , % )
| > line ( [ - 20 , 0 ] , % )
| > close ( % )
" #
) ;
}
#[ test ]
fn test_recast_shebang_new_lines ( ) {
let some_program_string = r #" #!/usr/local/env zoo kcl
2024-10-02 14:19:40 -05:00
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 10 , - 10 ] , % )
| > line ( [ 20 , 0 ] , % )
| > line ( [ 0 , 20 ] , % )
| > line ( [ - 20 , 0 ] , % )
| > close ( % )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" #!/usr/local/env zoo kcl
2024-10-02 14:19:40 -05:00
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 10 , - 10 ] , % )
| > line ( [ 20 , 0 ] , % )
| > line ( [ 0 , 20 ] , % )
| > line ( [ - 20 , 0 ] , % )
| > close ( % )
" #
) ;
}
#[ test ]
fn test_recast_shebang_with_comments ( ) {
let some_program_string = r #" #!/usr/local/env zoo kcl
// Yo yo my comments.
2024-10-02 14:19:40 -05:00
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 10 , - 10 ] , % )
| > line ( [ 20 , 0 ] , % )
| > line ( [ 0 , 20 ] , % )
| > line ( [ - 20 , 0 ] , % )
| > close ( % )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" #!/usr/local/env zoo kcl
// Yo yo my comments.
2024-10-02 14:19:40 -05:00
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 10 , - 10 ] , % )
| > line ( [ 20 , 0 ] , % )
| > line ( [ 0 , 20 ] , % )
| > line ( [ - 20 , 0 ] , % )
| > close ( % )
" #
) ;
}
#[ test ]
fn test_recast_large_file ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" // define nts
radius = 6.0
width = 144.0
length = 83.0
depth = 45.0
thk = 5
hole_diam = 5
2024-09-07 12:51:35 -04:00
// define a rectangular shape func
fn rectShape = ( pos , w , l ) = > {
2024-10-02 14:19:40 -05:00
rr = startSketchOn ( ' xy ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ pos [ 0 ] - ( w / 2 ) , pos [ 1 ] - ( l / 2 ) ] , % )
| > lineTo ( [ pos [ 0 ] + w / 2 , pos [ 1 ] - ( l / 2 ) ] , % , $edge1 )
| > lineTo ( [ pos [ 0 ] + w / 2 , pos [ 1 ] + l / 2 ] , % , $edge2 )
| > lineTo ( [ pos [ 0 ] - ( w / 2 ) , pos [ 1 ] + l / 2 ] , % , $edge3 )
| > close ( % , $edge4 )
return rr
}
// build the body of the focusrite scarlett solo gen 4
// only used for visualization
2024-10-02 14:19:40 -05:00
scarlett_body = rectShape ( [ 0 , 0 ] , width , length )
2024-09-07 12:51:35 -04:00
| > extrude ( depth , % )
| > fillet ( {
2024-11-25 09:21:55 +13:00
radius = radius ,
tags = [
2024-09-07 12:51:35 -04:00
edge2 ,
edge4 ,
getOppositeEdge ( edge2 ) ,
getOppositeEdge ( edge4 )
]
} , % )
// build the bracket sketch around the body
fn bracketSketch = ( w , d , t ) = > {
2024-10-02 14:19:40 -05:00
s = startSketchOn ( {
2024-09-07 12:51:35 -04:00
plane : {
2024-11-25 09:21:55 +13:00
origin : { x = 0 , y = length / 2 + thk , z = 0 } ,
x_axis : { x = 1 , y = 0 , z = 0 } ,
y_axis : { x = 0 , y = 0 , z = 1 } ,
z_axis : { x = 0 , y = 1 , z = 0 }
2024-09-07 12:51:35 -04:00
}
} )
| > startProfileAt ( [ - w / 2 - t , d + t ] , % )
| > lineTo ( [ - w / 2 - t , - t ] , % , $edge1 )
| > lineTo ( [ w / 2 + t , - t ] , % , $edge2 )
| > lineTo ( [ w / 2 + t , d + t ] , % , $edge3 )
| > lineTo ( [ w / 2 , d + t ] , % , $edge4 )
| > lineTo ( [ w / 2 , 0 ] , % , $edge5 )
| > lineTo ( [ - w / 2 , 0 ] , % , $edge6 )
| > lineTo ( [ - w / 2 , d + t ] , % , $edge7 )
| > close ( % , $edge8 )
return s
}
// build the body of the bracket
2024-10-02 14:19:40 -05:00
bracket_body = bracketSketch ( width , depth , thk )
2024-09-07 12:51:35 -04:00
| > extrude ( length + 10 , % )
| > fillet ( {
2024-11-25 09:21:55 +13:00
radius = radius ,
2024-09-07 12:51:35 -04:00
tags : [
getNextAdjacentEdge ( edge7 ) ,
getNextAdjacentEdge ( edge2 ) ,
getNextAdjacentEdge ( edge3 ) ,
getNextAdjacentEdge ( edge6 )
]
} , % )
// build the tabs of the mounting bracket (right side)
2024-10-02 14:19:40 -05:00
tabs_r = startSketchOn ( {
2024-09-07 12:51:35 -04:00
plane : {
2024-11-25 09:21:55 +13:00
origin : { x = 0 , y = 0 , z = depth + thk } ,
x_axis : { x = 1 , y = 0 , z = 0 } ,
y_axis : { x = 0 , y = 1 , z = 0 } ,
z_axis : { x = 0 , y = 0 , z = 1 }
2024-09-07 12:51:35 -04:00
}
} )
| > startProfileAt ( [ width / 2 + thk , length / 2 + thk ] , % )
| > line ( [ 10 , - 5 ] , % )
| > line ( [ 0 , - 10 ] , % )
| > line ( [ - 10 , - 5 ] , % )
| > close ( % )
2024-09-23 22:42:51 +10:00
| > hole ( circle ( {
2024-11-25 09:21:55 +13:00
center = [
2024-09-23 22:42:51 +10:00
width / 2 + thk + hole_diam ,
length / 2 - hole_diam
] ,
2024-11-25 09:21:55 +13:00
radius = hole_diam / 2
2024-09-23 22:42:51 +10:00
} , % ) , % )
2024-09-07 12:51:35 -04:00
| > extrude ( - thk , % )
| > patternLinear3d ( {
2024-11-25 09:21:55 +13:00
axis = [ 0 , - 1 , 0 ] ,
repetitions = 1 ,
distance = length - 10
2024-09-07 12:51:35 -04:00
} , % )
// build the tabs of the mounting bracket (left side)
2024-10-02 14:19:40 -05:00
tabs_l = startSketchOn ( {
2024-09-07 12:51:35 -04:00
plane : {
2024-11-25 09:21:55 +13:00
origin = { x = 0 , y = 0 , z = depth + thk } ,
x_axis = { x = 1 , y = 0 , z = 0 } ,
y_axis = { x = 0 , y = 1 , z = 0 } ,
z_axis = { x = 0 , y = 0 , z = 1 }
2024-09-07 12:51:35 -04:00
}
} )
| > startProfileAt ( [ - width / 2 - thk , length / 2 + thk ] , % )
| > line ( [ - 10 , - 5 ] , % )
| > line ( [ 0 , - 10 ] , % )
| > line ( [ 10 , - 5 ] , % )
| > close ( % )
2024-09-23 22:42:51 +10:00
| > hole ( circle ( {
2024-11-25 09:21:55 +13:00
center = [
2024-09-23 22:42:51 +10:00
- width / 2 - thk - hole_diam ,
length / 2 - hole_diam
] ,
2024-11-25 09:21:55 +13:00
radius = hole_diam / 2
2024-09-23 22:42:51 +10:00
} , % ) , % )
2024-09-07 12:51:35 -04:00
| > extrude ( - thk , % )
| > patternLinear3d ( {
2024-11-25 09:21:55 +13:00
axis = [ 0 , - 1 , 0 ] ,
repetitions = 1 ,
distance = length - 10
2024-09-07 12:51:35 -04:00
} , % )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
// Its VERY important this comes back with zero new lines.
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" // define nts
radius = 6.0
width = 144.0
length = 83.0
depth = 45.0
thk = 5
hole_diam = 5
2024-09-07 12:51:35 -04:00
// define a rectangular shape func
2024-11-27 15:46:58 +13:00
fn rectShape ( pos , w , l ) {
2024-10-02 14:19:40 -05:00
rr = startSketchOn ( ' xy ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ pos [ 0 ] - ( w / 2 ) , pos [ 1 ] - ( l / 2 ) ] , % )
| > lineTo ( [ pos [ 0 ] + w / 2 , pos [ 1 ] - ( l / 2 ) ] , % , $edge1 )
| > lineTo ( [ pos [ 0 ] + w / 2 , pos [ 1 ] + l / 2 ] , % , $edge2 )
| > lineTo ( [ pos [ 0 ] - ( w / 2 ) , pos [ 1 ] + l / 2 ] , % , $edge3 )
| > close ( % , $edge4 )
return rr
}
// build the body of the focusrite scarlett solo gen 4
// only used for visualization
2024-10-02 14:19:40 -05:00
scarlett_body = rectShape ( [ 0 , 0 ] , width , length )
2024-09-07 12:51:35 -04:00
| > extrude ( depth , % )
| > fillet ( {
2024-11-25 09:21:55 +13:00
radius = radius ,
tags = [
2024-09-07 12:51:35 -04:00
edge2 ,
edge4 ,
getOppositeEdge ( edge2 ) ,
getOppositeEdge ( edge4 )
]
} , % )
// build the bracket sketch around the body
2024-11-27 15:46:58 +13:00
fn bracketSketch ( w , d , t ) {
2024-10-02 14:19:40 -05:00
s = startSketchOn ( {
2024-11-25 09:21:55 +13:00
plane = {
origin = { x = 0 , y = length / 2 + thk , z = 0 } ,
x_axis = { x = 1 , y = 0 , z = 0 } ,
y_axis = { x = 0 , y = 0 , z = 1 } ,
z_axis = { x = 0 , y = 1 , z = 0 }
2024-09-07 12:51:35 -04:00
}
} )
| > startProfileAt ( [ - w / 2 - t , d + t ] , % )
| > lineTo ( [ - w / 2 - t , - t ] , % , $edge1 )
| > lineTo ( [ w / 2 + t , - t ] , % , $edge2 )
| > lineTo ( [ w / 2 + t , d + t ] , % , $edge3 )
| > lineTo ( [ w / 2 , d + t ] , % , $edge4 )
| > lineTo ( [ w / 2 , 0 ] , % , $edge5 )
| > lineTo ( [ - w / 2 , 0 ] , % , $edge6 )
| > lineTo ( [ - w / 2 , d + t ] , % , $edge7 )
| > close ( % , $edge8 )
return s
}
// build the body of the bracket
2024-10-02 14:19:40 -05:00
bracket_body = bracketSketch ( width , depth , thk )
2024-09-07 12:51:35 -04:00
| > extrude ( length + 10 , % )
| > fillet ( {
2024-11-25 09:21:55 +13:00
radius = radius ,
tags = [
2024-09-07 12:51:35 -04:00
getNextAdjacentEdge ( edge7 ) ,
getNextAdjacentEdge ( edge2 ) ,
getNextAdjacentEdge ( edge3 ) ,
getNextAdjacentEdge ( edge6 )
]
} , % )
// build the tabs of the mounting bracket (right side)
2024-10-02 14:19:40 -05:00
tabs_r = startSketchOn ( {
2024-11-25 09:21:55 +13:00
plane = {
origin = { x = 0 , y = 0 , z = depth + thk } ,
x_axis = { x = 1 , y = 0 , z = 0 } ,
y_axis = { x = 0 , y = 1 , z = 0 } ,
z_axis = { x = 0 , y = 0 , z = 1 }
2024-09-07 12:51:35 -04:00
}
} )
| > startProfileAt ( [ width / 2 + thk , length / 2 + thk ] , % )
| > line ( [ 10 , - 5 ] , % )
| > line ( [ 0 , - 10 ] , % )
| > line ( [ - 10 , - 5 ] , % )
| > close ( % )
2024-09-23 22:42:51 +10:00
| > hole ( circle ( {
2024-11-25 09:21:55 +13:00
center = [
2024-09-23 22:42:51 +10:00
width / 2 + thk + hole_diam ,
length / 2 - hole_diam
] ,
2024-11-25 09:21:55 +13:00
radius = hole_diam / 2
2024-09-23 22:42:51 +10:00
} , % ) , % )
2024-09-07 12:51:35 -04:00
| > extrude ( - thk , % )
| > patternLinear3d ( {
2024-11-25 09:21:55 +13:00
axis = [ 0 , - 1 , 0 ] ,
repetitions = 1 ,
distance = length - 10
2024-09-07 12:51:35 -04:00
} , % )
// build the tabs of the mounting bracket (left side)
2024-10-02 14:19:40 -05:00
tabs_l = startSketchOn ( {
2024-11-25 09:21:55 +13:00
plane = {
origin = { x = 0 , y = 0 , z = depth + thk } ,
x_axis = { x = 1 , y = 0 , z = 0 } ,
y_axis = { x = 0 , y = 1 , z = 0 } ,
z_axis = { x = 0 , y = 0 , z = 1 }
2024-09-07 12:51:35 -04:00
}
} )
| > startProfileAt ( [ - width / 2 - thk , length / 2 + thk ] , % )
| > line ( [ - 10 , - 5 ] , % )
| > line ( [ 0 , - 10 ] , % )
| > line ( [ 10 , - 5 ] , % )
| > close ( % )
2024-09-23 22:42:51 +10:00
| > hole ( circle ( {
2024-11-25 09:21:55 +13:00
center = [
2024-09-23 22:42:51 +10:00
- width / 2 - thk - hole_diam ,
length / 2 - hole_diam
] ,
2024-11-25 09:21:55 +13:00
radius = hole_diam / 2
2024-09-23 22:42:51 +10:00
} , % ) , % )
2024-09-07 12:51:35 -04:00
| > extrude ( - thk , % )
| > patternLinear3d ( {
2024-11-25 09:21:55 +13:00
axis = [ 0 , - 1 , 0 ] ,
repetitions = 1 ,
distance = length - 10
2024-09-07 12:51:35 -04:00
} , % )
" #
) ;
}
#[ test ]
fn test_recast_nested_var_declaration_in_fn_body ( ) {
let some_program_string = r #" fn cube = (pos, scale) => {
2024-10-02 14:19:40 -05:00
sg = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( pos , % )
| > line ( [ 0 , scale ] , % )
| > line ( [ scale , 0 ] , % )
| > line ( [ 0 , - scale ] , % )
| > close ( % )
| > extrude ( scale , % )
} " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-11-27 15:46:58 +13:00
r #" fn cube(pos, scale) {
2024-10-02 14:19:40 -05:00
sg = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( pos , % )
| > line ( [ 0 , scale ] , % )
| > line ( [ scale , 0 ] , % )
| > line ( [ 0 , - scale ] , % )
| > close ( % )
| > extrude ( scale , % )
}
" #
) ;
}
#[ test ]
fn test_recast_with_bad_indentation ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % )
| > line ( [ 0.6804562304 , 0.9087880491 ] , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % )
| > line ( [ 0.6804562304 , 0.9087880491 ] , % )
" #
) ;
}
#[ test ]
fn test_recast_with_bad_indentation_and_inline_comment ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % ) // hello world
| > line ( [ 0.6804562304 , 0.9087880491 ] , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % ) // hello world
| > line ( [ 0.6804562304 , 0.9087880491 ] , % )
" #
) ;
}
#[ test ]
fn test_recast_with_bad_indentation_and_line_comment ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % )
// hello world
| > line ( [ 0.6804562304 , 0.9087880491 ] , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % )
// hello world
| > line ( [ 0.6804562304 , 0.9087880491 ] , % )
" #
) ;
}
#[ test ]
fn test_recast_comment_in_a_fn_block ( ) {
let some_program_string = r #" fn myFn = () => {
// this is a comment
2024-11-25 09:21:55 +13:00
yo = { a = { b = { c = ' 123 ' } } } /* block
2024-09-07 12:51:35 -04:00
comment * /
2024-10-02 14:19:40 -05:00
key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
return things
} " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-11-27 15:46:58 +13:00
r #" fn myFn() {
2024-09-07 12:51:35 -04:00
// this is a comment
2024-11-25 09:21:55 +13:00
yo = { a = { b = { c = ' 123 ' } } } /* block
2024-09-07 12:51:35 -04:00
comment * /
2024-10-02 14:19:40 -05:00
key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
return things
}
" #
) ;
}
#[ test ]
fn test_recast_comment_under_variable ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
2024-10-02 14:19:40 -05:00
thing = ' foo '
2024-09-07 12:51:35 -04:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
2024-10-02 14:19:40 -05:00
thing = ' foo '
2024-09-07 12:51:35 -04:00
" #
) ;
}
#[ test ]
fn test_recast_multiline_comment_start_file ( ) {
let some_program_string = r #" // hello world
// I am a comment
2024-10-02 14:19:40 -05:00
key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
// hello
2024-10-02 14:19:40 -05:00
thing = ' foo '
2024-09-07 12:51:35 -04:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" // hello world
// I am a comment
2024-10-02 14:19:40 -05:00
key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
// hello
2024-10-02 14:19:40 -05:00
thing = ' foo '
2024-09-07 12:51:35 -04:00
" #
) ;
}
#[ test ]
fn test_recast_empty_comment ( ) {
let some_program_string = r #" // hello world
//
// I am a comment
2024-10-02 14:19:40 -05:00
key = 'c'
2024-09-07 12:51:35 -04:00
//
// I am a comment
2024-10-02 14:19:40 -05:00
thing = 'c'
2024-09-07 12:51:35 -04:00
2024-10-02 14:19:40 -05:00
foo = ' bar ' //
2024-09-07 12:51:35 -04:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" // hello world
//
// I am a comment
2024-10-02 14:19:40 -05:00
key = 'c'
2024-09-07 12:51:35 -04:00
//
// I am a comment
2024-10-02 14:19:40 -05:00
thing = 'c'
2024-09-07 12:51:35 -04:00
2024-10-02 14:19:40 -05:00
foo = ' bar ' //
2024-09-07 12:51:35 -04:00
" #
) ;
}
#[ test ]
fn test_recast_multiline_comment_under_variable ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
// hello
2024-10-02 14:19:40 -05:00
thing = ' foo '
2024-09-07 12:51:35 -04:00
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" key = 'c'
2024-09-07 12:51:35 -04:00
// this is also a comment
// hello
2024-10-02 14:19:40 -05:00
thing = ' foo '
2024-09-07 12:51:35 -04:00
" #
) ;
}
#[ test ]
fn test_recast_comment_at_start ( ) {
let test_program = r #"
/* comment at start */
2024-10-02 14:19:40 -05:00
mySk1 = startSketchAt ( [ 0 , 0 ] ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( test_program ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" /* comment at start */
2024-10-02 14:19:40 -05:00
mySk1 = startSketchAt ( [ 0 , 0 ] )
2024-09-07 12:51:35 -04:00
" #
) ;
}
#[ test ]
fn test_recast_lots_of_comments ( ) {
let some_program_string = r #" // comment at start
2024-10-02 14:19:40 -05:00
mySk1 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > lineTo ( [ 1 , 1 ] , % )
// comment here
| > lineTo ( [ 0 , 1 ] , % , $myTag )
| > lineTo ( [ 1 , 1 ] , % )
/* and
here
* /
// a comment between pipe expression statements
| > rx ( 90 , % )
// and another with just white space between others below
| > ry ( 45 , % )
| > rx ( 45 , % )
// one more for good measure"#;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" // comment at start
2024-10-02 14:19:40 -05:00
mySk1 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > lineTo ( [ 1 , 1 ] , % )
// comment here
| > lineTo ( [ 0 , 1 ] , % , $myTag )
| > lineTo ( [ 1 , 1 ] , % )
/* and
here * /
// a comment between pipe expression statements
| > rx ( 90 , % )
// and another with just white space between others below
| > ry ( 45 , % )
| > rx ( 45 , % )
// one more for good measure
" #
) ;
}
#[ test ]
fn test_recast_multiline_object ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ - 0.01 , - 0.08 ] , % )
| > line ( [ 0.62 , 4.15 ] , % , $seg01 )
| > line ( [ 2.77 , - 1.24 ] , % )
| > angledLineThatIntersects ( {
2024-11-25 09:21:55 +13:00
angle = 201 ,
offset = - 1.35 ,
intersectTag = seg01
2024-09-07 12:51:35 -04:00
} , % )
| > line ( [ - 0.42 , - 1.72 ] , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string ) ;
}
#[ test ]
fn test_recast_first_level_object ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" three = 3
2024-09-07 12:51:35 -04:00
2024-10-02 14:19:40 -05:00
yo = {
2024-11-25 09:21:55 +13:00
aStr = ' str ' ,
anum = 2 ,
identifier = three ,
binExp = 4 + 5
2024-09-07 12:51:35 -04:00
}
2024-10-02 14:19:40 -05:00
yo = [
2024-09-07 12:51:35 -04:00
1 ,
" 2, " ,
" three " ,
4 + 5 ,
" hey oooooo really long long long "
]
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted , some_program_string ) ;
}
#[ test ]
fn test_recast_new_line_before_comment ( ) {
let some_program_string = r #"
// this is a comment
2024-11-25 09:21:55 +13:00
yo = { a = { b = { c = ' 123 ' } } }
2024-09-07 12:51:35 -04:00
2024-10-02 14:19:40 -05:00
key = 'c'
things = " things "
2024-09-07 12:51:35 -04:00
// this is also a comment"#;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
let expected = some_program_string . trim ( ) ;
// Currently new parser removes an empty line
let actual = recasted . trim ( ) ;
assert_eq! ( actual , expected ) ;
}
#[ test ]
fn test_recast_comment_tokens_inside_strings ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" b = {
2024-11-25 09:21:55 +13:00
end = 141 ,
start = 125 ,
type_ = " NonCodeNode " ,
value = "
2024-09-07 12:51:35 -04:00
// a comment
"
} " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string . trim ( ) ) ;
}
#[ test ]
fn test_recast_array_new_line_in_pipe ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" myVar = 3
myVar2 = 5
myVar3 = 6
myAng = 40
myAng2 = 134
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 1 , 3.82 ] , % , $seg01 ) // ln-should-get-tag
| > angledLineToX ( [
- angleToMatchLengthX ( seg01 , myVar , % ) ,
myVar
] , % ) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
| > angledLineToY ( [
- angleToMatchLengthY ( seg01 , myVar , % ) ,
myVar
] , % ) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper"#;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string ) ;
}
#[ test ]
fn test_recast_array_new_line_in_pipe_custom ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" myVar = 3
myVar2 = 5
myVar3 = 6
myAng = 40
myAng2 = 134
part001 = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 1 , 3.82 ] , % , $seg01 ) // ln-should-get-tag
| > angledLineToX ( [
- angleToMatchLengthX ( seg01 , myVar , % ) ,
myVar
] , % ) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
| > angledLineToY ( [
- angleToMatchLengthY ( seg01 , myVar , % ) ,
myVar
] , % ) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast (
& FormatOptions {
tab_size : 3 ,
use_tabs : false ,
insert_final_newline : true ,
} ,
0 ,
) ;
assert_eq! ( recasted , some_program_string ) ;
}
#[ test ]
fn test_recast_after_rename_std ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" part001 = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0000000000 , 5.0000000000 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % )
2024-10-02 14:19:40 -05:00
part002 = " part002 "
things = [ part001 , 0.0 ]
blah = 1
foo = false
baz = { a : 1 , part001 : " thing " }
2024-09-07 12:51:35 -04:00
fn ghi = ( part001 ) = > {
return part001
}
" #;
2024-11-07 11:23:41 -05:00
let mut program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
program . rename_symbol ( " mySuperCoolPart " , 6 ) ;
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" mySuperCoolPart = startSketchOn('XY')
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0.0 , 5.0 ] , % )
| > line ( [ 0.4900857016 , - 0.0240763666 ] , % )
2024-10-02 14:19:40 -05:00
part002 = " part002 "
things = [ mySuperCoolPart , 0.0 ]
blah = 1
foo = false
2024-11-25 09:21:55 +13:00
baz = { a = 1 , part001 = " thing " }
2024-09-07 12:51:35 -04:00
2024-11-27 15:46:58 +13:00
fn ghi ( part001 ) {
2024-09-07 12:51:35 -04:00
return part001
}
" #
) ;
}
#[ test ]
fn test_recast_after_rename_fn_args ( ) {
let some_program_string = r #" fn ghi = (x, y, z) => {
return x
} " #;
2024-11-07 11:23:41 -05:00
let mut program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
program . rename_symbol ( " newName " , 10 ) ;
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-11-27 15:46:58 +13:00
r #" fn ghi(newName, y, z) {
2024-09-07 12:51:35 -04:00
return newName
}
" #
) ;
}
#[ test ]
fn test_recast_trailing_comma ( ) {
let some_program_string = r #" startSketchOn('XY')
| > startProfileAt ( [ 0 , 0 ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
radius = 1 ,
angle_start = 0 ,
angle_end = 180 ,
2024-09-07 12:51:35 -04:00
} , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
r #" startSketchOn('XY')
| > startProfileAt ( [ 0 , 0 ] , % )
| > arc ( {
2024-11-25 09:21:55 +13:00
radius = 1 ,
angle_start = 0 ,
angle_end = 180
2024-09-07 12:51:35 -04:00
} , % )
" #
) ;
}
#[ test ]
fn test_recast_negative_var ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" w = 20
l = 8
h = 10
2024-09-07 12:51:35 -04:00
2024-10-02 14:19:40 -05:00
firstExtrude = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , - l ] , % )
| > close ( % )
| > extrude ( h , % )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" w = 20
l = 8
h = 10
2024-09-07 12:51:35 -04:00
2024-10-02 14:19:40 -05:00
firstExtrude = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , - l ] , % )
| > close ( % )
| > extrude ( h , % )
" #
) ;
}
#[ test ]
fn test_recast_multiline_comment ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" w = 20
l = 8
h = 10
2024-09-07 12:51:35 -04:00
// This is my comment
// It has multiple lines
// And it's really long
2024-10-02 14:19:40 -05:00
firstExtrude = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , - l ] , % )
| > close ( % )
| > extrude ( h , % )
" #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! (
recasted ,
2024-10-02 14:19:40 -05:00
r #" w = 20
l = 8
h = 10
2024-09-07 12:51:35 -04:00
// This is my comment
// It has multiple lines
// And it's really long
2024-10-02 14:19:40 -05:00
firstExtrude = startSketchOn ( ' XY ' )
2024-09-07 12:51:35 -04:00
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , - l ] , % )
| > close ( % )
| > extrude ( h , % )
" #
) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_recast_math_start_negative ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r # "myVar = -5 + 6"# ;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_recast_math_negate_parens ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" wallMountL = 3.82
thickness = 0.5
2024-09-07 12:51:35 -04:00
startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 0 , - ( wallMountL - thickness ) ] , % )
| > line ( [ 0 , - ( 5 - thickness ) ] , % )
| > line ( [ 0 , - ( 5 - 1 ) ] , % )
| > line ( [ 0 , - ( - 5 - 1 ) ] , % ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_recast_math_nested_parens ( ) {
2024-10-02 14:19:40 -05:00
let some_program_string = r #" distance = 5
p = 3
FOS = 2
sigmaAllow = 8
width = 20
thickness = sqrt ( distance * p * FOS * 6 / ( sigmaAllow * width ) ) " #;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-10-02 14:19:40 -05:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn no_vardec_keyword ( ) {
let some_program_string = r # "distance = 5"# ;
2024-11-07 11:23:41 -05:00
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
assert_eq! ( recasted . trim ( ) , some_program_string ) ;
}
2024-11-20 08:23:30 -06:00
#[ test ]
fn recast_nested_fn ( ) {
let some_program_string = r #" fn f = () => {
2024-11-27 15:46:58 +13:00
return fn ( ) = > {
2024-11-20 08:23:30 -06:00
return 1
}
} " #;
let program = crate ::parser ::top_level_parse ( some_program_string ) . unwrap ( ) ;
let recasted = program . recast ( & Default ::default ( ) , 0 ) ;
let expected = " \
2024-11-27 15:46:58 +13:00
fn f ( ) {
return fn ( ) {
2024-11-20 08:23:30 -06:00
return 1
}
} " ;
assert_eq! ( recasted . trim ( ) , expected ) ;
}
2024-09-07 12:51:35 -04:00
#[ test ]
fn recast_literal ( ) {
use winnow ::Parser ;
for ( i , ( raw , expected , reason ) ) in [
(
" 5.0 " ,
" 5.0 " ,
" fractional numbers should stay fractional, i.e. don't reformat this to '5' " ,
) ,
(
" 5 " ,
" 5 " ,
" integers should stay integral, i.e. don't reformat this to '5.0' " ,
) ,
(
" 5.0000000 " ,
" 5.0 " ,
" if the number is f64 but not fractional, use its canonical format " ,
) ,
( " 5.1 " , " 5.1 " , " straightforward case works " ) ,
]
. into_iter ( )
. enumerate ( )
{
2024-11-07 11:23:41 -05:00
let tokens = crate ::token ::lexer ( raw , ModuleId ::default ( ) ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let literal = crate ::parser ::parser_impl ::unsigned_number_literal
. parse ( & tokens )
. unwrap ( ) ;
assert_eq! (
literal . recast ( ) ,
expected ,
" failed test {i}, which is testing that {reason} "
) ;
}
}
#[ test ]
fn recast_objects_no_comments ( ) {
let input = r #"
2024-10-02 14:19:40 -05:00
sketch002 = startSketchOn ( {
2024-09-07 12:51:35 -04:00
plane : {
2024-11-25 09:21:55 +13:00
origin : { x = 1 , y = 2 , z = 3 } ,
x_axis : { x = 4 , y = 5 , z = 6 } ,
y_axis : { x = 7 , y = 8 , z = 9 } ,
z_axis : { x = 10 , y = 11 , z = 12 }
2024-09-07 12:51:35 -04:00
}
} )
" #;
2024-10-02 14:19:40 -05:00
let expected = r #" sketch002 = startSketchOn({
2024-11-25 09:21:55 +13:00
plane = {
origin = { x = 1 , y = 2 , z = 3 } ,
x_axis = { x = 4 , y = 5 , z = 6 } ,
y_axis = { x = 7 , y = 8 , z = 9 } ,
z_axis = { x = 10 , y = 11 , z = 12 }
2024-09-07 12:51:35 -04:00
}
} )
" #;
2024-11-07 11:23:41 -05:00
let ast = crate ::parser ::top_level_parse ( input ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let actual = ast . recast ( & FormatOptions ::new ( ) , 0 ) ;
assert_eq! ( actual , expected ) ;
}
#[ test ]
fn recast_objects_with_comments ( ) {
use winnow ::Parser ;
for ( i , ( input , expected , reason ) ) in [ (
" \
{
2024-11-25 09:21:55 +13:00
a = 1 ,
// b = 2,
c = 3
2024-09-07 12:51:35 -04:00
} " ,
" \
{
2024-11-25 09:21:55 +13:00
a = 1 ,
// b = 2,
c = 3
2024-09-07 12:51:35 -04:00
} " ,
" preserves comments " ,
) ]
. into_iter ( )
. enumerate ( )
{
2024-11-07 11:23:41 -05:00
let tokens = crate ::token ::lexer ( input , ModuleId ::default ( ) ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
crate ::parser ::parser_impl ::print_tokens ( & tokens ) ;
let expr = crate ::parser ::parser_impl ::object . parse ( & tokens ) . unwrap ( ) ;
assert_eq! (
2024-11-27 15:46:58 +13:00
expr . recast ( & FormatOptions ::new ( ) , 0 , ExprContext ::Other ) ,
2024-09-07 12:51:35 -04:00
expected ,
" failed test {i}, which is testing that recasting {reason} "
) ;
}
}
#[ test ]
fn recast_array_with_comments ( ) {
use winnow ::Parser ;
for ( i , ( input , expected , reason ) ) in [
(
" \
[
1 ,
2 ,
3 ,
4 ,
5 ,
6 ,
7 ,
8 ,
9 ,
10 ,
11 ,
12 ,
13 ,
14 ,
15 ,
16 ,
17 ,
18 ,
19 ,
20 ,
] " ,
" \
[
1 ,
2 ,
3 ,
4 ,
5 ,
6 ,
7 ,
8 ,
9 ,
10 ,
11 ,
12 ,
13 ,
14 ,
15 ,
16 ,
17 ,
18 ,
19 ,
20
] " ,
" preserves multi-line arrays " ,
) ,
(
" \
[
1 ,
// 2,
3
] " ,
" \
[
1 ,
// 2,
3
] " ,
" preserves comments " ,
) ,
(
" \
[
1 ,
2 ,
// 3
] " ,
" \
[
1 ,
2 ,
// 3
] " ,
" preserves comments at the end of the array " ,
) ,
]
. into_iter ( )
. enumerate ( )
{
2024-11-07 11:23:41 -05:00
let tokens = crate ::token ::lexer ( input , ModuleId ::default ( ) ) . unwrap ( ) ;
2024-09-07 12:51:35 -04:00
let expr = crate ::parser ::parser_impl ::array_elem_by_elem . parse ( & tokens ) . unwrap ( ) ;
assert_eq! (
2024-11-27 15:46:58 +13:00
expr . recast ( & FormatOptions ::new ( ) , 0 , ExprContext ::Other ) ,
2024-09-07 12:51:35 -04:00
expected ,
" failed test {i}, which is testing that recasting {reason} "
) ;
}
}
}